mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
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:
@@ -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"
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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,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 ".")
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user