Receiving Data Using Zero-Copy driver

Hello, I’m trying to create Zero-Copy driver for my stm32f429. As describe here I should to declare handler (standard RTOS task) where DMA descriptor with incoming frame (that references the Ethernet buffer containing the received data) are swapped with free FreeRTOS+TCP descriptor. But in my MCU DMA descriptor have only 3 parameters: ~~~ /* DMA Descriptors. */ typedef struct { volatile u32 Stat; u32 Ctrl; u32 Addr; u32 Next; } RX_Desc; ~~~ So I can’t just swap two pointers. So I am trying to bind DMA descriptors with buffers in Pre-created FreeRTOS+TCP descriptors: ~~~ static void rxdescrinit (void) { u32 i,next; realisedDesc = xQueueCreate(3, sizeof(int)); RxBufIndex = 0; for (i = 0, next = 0; i < NUMRXBUF; i++) { if (++next == NUMRXBUF) next = 0;
/* this descriptors bind to DMA */
activeFrRtsDesc[i] = pxGetNetworkBufferWithDescriptor(
        ipTOTAL_ETHERNET_FRAME_SIZE, 0 );

/* this free descriptors are for swapping when frame come  */
freeFrtsDesc[i] = pxGetNetworkBufferWithDescriptor(
        ipTOTAL_ETHERNET_FRAME_SIZE, 0 );

/* Binding DMA and FreeRTOS descriptors */
Rx_Desc[i].Stat = DMA_RX_OWN;
Rx_Desc[i].Ctrl = DMA_RX_RCH | ETH_BUF_SIZE;
Rx_Desc[i].Addr = (u32)&activeFrRtsDesc[i]->pucEthernetBuffer;
Rx_Desc[i].Next = (u32)&Rx_Desc[next];
} ETH->DMARDLAR = (u32)&Rx_Desc[0]; } ~~~ When Ethernet interrupt occurs I swap activeFrRtsDesc and freeFrtsDesc: ~~~ void ETHIRQHandler( void ) { u32 i = RxBufIndex; do { if (RxDesc[i].Stat & DMARXERRORMASK) { } else if ((RxDesc[i].Stat & DMARXSEGMASK) != DMARXSEGMASK) { } else { /* save data len */ activeFrRtsDesc[i]->xDataLength = ((Rx_Desc[i].Stat >> 16) & 0x3FFF) – 4;
        /* swap activeFrRtsDesc and freeFrtsDesc */
        NetworkBufferDescriptor_t * tmp;
        tmp = activeFrRtsDesc[i];
        activeFrRtsDesc[i] = freeFrtsDesc[i];
        Rx_Desc[i].Addr = (u32)&activeFrRtsDesc[i]->pucEthernetBuffer;
        freeFrtsDesc[i] = tmp;
        /* activate freeRtos task that frees realised descriprors */
        xQueueSendFromISR(realisedDesc, &i, NULL);
    }
    Rx_Desc[i].Stat = DMA_RX_OWN;
    if (++i == NUM_RX_BUF) i = 0;
}while ( (Rx_Desc[i].Stat & DMA_RX_OWN) == 0);
RxBufIndex = i;
if (ETH->DMASR & INT_RBUIE) 
{
    /* Rx DMA suspended, resume DMA reception. */
    ETH->DMASR   = INT_RBUIE;
    ETH->DMARPDR = 0;
    ETH->DMASR |= INT_RBUIE;
}
/* Clear the interrupt pending bits. */
ETH->DMASR = INT_NISE | INT_RIE;
} ~~~ In prvEMACHandlerTask I proceed and free realised FreeRTOS+TCP descriptors: ~~~ static void prvEMACHandlerTask( void *pvParameters ) { IPStackEvent_t xRxEvent; int realisedBuffer; emacInit();
for( ;; )
{
    if (xQueueReceive( realisedDesc, &realisedBuffer, portMAX_DELAY) == pdFALSE)
        continue;

    /* processing realised descriptor */
    if( eConsiderFrameForProcessing( freeFrtsDesc[realisedBuffer]->pucEthernetBuffer )
            == eProcessBuffer )
    {
        /* The event about to be sent to the TCP/IP is an Rx event. */
        xRxEvent.eEventType = eNetworkRxEvent;

        /* pvData is used to point to the network buffer descriptor that
           now references the received data. */
        xRxEvent.pvData = ( void * ) freeFrtsDesc[realisedBuffer];

        /* Send the data to the TCP/IP stack. */
        if( xSendEventStructToIPTask( &xRxEvent, 0 ) == pdFALSE )
        {
            /* The buffer could not be sent to the IP task so the buffer
               must be released. */
            vReleaseNetworkBufferAndDescriptor( freeFrtsDesc[realisedBuffer] );

            /* Make a call to the standard trace macro to log the
               occurrence. */
            iptraceETHERNET_RX_EVENT_LOST();
        }
        else
        {
            /* The message was successfully sent to the TCP/IP stack.
               Call the standard trace macro to log the occurrence. */
            iptraceNETWORK_INTERFACE_RECEIVE();
        }
    }
    else
    {
        /* The Ethernet frame can be dropped, but the Ethernet buffer
           must be released. */
        vReleaseNetworkBufferAndDescriptor( freeFrtsDesc[realisedBuffer] );
    }
    /* alloc new descriptor for future */
    freeFrtsDesc[realisedBuffer] = pxGetNetworkBufferWithDescriptor(ipTOTAL_ETHERNET_FRAME_SIZE, 0 );
}
} ~~~ Does it correct? I get hardfault on eConsiderFrameForProcessing(). What am I doing wrong? Regards, Vasilij

Receiving Data Using Zero-Copy driver

Its very difficult to get into chip specifics without digging through the hardware manual, etc., so perhaps I can take a different approach and ask what is causing the hard fault? Is it a null reference, an invalid memory reference, or something else? The following might help: http://www.freertos.org/Debugging-Hard-Faults-On-Cortex-M-Microcontrollers.html

Receiving Data Using Zero-Copy driver

Hi Vasilij, It is complex to write a zero-copy NetworkInterface.
I’m trying to create Zero-Copy driver for my stm32f429. As describe here I should to declare handler (standard RTOS task) where DMA descriptor with incoming frame (that references the Ethernet buffer containing the received data) are swapped with free FreeRTOS+TCP descriptor. But in my MCU DMA descriptor have only 3 parameters:
~~~~ // DMA Descriptors. typedef struct { volatile u32 Stat; u32 Ctrl; u32 Addr; u32 Next; } RX_Desc; ~~~~ You only need to set the Addr field. DMA will read from that address while transmitting, or it will write to that address when receiving a packet. This is wrong: ~~~~ Rx_Desc[i].Addr = (u32)&activeFrRtsDesc[i]->pucEthernetBuffer; ~~~~ This is what you meant to do, without the &,l translating a uint8_t* to a uint32_t : ~~~~ Rx_Desc[i].Addr = (u32)activeFrRtsDesc[i]->pucEthernetBuffer; ~~~~ Now DMA will write to your pucEthernetBuffer in stead of currupting your Network Buffer 🙂
So I can’t just swap two pointers. So I am trying to bind DMA descriptors with buffers in Pre-created FreeRTOS+TCP descriptors
Your text is correct. I wouldn’t use arrays activeFrRtsDesc and freeFrtsDesc. The actual descriptors (Rx_Desc and Tx_Desc) will store the pointers for you and no-one will change them. The following function translates from a uint8_t* pucEthernetBuffer back to NetworkBufferDescriptor_t *: ~~~~ NetworkBufferDescriptort *pxPacketBufferto_NetworkBuffer( void *pvBuffer ); ~~~~ So when a transmission is complete, you can do the following: ~~~~ uint8t* ucBuffer = ( uint8t * )TxDesc[i].Addr; NetworkBufferDescriptort pxNetworkBuffer = pxPacketBuffer_to_NetworkBuffer( ( void * )ucBuffer ); / Remove the reference. / Tx_Desc[i].Addr = ( uint32_t )0u; / Release the buffer because it has been sent. */ vReleaseNetworkBufferAndDescriptor( pxNetworkBuffer ); ~~~~ Sending a packet will need: ~~~~ /* Pass the buffer to the descriptor. / Tx_Desc[i].Addr = ( uint32_t )pxDescriptor->pucEthernetBuffer; / Ask to set the IPv4 checksum. Also need an Interrupt on Completion / Tx_Desc[i].Stat |= ETH_DMATXDESC_CHECKSUMTCPUDPICMPFULL | ETH_DMATXDESC_IC; / The Network Buffer has been passed to DMA, no need to release it. */ bReleaseAfterSend = pdFALSE; ~~~~
When Ethernet interrupt occurs I swap activeFrRtsDesc and freeFrtsDesc:
My advice is: do as little as possible from within an ETH ISR. Two interrupts will be interesting for a zero-copy driver: reception and transmission complete. When these interrupts occur, just set a (volatile) flag and wake-up your prvEMACHandlerTask(). As is done in all examples of NetworkInterface.c FromprvEMACHandlerTask(), a normal task, you will follow Rx_Desc[] as long as the DMA_RX_OWN bit is low. ~~~~ NetworkBufferDescriptort *pxNetworkBuffer = ( NetworkBufferDescriptort * )RxDesc[i].Addr; /* Now pass pxNetworkBuffer to the IP-task in a normal way, using xSendEventStructToIPTask(). */ pxNetworkBuffer = pxGetNetworkBufferWithDescriptor( ); if( pxNetworkBuffer != NULL ) { RxDesc[i].Addr = pxNetworkBuffer->pucEthernetBuffer; } ~~~~
In prvEMACHandlerTask I proceed and free realised FreeRTOS+TCP descriptors: Does it correct? I get hardfault on eConsiderFrameForProcessing(). What am I doing wrong?
At least one assignment was wrong. But as I said above, I wouldn’t do processing from within the ISR. prvEMACHandlerTask() should have a pretty high priority so that after the ISR, it will quickly become active. The task will send the Network Buffers received to the IP-task, set new buffers, and it will release TX buffers that have been sent. The handling of received packets in your prvEMACHandlerTask() looks good. Call eConsiderFrameForProcessing() as early as possible in order to spare a Network Buffer. If you’re not taking a Network Buffer, there is no need to release it. All you’d have to do is to set DMA_RX_OWN again. Do not forget to set ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES = 1 because you already filter the packets. Good luck

Receiving Data Using Zero-Copy driver

Hello Hein Tibosch, Thank you a lot for explanations! It realy helped. I wrote own zero-copy NetworkInterface as you said. It works! Here my NetworkInterface:

Receiving Data Using Zero-Copy driver

Hi Vasilij, A lot is good now, here below I added some “//” comments to make things even better 🙂 ~~~~ BaseTypet xNetworkInterfaceInitialise( void ) { if( xEMACTaskHandle == NULL ) { xTaskCreate( prvEMACHandlerTask, “EMAC”, 20 * configMINIMALSTACKSIZE, NULL, configMAXPRIORITIES – 1, &xEMACTaskHandle ); } // Only return pdPASS if you have detected a Link Status and if the device (PHY) is usable. // The function will be called again and again until it returns pdPASS return pdPASS; } ~~~~ ~~~~ BaseTypet xNetworkInterfaceOutput( xNetworkBufferDescriptort * const pxDescriptor, BaseType_t bReleaseAfterSend ) { static u8 TxBufIndex = 0; u32 ulTransmitSize = pxDescriptor->xDataLength;
u32 i = TxBufIndex;
/* Wait until previous packet transmitted. */
// In a later version you might want to make this blocking and get woken-up // by an ISR: TX-complete or so. while (TxDesc[i].CtrlStat & DMATX_OWN); // The TX-complete event could also trigger cleaning up old TX buffers. // Now you’re leaving Network Buffers unused for a longer period // of time.
uint8_t* ucBuffer = ( uint8_t * )Tx_Desc[i].Addr;
if ( ucBuffer != 0 )
{
    NetworkBufferDescriptor_t *pxNetworkBuffer = 
        pxPacketBuffer_to_NetworkBuffer( ( void * )ucBuffer );
    vReleaseNetworkBufferAndDescriptor( pxNetworkBuffer );
    Tx_Desc[i].Addr = ( uint32_t )0u;
}


// Before actually sending a packet, please check if the Link Status
// is still high. If it is low, just do not send anything and release
// the Network Buffer as below.
...
Tx_Desc[i].Addr = ( uint32_t )pxDescriptor->pucEthernetBuffer;
// You are passing the ownership of 'pxDescriptor' to DMA, soa you may not
// release it anymore. Set bReleaseAfterSend to false here:
bReleaseAfterSend = pdFALSE;
// Also make sure that '#define ipconfigZERO_COPY_TX_DRIVER   1'

Tx_Desc[i].Size = ulTransmitSize;

Tx_Desc[i].CtrlStat |= DMA_TX_OWN;
if (++i == NUM_TX_BUF) i = 0;
TxBufIndex = i;
/* Start frame transmission. */
ETH->DMASR   = DSR_TPSS;
ETH->DMATPDR = 0;

iptraceNETWORK_INTERFACE_TRANSMIT();
if( bReleaseAfterSend != pdFALSE )
{
    vReleaseNetworkBufferAndDescriptor( pxDescriptor );
}
~~~~ ~~~~ void ETHIRQHandler( void ) { // What I miss here is the checks for the RX and TX-complete interrupts // These would be good reasons to wake-up your task. BaseTypet pxHigherPriorityTaskWoken; vTaskNotifyGiveFromISR( xEMACTaskHandle, &pxHigherPriorityTaskWoken ); // I tend to put ‘portYIELDFROMISR()’ as the last call in an ISR portYIELDFROMISR( pxHigherPriorityTaskWoken ); if (ETH->DMASR & INTRBUIE) { /* Rx DMA suspended, resume DMA reception. */ ETH->DMASR = INTRBUIE; ETH->DMARPDR = 0; ETH->DMASR |= INTRBUIE; } /* Clear the interrupt pending bits. */ ETH->DMASR = INTNISE | INT_RIE; } ~~~~ ~~~~ static void prvEMACHandlerTask( void *pvParameters ) { IPStackEvent_t xRxEvent; uint8_t *pucTemp; NetworkBufferDescriptor_t *pxDescriptor;
_xNetworkInterfaceInitialise();

for( ;; )
{
    ulTaskNotifyTake( pdFALSE, portMAX_DELAY );
    u32 i = RxBufIndex;
    do
    {
        if (Rx_Desc[i].Stat & DMA_RX_ERROR_MASK)
        {
// What here? Reset the descriptor maybe and carry on? } else if ((RxDesc[i].Stat & DMARXSEGMASK) != DMARXSEGMASK) { // What does ‘DMARXSEGMASK’ stand for? } else { pxDescriptor = pxGetNetworkBufferWithDescriptor( ipTOTALETHERNETFRAME_SIZE, 0 ); pucTemp = pxDescriptor->pucEthernetBuffer;
            pxDescriptor->xDataLength = ((Rx_Desc[i].Stat >> 16) & 0x3FFF) - 4;
            pxDescriptor->pucEthernetBuffer = (u8 *)(Rx_Desc[i].Addr);

            Rx_Desc[i].Addr = (u32)pucTemp;
// Oops: you don’t have to do such difficult things ! // The IP-task will have done this and there are access functions for this, most importantly: // NetworkBufferDescriptort *pxPacketBufferto_NetworkBuffer( void *pvBuffer ) // Here is some new code:
// Get the char buffer:
uint8_t *pucBuffer = (uint8_t *)(Rx_Desc[i].Addr);
// Lookup to which Network Buffer it belongs (using the back-pointer) :
NetworkBufferDescriptor_t *pxDescriptor = pxPacketBuffer_to_NetworkBuffer( (void *)pucBuffer );
// And take the buffer away, assign NULL or a new buffer:
Rx_Desc[i].Addr = (u32)NULL;

            *( ( NetworkBufferDescriptor_t ** )
                    ( pxDescriptor->pucEthernetBuffer - ipBUFFER_PADDING ) ) = 
                pxDescriptor;
            if( eConsiderFrameForProcessing( pxDescriptor->pucEthernetBuffer )
                    == eProcessBuffer )
            {
                xRxEvent.eEventType = eNetworkRxEvent;
                xRxEvent.pvData = ( void * ) pxDescriptor;
                if( xSendEventStructToIPTask( &xRxEvent, 0 ) == pdFALSE )
                {
                    vReleaseNetworkBufferAndDescriptor( pxDescriptor );
                    iptraceETHERNET_RX_EVENT_LOST();
                }
                else
                {
                    iptraceNETWORK_INTERFACE_RECEIVE();
                }
            }
            else
            {
// You’re dropping this packet but you could re-use it for the next DMA transaction // That would save time
                /* The Ethernet frame can be dropped, but the Ethernet buffer
                   must be released. */
                vReleaseNetworkBufferAndDescriptor( pxDescriptor );
            }
        }
// Don’t set “Stat = DMARXOWN” but “Stat |= DMARXOWN” // Unless you want to clear all other flags. RxDesc[i].Stat = DMARX_OWN; // So what I’m missing here is reloading the ‘Addr’ field
        if (++i == NUM_RX_BUF) i = 0;
    }while ( (Rx_Desc[i].Stat & DMA_RX_OWN) == 0);
    RxBufIndex = i;

}
} ~~~~ If things aren’t clear, please ask. Regards.

Receiving Data Using Zero-Copy driver

Hello Hein Tibosch, Thank you a lot for help. I have not impruved my driver accoding your tips, but I will:). I have a problem with current zero-copy dirver: I figured out that it works with tcp fine, but does not pinging at all. I checked ipconfigREPLYTOINCOMING_PINGS it sets to 1 (my FreeRTOSIPConfig.h in attachments). And I have some question about your tips:
Only return pdPASS if you have detected a Link Status and if the device (PHY) is usable. The function will be called again and again until it returns pdPASS
I can create FreeRTOS+TCP buffer descriptors only after sheduler starting, isn’t it? I use it for first initialization of rx and tx DMA descriptors for EMAC. So I can to init my EMAC and phy only after sheduler starting (for example in prvEMACHandlerTask). That why I init my EMAC and PHY not in xNetworkInterfaceInitialise. Haw can I to workaround it?
Don’t set “Stat = DMARXOWN” but “Stat |= DMARXOWN” Unless you want to clear all other flags.
But I don’t need any additional flags in Stat, why shouldn’t to erase them? Regards, Vasilij