diff --git a/components/esp_eth/Kconfig b/components/esp_eth/Kconfig index e6aba32892d..47bd0936b00 100644 --- a/components/esp_eth/Kconfig +++ b/components/esp_eth/Kconfig @@ -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" diff --git a/components/esp_eth/src/mac/esp_eth_mac_esp_dma.c b/components/esp_eth/src/mac/esp_eth_mac_esp_dma.c index 46229e28811..138b35e0742 100644 --- a/components/esp_eth/src/mac/esp_eth_mac_esp_dma.c +++ b/components/esp_eth/src/mac/esp_eth_mac_esp_dma.c @@ -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); diff --git a/components/esp_eth/test_apps/test_app_driver/main/esp_eth_test_esp_emac.c b/components/esp_eth/test_apps/test_app_driver/main/esp_eth_test_esp_emac.c index 4a2c53e28a1..69d8b0afde8 100644 --- a/components/esp_eth/test_apps/test_app_driver/main/esp_eth_test_esp_emac.c +++ b/components/esp_eth/test_apps/test_app_driver/main/esp_eth_test_esp_emac.c @@ -9,6 +9,7 @@ #include #include +#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)); } diff --git a/components/esp_hal_emac/include/hal/emac_hal.h b/components/esp_hal_emac/include/hal/emac_hal.h index d2f2a62e099..e8c0e3228a6 100644 --- a/components/esp_hal_emac/include/hal/emac_hal.h +++ b/components/esp_hal_emac/include/hal/emac_hal.h @@ -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; diff --git a/examples/ethernet/iperf/main/CMakeLists.txt b/examples/ethernet/iperf/main/CMakeLists.txt index 2f10effdf36..57a06e4fc75 100644 --- a/examples/ethernet/iperf/main/CMakeLists.txt +++ b/examples/ethernet/iperf/main/CMakeLists.txt @@ -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 ".") diff --git a/examples/ethernet/iperf/pytest_eth_iperf.py b/examples/ethernet/iperf/pytest_eth_iperf.py index 83fa791c142..7d8a9ae4b86 100644 --- a/examples/ethernet/iperf/pytest_eth_iperf.py +++ b/examples/ethernet/iperf/pytest_eth_iperf.py @@ -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) diff --git a/examples/ethernet/iperf/sdkconfig.ci.psram_ip101_esp32p4 b/examples/ethernet/iperf/sdkconfig.ci.psram_ip101_esp32p4 new file mode 100644 index 00000000000..fa0ec65b78d --- /dev/null +++ b/examples/ethernet/iperf/sdkconfig.ci.psram_ip101_esp32p4 @@ -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 diff --git a/examples/ethernet/iperf/sdkconfig.ci.psram_yt8531_esp32s31 b/examples/ethernet/iperf/sdkconfig.ci.psram_yt8531_esp32s31 new file mode 100644 index 00000000000..943e00510b8 --- /dev/null +++ b/examples/ethernet/iperf/sdkconfig.ci.psram_yt8531_esp32s31 @@ -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