diff --git a/cpu/stm32/include/periph_cpu.h b/cpu/stm32/include/periph_cpu.h index f93b6e94dc..bdace91256 100644 --- a/cpu/stm32/include/periph_cpu.h +++ b/cpu/stm32/include/periph_cpu.h @@ -1080,6 +1080,10 @@ typedef struct eth_dma_desc { * @name Flags in the status word of the Ethernet enhanced TX DMA descriptor * @{ */ +#define TX_DESC_STAT_UF (BIT1) /**< If set, an underflow occurred while sending */ +#define TX_DESC_STAT_EC (BIT8) /**< If set, TX was aborted due to excessive collisions (half-duplex only) */ +#define TX_DESC_STAT_NC (BIT10) /**< If set, no carrier was detected (TX aborted) */ +#define TX_DESC_STAT_ES (BIT15) /**< If set, one or more error occurred */ #define TX_DESC_STAT_TTSS (BIT17) /**< If set, the descriptor contains a valid PTP timestamp */ /** * @brief Indicates if TDES3 points to the next DMA descriptor (1), or to a second buffer (0) @@ -1089,6 +1093,7 @@ typedef struct eth_dma_desc { * always set by the driver */ #define TX_DESC_STAT_TCH (BIT20) +#define TX_DESC_STAT_TER (BIT21) /**< If set, DMA will return to first descriptor in ring afterwards */ /** * @brief Checksum insertion control * diff --git a/cpu/stm32/periph/eth.c b/cpu/stm32/periph/eth.c index 3cdb06d03b..93fa145670 100644 --- a/cpu/stm32/periph/eth.c +++ b/cpu/stm32/periph/eth.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 TriaGnoSys GmbH + * 2020 Otto-von-Guericke-Universität Magdeburg * * This file is subject to the terms and conditions of the GNU Lesser General * Public License v2.1. See the file LICENSE in the top level directory for more @@ -14,9 +15,11 @@ * @brief Low-level ETH driver implementation * * @author Víctor Ariño + * @author Marian Buschsieweke * * @} */ +#include #include #include "bitarithm.h" @@ -46,6 +49,8 @@ #define CLOCK_RANGE ETH_MACMIIAR_CR_Div102 #endif /* CLOCK_CORECLOCK < (20000000U) */ +#define MIN(a, b) (((a) <= (b)) ? (a) : (b)) + /* Descriptors */ static edma_desc_t rx_desc[ETH_RX_BUFFER_COUNT]; static edma_desc_t tx_desc[ETH_TX_BUFFER_COUNT]; @@ -115,7 +120,7 @@ void stm32_eth_set_mac(const char *mac) /** Initialization of the DMA descriptors to be used */ static void _init_buffer(void) { - int i; + size_t i; for (i = 0; i < ETH_RX_BUFFER_COUNT; i++) { rx_desc[i].status = RX_DESC_STAT_OWN; rx_desc[i].control = RX_DESC_CTRL_RCH | (ETH_RX_BUFFER_SIZE & 0x0fff); @@ -274,58 +279,85 @@ int stm32_eth_send(const struct iolist *iolist) return ret; } -static int _try_receive(char *data, int max_len, int block) +static ssize_t get_rx_frame_size(void) { - int copy, len = 0; - int copied = 0; - int drop = (data || max_len > 0); - - edma_desc_t *p = rx_curr; - for (int i = 0; i < ETH_RX_BUFFER_COUNT && len == 0; i++) { - /* try receiving, if the block is set, simply wait for the rest of - * the packet to complete, otherwise just break */ - while (p->status & RX_DESC_STAT_OWN) { - if (!block) { - break; - } + edma_desc_t *i = rx_curr; + uint32_t status; + while (1) { + /* Wait until DMA gave up control over descriptor */ + while ((status = i->status) & RX_DESC_STAT_OWN) { } + DEBUG("stm32_eth: get_rx_frame_size(): FS=%c, LS=%c, DE=%c, FL=%lu\n", + (status & RX_DESC_STAT_FS) ? '1' : '0', + (status & RX_DESC_STAT_LS) ? '1' : '0', + (status & RX_DESC_STAT_DE) ? '1' : '0', + ((status >> 16) & 0x3fff) - ETHERNET_FCS_LEN); + if (status & RX_DESC_STAT_LS) { + break; } - - /* amount of data to copy */ - copy = ETH_RX_BUFFER_SIZE; - if (p->status & (RX_DESC_STAT_LS | RX_DESC_STAT_FL)) { - len = ((p->status >> 16) & 0x3FFF) - 4; - copy = len - copied; - } - - if (drop) { - /* copy the data if possible */ - if (data && max_len >= copy) { - memcpy(data, p->buffer_addr, copy); - max_len -= copy; - } - else if (max_len < copy) { - len = -1; - } - p->status = RX_DESC_STAT_OWN; - } - p = p->desc_next; + i = i->desc_next; } - if (drop) { - rx_curr = p; + if (status & RX_DESC_STAT_DE) { + return -1; } - return len; + /* bits 16-29 contain the frame length including 4 B frame check sequence */ + return ((status >> 16) & 0x3fff) - ETHERNET_FCS_LEN; } -int stm32_eth_try_receive(char *data, unsigned max_len) +static void drop_frame_and_update_rx_curr(void) { - return _try_receive(data, max_len, 0); + while (1) { + uint32_t old_status = rx_curr->status; + /* hand over old descriptor to DMA */ + rx_curr->status = RX_DESC_STAT_OWN; + rx_curr = rx_curr->desc_next; + if (old_status & RX_DESC_STAT_LS) { + /* reached last DMA descriptor of frame ==> done */ + return; + } + } } -int stm32_eth_receive_blocking(char *data, unsigned max_len) +int stm32_eth_receive(void *buf, size_t max_len) { - return _try_receive(data, max_len, 1); + char *data = buf; + /* Determine the size of received frame. The frame might span multiple + * DMA buffers */ + ssize_t size = get_rx_frame_size(); + + if (size == -1) { + DEBUG("stm32_eth: Received frame was too large for DMA buffer(s)\n"); + drop_frame_and_update_rx_curr(); + return -EOVERFLOW; + } + + if (!buf) { + if (max_len) { + DEBUG("stm32_eth: Dropping frame as requested by upper layer\n"); + drop_frame_and_update_rx_curr(); + } + return size; + } + + if (max_len < (size_t)size) { + DEBUG("stm32_eth: Buffer provided by upper layer is too small\n"); + drop_frame_and_update_rx_curr(); + return -ENOBUFS; + } + + size_t remain = size; + while (remain) { + size_t chunk = MIN(remain, ETH_RX_BUFFER_SIZE); + memcpy(data, rx_curr->buffer_addr, chunk); + data += chunk; + remain -= chunk; + /* Hand over descriptor to DMA */ + rx_curr->status = RX_DESC_STAT_OWN; + rx_curr = rx_curr->desc_next; + } + + return size; } int stm32_eth_get_rx_status_owned(void) diff --git a/drivers/stm32_eth/stm32_eth.c b/drivers/stm32_eth/stm32_eth.c index 072402a958..9091438337 100644 --- a/drivers/stm32_eth/stm32_eth.c +++ b/drivers/stm32_eth/stm32_eth.c @@ -34,7 +34,7 @@ netdev_t *_netdev; void stm32_eth_set_mac(const char *mac); void stm32_eth_get_mac(char *out); int stm32_eth_init(void); -int stm32_eth_receive_blocking(char *data, unsigned max_len); +int stm32_eth_receive(void *data, size_t max_len); int stm32_eth_send(const struct iolist *iolist); int stm32_eth_get_rx_status_owned(void); @@ -73,7 +73,7 @@ static int _recv(netdev_t *netdev, void *buf, size_t len, void *info) if (!stm32_eth_get_rx_status_owned()){ mutex_lock(&_rx); } - int ret = stm32_eth_receive_blocking((char *)buf, len); + int ret = stm32_eth_receive(buf, len); DEBUG("stm32_eth_netdev: _recev: %d\n", ret); return ret;