cpu/stm32/periph_eth: Fix RX logic

If any incoming frame is bigger than a single DMA buffer, the Ethernet DMA will
split the content and use multiple DMA buffers instead. But only the DMA
descriptor of the last Ethernet frame segment will contain the frame length.

Previously, the frame length calculation, reassembly of the frame, and the
freeing of DMA descriptors was completely broken and only worked in case the
received frame was small enough to fit into one DMA buffer. This is now fixed,
so that smaller DMA buffers can safely be used now.

Additionally the interface was simplified: Previously two receive flavors were
implemented, with only one ever being used. None of those function was
public due to missing declarations in headers. The unused interface was
dropped and the remaining was streamlined to better fit the use case.
This commit is contained in:
Marian Buschsieweke 2020-07-24 17:43:01 +02:00
parent 53375f04bf
commit 932c311ee2
No known key found for this signature in database
GPG Key ID: 61F64C6599B1539F
3 changed files with 80 additions and 43 deletions

View File

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

View File

@ -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 <victor.arino@triagnosys.com>
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*
* @}
*/
#include <errno.h>
#include <string.h>
#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) {
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;
}
i = i->desc_next;
}
/* 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 (status & RX_DESC_STAT_DE) {
return -1;
}
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;
/* bits 16-29 contain the frame length including 4 B frame check sequence */
return ((status >> 16) & 0x3fff) - ETHERNET_FCS_LEN;
}
if (drop) {
rx_curr = p;
}
return 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)

View File

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