The problem is that the low_level_input and low_level_output (the glue between LWIP and ETH PHY) never worked properly. AFAIK no actual full working code, either polled or interrupt drive, was ever published for the 32F4 for this. Yeah, one particularly rude guy on the ST forum claimed he had some working code, and I am sure he had, but no complete source ever surfaced, which one could just use. Only bits here and there. I had a Monday afternoon guy working for me for about 10 years (doing various jobs mostly unrelated to this) and I think he spent 6 months of Mondays on the ETH glue, LWIP and TLS.
MbedTLS sits on top of LWIP, mostly.
For some of the later chips, sources claimed to be good did/do exist but I wasn't paying attention.
To write that code "by reading the RM" is incredibly complex. The RM doesn't tell you how to do it. ST publish some appnotes with code in them but you get the same half-baked stuff you get with button pressing in Cube MX. Reason it's half baked is stuff like handling the link up/down status changes, handling various IP options (fixed, dhcp, etc). Here's just a bit of my code which is now rock solid and has been running 24/7 on a number of test boards for at least a year. It probably originated from MX. Of course Mr know-it-all "Piranha" on the ST forum tells you it is a load of buggy crap... To understand what it does
in detail , or to write it originally, you need to be a total expert on TCP/IP, the 32F4 ETH subsystem, LWIP internal operation. This is no good; most people need finished code.
/**
* @brief This function should do the actual transmission of the packet. The packet is
* contained in the pbuf that is passed to the function. This pbuf
* might be chained.
*
* @param netif the lwip network interface structure for this ethernetif
* @param p the MAC packet to send (e.g. IP packet including MAC addresses and type)
* @return ERR_OK if the packet could be sent
* an err_t value if the packet couldn't be sent
*
* @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to
* strange results. You might consider waiting for space in the DMA queue
* to become available since the stack doesn't retry to send a packet
* dropped because of memory failure (except for the TCP timers).
*/
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
err_t errval;
struct pbuf *q;
uint8_t *buffer = (uint8_t *)(EthHandle.TxDesc->Buffer1Addr);
__IO ETH_DMADescTypeDef *DmaTxDesc;
uint32_t framelength = 0;
uint32_t bufferoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t payloadoffset = 0;
DmaTxDesc = EthHandle.TxDesc;
bufferoffset = 0;
/* copy frame from pbufs to driver buffers */
for(q = p; q != NULL; q = q->next)
{
/* Is this buffer available? If not, goto error */
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error;
}
/* Get bytes in current lwIP buffer */
byteslefttocopy = q->len;
payloadoffset = 0;
/* Check if the length of data to copy is bigger than Tx buffer size*/
// This code never runs. See
// [url]https://www.eevblog.com/forum/microcontrollers/anyone-here-familiar-with-lwip/msg4693118/#msg4693118[/url]
while( (byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE )
{
//osDelay(2); - was a buffer overwrite issue, not possible to reproduce later
// see mod at the end of IF_HAL_ETH_TransmitFrame() which is a better fix
// Copy data to Tx buffer - should use DMA but actually the perf diff is negligible
#ifdef SPEED_TEST
TopLED(true);
#endif
memcpy_fast( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset) );
#ifdef SPEED_TEST
TopLED(false);
#endif
/* Point to next descriptor */
DmaTxDesc = (ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
/* Check if the buffer is available */
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error;
}
buffer = (uint8_t *)(DmaTxDesc->Buffer1Addr);
byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
/* Copy the remaining bytes */
#ifdef SPEED_TEST
TopLED(true);
#endif
memcpy_fast( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), byteslefttocopy );
#ifdef SPEED_TEST
TopLED(false);
#endif
bufferoffset = bufferoffset + byteslefttocopy;
framelength = framelength + byteslefttocopy;
}
/* Prepare transmit descriptors to give to DMA */
IF_HAL_ETH_TransmitFrame(&EthHandle, framelength);
errval = ERR_OK;
error:
/* When Transmit Underflow flag is set, clear it and issue a Transmit Poll Demand to resume transmission */
if ((EthHandle.Instance->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET)
{
/* Clear TUS ETHERNET DMA flag */
EthHandle.Instance->DMASR = ETH_DMASR_TUS;
/* Resume DMA transmission*/
//__DMB();
EthHandle.Instance->DMATPDR = 0; // Any value issues a descriptor list poll demand.
}
return errval;
}
/**
* @brief Should allocate a pbuf and transfer the bytes of the incoming
* packet from the interface into the pbuf.
*
* @param netif the lwip network interface structure for this ethernetif
* @return a pbuf filled with the received packet (including MAC header)
* NULL on memory error
*/
static struct pbuf * low_level_input(struct netif *netif)
{
struct pbuf *p = NULL, *q = NULL;
uint16_t len = 0;
uint8_t *buffer;
__IO ETH_DMADescTypeDef *dmarxdesc;
uint32_t bufferoffset = 0;
uint32_t payloadoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t i=0;
/* get received frame */
HAL_StatusTypeDef status = IF_HAL_ETH_GetReceivedFrame(&EthHandle);
if (status != HAL_OK)
{
return NULL; // Return if no RX data
}
else
{
rxactive=true; // set "seen rx data" flag
}
/* Obtain the size of the packet and put it into the "len" variable. */
len = EthHandle.RxFrameInfos.length;
buffer = (uint8_t *)EthHandle.RxFrameInfos.buffer;
// Dump unwanted multicasts, unless g_eth_multi=true.
if (should_accept_ethernet_packet(buffer, len))
{
/* We allocate a pbuf chain of pbufs from the Lwip buffer pool */
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
}
// Load the packet (if not rejected above) into LWIP's buffer
if (p != NULL)
{
dmarxdesc = EthHandle.RxFrameInfos.FSRxDesc;
bufferoffset = 0;
for(q = p; q != NULL; q = q->next)
{
byteslefttocopy = q->len;
payloadoffset = 0;
/* Check if the length of bytes to copy in current pbuf is bigger than Rx buffer size */
// This code never runs. See
// [url]https://www.eevblog.com/forum/microcontrollers/anyone-here-familiar-with-lwip/msg4693118/#msg4693118[/url]
while( (byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE )
{
/* Copy data to pbuf */
#ifdef SPEED_TEST
TopLED(true);
#endif
memcpy_fast( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), (ETH_RX_BUF_SIZE - bufferoffset));
#ifdef SPEED_TEST
TopLED(false);
#endif
/* Point to next descriptor */
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
buffer = (uint8_t *)(dmarxdesc->Buffer1Addr);
byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
/* Copy remaining data in pbuf */
#ifdef SPEED_TEST
TopLED(true);
#endif
memcpy_fast( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), byteslefttocopy);
#ifdef SPEED_TEST
TopLED(false);
#endif
bufferoffset = bufferoffset + byteslefttocopy;
}
}
/* Release descriptors to DMA. This tells the ETH DMA that the packet has been read */
/* Point to first descriptor */
dmarxdesc = EthHandle.RxFrameInfos.FSRxDesc;
/* Set Own bit in Rx descriptors: gives the buffers back to DMA */
for (i=0; i< EthHandle.RxFrameInfos.SegCount; i++)
{
//__DMB(); - fossil code for the 32F417, apparently.
dmarxdesc->Status |= ETH_DMARXDESC_OWN;
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
}
/* Clear Segment_Count */
EthHandle.RxFrameInfos.SegCount =0;
/* When Rx Buffer unavailable flag is set: clear it and resume reception */
if ((EthHandle.Instance->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET)
{
/* Clear RBUS ETHERNET DMA flag */
EthHandle.Instance->DMASR = ETH_DMASR_RBUS;
/* Resume DMA reception */
EthHandle.Instance->DMARPDR = 0;
}
return p;
}
To port this code, one needs a truly deep understanding of the ETH subsystem functionality, and of LWIP internal structure which is especially badly documented (it's a solid bit of code but the devs moved on years ago). Obviously there are people here and elsewhere who have this but AFAICT none of them do consultancy
My finished code transmits out of LWIP when LWIP has something to send (no interrupts needed) and this works because ETH is so fast that the stuff disappears down the cable in not many tens of microseconds. The receive code should use interrupts but doesn't due to various problems (complex issues with calling LWIP back end from an ISR, etc) and is instead polled in an RTOS task, with the poll interval being 10ms and then adaptively shrinking until the data stops, with performance way short of 100mbps theoretical limit but at about 1MB/sec is loads good enough for the job. I have zero interest in re-doing this for another CPU in my remaining actuarial life expectancy
There are also zero-copy versions, which may even work... But I measured the time spent in the memcpy calls (see above code) and they are insignificant.
USB code basically worked (CDC and MSC) except there was no flow control on CDC (I did that, with help here) and MSC was workable only with an ISR taking the whole 15ms FLASH write time. Various past threads. It's OK for my application but most users would regard that as unworkable except with a RAM disk. Again, the code is hugely complex and would take an expert to port to a different USB controller.
ST bought USB from Synopsys. Does anyone know who they got ETH from?