Explorar o código

esp_eth: receive buffer allocation optimization

Receive buffers are allocated with a size equal to actual received frame size
Ondrej %!s(int64=3) %!d(string=hai) anos
pai
achega
6fff81d970

+ 183 - 59
components/esp_eth/src/esp_eth_mac_dm9051.c

@@ -30,6 +30,15 @@ static const char *TAG = "dm9051.mac";
 
 #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;
@@ -49,6 +58,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)
@@ -383,44 +393,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;
@@ -628,6 +600,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;
 
+    ESP_GOTO_ON_FALSE(length <= ETH_MAX_PACKET_SIZE, ESP_ERR_INVALID_ARG, err,
+                        TAG, "frame size is too big (actual %u, maximum %u)", length, ETH_MAX_PACKET_SIZE);
+
     int64_t wait_time =  esp_timer_get_time();
     do {
         ESP_GOTO_ON_ERROR(dm9051_register_read(emac, DM9051_TCR, &tcr), err, TAG, "read TCR failed");
@@ -650,47 +625,137 @@ 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;
+    ESP_GOTO_ON_ERROR(dm9051_register_read(emac, DM9051_MRRH, &mrrh), err, TAG, "read MDRAH failed");
+    ESP_GOTO_ON_ERROR(dm9051_register_read(emac, DM9051_MRRL, &mrrl), err, TAG, "read MDRAL failed");
+    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;
+    }
+    ESP_GOTO_ON_ERROR(dm9051_register_write(emac, DM9051_MRRH, addr >> 8), err, TAG, "write MDRAH failed");
+    ESP_GOTO_ON_ERROR(dm9051_register_write(emac, DM9051_MRRL, addr & 0xFF), err, TAG, "write MDRAL failed");
+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 */
     ESP_GOTO_ON_ERROR(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte), err, TAG, "read MRCMDX failed");
     ESP_GOTO_ON_ERROR(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte), err, TAG, "read MRCMDX failed");
     /* rxbyte must be 0xFF, 0 or 1 */
     if (rxbyte > 1) {
-        ESP_GOTO_ON_ERROR(mac->stop(mac), err, TAG, "stop dm9051 failed");
+        ESP_GOTO_ON_ERROR(emac->parent.stop(&emac->parent), err, TAG, "stop dm9051 failed");
         /* reset rx fifo pointer */
         ESP_GOTO_ON_ERROR(dm9051_register_write(emac, DM9051_MPTRCR, MPTRCR_RST_RX), err, TAG, "write MPTRCR failed");
         esp_rom_delay_us(10);
-        ESP_GOTO_ON_ERROR(mac->start(mac), err, TAG, "start dm9051 failed");
+        ESP_GOTO_ON_ERROR(emac->parent.start(&emac->parent), err, TAG, "start dm9051 failed");
         ESP_GOTO_ON_FALSE(false, ESP_FAIL, err, TAG, "reset rx fifo pointer");
     } else if (rxbyte) {
         ESP_GOTO_ON_ERROR(dm9051_memory_peek(emac, (uint8_t *)&header, sizeof(header)), err, TAG, "peek rx header failed");
-        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;
+        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);
+            ESP_GOTO_ON_FALSE(false, ESP_FAIL, err, TAG, "receive status error: %xH", 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;
+    ESP_GOTO_ON_ERROR(dm9051_get_recv_byte_count(emac, &rx_len), err, TAG, "get rx frame length failed");
+    ESP_GOTO_ON_ERROR(dm9051_skip_recv_frame(emac, rx_len), err, TAG, "skipping frame in RX memory failed");
+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;
+
+    ESP_GOTO_ON_ERROR(dm9051_get_recv_byte_count(emac, &byte_count), err, TAG, "get rx frame length failed");
+    // 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
+    ESP_GOTO_ON_FALSE(copy_len >= ETH_MIN_PACKET_SIZE - ETH_CRC_LEN, ESP_ERR_INVALID_SIZE, err, TAG, "invalid frame length %u", 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) {
+        ESP_GOTO_ON_ERROR(dm9051_get_recv_byte_count(emac, &byte_count), err, TAG, "get rx frame length failed");
+        /* silently return when no frame is waiting */
+        if (!byte_count) {
             goto err;
         }
-        ESP_GOTO_ON_ERROR(dm9051_memory_read(emac, (uint8_t *)&header, sizeof(header)), err, TAG, "read rx header failed");
-        ESP_GOTO_ON_ERROR(dm9051_memory_read(emac, buf, rx_len), err, TAG, "read rx data failed");
-        ESP_GOTO_ON_FALSE(!(header.status & 0xBF), ESP_FAIL, err, TAG, "receive status error: %xH", header.status);
-        *length = rx_len - 4; // substract the CRC length (4Bytes)
-        /* dummy read, get the most updated data */
-        ESP_GOTO_ON_ERROR(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte), err, TAG, "read MRCMDX failed");
-        ESP_GOTO_ON_ERROR(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte), err, TAG, "read MRCMDX failed");
-        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;
+    ESP_GOTO_ON_ERROR(dm9051_memory_read(emac, emac->rx_buffer, byte_count), err, TAG, "read rx data failed");
+    memcpy(buf, emac->rx_buffer + DM9051_RX_HDR_SIZE, copy_len);
+    *length = copy_len;
+
+    /* dummy read, get the most updated data */
+    ESP_GOTO_ON_ERROR(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte), err, TAG, "read MRCMDX failed");
+    /* check for remaing packets */
+    ESP_GOTO_ON_ERROR(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte), err, TAG, "read MRCMDX failed");
+    emac->packets_remain = rxbyte > 0;
     return ESP_OK;
 err:
+    *length = 0;
     return ret;
 }
 
@@ -735,12 +800,66 @@ 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;
+    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;
+                dm9051_alloc_recv_buf(emac, &buffer, &frame_len);
+                /* there is a waiting frame */
+                if (frame_len) {
+                    /* we have memory to receive the frame of maximal size previously defined */
+                    if (buffer != NULL) {
+                        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 {
+                        ESP_LOGE(TAG, "no mem for receive buffer");
+                        dm9051_flush_recv_frame(emac);
+                    }
+                }
+            } 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);
     spi_bus_remove_device(emac->spi_hdl);
     vSemaphoreDelete(emac->spi_lock);
+    heap_caps_free(emac->rx_buffer);
     free(emac);
     return ESP_OK;
 }
@@ -800,6 +919,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);
     ESP_GOTO_ON_FALSE(xReturned == pdPASS, NULL, err, TAG, "create dm9051 task failed");
+
+    emac->rx_buffer = heap_caps_malloc(ETH_MAX_PACKET_SIZE + DM9051_RX_HDR_SIZE, MALLOC_CAP_DMA);
+    ESP_GOTO_ON_FALSE(emac->rx_buffer, NULL, err, TAG, "RX buffer allocation failed");
+
     return &(emac->parent);
 
 err:
@@ -810,6 +933,7 @@ err:
         if (emac->spi_lock) {
             vSemaphoreDelete(emac->spi_lock);
         }
+        heap_caps_free(emac->rx_buffer);
         free(emac);
     }
     return ret;

+ 23 - 12
components/esp_eth/src/esp_eth_mac_esp.c

@@ -275,24 +275,35 @@ 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);
+            /* set max expected frame len */
+            uint32_t frame_len = ETH_MAX_PACKET_SIZE;
+            buffer = emac_hal_alloc_recv_buf(&emac->hal, &frame_len);
+            /* there is a waiting frame */
+            if (frame_len) {
+                /* we have memory to receive the frame of maximal size previously defined */
+                if (buffer != NULL) {
+                    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);
+                    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);
                 }
-            } else {
-                free(buffer);
             }
 #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

+ 154 - 72
components/esp_eth/src/esp_eth_mac_ksz8851snl.c

@@ -20,6 +20,8 @@
 #include "ksz8851.h"
 
 
+#define KSZ8851_ETH_MAC_RX_BUF_SIZE_AUTO (0)
+
 typedef struct {
     esp_eth_mac_t parent;
     esp_eth_mediator_t *eth;
@@ -32,6 +34,11 @@ typedef struct {
     uint8_t *tx_buffer;
 } emac_ksz8851snl_t;
 
+typedef struct {
+    uint32_t copy_len;
+    uint32_t byte_cnt;
+}__attribute__((packed)) ksz8851_auto_buf_info_t;
+
 typedef enum {
     KSZ8851_SPI_COMMAND_READ_REG   = 0x0U,
     KSZ8851_SPI_COMMAND_WRITE_REG  = 0x1U,
@@ -44,7 +51,6 @@ typedef enum {
     KSZ8851_QMU_PACKET_PADDING = 16U,
 } ksz8851_qmu_packet_size_t;
 
-
 static const char *TAG = "ksz8851snl-mac";
 
 static const unsigned KSZ8851_SPI_COMMAND_BITS    = 2U;
@@ -223,7 +229,7 @@ static esp_err_t init_set_defaults(emac_ksz8851snl_t *emac)
                                        RXCR1_RXUDPFCC | RXCR1_RXTCPFCC | RXCR1_RXIPFCC | RXCR1_RXPAFMA | RXCR1_RXFCE | RXCR1_RXBE | RXCR1_RXUE | RXCR1_RXME), err, TAG, "RXCR1 write failed");
     ESP_GOTO_ON_ERROR(ksz8851_set_bits(emac, KSZ8851_RXCR2,
                                        (4 << RXCR2_SRDBL_SHIFT) | RXCR2_IUFFP | RXCR2_RXIUFCEZ | RXCR2_UDPLFE | RXCR2_RXICMPFCC), err, TAG, "RXCR2 write failed");
-    ESP_GOTO_ON_ERROR(ksz8851_set_bits(emac, KSZ8851_RXQCR, RXQCR_RXIPHTOE | RXQCR_RXFCTE | RXQCR_ADRFE), err, TAG, "RXQCR write failed");
+    ESP_GOTO_ON_ERROR(ksz8851_set_bits(emac, KSZ8851_RXQCR, RXQCR_RXFCTE | RXQCR_ADRFE), err, TAG, "RXQCR write failed");
     ESP_GOTO_ON_ERROR(ksz8851_clear_bits(emac, KSZ8851_P1CR, P1CR_FORCE_DUPLEX), err, TAG, "P1CR write failed");
     ESP_GOTO_ON_ERROR(ksz8851_set_bits(emac, KSZ8851_P1CR, P1CR_RESTART_AN), err, TAG, "P1CR write failed");
     ESP_GOTO_ON_ERROR(ksz8851_set_bits(emac, KSZ8851_ISR, ISR_ALL), err, TAG, "ISR write failed");
@@ -311,7 +317,8 @@ static esp_err_t emac_ksz8851snl_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint
         return ESP_ERR_TIMEOUT;
     }
 
-    ESP_GOTO_ON_FALSE(length <= KSZ8851_QMU_PACKET_LENGTH, ESP_ERR_INVALID_ARG, err, TAG, "packet is too big");
+    ESP_GOTO_ON_FALSE(length <= KSZ8851_QMU_PACKET_LENGTH, ESP_ERR_INVALID_ARG, err,
+                        TAG, "frame size is too big (actual %u, maximum %u)", length, ETH_MAX_PACKET_SIZE);
     // NOTE(v.chistyakov): 4 bytes header + length aligned to 4 bytes
     unsigned transmit_length = 4U + ((length + 3U) & ~0x3U);
 
@@ -352,61 +359,121 @@ err:
     return ret;
 }
 
-static esp_err_t emac_ksz8851_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length)
+static esp_err_t emac_ksz8851_get_recv_byte_count(emac_ksz8851snl_t *emac, uint16_t *size)
 {
-    esp_err_t ret           = ESP_OK;
-    emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent);
-    if (!ksz8851_mutex_lock(emac)) {
-        return ESP_ERR_TIMEOUT;
-    }
-
-    ESP_GOTO_ON_FALSE(buf, ESP_ERR_INVALID_ARG, err, TAG, "receive buffer can not be null");
-    ESP_GOTO_ON_FALSE(length, ESP_ERR_INVALID_ARG, err, TAG, "receive buffer length can not be null");
-    ESP_GOTO_ON_FALSE(*length > 0U, ESP_ERR_INVALID_ARG, err, TAG, "receive buffer length must be greater than zero");
-
+    esp_err_t ret = ESP_OK;
+    *size = 0;
     uint16_t header_status;
     ESP_GOTO_ON_ERROR(ksz8851_read_reg(emac, KSZ8851_RXFHSR, &header_status), err, TAG, "RXFHSR read failed");
-
     uint16_t byte_count;
     ESP_GOTO_ON_ERROR(ksz8851_read_reg(emac, KSZ8851_RXFHBCR, &byte_count), err, TAG, "RXFHBCR read failed");
-    byte_count &= RXFHBCR_RXBC_MASK;
-
-    // NOTE(v.chistyakov): do not include 2 bytes padding at the beginning and 4 bytes CRC at the end
-    const unsigned frame_size = byte_count - 6U;
-    ESP_GOTO_ON_FALSE(frame_size <= *length, ESP_FAIL, err, TAG, "frame size is greater than length");
-
     if (header_status & RXFHSR_RXFV) {
-        // NOTE(v.chistyakov): 4 dummy + 4 header + alignment
-        const unsigned receive_size = 8U + ((byte_count + 3U) & ~0x3U);
-        spi_transaction_ext_t trans = {
-            .base.flags     = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY,
-            .base.cmd       = KSZ8851_SPI_COMMAND_READ_FIFO,
-            .base.length    = receive_size * 8U, // NOTE(v.chistyakov): bits
-            .base.rx_buffer = emac->rx_buffer,
-            .command_bits   = 2U,
-            .address_bits   = 6U,
-        };
-
-        ESP_GOTO_ON_ERROR(ksz8851_clear_bits(emac, KSZ8851_RXFDPR, RXFDPR_RXFP_MASK), err, TAG, "RXFDPR write failed");
-        ESP_GOTO_ON_ERROR(ksz8851_set_bits(emac, KSZ8851_RXQCR, RXQCR_SDA), err, TAG, "RXQCR write failed");
-        if (spi_device_polling_transmit(emac->spi_hdl, &trans.base) != ESP_OK) {
-            ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
-            ret = ESP_FAIL;
-        }
-        ESP_GOTO_ON_ERROR(ksz8851_clear_bits(emac, KSZ8851_RXQCR, RXQCR_SDA), err, TAG, "RXQCR write failed");
-
-        // NOTE(v.chistyakov): skip 4 dummy, 4 header, 2 padding
-        memcpy(buf, emac->rx_buffer + 10U, frame_size);
-        *length = frame_size;
-        ESP_LOGV(TAG, "received frame of size %u", frame_size);
+        *size = byte_count & RXFHBCR_RXBC_MASK;
     } else if (header_status & (RXFHSR_RXCE | RXFHSR_RXRF | RXFHSR_RXFTL | RXFHSR_RXMR | RXFHSR_RXUDPFCS | RXFHSR_RXTCPFCS |
                                 RXFHSR_RXIPFCS | RXFHSR_RXICMPFCS)) {
         // NOTE(v.chistyakov): RRXEF is a self-clearing bit
         ESP_GOTO_ON_ERROR(ksz8851_set_bits(emac, KSZ8851_RXQCR, RXQCR_RRXEF), err, TAG, "RXQCR write failed");
-        *length = 0U;
     }
 err:
+    return ret;
+}
+
+static esp_err_t emac_ksz8851_alloc_recv_buf(emac_ksz8851snl_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;
+
+    ESP_GOTO_ON_ERROR(emac_ksz8851_get_recv_byte_count(emac, &byte_count), err, TAG, "get receive frame byte count failed");
+    // 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
+    ESP_GOTO_ON_FALSE(copy_len >= ETH_MIN_PACKET_SIZE - ETH_CRC_LEN, ESP_ERR_INVALID_SIZE, err, TAG, "invalid frame length %u", copy_len);
+    *buf = malloc(copy_len);
+    if (*buf != NULL) {
+        ksz8851_auto_buf_info_t *buff_info = (ksz8851_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_ksz8851_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length)
+{
+    esp_err_t ret           = ESP_OK;
+    emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent);
+    uint16_t copy_len       = 0;
+    uint16_t rx_len;
+    uint16_t byte_count;
+
+    ESP_GOTO_ON_FALSE(buf, ESP_ERR_INVALID_ARG, err, TAG, "receive buffer can not be null");
+    ESP_GOTO_ON_FALSE(length, ESP_ERR_INVALID_ARG, err, TAG, "receive buffer length can not be null");
+    if (*length != KSZ8851_ETH_MAC_RX_BUF_SIZE_AUTO) {
+        ESP_GOTO_ON_ERROR(emac_ksz8851_get_recv_byte_count(emac, &byte_count), err, TAG, "get receive frame byte count failed");
+        // 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
+        copy_len = rx_len > *length ? *length : rx_len;
+    } else {
+        ksz8851_auto_buf_info_t *buff_info = (ksz8851_auto_buf_info_t *)buf;
+        copy_len = buff_info->copy_len;
+        byte_count = buff_info->byte_cnt;
+    }
+
+    // NOTE(v.chistyakov): 4 dummy + 4 header + alignment
+    const unsigned receive_size = 8U + ((byte_count + 3U) & ~0x3U);
+    spi_transaction_ext_t trans = {
+        .base.flags     = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY,
+        .base.cmd       = KSZ8851_SPI_COMMAND_READ_FIFO,
+        .base.length    = receive_size * 8U, // NOTE(v.chistyakov): bits
+        .base.rx_buffer = emac->rx_buffer,
+        .command_bits   = 2U,
+        .address_bits   = 6U,
+    };
+    if (!ksz8851_mutex_lock(emac)) {
+        return ESP_ERR_TIMEOUT;
+    }
+    ESP_GOTO_ON_ERROR(ksz8851_clear_bits(emac, KSZ8851_RXFDPR, RXFDPR_RXFP_MASK), err, TAG, "RXFDPR write failed");
+    ESP_GOTO_ON_ERROR(ksz8851_set_bits(emac, KSZ8851_RXQCR, RXQCR_SDA), err, TAG, "RXQCR write failed");
+    if (spi_device_polling_transmit(emac->spi_hdl, &trans.base) != ESP_OK) {
+        ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+        ret = ESP_FAIL;
+    }
+    ESP_GOTO_ON_ERROR(ksz8851_clear_bits(emac, KSZ8851_RXQCR, RXQCR_SDA), err, TAG, "RXQCR write failed");
     ksz8851_mutex_unlock(emac);
+    // NOTE(v.chistyakov): skip 4 dummy, 4 header
+    memcpy(buf, emac->rx_buffer + 8U, copy_len);
+    *length = copy_len;
+    return ret;
+err:
+    *length = 0U;
+    return ret;
+}
+
+static esp_err_t emac_ksz8851_flush_recv_queue(emac_ksz8851snl_t *emac)
+{
+    esp_err_t ret = ESP_OK;
+    ESP_GOTO_ON_ERROR(ksz8851_clear_bits(emac, KSZ8851_RXCR1, RXCR1_RXE), err, TAG, "RXCR1 write failed");
+    ESP_GOTO_ON_ERROR(ksz8851_set_bits(emac, KSZ8851_RXCR1, RXCR1_FRXQ), err, TAG, "RXCR1 write failed");
+    ESP_GOTO_ON_ERROR(ksz8851_clear_bits(emac, KSZ8851_RXCR1, RXCR1_FRXQ), err, TAG, "RXCR1 write failed");
+    ESP_GOTO_ON_ERROR(ksz8851_set_bits(emac, KSZ8851_RXCR1, RXCR1_RXE), err, TAG, "RXCR1 write failed");
+err:
     return ret;
 }
 
@@ -568,18 +635,6 @@ static esp_err_t emac_ksz8851_set_peer_pause_ability(esp_eth_mac_t *mac, uint32_
     return ESP_ERR_NOT_SUPPORTED;
 }
 
-static esp_err_t emac_ksz8851_del(esp_eth_mac_t *mac)
-{
-    emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent);
-    vTaskDelete(emac->rx_task_hdl);
-    spi_bus_remove_device(emac->spi_hdl);
-    vSemaphoreDelete(emac->spi_lock);
-    heap_caps_free(emac->rx_buffer);
-    heap_caps_free(emac->tx_buffer);
-    free(emac);
-    return ESP_OK;
-}
-
 static void emac_ksz8851snl_task(void *arg)
 {
     emac_ksz8851snl_t *emac = (emac_ksz8851snl_t *)arg;
@@ -633,21 +688,36 @@ static void emac_ksz8851snl_task(void *arg)
             frame_count = (frame_count & RXFCTR_RXFC_MASK) >> RXFCTR_RXFC_SHIFT;
 
             while (frame_count--) {
-                uint32_t length = ETH_MAX_PACKET_SIZE;
-                uint8_t *packet = malloc(ETH_MAX_PACKET_SIZE);
-                if (!packet) {
-                    continue;
-                }
-
-                if (emac->parent.receive(&emac->parent, packet, &length) == ESP_OK && length) {
-                    emac->eth->stack_input(emac->eth, packet, length);
-                    // NOTE(v.chistyakov): the packet is freed in the upper layers
-                } else {
-                    free(packet);
-                    ksz8851_clear_bits(emac, KSZ8851_RXCR1, RXCR1_RXE);
-                    ksz8851_set_bits(emac, KSZ8851_RXCR1, RXCR1_FRXQ);
-                    ksz8851_clear_bits(emac, KSZ8851_RXCR1, RXCR1_FRXQ);
-                    ksz8851_set_bits(emac, KSZ8851_RXCR1, RXCR1_RXE);
+                /* define max expected frame len */
+                uint32_t frame_len = ETH_MAX_PACKET_SIZE;
+                uint8_t *buffer;
+                emac_ksz8851_alloc_recv_buf(emac, &buffer, &frame_len);
+                /* there is a waiting frame */
+                if (frame_len) {
+                    /* we have memory to receive the frame of maximal size previously defined */
+                    if (buffer != NULL) {
+                        uint32_t buf_len = KSZ8851_ETH_MAC_RX_BUF_SIZE_AUTO;
+                        if (emac->parent.receive(&emac->parent, buffer, &buf_len) == ESP_OK) {
+                            if (buf_len == 0) {
+                                emac_ksz8851_flush_recv_queue(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");
+                            emac_ksz8851_flush_recv_queue(emac);
+                            free(buffer);
+                        }
+                    } else {
+                        ESP_LOGE(TAG, "no mem for receive buffer");
+                        emac_ksz8851_flush_recv_queue(emac);
+                    }
                 }
             }
             ksz8851_write_reg(emac, KSZ8851_IER, ier);
@@ -656,6 +726,18 @@ static void emac_ksz8851snl_task(void *arg)
     vTaskDelete(NULL);
 }
 
+static esp_err_t emac_ksz8851_del(esp_eth_mac_t *mac)
+{
+    emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent);
+    vTaskDelete(emac->rx_task_hdl);
+    spi_bus_remove_device(emac->spi_hdl);
+    vSemaphoreDelete(emac->spi_lock);
+    heap_caps_free(emac->rx_buffer);
+    heap_caps_free(emac->tx_buffer);
+    free(emac);
+    return ESP_OK;
+}
+
 esp_eth_mac_t *esp_eth_mac_new_ksz8851snl(const eth_ksz8851snl_config_t *ksz8851snl_config,
         const eth_mac_config_t *mac_config)
 {

+ 177 - 62
components/esp_eth/src/esp_eth_mac_w5500.c

@@ -28,6 +28,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;
@@ -39,6 +47,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)
@@ -291,59 +300,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;
@@ -509,6 +465,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;
 
+    ESP_GOTO_ON_FALSE(length <= ETH_MAX_PACKET_SIZE, ESP_ERR_INVALID_ARG, err,
+                        TAG, "frame size is too big (actual %u, maximum %u)", length, ETH_MAX_PACKET_SIZE);
     // check if there're free memory to store this packet
     uint16_t free_size = 0;
     ESP_GOTO_ON_ERROR(w5500_get_tx_free_size(emac, &free_size), err, TAG, "get free size failed");
@@ -542,12 +500,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
+        ESP_GOTO_ON_ERROR(w5500_read(emac, W5500_REG_SOCK_RX_RD(0), &offset, sizeof(offset)), err, TAG, "read RX RD failed");
+        offset = __builtin_bswap16(offset);
+        // read head
+        ESP_GOTO_ON_ERROR(w5500_read_buffer(emac, &rx_len, sizeof(rx_len), offset), err, TAG, "read frame header failed");
+        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
+        ESP_GOTO_ON_FALSE(copy_len >= ETH_MIN_PACKET_SIZE - ETH_CRC_LEN, ESP_ERR_INVALID_SIZE, err, TAG, "invalid frame length %u", 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
+            ESP_GOTO_ON_ERROR(w5500_read(emac, W5500_REG_SOCK_RX_RD(0), &offset, sizeof(offset)), err, TAG, "read RX RD failed");
+            offset = __builtin_bswap16(offset);
+            // read head first
+            ESP_GOTO_ON_ERROR(w5500_read_buffer(emac, &rx_len, sizeof(rx_len), offset), err, TAG, "read frame header failed");
+            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
+    ESP_GOTO_ON_ERROR(w5500_read_buffer(emac, emac->rx_buffer, copy_len, offset), err, TAG, "read payload failed, len=%d, offset=%d", rx_len, offset);
+    memcpy(buf, emac->rx_buffer, copy_len);
+    offset += rx_len;
+    // update read pointer
+    offset = __builtin_bswap16(offset);
+    ESP_GOTO_ON_ERROR(w5500_write(emac, W5500_REG_SOCK_RX_RD(0), &offset, sizeof(offset)), err, TAG, "write RX RD failed");
+    /* issue RECV command */
+    ESP_GOTO_ON_ERROR(w5500_send_command(emac, W5500_SCR_RECV, 100), err, TAG, "issue RECV command failed");
+    // check if there're more data need to process
+    remain_bytes -= rx_len + 2;
+    emac->packets_remain = remain_bytes > 0;
+
+    *length = rx_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;
 
@@ -558,26 +607,86 @@ static esp_err_t emac_w5500_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *
         offset = __builtin_bswap16(offset);
         // read head first
         ESP_GOTO_ON_ERROR(w5500_read_buffer(emac, &rx_len, sizeof(rx_len), offset), err, TAG, "read frame header failed");
-        rx_len = __builtin_bswap16(rx_len) - 2; // data size includes 2 bytes of header
-        offset += 2;
-        // read the payload
-        ESP_GOTO_ON_ERROR(w5500_read_buffer(emac, buf, rx_len, offset), err, TAG, "read payload failed, len=%d, offset=%d", rx_len, offset);
-        offset += rx_len;
         // update read pointer
-        offset = __builtin_bswap16(offset);
+        offset = rx_len;
         ESP_GOTO_ON_ERROR(w5500_write(emac, W5500_REG_SOCK_RX_RD(0), &offset, sizeof(offset)), err, TAG, "write RX RD failed");
         /* issue RECV command */
         ESP_GOTO_ON_ERROR(w5500_send_command(emac, W5500_SCR_RECV, 100), err, TAG, "issue RECV command failed");
         // check if there're more data need to process
-        remain_bytes -= rx_len + 2;
+        rx_len = __builtin_bswap16(rx_len);
+        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;
+    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;
+                emac_w5500_alloc_recv_buf(emac, &buffer, &frame_len);
+                /* there is a waiting frame */
+                if (frame_len) {
+                    /* we have memory to receive the frame of maximal size previously defined */
+                    if (buffer != NULL) {
+                        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 {
+                        ESP_LOGE(TAG, "no mem for receive buffer");
+                        emac_w5500_flush_recv_frame(emac);
+                    }
+                }
+            } while (emac->packets_remain);
+        }
+    }
+    vTaskDelete(NULL);
+}
+
 static esp_err_t emac_w5500_init(esp_eth_mac_t *mac)
 {
     esp_err_t ret = ESP_OK;
@@ -621,6 +730,7 @@ static esp_err_t emac_w5500_del(esp_eth_mac_t *mac)
     vTaskDelete(emac->rx_task_hdl);
     spi_bus_remove_device(emac->spi_hdl);
     vSemaphoreDelete(emac->spi_lock);
+    heap_caps_free(emac->rx_buffer);
     free(emac);
     return ESP_OK;
 }
@@ -679,6 +789,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);
     ESP_GOTO_ON_FALSE(xReturned == pdPASS, NULL, err, TAG, "create w5500 task failed");
+
+    emac->rx_buffer = heap_caps_malloc(ETH_MAX_PACKET_SIZE, MALLOC_CAP_DMA);
+    ESP_GOTO_ON_FALSE(emac->rx_buffer, NULL, err, TAG, "RX buffer allocation failed");
+
     return &(emac->parent);
 
 err:
@@ -689,6 +803,7 @@ err:
         if (emac->spi_lock) {
             vSemaphoreDelete(emac->spi_lock);
         }
+        heap_caps_free(emac->rx_buffer);
         free(emac);
     }
     return ret;

+ 140 - 29
components/hal/emac_hal.c

@@ -12,6 +12,17 @@
 
 #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;
+
 void emac_hal_iomux_init_mii(void)
 {
     /* TX_CLK to GPIO0 */
@@ -287,7 +298,7 @@ void emac_hal_init_dma_default(emac_hal_context_t *hal, emac_hal_dma_config_t *h
     /* Receive Threshold Control */
     emac_ll_set_recv_threshold(hal->dma_regs, EMAC_LL_RECEIVE_THRESHOLD_CONTROL_64);
     /* Allow the DMA to process a second frame of Transmit data even before obtaining the status for the first frame */
-    emac_ll_opt_second_frame_enable(hal->dma_regs, true);;
+    emac_ll_opt_second_frame_enable(hal->dma_regs, true);
 
     /* DMABMR Configuration */
     /* Enable Mixed Burst */
@@ -380,11 +391,6 @@ esp_err_t emac_hal_stop(emac_hal_context_t *hal)
     return ESP_OK;
 }
 
-uint32_t emac_hal_get_tx_desc_owner(emac_hal_context_t *hal)
-{
-    return hal->tx_desc->TDES0.Own;
-}
-
 uint32_t emac_hal_transmit_frame(emac_hal_context_t *hal, uint8_t *buf, uint32_t length)
 {
     /* Get the number of Tx buffers to use for the frame */
@@ -537,29 +543,139 @@ err:
     return 0;
 }
 
+uint8_t *emac_hal_alloc_recv_buf(emac_hal_context_t *hal, uint32_t *size)
+{
+    eth_dma_rx_descriptor_t *desc_iter = hal->rx_desc;
+    uint32_t used_descs = 0;
+    uint32_t ret_len = 0;
+    uint32_t copy_len = 0;
+    uint8_t *buf = NULL;
+
+    /* Traverse descriptors owned by CPU */
+    while ((desc_iter->RDES0.Own != EMAC_LL_DMADESC_OWNER_DMA) && (used_descs < CONFIG_ETH_DMA_RX_BUFFER_NUM)) {
+        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;
+            break;
+        }
+        /* point to next descriptor */
+        desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
+    }
+    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 = NULL;
-    eth_dma_rx_descriptor_t *first_desc = NULL;
+    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 seg_count = 0;
     uint32_t ret_len = 0;
     uint32_t copy_len = 0;
-    uint32_t write_len = 0;
     uint32_t frame_count = 0;
 
-    first_desc = hal->rx_desc;
-    desc_iter = hal->rx_desc;
+    if (size != EMAC_HAL_BUF_SIZE_AUTO) {
+        /* Traverse descriptors owned by CPU */
+        while ((desc_iter->RDES0.Own != EMAC_LL_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_LL_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;
+        while(copy_len > CONFIG_ETH_DMA_BUFFER_SIZE) {
+            used_descs--;
+            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_LL_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_LL_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_LL_DMADESC_OWNER_DMA;
+            used_descs--;
+        }
+        /* update rxdesc */
+        hal->rx_desc = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
+        /* poll rx demand */
+        emac_ll_receive_poll_demand(hal->dma_regs, 0);
+        frame_count--;
+    }
+    *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_LL_DMADESC_OWNER_DMA) && (used_descs < CONFIG_ETH_DMA_RX_BUFFER_NUM) && !frame_count) {
         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;
+            frame_len = desc_iter->RDES0.FrameLength - ETH_CRC_LENGTH;
             /* update unhandled frame count */
             frame_count++;
         }
@@ -570,8 +686,9 @@ uint32_t emac_hal_receive_frame(emac_hal_context_t *hal, uint8_t *buf, uint32_t
         /* 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 there is at least one frame waiting */
+    if (frame_len) {
         /* check how many frames left to handle */
         while ((desc_iter->RDES0.Own != EMAC_LL_DMADESC_OWNER_DMA) && (used_descs < CONFIG_ETH_DMA_RX_BUFFER_NUM)) {
             used_descs++;
@@ -582,27 +699,21 @@ 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++) {
-            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;
-            /* Set Own bit in Rx descriptors: gives the buffers back to DMA */
+        /* return descriptors to DMA */
+        while (!desc_iter->RDES0.LastDescriptor) {
             desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
             desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
+            used_descs--;
         }
-        memcpy(buf, (void *)(desc_iter->Buffer1Addr), copy_len);
         desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
+        used_descs--;
         /* update rxdesc */
         hal->rx_desc = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
         /* poll rx demand */
         emac_ll_receive_poll_demand(hal->dma_regs, 0);
         frame_count--;
-        used_descs--;
     }
     *frames_remain = frame_count;
     *free_desc = CONFIG_ETH_DMA_RX_BUFFER_NUM - used_descs;
-    return ret_len;
+    return frame_len;
 }

+ 62 - 2
components/hal/include/hal/emac_hal.h

@@ -18,6 +18,12 @@ extern "C" {
 #include "soc/emac_mac_struct.h"
 #include "soc/emac_ext_struct.h"
 
+/**
+ * @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
 *
@@ -237,14 +243,68 @@ void emac_hal_start(emac_hal_context_t *hal);
  */
 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 Transmit data from multiple buffers over EMAC in single Ethernet frame. Data will be joint into
+ *        single frame in order in which the buffers are stored in input array.
+ *
+ * @param[in] hal EMAC HAL context infostructure
+ * @param[in] buffs array of pointers to buffers to be transmitted
+ * @param[in] lengths array of lengths of the buffers
+ * @param[in] inbuffs_cnt number of buffers (i.e. input arrays size)
+ * @return number of transmitted bytes when success
+ *
+ * @pre @p lengths array must have the same size as @p buffs array and their elements need to be stored in the same
+ *      order, i.e. lengths[1] is a length assocaited with data buffer referenced at buffs[1] position.
+ */
 uint32_t emac_hal_transmit_multiple_buf_frame(emac_hal_context_t *hal, uint8_t **buffs, uint32_t *lengths, uint32_t inbuffs_cnt);
 
+/**
+ * @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);
 
 uint32_t emac_hal_get_intr_enable_status(emac_hal_context_t *hal);