feat(esp_eth): support EMAC buffers and descriptors in PSRAM

DMA descriptors stay in internal SRAM and are accessed by the CPU
through the non-cacheable alias, so they need no cache maintenance.
Tx/Rx buffers may optionally be allocated in PSRAM.
This commit is contained in:
Ondrej Kosta
2026-09-18 14:10:48 +02:00
parent 26dd294892
commit e9be3202fa
8 changed files with 204 additions and 106 deletions
+14 -3
View File
@@ -19,9 +19,10 @@ menu "Ethernet"
default 512
help
Set the size of each buffer used by Ethernet MAC DMA.
On targets where internal RAM is accessed through L1 cache (ESP32-P4),
this size must be a multiple of the cache line size (64 bytes), e.g.
256, 512, 1536.
When the buffers are accessed through a data cache (internal RAM on
ESP32-P4, or PSRAM if ETH_DMA_USE_PSRAM is enabled), this size must
be a multiple of the data cache line size. Cache line size is
typically 64 bytes, hence 256, 512, 1536, for example.
config ETH_DMA_RX_BUFFER_NUM
int "Amount of Ethernet DMA Rx buffers"
@@ -40,6 +41,16 @@ menu "Ethernet"
Number of DMA transmit buffers. Each buffer's size is ETH_DMA_BUFFER_SIZE.
Larger number of buffers could increase throughput somehow.
config ETH_DMA_USE_PSRAM
bool "Allocate Ethernet DMA buffers in PSRAM"
depends on SPIRAM && SOC_PSRAM_DMA_CAPABLE
default n
help
Place EMAC DMA Tx/Rx buffers in PSRAM instead of internal SRAM.
This reduces internal RAM usage at the cost of higher DMA access latency.
The DMA descriptors are always kept in internal SRAM.
DMA buffers must still be cache-line aligned (see ETH_DMA_BUFFER_SIZE).
if ETH_DMA_RX_BUFFER_NUM > 15
config ETH_SOFT_FLOW_CONTROL
bool "Enable software flow control"
+117 -70
View File
@@ -6,11 +6,16 @@
#include "esp_check.h"
#include "esp_assert.h"
#include "esp_attr.h"
#include "esp_macros.h"
#include "sdkconfig.h"
#include "soc/soc.h"
#include "soc/soc_caps.h"
#include "esp_cache.h"
#include "esp_timer.h"
#include "hal/emac_hal.h"
#include "esp_heap_caps.h"
#include "esp_private/esp_cache_private.h"
#include "esp_private/eth_mac_esp_dma.h"
#define ETH_CRC_LENGTH (4)
@@ -20,42 +25,56 @@
#define EMAC_TDES0_FS_CTRL_FLAGS_MASK 0x0FCC0000 // modifiable bits mask associated with the First Segment
#define EMAC_TDES0_LS_CTRL_FLAGS_MASK 0x40000000 // modifiable bits mask associated with the Last Segment
#define PTP_TX_TIMESTAMP_TO 50 // maximum loops observed on P4 was 31 @ETH frame 1500B
/* Tx timestamp is captured when the frame has left the MAC:
* a 1522 B frame is ~1.2 ms on the wire at 10 Mbps. Success returns as soon as
* the timestamp appears, so this budget only bounds the failure path. */
#define PTP_TX_TIMESTAMP_TO_US 2000
/* Descriptors stay in internal DMA RAM. Tx/Rx buffers follow ETH_DMA_USE_PSRAM config option. */
#define EMAC_DMA_DESC_MALLOC_CAPS (MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
#if CONFIG_ETH_DMA_USE_PSRAM
#define EMAC_DMA_BUF_MALLOC_CAPS (MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)
#else
#define EMAC_DMA_BUF_MALLOC_CAPS (MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
#endif
/* Addresses programmed into the EMAC and stored in the chain links are the cacheable ones.
Where internal RAM is behind the cache, translate to the non-cacheable alias before the
CPU dereferences a descriptor so the data path needs no cache maintenance. */
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
#if !SOC_NON_CACHEABLE_OFFSET_SRAM
#error "EMAC DMA descriptors require a non-cacheable alias of the internal RAM"
#endif
#define EMAC_DESC_TO_NC(addr) ((void *)((uintptr_t)(addr) + SOC_NON_CACHEABLE_OFFSET_SRAM))
#else
#define EMAC_DESC_TO_NC(addr) ((void *)(uintptr_t)(addr))
#endif
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE || CONFIG_ETH_DMA_USE_PSRAM
#define DMA_CACHE_WB(addr, size) do { \
esp_err_t msync_ret = esp_cache_msync((void *)addr, size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); \
assert(msync_ret == ESP_OK); \
(void)msync_ret; \
} while(0)
#else
#define DMA_CACHE_WB(addr, size)
#endif
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
#define DMA_CACHE_INVALIDATE(addr, size) do { \
esp_err_t msync_ret = esp_cache_msync((void *)addr, size, ESP_CACHE_MSYNC_FLAG_DIR_M2C); \
assert(msync_ret == ESP_OK); \
(void)msync_ret; \
} while(0)
#else
#define DMA_CACHE_WB(addr, size)
#define DMA_CACHE_INVALIDATE(addr, size)
#endif
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
ESP_STATIC_ASSERT((CONFIG_ETH_DMA_BUFFER_SIZE % CONFIG_CACHE_L1_CACHE_LINE_SIZE) == 0,
"CONFIG_ETH_DMA_BUFFER_SIZE must be a multiple of the L1 cache line size");
#endif
static const char *TAG = "esp.emac.dma";
struct emac_esp_dma_t {
emac_hal_context_t hal;
uint32_t tx_desc_flags;
uint32_t rx_desc_flags;
void *descriptors;
eth_dma_rx_descriptor_t *rx_desc;
eth_dma_tx_descriptor_t *tx_desc;
void *descriptors; /* cacheable alias of the descriptor pool, as seen by the DMA */
eth_dma_rx_descriptor_t *rx_desc; /* non-cacheable alias */
eth_dma_tx_descriptor_t *tx_desc; /* non-cacheable alias */
uint8_t *rx_buf[CONFIG_ETH_DMA_RX_BUFFER_NUM];
uint8_t *tx_buf[CONFIG_ETH_DMA_TX_BUFFER_NUM];
};
@@ -67,16 +86,27 @@ typedef struct {
uint32_t copy_len;
} __attribute__((packed)) emac_esp_dma_auto_buf_info_t;
FORCE_INLINE_ATTR eth_dma_rx_descriptor_t *emac_esp_dma_next_rx_desc(eth_dma_rx_descriptor_t *desc)
{
return (eth_dma_rx_descriptor_t *)EMAC_DESC_TO_NC(desc->Buffer2NextDescAddr);
}
FORCE_INLINE_ATTR eth_dma_tx_descriptor_t *emac_esp_dma_next_tx_desc(eth_dma_tx_descriptor_t *desc)
{
return (eth_dma_tx_descriptor_t *)EMAC_DESC_TO_NC(desc->Buffer2NextDescAddr);
}
void emac_esp_dma_reset(emac_esp_dma_handle_t emac_esp_dma)
{
/* the chain is linked by cacheable addresses since it is walked by the DMA too */
eth_dma_rx_descriptor_t *rx_desc_c = (eth_dma_rx_descriptor_t *)(emac_esp_dma->descriptors);
eth_dma_tx_descriptor_t *tx_desc_c = (eth_dma_tx_descriptor_t *)(rx_desc_c + CONFIG_ETH_DMA_RX_BUFFER_NUM);
/* reset DMA descriptors */
emac_esp_dma->rx_desc = (eth_dma_rx_descriptor_t *)(emac_esp_dma->descriptors);
emac_esp_dma->tx_desc = (eth_dma_tx_descriptor_t *)(emac_esp_dma->descriptors +
sizeof(eth_dma_rx_descriptor_t) * CONFIG_ETH_DMA_RX_BUFFER_NUM);
emac_esp_dma->rx_desc = EMAC_DESC_TO_NC(rx_desc_c);
emac_esp_dma->tx_desc = EMAC_DESC_TO_NC(tx_desc_c);
/* init rx chain */
for (int i = 0; i < CONFIG_ETH_DMA_RX_BUFFER_NUM; i++) {
/* Set Own bit of the Rx descriptor Status: DMA */
emac_esp_dma->rx_desc[i].RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
/* Set Buffer1 size and Second Address Chained bit */
emac_esp_dma->rx_desc[i].RDES1.SecondAddressChained = 1;
emac_esp_dma->rx_desc[i].RDES1.ReceiveBuffer1Size = CONFIG_ETH_DMA_BUFFER_SIZE;
@@ -85,13 +115,14 @@ void emac_esp_dma_reset(emac_esp_dma_handle_t emac_esp_dma)
/* point to the buffer */
emac_esp_dma->rx_desc[i].Buffer1Addr = (uint32_t)(emac_esp_dma->rx_buf[i]);
/* point to next descriptor */
emac_esp_dma->rx_desc[i].Buffer2NextDescAddr = (uint32_t)(emac_esp_dma->rx_desc + i + 1);
emac_esp_dma->rx_desc[i].Buffer2NextDescAddr = (uint32_t)(rx_desc_c + i + 1);
/* For last descriptor, set next descriptor address register equal to the first descriptor base address */
if (i == CONFIG_ETH_DMA_RX_BUFFER_NUM - 1) {
emac_esp_dma->rx_desc[i].Buffer2NextDescAddr = (uint32_t)(emac_esp_dma->rx_desc);
emac_esp_dma->rx_desc[i].Buffer2NextDescAddr = (uint32_t)rx_desc_c;
}
DMA_CACHE_WB(&emac_esp_dma->rx_desc[i], EMAC_HAL_DMA_DESC_SIZE);
/* Set Own bit of the Rx descriptor Status: DMA (last, the descriptor must be fully initialized first) */
emac_esp_dma->rx_desc[i].RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
}
/* init tx chain */
@@ -103,17 +134,16 @@ void emac_esp_dma_reset(emac_esp_dma_handle_t emac_esp_dma)
/* point to the buffer */
emac_esp_dma->tx_desc[i].Buffer1Addr = (uint32_t)(emac_esp_dma->tx_buf[i]);
/* point to next descriptor */
emac_esp_dma->tx_desc[i].Buffer2NextDescAddr = (uint32_t)(emac_esp_dma->tx_desc + i + 1);
emac_esp_dma->tx_desc[i].Buffer2NextDescAddr = (uint32_t)(tx_desc_c + i + 1);
/* For last descriptor, set next descriptor address register equal to the first descriptor base address */
if (i == CONFIG_ETH_DMA_TX_BUFFER_NUM - 1) {
emac_esp_dma->tx_desc[i].Buffer2NextDescAddr = (uint32_t)(emac_esp_dma->tx_desc);
emac_esp_dma->tx_desc[i].Buffer2NextDescAddr = (uint32_t)tx_desc_c;
}
DMA_CACHE_WB(&emac_esp_dma->tx_desc[i], EMAC_HAL_DMA_DESC_SIZE);
}
/* set base address of the first descriptor */
emac_hal_set_rx_tx_desc_addr(&emac_esp_dma->hal, emac_esp_dma->rx_desc, emac_esp_dma->tx_desc);
emac_hal_set_rx_tx_desc_addr(&emac_esp_dma->hal, rx_desc_c, tx_desc_c);
}
void emac_esp_dma_set_tdes0_ctrl_bits(emac_esp_dma_handle_t emac_esp_dma, uint32_t flag)
@@ -135,6 +165,28 @@ void emac_esp_dma_ts_enable(emac_esp_dma_handle_t emac_esp_dma, bool enable)
}
}
FORCE_INLINE_ATTR void emac_esp_dma_return_tx_desc_dma(emac_esp_dma_handle_t emac_esp_dma, uint32_t desc_cnt)
{
/* Hand a multi-buffer frame to the DMA last-to-first (FS descriptor last).
*
* The Tx engine can already be walking the ring from a previous frame. If it
* fetched the new first segment while later segments were still CPU-owned, it
* would see a break in the chain.
*/
uint32_t max_i = desc_cnt - 1;
eth_dma_tx_descriptor_t *desc_arr[desc_cnt];
desc_arr[0] = emac_esp_dma->tx_desc;
for (int i = 1; i < desc_cnt; i++) {
desc_arr[i] = emac_esp_dma_next_tx_desc(desc_arr[i - 1]);
}
for (int i = max_i; i >= 0; i--) {
/* Set Own bit of the Tx descriptor Status */
desc_arr[i]->TDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
}
/* update position of next to be used descriptor */
emac_esp_dma->tx_desc = emac_esp_dma_next_tx_desc(desc_arr[max_i]);
}
uint32_t emac_esp_dma_transmit_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t *buf, uint32_t length)
{
/* Get the number of Tx buffers to use for the frame */
@@ -155,7 +207,6 @@ uint32_t emac_esp_dma_transmit_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t
eth_dma_tx_descriptor_t *desc_iter = emac_esp_dma->tx_desc;
/* A frame is transmitted in multiple descriptor */
for (size_t i = 0; i < bufcount; i++) {
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* Check if the descriptor is owned by the Ethernet DMA (when 1) or CPU (when 0) */
if (desc_iter->TDES0.Own != EMAC_LL_DMADESC_OWNER_CPU) {
goto err;
@@ -187,15 +238,11 @@ uint32_t emac_esp_dma_transmit_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t
}
DMA_CACHE_WB(desc_iter->Buffer1Addr, CONFIG_ETH_DMA_BUFFER_SIZE);
/* Point to next descriptor */
desc_iter = (eth_dma_tx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
desc_iter = emac_esp_dma_next_tx_desc(desc_iter);
}
/* Set Own bit of the Tx descriptor Status: gives the buffer back to ETHERNET DMA */
for (size_t i = 0; i < bufcount; i++) {
emac_esp_dma->tx_desc->TDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(emac_esp_dma->tx_desc, EMAC_HAL_DMA_DESC_SIZE);
emac_esp_dma->tx_desc = (eth_dma_tx_descriptor_t *)(emac_esp_dma->tx_desc->Buffer2NextDescAddr);
}
/* Give the buffers back to ETHERNET DMA and update position of next to be used descriptor */
emac_esp_dma_return_tx_desc_dma(emac_esp_dma, bufcount);
emac_hal_transmit_poll_demand(&emac_esp_dma->hal);
return sentout;
err:
@@ -217,7 +264,6 @@ uint32_t emac_esp_dma_transmit_frame_ext(emac_esp_dma_handle_t emac_esp_dma, ema
#endif
/* A frame is transmitted in multiple descriptor */
while (dma_bufcount < CONFIG_ETH_DMA_TX_BUFFER_NUM) {
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* Check if the descriptor is owned by the Ethernet DMA (when 1) or CPU (when 0) */
if (desc_iter->TDES0.Own != EMAC_LL_DMADESC_OWNER_CPU) {
goto err;
@@ -286,27 +332,21 @@ uint32_t emac_esp_dma_transmit_frame_ext(emac_esp_dma_handle_t emac_esp_dma, ema
}
/* Point to next descriptor */
desc_iter = (eth_dma_tx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
}
/* Set Own bit of the Tx descriptor Status: gives the buffer back to ETHERNET DMA */
for (size_t i = 0; i < dma_bufcount; i++) {
emac_esp_dma->tx_desc->TDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(emac_esp_dma->tx_desc, EMAC_HAL_DMA_DESC_SIZE);
emac_esp_dma->tx_desc = (eth_dma_tx_descriptor_t *)(emac_esp_dma->tx_desc->Buffer2NextDescAddr);
desc_iter = emac_esp_dma_next_tx_desc(desc_iter);
}
/* Give the buffers back to ETHERNET DMA and update position of next to be used descriptor */
emac_esp_dma_return_tx_desc_dma(emac_esp_dma, dma_bufcount);
emac_hal_transmit_poll_demand(&emac_esp_dma->hal);
#if SOC_EMAC_IEEE1588V2_SUPPORTED
if (ts != NULL) {
uint32_t timeout = 0;
const int64_t start_us = esp_timer_get_time();
esp_err_t ts_ret;
do {
timeout++;
DMA_CACHE_INVALIDATE(desc_last, EMAC_HAL_DMA_DESC_SIZE);
} while (emac_hal_get_txdesc_timestamp(&emac_esp_dma->hal, desc_last, &ts->seconds, &ts->nanoseconds) == ESP_ERR_INVALID_STATE &&
timeout < PTP_TX_TIMESTAMP_TO);
if (timeout >= PTP_TX_TIMESTAMP_TO) {
ts_ret = emac_hal_get_txdesc_timestamp(&emac_esp_dma->hal, desc_last, &ts->seconds, &ts->nanoseconds);
} while (ts_ret == ESP_ERR_INVALID_STATE && (esp_timer_get_time() - start_us) < PTP_TX_TIMESTAMP_TO_US);
if (ts_ret != ESP_OK) {
/* zeros indicate invalid time stamp since it is not possible to ever get "zero time" under normal conditions */
ts->seconds = 0;
ts->nanoseconds = 0;
@@ -324,7 +364,6 @@ static esp_err_t emac_esp_dma_get_valid_recv_len(emac_esp_dma_handle_t emac_esp_
*ret_len = 0;
eth_dma_rx_descriptor_t *desc_iter = emac_esp_dma->rx_desc;
uint32_t used_descs = 0;
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* Traverse descriptors owned by CPU */
while ((desc_iter->RDES0.Own == EMAC_LL_DMADESC_OWNER_CPU) && (used_descs < CONFIG_ETH_DMA_RX_BUFFER_NUM)) {
@@ -346,8 +385,7 @@ static esp_err_t emac_esp_dma_get_valid_recv_len(emac_esp_dma_handle_t emac_esp_
emac_esp_dma->rx_desc = desc_iter;
}
/* point to next descriptor */
desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
desc_iter = emac_esp_dma_next_rx_desc(desc_iter);
}
return ESP_OK;
@@ -359,7 +397,6 @@ void emac_esp_dma_get_remain_frames(emac_esp_dma_handle_t emac_esp_dma, uint32_t
*remain_frames = 0;
uint32_t used_descs = 0;
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* Traverse descriptors owned by CPU */
while ((desc_iter->RDES0.Own == EMAC_LL_DMADESC_OWNER_CPU) && (used_descs < CONFIG_ETH_DMA_RX_BUFFER_NUM)) {
used_descs++;
@@ -368,8 +405,7 @@ void emac_esp_dma_get_remain_frames(emac_esp_dma_handle_t emac_esp_dma, uint32_t
(*remain_frames)++;
}
/* point to next descriptor */
desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
desc_iter = emac_esp_dma_next_rx_desc(desc_iter);
}
*free_descs = CONFIG_ETH_DMA_RX_BUFFER_NUM - used_descs;
}
@@ -434,16 +470,14 @@ uint32_t emac_esp_dma_receive_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t
copy_len -= CONFIG_ETH_DMA_BUFFER_SIZE;
/* Set Own bit in Rx descriptors: gives the buffers back to DMA */
desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
desc_iter = emac_esp_dma_next_rx_desc(desc_iter);
}
DMA_CACHE_INVALIDATE(desc_iter->Buffer1Addr, CONFIG_ETH_DMA_BUFFER_SIZE);
memcpy(buf, (void *)(desc_iter->Buffer1Addr), copy_len);
/* `copy_len` does not include CRC (which may be stored in separate buffer), hence check if we reached the last descriptor */
while (!desc_iter->RDES0.LastDescriptor) {
desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
desc_iter = emac_esp_dma_next_rx_desc(desc_iter);
}
#if SOC_EMAC_IEEE1588V2_SUPPORTED
if (ts != NULL) {
@@ -456,10 +490,9 @@ uint32_t emac_esp_dma_receive_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t
#endif
/* return last descriptor to DMA */
desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* update rxdesc */
emac_esp_dma->rx_desc = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
emac_esp_dma->rx_desc = emac_esp_dma_next_rx_desc(desc_iter);
/* poll rx demand */
emac_hal_receive_poll_demand(&emac_esp_dma->hal);
}
@@ -470,19 +503,15 @@ void emac_esp_dma_flush_recv_frame(emac_esp_dma_handle_t emac_esp_dma)
{
eth_dma_rx_descriptor_t *desc_iter = emac_esp_dma->rx_desc;
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* While not last descriptor => return back to DMA */
while (!desc_iter->RDES0.LastDescriptor) {
desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
desc_iter = emac_esp_dma_next_rx_desc(desc_iter);
}
/* the last descriptor */
desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* update rxdesc */
emac_esp_dma->rx_desc = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
emac_esp_dma->rx_desc = emac_esp_dma_next_rx_desc(desc_iter);
/* poll rx demand */
emac_hal_receive_poll_demand(&emac_esp_dma->hal);
}
@@ -512,15 +541,33 @@ esp_err_t emac_esp_new_dma(const emac_esp_dma_config_t *config, emac_esp_dma_han
/* alloc memory for ethernet dma descriptor */
uint32_t desc_size = CONFIG_ETH_DMA_RX_BUFFER_NUM * sizeof(eth_dma_rx_descriptor_t) +
CONFIG_ETH_DMA_TX_BUFFER_NUM * sizeof(eth_dma_tx_descriptor_t);
emac_esp_dma->descriptors = heap_caps_aligned_calloc(EMAC_LL_DMA_MEM_ALIGNMENT, 1, desc_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
emac_esp_dma->descriptors = heap_caps_aligned_calloc(EMAC_LL_DMA_MEM_ALIGNMENT, 1, desc_size, EMAC_DMA_DESC_MALLOC_CAPS);
ESP_GOTO_ON_FALSE(emac_esp_dma->descriptors, ESP_ERR_NO_MEM, err, TAG, "no mem for descriptors");
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
/* the descriptors are accessed through the non-cacheable alias from now on, so write back and drop
whatever the allocation left in the cache */
size_t cache_line_size = esp_cache_get_line_size_by_addr(emac_esp_dma->descriptors);
if (cache_line_size > 0) {
ESP_GOTO_ON_ERROR(esp_cache_msync(emac_esp_dma->descriptors, ESP_ALIGN_UP(desc_size, cache_line_size),
ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE),
err, TAG, "failed to sync descriptors cache");
}
#endif
/* check the user-configured buffer size is a multiple of the data cache line size */
size_t buf_cache_line_size = 0;
ESP_GOTO_ON_ERROR(esp_cache_get_alignment(EMAC_DMA_BUF_MALLOC_CAPS, &buf_cache_line_size),
err, TAG, "failed to get DMA buffer cache alignment");
ESP_GOTO_ON_FALSE(buf_cache_line_size == 0 || (CONFIG_ETH_DMA_BUFFER_SIZE % buf_cache_line_size) == 0,
ESP_ERR_INVALID_SIZE, err, TAG,
"ETH_DMA_BUFFER_SIZE (%d) must be a multiple of the data cache line size (%zu)",
CONFIG_ETH_DMA_BUFFER_SIZE, buf_cache_line_size);
/* alloc memory for ethernet dma buffer */
for (int i = 0; i < CONFIG_ETH_DMA_RX_BUFFER_NUM; i++) {
emac_esp_dma->rx_buf[i] = heap_caps_aligned_calloc(EMAC_LL_DMA_MEM_ALIGNMENT, 1, CONFIG_ETH_DMA_BUFFER_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
emac_esp_dma->rx_buf[i] = heap_caps_aligned_calloc(EMAC_LL_DMA_MEM_ALIGNMENT, 1, CONFIG_ETH_DMA_BUFFER_SIZE, EMAC_DMA_BUF_MALLOC_CAPS);
ESP_GOTO_ON_FALSE(emac_esp_dma->rx_buf[i], ESP_ERR_NO_MEM, err, TAG, "no mem for RX DMA buffers");
}
for (int i = 0; i < CONFIG_ETH_DMA_TX_BUFFER_NUM; i++) {
emac_esp_dma->tx_buf[i] = heap_caps_aligned_calloc(EMAC_LL_DMA_MEM_ALIGNMENT, 1, CONFIG_ETH_DMA_BUFFER_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
emac_esp_dma->tx_buf[i] = heap_caps_aligned_calloc(EMAC_LL_DMA_MEM_ALIGNMENT, 1, CONFIG_ETH_DMA_BUFFER_SIZE, EMAC_DMA_BUF_MALLOC_CAPS);
ESP_GOTO_ON_FALSE(emac_esp_dma->tx_buf[i], ESP_ERR_NO_MEM, err, TAG, "no mem for TX DMA buffers");
}
emac_hal_init(&emac_esp_dma->hal);
@@ -9,6 +9,7 @@
#include <string.h>
#include <inttypes.h>
#include "esp_err.h"
#include "esp_eth_spec.h"
#include "time.h"
#include "freertos/FreeRTOS.h"
@@ -422,7 +423,11 @@ TEST_CASE("internal emac erroneous frames", "[esp_emac]")
ESP_LOGI(TAG, "Verify non-failure frame condition");
for (i = 1; i <= TEST_FRAMES_NUM; i++) {
test_pkt->data[0] = frame_id++;
TEST_ESP_OK(esp_eth_transmit(eth_handle, test_pkt, transmit_size));
if (esp_eth_transmit(eth_handle, test_pkt, transmit_size) == ESP_ERR_NO_MEM) {
// we are too fast, wait for a bit and try again
vTaskDelay(2);
TEST_ESP_OK(esp_eth_transmit(eth_handle, test_pkt, transmit_size));
}
// if we have only 10 or less Rx buffers, they can be all used pretty fast => wait to be freed prior next Tx
if (CONFIG_ETH_DMA_RX_BUFFER_NUM <= 10 && !(i % (CONFIG_ETH_DMA_RX_BUFFER_NUM / 2))) {
ESP_LOGI(TAG, "wait prior Tx (frame num %i)", i);
@@ -450,7 +455,11 @@ TEST_CASE("internal emac erroneous frames", "[esp_emac]")
if (!(i % 2)) {
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_MAC_ESP_CMD_SET_TDES0_CFG_BITS, &emac_tx_dbg_flag));
}
TEST_ESP_OK(esp_eth_transmit(eth_handle, test_pkt, transmit_size));
if (esp_eth_transmit(eth_handle, test_pkt, transmit_size) == ESP_ERR_NO_MEM) {
// we are too fast, wait for a bit and try again
vTaskDelay(2);
TEST_ESP_OK(esp_eth_transmit(eth_handle, test_pkt, transmit_size));
}
if (!(i % 2)) {
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_MAC_ESP_CMD_CLEAR_TDES0_CFG_BITS, &emac_tx_dbg_flag));
}
+4 -27
View File
@@ -19,20 +19,6 @@ extern "C" {
#if SOC_EMAC_SUPPORTED
#include "hal/emac_ll.h"
/**
* @brief Macros to check descriptors datatype size
*/
#define STR(s) #s
#define TYPE_SIZE_ERR_MSG(DATATYPE, SIZE) #DATATYPE " should occupy " STR(SIZE) " bytes in memory"
#define ASSERT_TYPE_SIZE(DATATYPE, SIZE) ESP_STATIC_ASSERT(sizeof(DATATYPE) == SIZE, TYPE_SIZE_ERR_MSG(DATATYPE, SIZE))
#if SOC_IS(ESP32P4)
// Descriptor must be 64B aligned for ESP32P4 due to cache arrangement
#define EMAC_HAL_DMA_DESC_SIZE (64)
#else
#define EMAC_HAL_DMA_DESC_SIZE (32)
#endif
/* DMA descriptor control bits */
#define EMAC_HAL_TDES0_INTR_ON_COMPLET (1 << 30)
#define EMAC_HAL_TDES0_CRC_APPEND_DISABLE (1 << 27)
@@ -52,6 +38,7 @@ extern "C" {
/**
* @brief Ethernet DMA TX Descriptor
*
* Hardware layout is 32 bytes. Cache-line padding (e.g. 64B on ESP32-P4) must be applied at allocation time.
*/
typedef struct {
volatile union {
@@ -101,17 +88,13 @@ typedef struct {
uint32_t Reserved2; /*!< Reserved */
uint32_t TimeStampLow; /*!< Transmit Frame Timestamp Low */
uint32_t TimeStampHigh; /*!< Transmit Frame Timestamp High */
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
// descriptor must be aligned (due to cache arrangement)
uint8_t CacheAlign[EMAC_HAL_DMA_DESC_SIZE - 32]; // 32 is size of EMAC DMA descriptor without alignment
#endif
} eth_dma_tx_descriptor_t;
ASSERT_TYPE_SIZE(eth_dma_tx_descriptor_t, EMAC_HAL_DMA_DESC_SIZE);
ESP_STATIC_ASSERT(sizeof(eth_dma_tx_descriptor_t) == 32, "eth_dma_tx_descriptor_t should occupy 32 bytes in memory");
/**
* @brief Ethernet DMA RX Descriptor
*
* Hardware layout is 32 bytes. Cache-line padding (e.g. 64B on ESP32-P4) must be applied at allocation time.
*/
typedef struct {
volatile union {
@@ -179,14 +162,8 @@ typedef struct {
uint32_t Reserved; /*!< Reserved */
uint32_t TimeStampLow; /*!< Receive frame timestamp low */
uint32_t TimeStampHigh; /*!< Receive frame timestamp high */
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
// descriptor must be aligned (due to cache arrangement)
uint8_t CacheAlign[EMAC_HAL_DMA_DESC_SIZE - 32]; // 32 is size of EMAC DMA descriptor without alignment
#endif
} eth_dma_rx_descriptor_t;
ASSERT_TYPE_SIZE(eth_dma_rx_descriptor_t, EMAC_HAL_DMA_DESC_SIZE);
ESP_STATIC_ASSERT(sizeof(eth_dma_rx_descriptor_t) == 32, "eth_dma_rx_descriptor_t should occupy 32 bytes in memory");
typedef struct emac_mac_dev_s *emac_mac_soc_regs_t;
typedef struct emac_dma_dev_s *emac_dma_soc_regs_t;
+1 -1
View File
@@ -1,4 +1,4 @@
idf_component_register(SRCS "cmd_ethernet.c"
"ethernet_iperf_main.c"
PRIV_REQUIRES fatfs esp_netif esp_eth
PRIV_REQUIRES fatfs esp_netif esp_eth esp_psram
INCLUDE_DIRS ".")
+22 -3
View File
@@ -80,6 +80,8 @@ ETH_IPERF_THRESHOLDS_SPI_ETH = {
def test_esp_eth_iperf(
dut: Dut,
log_performance: Callable[[str, object], None],
tcp_tx_bw_lim: int | None = NO_BANDWIDTH_LIMIT,
tcp_rx_bw_lim: int | None = NO_BANDWIDTH_LIMIT,
udp_tx_bw_lim: int | None = NO_BANDWIDTH_LIMIT,
udp_rx_bw_lim: int | None = NO_BANDWIDTH_LIMIT,
spi_eth: bool | None = False,
@@ -105,8 +107,8 @@ def test_esp_eth_iperf(
test_utility = IperfTestUtilityEth(dut, 'ethernet', pc_nic_ip, pc_iperf_log_file, test_result)
# 3. run test for TCP Tx, Rx and UDP Tx, Rx
test_utility.run_test('tcp', 'tx', 0, NO_BANDWIDTH_LIMIT)
test_utility.run_test('tcp', 'rx', 0, NO_BANDWIDTH_LIMIT)
test_utility.run_test('tcp', 'tx', 0, tcp_tx_bw_lim)
test_utility.run_test('tcp', 'rx', 0, tcp_rx_bw_lim)
test_utility.run_test('udp', 'tx', 0, udp_tx_bw_lim)
test_utility.run_test('udp', 'rx', 0, udp_rx_bw_lim)
@@ -155,6 +157,7 @@ def test_esp_eth_iperf_ip101(
[
pytest.param('default_ip101_esp32p4', 'esp32p4', marks=[pytest.mark.eth_ip101]),
pytest.param('default_ip101_esp32p4v1', 'esp32p4', marks=[pytest.mark.eth_ip101, pytest.mark.esp32p4_rev1]),
pytest.param('psram_ip101_esp32p4', 'esp32p4', marks=[pytest.mark.eth_ip101]),
],
indirect=['target'],
)
@@ -290,4 +293,20 @@ def test_esp_eth_iperf_yt8531(
dut: Dut,
log_performance: Callable[[str, object], None],
) -> None:
test_esp_eth_iperf(dut, log_performance)
test_esp_eth_iperf(dut, log_performance, udp_rx_bw_lim=150)
@pytest.mark.eth_yt8531
@pytest.mark.parametrize(
'config',
[
'psram_yt8531_esp32s31',
],
indirect=True,
)
@idf_parametrize('target', ['esp32s31'], indirect=['target'])
def test_esp_eth_iperf_yt8531_psram(
dut: Dut,
log_performance: Callable[[str, object], None],
) -> None:
test_esp_eth_iperf(dut, log_performance, udp_rx_bw_lim=95)
@@ -0,0 +1,20 @@
# Common / performance options live in sdkconfig.defaults
CONFIG_IDF_TARGET="esp32p4"
CONFIG_ETH_ENABLED=y
CONFIG_ETH_USE_ESP32_EMAC=y
# Config Ethernet Init
CONFIG_ETHERNET_INTERNAL_SUPPORT=y
CONFIG_ETHERNET_PHY_IP101=y
CONFIG_ETHERNET_PHY_INTERFACE_RMII=y
CONFIG_ETHERNET_MDC_GPIO=31
CONFIG_ETHERNET_MDIO_GPIO=52
CONFIG_ETHERNET_PHY_RST_GPIO=51
CONFIG_ETHERNET_PHY_ADDR=1
# Allocate EMAC buffers in PSRAM
CONFIG_SPIRAM=y
CONFIG_SPIRAM_SPEED_200M=y # to able to derive RMII CLKs
CONFIG_ETH_DMA_USE_PSRAM=y
@@ -0,0 +1,15 @@
# Common / performance options live in sdkconfig.defaults
CONFIG_IDF_TARGET="esp32s31"
CONFIG_ETH_ENABLED=y
CONFIG_ETH_USE_ESP32_EMAC=y
# Config Ethernet Init
CONFIG_ETHERNET_INTERNAL_SUPPORT=y
CONFIG_ETHERNET_PHY_YT8531=y
# Allocate EMAC buffers in PSRAM
CONFIG_SPIRAM=y
CONFIG_SPIRAM_SPEED_250M=y # to able to derive RGMII CLKs
CONFIG_ETH_DMA_USE_PSRAM=y