From f104be7b930c96aaa04e5d1894016ccf549f76be Mon Sep 17 00:00:00 2001 From: Ondrej Date: Thu, 20 Oct 2022 09:40:56 +0000 Subject: [PATCH] esp_eth: receive buffer allocation optimization Receive buffers are allocated with a size equal to actual received frame size --- components/esp_eth/src/esp_eth_mac_dm9051.c | 249 +++++++++++++++----- components/esp_eth/src/esp_eth_mac_esp32.c | 33 ++- components/esp_eth/src/esp_eth_mac_w5500.c | 241 ++++++++++++++----- components/hal/esp32/emac_hal.c | 169 +++++++++++-- components/hal/esp32/include/hal/emac.h | 49 ++++ 5 files changed, 580 insertions(+), 161 deletions(-) diff --git a/components/esp_eth/src/esp_eth_mac_dm9051.c b/components/esp_eth/src/esp_eth_mac_dm9051.c index 9ac8193464..7b7ffefd17 100644 --- a/components/esp_eth/src/esp_eth_mac_dm9051.c +++ b/components/esp_eth/src/esp_eth_mac_dm9051.c @@ -39,6 +39,15 @@ static const char *TAG = "emac_dm9051"; #define DM9051_SPI_LOCK_TIMEOUT_MS (50) #define DM9051_PHY_OPERATION_TIMEOUT_US (1000) +#define DM9051_RX_MEM_START_ADDR (3072) +#define DM9051_RX_MEM_MAX_SIZE (16384) +#define DM9051_RX_HDR_SIZE (4) +#define DM9051_ETH_MAC_RX_BUF_SIZE_AUTO (0) + +typedef struct { + uint32_t copy_len; + uint32_t byte_cnt; +}__attribute__((packed)) dm9051_auto_buf_info_t; typedef struct { uint8_t flag; @@ -58,6 +67,7 @@ typedef struct { uint8_t addr[6]; bool packets_remain; bool flow_ctrl_enabled; + uint8_t *rx_buffer; } emac_dm9051_t; static inline bool dm9051_lock(emac_dm9051_t *emac) @@ -394,44 +404,6 @@ IRAM_ATTR static void dm9051_isr_handler(void *arg) } } -static void emac_dm9051_task(void *arg) -{ - emac_dm9051_t *emac = (emac_dm9051_t *)arg; - uint8_t status = 0; - uint8_t *buffer = NULL; - uint32_t length = 0; - while (1) { - // check if the task receives any notification - if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)) == 0 && // if no notification ... - gpio_get_level(emac->int_gpio_num) == 0) { // ...and no interrupt asserted - continue; // -> just continue to check again - } - /* clear interrupt status */ - dm9051_register_read(emac, DM9051_ISR, &status); - dm9051_register_write(emac, DM9051_ISR, status); - /* packet received */ - if (status & ISR_PR) { - do { - length = ETH_MAX_PACKET_SIZE; - buffer = heap_caps_malloc(length, MALLOC_CAP_DMA); - if (!buffer) { - ESP_LOGE(TAG, "no mem for receive buffer"); - } else if (emac->parent.receive(&emac->parent, buffer, &length) == ESP_OK) { - /* pass the buffer to stack (e.g. TCP/IP layer) */ - if (length) { - emac->eth->stack_input(emac->eth, buffer, length); - } else { - free(buffer); - } - } else { - free(buffer); - } - } while (emac->packets_remain); - } - } - vTaskDelete(NULL); -} - static esp_err_t emac_dm9051_set_mediator(esp_eth_mac_t *mac, esp_eth_mediator_t *eth) { esp_err_t ret = ESP_OK; @@ -643,6 +615,9 @@ static esp_err_t emac_dm9051_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32_t /* Check if last transmit complete */ uint8_t tcr = 0; + MAC_CHECK(length <= ETH_MAX_PACKET_SIZE,"frame size is too big (actual %u, maximum %u)", err, ESP_ERR_INVALID_ARG, + length, ETH_MAX_PACKET_SIZE); + int64_t wait_time = esp_timer_get_time(); do { MAC_CHECK(dm9051_register_read(emac, DM9051_TCR, &tcr) == ESP_OK, "read TCR failed", err, ESP_FAIL); @@ -665,50 +640,138 @@ err: return ret; } -static esp_err_t emac_dm9051_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length) +static esp_err_t dm9051_skip_recv_frame(emac_dm9051_t *emac, uint16_t rx_length) +{ + esp_err_t ret = ESP_OK; + uint8_t mrrh, mrrl; + MAC_CHECK(dm9051_register_read(emac, DM9051_MRRH, &mrrh) == ESP_OK, "read MDRAH failed", err, ESP_FAIL); + MAC_CHECK(dm9051_register_read(emac, DM9051_MRRL, &mrrl) == ESP_OK, "read MDRAL failed", err, ESP_FAIL); + uint16_t addr = mrrh << 8 | mrrl; + /* include 4B for header */ + addr += rx_length + DM9051_RX_HDR_SIZE; + if (addr > DM9051_RX_MEM_MAX_SIZE) { + addr = addr - DM9051_RX_MEM_MAX_SIZE + DM9051_RX_MEM_START_ADDR; + } + MAC_CHECK(dm9051_register_write(emac, DM9051_MRRH, addr >> 8) == ESP_OK, "write MDRAH failed", err, ESP_FAIL); + MAC_CHECK(dm9051_register_write(emac, DM9051_MRRL, addr & 0xFF) == ESP_OK, "write MDRAL failed", err, ESP_FAIL); +err: + return ret; +} + +static esp_err_t dm9051_get_recv_byte_count(emac_dm9051_t *emac, uint16_t *size) { esp_err_t ret = ESP_OK; - emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent); uint8_t rxbyte = 0; - uint16_t rx_len = 0; __attribute__((aligned(4))) dm9051_rx_header_t header; // SPI driver needs the rx buffer 4 byte align - emac->packets_remain = false; + + *size = 0; /* dummy read, get the most updated data */ MAC_CHECK(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte) == ESP_OK, "read MRCMDX failed", err, ESP_FAIL); MAC_CHECK(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte) == ESP_OK, "read MRCMDX failed", err, ESP_FAIL); /* rxbyte must be 0xFF, 0 or 1 */ if (rxbyte > 1) { - MAC_CHECK(mac->stop(mac) == ESP_OK, "stop dm9051 failed", err, ESP_FAIL); + MAC_CHECK(emac->parent.stop(&emac->parent) == ESP_OK, "stop dm9051 failed", err, ESP_FAIL); /* reset rx fifo pointer */ MAC_CHECK(dm9051_register_write(emac, DM9051_MPTRCR, MPTRCR_RST_RX) == ESP_OK, "write MPTRCR failed", err, ESP_FAIL); esp_rom_delay_us(10); - MAC_CHECK(mac->start(mac) == ESP_OK, "start dm9051 failed", err, ESP_FAIL); + MAC_CHECK(emac->parent.start(&emac->parent) == ESP_OK, "start dm9051 failed", err, ESP_FAIL); MAC_CHECK(false, "reset rx fifo pointer", err, ESP_FAIL); } else if (rxbyte) { - MAC_CHECK(dm9051_memory_peek(emac, (uint8_t *)&header, sizeof(header)) == ESP_OK, - "peek rx header failed", err, ESP_FAIL); - rx_len = header.length_low + (header.length_high << 8); - /* check if the buffer can hold all the incoming data */ - if (*length < rx_len - 4) { - ESP_LOGE(TAG, "buffer size too small, needs %d", rx_len - 4); - /* tell upper layer the size we need */ - *length = rx_len - 4; - ret = ESP_ERR_INVALID_SIZE; + MAC_CHECK(dm9051_memory_peek(emac, (uint8_t *)&header, sizeof(header)) == ESP_OK, "peek rx header failed", err, ESP_FAIL); + uint16_t rx_len = header.length_low + (header.length_high << 8); + if (header.status & 0xBF) { + /* erroneous frames should not be forwarded by DM9051, however, if it happens, just skip it */ + dm9051_skip_recv_frame(emac, rx_len); + MAC_CHECK(false, "receive status error: %xH", err, ESP_FAIL, header.status); + } + *size = rx_len; + } +err: + return ret; +} + +static esp_err_t dm9051_flush_recv_frame(emac_dm9051_t *emac) +{ + esp_err_t ret = ESP_OK; + uint16_t rx_len; + MAC_CHECK(dm9051_get_recv_byte_count(emac, &rx_len) == ESP_OK, "get rx frame length failed", err, ESP_FAIL); + MAC_CHECK(dm9051_skip_recv_frame(emac, rx_len) == ESP_OK, "skipping frame in RX memory failed", err, ESP_FAIL); +err: + return ret; +} + +static esp_err_t dm9051_alloc_recv_buf(emac_dm9051_t *emac, uint8_t **buf, uint32_t *length) +{ + esp_err_t ret = ESP_OK; + uint16_t rx_len = 0; + uint16_t byte_count; + *buf = NULL; + + MAC_CHECK(dm9051_get_recv_byte_count(emac, &byte_count) == ESP_OK, "get rx frame length failed", err, ESP_FAIL); + // silently return when no frame is waiting + if (!byte_count) { + goto err; + } + // do not include 4 bytes CRC at the end + rx_len = byte_count - ETH_CRC_LEN; + // frames larger than expected will be truncated + uint16_t copy_len = rx_len > *length ? *length : rx_len; + // runt frames are not forwarded, but check the length anyway since it could be corrupted at SPI bus + MAC_CHECK(copy_len >= ETH_MIN_PACKET_SIZE - ETH_CRC_LEN, "invalid frame length %u", err, ESP_ERR_INVALID_SIZE, copy_len); + *buf = malloc(copy_len); + if (*buf != NULL) { + dm9051_auto_buf_info_t *buff_info = (dm9051_auto_buf_info_t *)*buf; + buff_info->copy_len = copy_len; + buff_info->byte_cnt = byte_count; + } else { + ret = ESP_ERR_NO_MEM; + goto err; + } +err: + *length = rx_len; + return ret; +} + +static esp_err_t emac_dm9051_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length) +{ + esp_err_t ret = ESP_OK; + emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent); + uint16_t rx_len = 0; + uint8_t rxbyte; + uint16_t copy_len = 0; + uint16_t byte_count = 0; + emac->packets_remain = false; + + if (*length != DM9051_ETH_MAC_RX_BUF_SIZE_AUTO) { + MAC_CHECK(dm9051_get_recv_byte_count(emac, &byte_count) == ESP_OK,"get rx frame length failed", err, ESP_FAIL); + /* silently return when no frame is waiting */ + if (!byte_count) { goto err; } - MAC_CHECK(dm9051_memory_read(emac, (uint8_t *)&header, sizeof(header)) == ESP_OK, - "read rx header failed", err, ESP_FAIL); - MAC_CHECK(dm9051_memory_read(emac, buf, rx_len) == ESP_OK, "read rx data failed", err, ESP_FAIL); - MAC_CHECK(!(header.status & 0xBF), "receive status error: %xH", err, ESP_FAIL, header.status); - *length = rx_len - 4; // substract the CRC length (4Bytes) - /* dummy read, get the most updated data */ - MAC_CHECK(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte) == ESP_OK, "read MRCMDX failed", err, ESP_FAIL); - MAC_CHECK(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte) == ESP_OK, "read MRCMDX failed", err, ESP_FAIL); - emac->packets_remain = rxbyte > 0; + /* do not include 4 bytes CRC at the end */ + rx_len = byte_count - ETH_CRC_LEN; + /* frames larger than expected will be truncated */ + copy_len = rx_len > *length ? *length : rx_len; + } else { + dm9051_auto_buf_info_t *buff_info = (dm9051_auto_buf_info_t *)buf; + copy_len = buff_info->copy_len; + byte_count = buff_info->byte_cnt; } + + byte_count += DM9051_RX_HDR_SIZE; + MAC_CHECK(dm9051_memory_read(emac, emac->rx_buffer, byte_count) == ESP_OK, "read rx data failed", err, ESP_FAIL); + memcpy(buf, emac->rx_buffer + DM9051_RX_HDR_SIZE, copy_len); + *length = copy_len; + + /* dummy read, get the most updated data */ + MAC_CHECK(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte) == ESP_OK, "read MRCMDX failed", err, ESP_FAIL); + /* check for remaing packets */ + MAC_CHECK(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte) == ESP_OK, "read MRCMDX failed", err, ESP_FAIL); + emac->packets_remain = rxbyte > 0; return ESP_OK; err: + *length = 0; return ret; } @@ -753,11 +816,68 @@ static esp_err_t emac_dm9051_deinit(esp_eth_mac_t *mac) return ESP_OK; } +static void emac_dm9051_task(void *arg) +{ + emac_dm9051_t *emac = (emac_dm9051_t *)arg; + uint8_t status = 0; + esp_err_t ret; + while (1) { + // check if the task receives any notification + if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)) == 0 && // if no notification ... + gpio_get_level(emac->int_gpio_num) == 0) { // ...and no interrupt asserted + continue; // -> just continue to check again + } + /* clear interrupt status */ + dm9051_register_read(emac, DM9051_ISR, &status); + dm9051_register_write(emac, DM9051_ISR, status); + /* packet received */ + if (status & ISR_PR) { + do { + /* define max expected frame len */ + uint32_t frame_len = ETH_MAX_PACKET_SIZE; + uint8_t *buffer; + if ((ret = dm9051_alloc_recv_buf(emac, &buffer, &frame_len)) == ESP_OK) { + if (buffer != NULL) { + /* we have memory to receive the frame of maximal size previously defined */ + uint32_t buf_len = DM9051_ETH_MAC_RX_BUF_SIZE_AUTO; + if (emac->parent.receive(&emac->parent, buffer, &buf_len) == ESP_OK) { + if (buf_len == 0) { + dm9051_flush_recv_frame(emac); + free(buffer); + } else if (frame_len > buf_len) { + ESP_LOGE(TAG, "received frame was truncated"); + free(buffer); + } else { + ESP_LOGD(TAG, "receive len=%u", buf_len); + /* pass the buffer to stack (e.g. TCP/IP layer) */ + emac->eth->stack_input(emac->eth, buffer, buf_len); + } + } else { + ESP_LOGE(TAG, "frame read from module failed"); + dm9051_flush_recv_frame(emac); + free(buffer); + } + } else if (frame_len) { + ESP_LOGE(TAG, "invalid combination of frame_len(%u) and buffer pointer(%p)", frame_len, buffer); + } + } else if (ret == ESP_ERR_NO_MEM) { + ESP_LOGE(TAG, "no mem for receive buffer"); + dm9051_flush_recv_frame(emac); + } else { + ESP_LOGE(TAG, "unexpected error 0x%x", ret); + } + } while (emac->packets_remain); + } + } + vTaskDelete(NULL); +} + static esp_err_t emac_dm9051_del(esp_eth_mac_t *mac) { emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent); vTaskDelete(emac->rx_task_hdl); vSemaphoreDelete(emac->spi_lock); + heap_caps_free(emac->rx_buffer); free(emac); return ESP_OK; } @@ -805,6 +925,10 @@ esp_eth_mac_t *esp_eth_mac_new_dm9051(const eth_dm9051_config_t *dm9051_config, BaseType_t xReturned = xTaskCreatePinnedToCore(emac_dm9051_task, "dm9051_tsk", mac_config->rx_task_stack_size, emac, mac_config->rx_task_prio, &emac->rx_task_hdl, core_num); MAC_CHECK(xReturned == pdPASS, "create dm9051 task failed", err, NULL); + + emac->rx_buffer = heap_caps_malloc(ETH_MAX_PACKET_SIZE + DM9051_RX_HDR_SIZE, MALLOC_CAP_DMA); + MAC_CHECK(emac->rx_buffer, "RX buffer allocation failed", err, NULL); + return &(emac->parent); err: @@ -815,6 +939,7 @@ err: if (emac->spi_lock) { vSemaphoreDelete(emac->spi_lock); } + heap_caps_free(emac->rx_buffer); free(emac); } return ret; diff --git a/components/esp_eth/src/esp_eth_mac_esp32.c b/components/esp_eth/src/esp_eth_mac_esp32.c index e72d6897d2..bd82b8baa8 100644 --- a/components/esp_eth/src/esp_eth_mac_esp32.c +++ b/components/esp_eth/src/esp_eth_mac_esp32.c @@ -286,24 +286,33 @@ static void emac_esp32_rx_task(void *arg) { emac_esp32_t *emac = (emac_esp32_t *)arg; uint8_t *buffer = NULL; - uint32_t length = 0; while (1) { // block indefinitely until got notification from underlay event ulTaskNotifyTake(pdTRUE, portMAX_DELAY); do { - length = ETH_MAX_PACKET_SIZE; - buffer = malloc(length); - if (!buffer) { - ESP_LOGE(TAG, "no mem for receive buffer"); - } else if (emac_esp32_receive(&emac->parent, buffer, &length) == ESP_OK) { - /* pass the buffer to stack (e.g. TCP/IP layer) */ - if (length) { - emac->eth->stack_input(emac->eth, buffer, length); - } else { + /* set max expected frame len */ + uint32_t frame_len = ETH_MAX_PACKET_SIZE; + buffer = emac_hal_alloc_recv_buf(&emac->hal, &frame_len); + if (buffer != NULL) { + /* we have memory to receive the frame of maximal size previously defined */ + uint32_t recv_len = emac_hal_receive_frame(&emac->hal, buffer, EMAC_HAL_BUF_SIZE_AUTO, &emac->frames_remain, &emac->free_rx_descriptor); + if (recv_len == 0) { + ESP_LOGE(TAG, "frame copy error"); free(buffer); + /* ensure that interface to EMAC does not get stuck with unprocessed frames */ + emac_hal_flush_recv_frame(&emac->hal, &emac->frames_remain, &emac->free_rx_descriptor); + } else if (frame_len > recv_len) { + ESP_LOGE(TAG, "received frame was truncated"); + free(buffer); + } else { + ESP_LOGD(TAG, "receive len= %d", recv_len); + emac->eth->stack_input(emac->eth, buffer, recv_len); } - } else { - free(buffer); + /* if allocation failed and there is a waiting frame */ + } else if (frame_len) { + ESP_LOGE(TAG, "no mem for receive buffer"); + /* ensure that interface to EMAC does not get stuck with unprocessed frames */ + emac_hal_flush_recv_frame(&emac->hal, &emac->frames_remain, &emac->free_rx_descriptor); } #if CONFIG_ETH_SOFT_FLOW_CONTROL // we need to do extra checking of remained frames in case there are no unhandled frames left, but pause frame is still undergoing diff --git a/components/esp_eth/src/esp_eth_mac_w5500.c b/components/esp_eth/src/esp_eth_mac_w5500.c index e7f5def779..f3c1ebcd29 100644 --- a/components/esp_eth/src/esp_eth_mac_w5500.c +++ b/components/esp_eth/src/esp_eth_mac_w5500.c @@ -43,6 +43,14 @@ static const char *TAG = "w5500-mac"; #define W5500_SPI_LOCK_TIMEOUT_MS (50) #define W5500_TX_MEM_SIZE (0x4000) #define W5500_RX_MEM_SIZE (0x4000) +#define W5500_ETH_MAC_RX_BUF_SIZE_AUTO (0) + +typedef struct { + uint32_t offset; + uint32_t copy_len; + uint32_t rx_len; + uint32_t remain; +}__attribute__((packed)) emac_w5500_auto_buf_info_t; typedef struct { esp_eth_mac_t parent; @@ -54,6 +62,7 @@ typedef struct { int int_gpio_num; uint8_t addr[6]; bool packets_remain; + uint8_t *rx_buffer; } emac_w5500_t; static inline bool w5500_lock(emac_w5500_t *emac) @@ -307,59 +316,6 @@ err: return ret; } -IRAM_ATTR static void w5500_isr_handler(void *arg) -{ - emac_w5500_t *emac = (emac_w5500_t *)arg; - BaseType_t high_task_wakeup = pdFALSE; - /* notify w5500 task */ - vTaskNotifyGiveFromISR(emac->rx_task_hdl, &high_task_wakeup); - if (high_task_wakeup != pdFALSE) { - portYIELD_FROM_ISR(); - } -} - -static void emac_w5500_task(void *arg) -{ - emac_w5500_t *emac = (emac_w5500_t *)arg; - uint8_t status = 0; - uint8_t *buffer = NULL; - uint32_t length = 0; - while (1) { - // check if the task receives any notification - if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)) == 0 && // if no notification ... - gpio_get_level(emac->int_gpio_num) != 0) { // ...and no interrupt asserted - continue; // -> just continue to check again - } - - /* read interrupt status */ - w5500_read(emac, W5500_REG_SOCK_IR(0), &status, sizeof(status)); - /* packet received */ - if (status & W5500_SIR_RECV) { - status = W5500_SIR_RECV; - // clear interrupt status - w5500_write(emac, W5500_REG_SOCK_IR(0), &status, sizeof(status)); - do { - length = ETH_MAX_PACKET_SIZE; - buffer = heap_caps_malloc(length, MALLOC_CAP_DMA); - if (!buffer) { - ESP_LOGE(TAG, "no mem for receive buffer"); - break; - } else if (emac->parent.receive(&emac->parent, buffer, &length) == ESP_OK) { - /* pass the buffer to stack (e.g. TCP/IP layer) */ - if (length) { - emac->eth->stack_input(emac->eth, buffer, length); - } else { - free(buffer); - } - } else { - free(buffer); - } - } while (emac->packets_remain); - } - } - vTaskDelete(NULL); -} - static esp_err_t emac_w5500_set_mediator(esp_eth_mac_t *mac, esp_eth_mediator_t *eth) { esp_err_t ret = ESP_OK; @@ -525,6 +481,8 @@ static esp_err_t emac_w5500_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32_t emac_w5500_t *emac = __containerof(mac, emac_w5500_t, parent); uint16_t offset = 0; + MAC_CHECK(length <= ETH_MAX_PACKET_SIZE, "frame size is too big (actual %u, maximum %u)", err, ESP_ERR_INVALID_ARG, + length, ETH_MAX_PACKET_SIZE); // check if there're free memory to store this packet uint16_t free_size = 0; MAC_CHECK(w5500_get_tx_free_size(emac, &free_size) == ESP_OK, "get free size failed", err, ESP_FAIL); @@ -558,12 +516,103 @@ err: return ret; } +static esp_err_t emac_w5500_alloc_recv_buf(emac_w5500_t *emac, uint8_t **buf, uint32_t *length) +{ + esp_err_t ret = ESP_OK; + uint16_t offset = 0; + uint16_t rx_len = 0; + uint32_t copy_len = 0; + uint16_t remain_bytes = 0; + *buf = NULL; + + w5500_get_rx_received_size(emac, &remain_bytes); + if (remain_bytes) { + // get current read pointer + MAC_CHECK(w5500_read(emac, W5500_REG_SOCK_RX_RD(0), &offset, sizeof(offset)) == ESP_OK, "read RX RD failed", err, ESP_FAIL); + offset = __builtin_bswap16(offset); + // read head + MAC_CHECK(w5500_read_buffer(emac, &rx_len, sizeof(rx_len), offset) == ESP_OK, "read frame header failed", err, ESP_FAIL); + rx_len = __builtin_bswap16(rx_len) - 2; // data size includes 2 bytes of header + // frames larger than expected will be truncated + copy_len = rx_len > *length ? *length : rx_len; + // runt frames are not forwarded by W5500 (tested on target), but check the length anyway since it could be corrupted at SPI bus + MAC_CHECK(copy_len >= ETH_MIN_PACKET_SIZE - ETH_CRC_LEN, "invalid frame length %u", err, ESP_ERR_INVALID_SIZE, copy_len); + *buf = malloc(copy_len); + if (*buf != NULL) { + emac_w5500_auto_buf_info_t *buff_info = (emac_w5500_auto_buf_info_t *)*buf; + buff_info->offset = offset; + buff_info->copy_len = copy_len; + buff_info->rx_len = rx_len; + buff_info->remain = remain_bytes; + } else { + ret = ESP_ERR_NO_MEM; + goto err; + } + } +err: + *length = rx_len; + return ret; +} + static esp_err_t emac_w5500_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length) { esp_err_t ret = ESP_OK; emac_w5500_t *emac = __containerof(mac, emac_w5500_t, parent); uint16_t offset = 0; uint16_t rx_len = 0; + uint16_t copy_len = 0; + uint16_t remain_bytes = 0; + emac->packets_remain = false; + + if (*length != W5500_ETH_MAC_RX_BUF_SIZE_AUTO) { + w5500_get_rx_received_size(emac, &remain_bytes); + if (remain_bytes) { + // get current read pointer + MAC_CHECK(w5500_read(emac, W5500_REG_SOCK_RX_RD(0) == ESP_OK, &offset, sizeof(offset)), "read RX RD failed", err, ESP_FAIL); + offset = __builtin_bswap16(offset); + // read head first + MAC_CHECK(w5500_read_buffer(emac, &rx_len, sizeof(rx_len), offset) == ESP_OK, "read frame header failed", err, ESP_FAIL); + rx_len = __builtin_bswap16(rx_len) - 2; // data size includes 2 bytes of header + // frames larger than expected will be truncated + copy_len = rx_len > *length ? *length : rx_len; + } else { + // silently return when no frame is waiting + goto err; + } + } else { + emac_w5500_auto_buf_info_t *buff_info = (emac_w5500_auto_buf_info_t *)buf; + offset = buff_info->offset; + copy_len = buff_info->copy_len; + rx_len = buff_info->rx_len; + remain_bytes = buff_info->remain; + } + // 2 bytes of header + offset += 2; + // read the payload + MAC_CHECK(w5500_read_buffer(emac, emac->rx_buffer, copy_len, offset) == ESP_OK, "read payload failed, len=%d, offset=%d", err, ESP_FAIL, rx_len, offset); + memcpy(buf, emac->rx_buffer, copy_len); + offset += rx_len; + // update read pointer + offset = __builtin_bswap16(offset); + MAC_CHECK(w5500_write(emac, W5500_REG_SOCK_RX_RD(0), &offset, sizeof(offset)) == ESP_OK, "write RX RD failed", err, ESP_FAIL); + /* issue RECV command */ + MAC_CHECK(w5500_send_command(emac, W5500_SCR_RECV, 100) == ESP_OK, "issue RECV command failed", err, ESP_FAIL); + // check if there're more data need to process + remain_bytes -= rx_len + 2; + emac->packets_remain = remain_bytes > 0; + + *length = copy_len; + return ret; +err: + *length = 0; + return ret; +} + +static esp_err_t emac_w5500_flush_recv_frame(emac_w5500_t *emac) +{ + esp_err_t ret = ESP_OK; + uint16_t offset = 0; + uint16_t rx_len = 0; uint16_t remain_bytes = 0; emac->packets_remain = false; @@ -574,26 +623,90 @@ static esp_err_t emac_w5500_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t * offset = __builtin_bswap16(offset); // read head first MAC_CHECK(w5500_read_buffer(emac, &rx_len, sizeof(rx_len), offset) == ESP_OK, "read frame header failed", err, ESP_FAIL); - rx_len = __builtin_bswap16(rx_len) - 2; // data size includes 2 bytes of header - offset += 2; - // read the payload - MAC_CHECK(w5500_read_buffer(emac, buf, rx_len, offset) == ESP_OK, "read payload failed, len=%d, offset=%d", err, ESP_FAIL, rx_len, offset); - offset += rx_len; // update read pointer + rx_len = __builtin_bswap16(rx_len); + offset += rx_len; offset = __builtin_bswap16(offset); MAC_CHECK(w5500_write(emac, W5500_REG_SOCK_RX_RD(0), &offset, sizeof(offset)) == ESP_OK, "write RX RD failed", err, ESP_FAIL); /* issue RECV command */ MAC_CHECK(w5500_send_command(emac, W5500_SCR_RECV, 100) == ESP_OK, "issue RECV command failed", err, ESP_FAIL); // check if there're more data need to process - remain_bytes -= rx_len + 2; + remain_bytes -= rx_len; emac->packets_remain = remain_bytes > 0; } - - *length = rx_len; err: return ret; } +IRAM_ATTR static void w5500_isr_handler(void *arg) +{ + emac_w5500_t *emac = (emac_w5500_t *)arg; + BaseType_t high_task_wakeup = pdFALSE; + /* notify w5500 task */ + vTaskNotifyGiveFromISR(emac->rx_task_hdl, &high_task_wakeup); + if (high_task_wakeup != pdFALSE) { + portYIELD_FROM_ISR(); + } +} + +static void emac_w5500_task(void *arg) +{ + emac_w5500_t *emac = (emac_w5500_t *)arg; + uint8_t status = 0; + uint8_t *buffer = NULL; + uint32_t frame_len = 0; + uint32_t buf_len = 0; + esp_err_t ret; + while (1) { + /* check if the task receives any notification */ + if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)) == 0 && // if no notification ... + gpio_get_level(emac->int_gpio_num) != 0) { // ...and no interrupt asserted + continue; // -> just continue to check again + } + /* read interrupt status */ + w5500_read(emac, W5500_REG_SOCK_IR(0), &status, sizeof(status)); + /* packet received */ + if (status & W5500_SIR_RECV) { + status = W5500_SIR_RECV; + /* clear interrupt status */ + w5500_write(emac, W5500_REG_SOCK_IR(0), &status, sizeof(status)); + do { + /* define max expected frame len */ + frame_len = ETH_MAX_PACKET_SIZE; + if ((ret = emac_w5500_alloc_recv_buf(emac, &buffer, &frame_len)) == ESP_OK) { + if (buffer != NULL) { + /* we have memory to receive the frame of maximal size previously defined */ + buf_len = W5500_ETH_MAC_RX_BUF_SIZE_AUTO; + if (emac->parent.receive(&emac->parent, buffer, &buf_len) == ESP_OK) { + if (buf_len == 0) { + free(buffer); + } else if (frame_len > buf_len) { + ESP_LOGE(TAG, "received frame was truncated"); + free(buffer); + } else { + ESP_LOGD(TAG, "receive len=%u", buf_len); + /* pass the buffer to stack (e.g. TCP/IP layer) */ + emac->eth->stack_input(emac->eth, buffer, buf_len); + } + } else { + ESP_LOGE(TAG, "frame read from module failed"); + free(buffer); + } + } else if (frame_len) { + ESP_LOGE(TAG, "invalid combination of frame_len(%u) and buffer pointer(%p)", frame_len, buffer); + } + } else if (ret == ESP_ERR_NO_MEM) { + ESP_LOGE(TAG, "no mem for receive buffer"); + emac_w5500_flush_recv_frame(emac); + } else { + ESP_LOGE(TAG, "unexpected error 0x%x", ret); + } + } while (emac->packets_remain); + } + } + vTaskDelete(NULL); +} + static esp_err_t emac_w5500_init(esp_eth_mac_t *mac) { esp_err_t ret = ESP_OK; @@ -636,6 +749,7 @@ static esp_err_t emac_w5500_del(esp_eth_mac_t *mac) emac_w5500_t *emac = __containerof(mac, emac_w5500_t, parent); vTaskDelete(emac->rx_task_hdl); vSemaphoreDelete(emac->spi_lock); + heap_caps_free(emac->rx_buffer); free(emac); return ESP_OK; } @@ -682,6 +796,10 @@ esp_eth_mac_t *esp_eth_mac_new_w5500(const eth_w5500_config_t *w5500_config, con BaseType_t xReturned = xTaskCreatePinnedToCore(emac_w5500_task, "w5500_tsk", mac_config->rx_task_stack_size, emac, mac_config->rx_task_prio, &emac->rx_task_hdl, core_num); MAC_CHECK(xReturned == pdPASS, "create w5500 task failed", err, NULL); + + emac->rx_buffer = heap_caps_malloc(ETH_MAX_PACKET_SIZE, MALLOC_CAP_DMA); + MAC_CHECK(emac->rx_buffer, "RX buffer allocation failed", err, NULL); + return &(emac->parent); err: @@ -692,6 +810,7 @@ err: if (emac->spi_lock) { vSemaphoreDelete(emac->spi_lock); } + heap_caps_free(emac->rx_buffer); free(emac); } return ret; diff --git a/components/hal/esp32/emac_hal.c b/components/hal/esp32/emac_hal.c index 4484eeb24e..f24b7af80d 100644 --- a/components/hal/esp32/emac_hal.c +++ b/components/hal/esp32/emac_hal.c @@ -21,6 +21,18 @@ #define ETH_CRC_LENGTH (4) +#ifndef NDEBUG +#define EMAC_HAL_BUF_MAGIC_ID 0x1E1C8416 +#endif // NDEBUG + +typedef struct { +#ifndef NDEBUG + uint32_t magic_id; +#endif // NDEBUG + uint32_t copy_len; +}__attribute__((packed)) emac_hal_auto_buf_info_t; + + #if CONFIG_ETH_RMII_CLK_OUTPUT static void emac_config_apll_clock(void) { @@ -531,41 +543,85 @@ err: return 0; } -uint32_t emac_hal_receive_frame(emac_hal_context_t *hal, uint8_t *buf, uint32_t size, uint32_t *frames_remain, uint32_t *free_desc) +uint8_t *emac_hal_alloc_recv_buf(emac_hal_context_t *hal, uint32_t *size) { - eth_dma_rx_descriptor_t *desc_iter = NULL; - eth_dma_rx_descriptor_t *first_desc = NULL; + eth_dma_rx_descriptor_t *desc_iter = hal->rx_desc; uint32_t used_descs = 0; - uint32_t seg_count = 0; uint32_t ret_len = 0; uint32_t copy_len = 0; - uint32_t write_len = 0; - uint32_t frame_count = 0; + uint8_t *buf = NULL; - first_desc = hal->rx_desc; - desc_iter = hal->rx_desc; /* Traverse descriptors owned by CPU */ - while ((desc_iter->RDES0.Own != EMAC_DMADESC_OWNER_DMA) && (used_descs < CONFIG_ETH_DMA_RX_BUFFER_NUM) && !frame_count) { + while ((desc_iter->RDES0.Own != EMAC_DMADESC_OWNER_DMA) && (used_descs < CONFIG_ETH_DMA_RX_BUFFER_NUM)) { used_descs++; - seg_count++; /* Last segment in frame */ if (desc_iter->RDES0.LastDescriptor) { /* Get the Frame Length of the received packet: substruct 4 bytes of the CRC */ ret_len = desc_iter->RDES0.FrameLength - ETH_CRC_LENGTH; /* packets larger than expected will be truncated */ - copy_len = ret_len > size ? size : ret_len; - /* update unhandled frame count */ - frame_count++; - } - /* First segment in frame */ - if (desc_iter->RDES0.FirstDescriptor) { - first_desc = desc_iter; + copy_len = ret_len > *size ? *size : ret_len; + break; } /* point to next descriptor */ desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr); } - /* there's at least one frame to process */ - if (frame_count) { + if (copy_len > 0) { + buf = malloc(copy_len); + if (buf != NULL) { + emac_hal_auto_buf_info_t *buff_info = (emac_hal_auto_buf_info_t *)buf; + /* no need to check allocated buffer min lenght prior writing since we know that EMAC DMA is configured to + not forward erroneous or undersized frames (less than 64B), see emac_hal_init_dma_default */ +#ifndef NDEBUG + buff_info->magic_id = EMAC_HAL_BUF_MAGIC_ID; +#endif // NDEBUG + buff_info->copy_len = copy_len; + } + } + /* indicate actual size of received frame */ + *size = ret_len; + return buf; +} + +uint32_t emac_hal_receive_frame(emac_hal_context_t *hal, uint8_t *buf, uint32_t size, uint32_t *frames_remain, uint32_t *free_desc) +{ + eth_dma_rx_descriptor_t *desc_iter = hal->rx_desc; + eth_dma_rx_descriptor_t *first_desc = hal->rx_desc; + uint32_t used_descs = 0; + uint32_t ret_len = 0; + uint32_t copy_len = 0; + uint32_t frame_count = 0; + + if (size != EMAC_HAL_BUF_SIZE_AUTO) { + /* Traverse descriptors owned by CPU */ + while ((desc_iter->RDES0.Own != EMAC_DMADESC_OWNER_DMA) && (used_descs < CONFIG_ETH_DMA_RX_BUFFER_NUM) && !frame_count) { + used_descs++; + /* Last segment in frame */ + if (desc_iter->RDES0.LastDescriptor) { + /* Get the Frame Length of the received packet: substruct 4 bytes of the CRC */ + ret_len = desc_iter->RDES0.FrameLength - ETH_CRC_LENGTH; + /* packets larger than expected will be truncated */ + copy_len = ret_len > size ? size : ret_len; + /* update unhandled frame count */ + frame_count++; + } + /* First segment in frame */ + if (desc_iter->RDES0.FirstDescriptor) { + first_desc = desc_iter; + } + /* point to next descriptor */ + desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr); + } + } else { + emac_hal_auto_buf_info_t *buff_info = (emac_hal_auto_buf_info_t *)buf; +#ifndef NDEBUG + /* check that buffer was allocated by emac_hal_alloc_recv_buf */ + assert(buff_info->magic_id == EMAC_HAL_BUF_MAGIC_ID); +#endif // NDEBUG + copy_len = buff_info->copy_len; + ret_len = copy_len; + } + + if (copy_len) { /* check how many frames left to handle */ while ((desc_iter->RDES0.Own != EMAC_DMADESC_OWNER_DMA) && (used_descs < CONFIG_ETH_DMA_RX_BUFFER_NUM)) { used_descs++; @@ -576,31 +632,92 @@ uint32_t emac_hal_receive_frame(emac_hal_context_t *hal, uint8_t *buf, uint32_t desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr); } desc_iter = first_desc; - for (size_t i = 0; i < seg_count - 1; i++) { + while(copy_len > CONFIG_ETH_DMA_BUFFER_SIZE) { used_descs--; - write_len = copy_len < CONFIG_ETH_DMA_BUFFER_SIZE ? copy_len : CONFIG_ETH_DMA_BUFFER_SIZE; - /* copy data to buffer */ - memcpy(buf, (void *)(desc_iter->Buffer1Addr), write_len); - buf += write_len; - copy_len -= write_len; + memcpy(buf, (void *)(desc_iter->Buffer1Addr), CONFIG_ETH_DMA_BUFFER_SIZE); + buf += CONFIG_ETH_DMA_BUFFER_SIZE; + copy_len -= CONFIG_ETH_DMA_BUFFER_SIZE; /* Set Own bit in Rx descriptors: gives the buffers back to DMA */ desc_iter->RDES0.Own = EMAC_DMADESC_OWNER_DMA; desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr); } memcpy(buf, (void *)(desc_iter->Buffer1Addr), copy_len); desc_iter->RDES0.Own = EMAC_DMADESC_OWNER_DMA; + used_descs--; + /* `copy_len` does not include CRC, hence check if we reached the last descriptor */ + while (!desc_iter->RDES0.LastDescriptor) { + desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr); + desc_iter->RDES0.Own = EMAC_DMADESC_OWNER_DMA; + used_descs--; + } /* update rxdesc */ hal->rx_desc = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr); /* poll rx demand */ hal->dma_regs->dmarxpolldemand = 0; frame_count--; - used_descs--; } *frames_remain = frame_count; *free_desc = CONFIG_ETH_DMA_RX_BUFFER_NUM - used_descs; return ret_len; } +uint32_t emac_hal_flush_recv_frame(emac_hal_context_t *hal, uint32_t *frames_remain, uint32_t *free_desc) +{ + eth_dma_rx_descriptor_t *desc_iter = hal->rx_desc; + eth_dma_rx_descriptor_t *first_desc = hal->rx_desc; + uint32_t used_descs = 0; + uint32_t frame_len = 0; + uint32_t frame_count = 0; + + /* Traverse descriptors owned by CPU */ + while ((desc_iter->RDES0.Own != EMAC_DMADESC_OWNER_DMA) && (used_descs < CONFIG_ETH_DMA_RX_BUFFER_NUM) && !frame_count) { + used_descs++; + /* Last segment in frame */ + if (desc_iter->RDES0.LastDescriptor) { + /* Get the Frame Length of the received packet: substruct 4 bytes of the CRC */ + frame_len = desc_iter->RDES0.FrameLength - ETH_CRC_LENGTH; + /* update unhandled frame count */ + frame_count++; + } + /* First segment in frame */ + if (desc_iter->RDES0.FirstDescriptor) { + first_desc = desc_iter; + } + /* point to next descriptor */ + desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr); + } + + /* if there is at least one frame waiting */ + if (frame_len) { + /* check how many frames left to handle */ + while ((desc_iter->RDES0.Own != EMAC_DMADESC_OWNER_DMA) && (used_descs < CONFIG_ETH_DMA_RX_BUFFER_NUM)) { + used_descs++; + if (desc_iter->RDES0.LastDescriptor) { + frame_count++; + } + /* point to next descriptor */ + desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr); + } + desc_iter = first_desc; + /* return descriptors to DMA */ + while (!desc_iter->RDES0.LastDescriptor) { + desc_iter->RDES0.Own = EMAC_DMADESC_OWNER_DMA; + desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr); + used_descs--; + } + desc_iter->RDES0.Own = EMAC_DMADESC_OWNER_DMA; + used_descs--; + /* update rxdesc */ + hal->rx_desc = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr); + /* poll rx demand */ + hal->dma_regs->dmarxpolldemand = 0; + frame_count--; + } + *frames_remain = frame_count; + *free_desc = CONFIG_ETH_DMA_RX_BUFFER_NUM - used_descs; + return frame_len; +} + IRAM_ATTR void emac_hal_isr(void *arg) { emac_hal_context_t *hal = (emac_hal_context_t *)arg; diff --git a/components/hal/esp32/include/hal/emac.h b/components/hal/esp32/include/hal/emac.h index d1f0d712cb..d8076d6f45 100644 --- a/components/hal/esp32/include/hal/emac.h +++ b/components/hal/esp32/include/hal/emac.h @@ -176,6 +176,12 @@ extern "C" { #define EMAC_DMA_ARBITRATION_ROUNDROBIN_RXTX_3_1 (2) #define EMAC_DMA_ARBITRATION_ROUNDROBIN_RXTX_4_1 (3) +/** + * @brief Indicate to ::emac_hal_receive_frame that receive frame buffer was allocated by ::emac_hal_alloc_recv_buf + * + */ +#define EMAC_HAL_BUF_SIZE_AUTO 0 + /** * @brief Ethernet DMA TX Descriptor * @@ -394,10 +400,53 @@ esp_err_t emac_hal_stop(emac_hal_context_t *hal); uint32_t emac_hal_get_tx_desc_owner(emac_hal_context_t *hal); +/** + * @brief Transmit data from buffer over EMAC + * + * @param[in] hal EMAC HAL context infostructure + * @param[in] buf buffer to be transmitted + * @param[in] length length of the buffer + * @return number of transmitted bytes when success + */ uint32_t emac_hal_transmit_frame(emac_hal_context_t *hal, uint8_t *buf, uint32_t length); +/** + * @brief Allocate buffer with size equal to actually received Ethernet frame size. + * + * @param[in] hal EMAC HAL context infostructure + * @param[in, out] size as an input defines maximum size of buffer to be allocated. As an output, indicates actual size of received + * Ethernet frame which is waiting to be processed. Returned size may be 0 when there is no waiting frame. + * + * @note If maximum allowed size of buffer to be allocated is less than actual size of received Ethernet frame, the buffer + * is allocated with that limit and the frame will be truncated by emac_hal_receive_frame. + * + * @return Pointer to allocated buffer + * NULL when allocation fails or when there is no waiting Ethernet frame + */ +uint8_t *emac_hal_alloc_recv_buf(emac_hal_context_t *hal, uint32_t *size); + +/** + * @brief Copy received Ethernet frame from EMAC DMA memory space to application. + * + * @param[in] hal EMAC HAL context infostructure + * @param[in] buf buffer into which the Ethernet frame is to be copied + * @param[in] size buffer size. When buffer was allocated by ::emac_hal_alloc_recv_buf, this parameter needs to be set + * to EMAC_HAL_BUF_SIZE_AUTO + * @param[out] frames_remain number of frames remaining to be processed + * @param[out] free_desc muber of free DMA Rx descriptors + * + * @return number of copied bytes when success + * 0 when there is no waiting Ethernet frame or on error + * + * @note FCS field is never copied + * @note If buffer size is less than actual size of received Ethernet frame, the frame will be truncated. + * @note When this function is called with EMAC_HAL_BUF_SIZE_AUTO size parameter, buffer needs to be allocated by + * ::emac_hal_alloc_recv_buf function at first. + */ uint32_t emac_hal_receive_frame(emac_hal_context_t *hal, uint8_t *buf, uint32_t size, uint32_t *frames_remain, uint32_t *free_desc); +uint32_t emac_hal_flush_recv_frame(emac_hal_context_t *hal, uint32_t *frames_remain, uint32_t *free_desc); + void emac_hal_enable_flow_ctrl(emac_hal_context_t *hal, bool enable); void emac_hal_isr(void *arg);