Receiving Data Using Zero-Copy driver

yanvasilij wrote on Wednesday, February 01, 2017:

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 rx_descr_init (void) 
{
  u32 i,next;

  realisedDesc = xQueueCreate(3, sizeof(int));
  RxBufIndex = 0;
  for (i = 0, next = 0; i < NUM_RX_BUF; i++) {
    if (++next == NUM_RX_BUF) 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 ETH_IRQHandler( void )
{
	u32 i = RxBufIndex;
	do
	{
		if (Rx_Desc[i].Stat & DMA_RX_ERROR_MASK)
		{
		}
		else if ((Rx_Desc[i].Stat & DMA_RX_SEG_MASK) != DMA_RX_SEG_MASK)
		{
		}
		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

rtel wrote on Wednesday, February 01, 2017:

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

heinbali01 wrote on Thursday, February 02, 2017:

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 :slight_smile:

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

    NetworkBufferDescriptor_t *pxPacketBuffer_to_NetworkBuffer( void *pvBuffer );

So when a transmission is complete, you can do the following:

    uint8_t* ucBuffer = ( uint8_t * )Tx_Desc[i].Addr;
    NetworkBufferDescriptor_t *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.

    NetworkBufferDescriptor_t *pxNetworkBuffer = ( NetworkBufferDescriptor_t * )Rx_Desc[i].Addr;
    /* Now pass pxNetworkBuffer to the IP-task in a normal way,
    using xSendEventStructToIPTask(). */
    pxNetworkBuffer = pxGetNetworkBufferWithDescriptor( );
    if( pxNetworkBuffer != NULL )
    {
        Rx_Desc[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

yanvasilij wrote on Friday, February 03, 2017:

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:

heinbali01 wrote on Friday, February 03, 2017:

Hi Vasilij,

A lot is good now, here below I added some “//” comments to make things even better :slight_smile:

BaseType_t xNetworkInterfaceInitialise( void )
{
	if( xEMACTaskHandle == NULL )
	{
		xTaskCreate( prvEMACHandlerTask, "EMAC", 20 * configMINIMAL_STACK_SIZE, NULL,
				configMAX_PRIORITIES - 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;
}
BaseType_t xNetworkInterfaceOutput( xNetworkBufferDescriptor_t * 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 (Tx_Desc[i].CtrlStat & DMA_TX_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 ETH_IRQHandler( 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.
	BaseType_t pxHigherPriorityTaskWoken;
	vTaskNotifyGiveFromISR( xEMACTaskHandle, &pxHigherPriorityTaskWoken );

// I tend to put 'portYIELD_FROM_ISR()' as the last call in an ISR
	portYIELD_FROM_ISR( pxHigherPriorityTaskWoken );
	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;
}
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 ((Rx_Desc[i].Stat & DMA_RX_SEG_MASK) != DMA_RX_SEG_MASK)
			{
// What does 'DMA_RX_SEG_MASK' stand for?
			}
			else
			{
				pxDescriptor = pxGetNetworkBufferWithDescriptor( ipTOTAL_ETHERNET_FRAME_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:
// NetworkBufferDescriptor_t *pxPacketBuffer_to_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 = DMA_RX_OWN" but "Stat |= DMA_RX_OWN"
// Unless you want to clear all other flags.
			Rx_Desc[i].Stat = DMA_RX_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.

yanvasilij wrote on Tuesday, February 14, 2017:

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 ipconfigREPLY_TO_INCOMING_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 = DMA_RX_OWN” but “Stat |= DMA_RX_OWN” 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