Merge branch 'feature/emac_buff' into 'master'

esp_eth: receive buffer allocation optimization

Closes IDFGH-7769

See merge request espressif/esp-idf!19977
This commit is contained in:
Ondrej Kosta
2022-11-24 00:48:19 +08:00
6 changed files with 742 additions and 239 deletions

View File

@@ -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;

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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;