Real time embedded FreeRTOS RSS feed 
Homepage FreeRTOS+ Products FreeRTOS Labs Integration Services Contact / Enquiries

Porting FreeRTOS+UDP to a Different Microcontroller


Summary Bullet Points

embedded Ethernet port layer
The network interface port layer sits between the IP
stack and the embedded Ethernet hardware drivers
  • Only the network input and output functions are hardware dependent. The effort in porting the IP stack is therefore limited to the implementation of a network interface.

  • The network interface interfaces the IP stack to the embedded Ethernet peripheral drivers. This page assumes Ethernet peripheral drivers already exist, and are known to work.

  • FreeRTOS+UDP expects the network interface port layer to provide two functions, one to initialise the Ethernet hardware, and another to send data to the network.

  • FreeRTOS+UDP provides the network interface port layer with a software interface that allows the network interface to obtain Ethernet buffers to store raw Ethernet frames, and to obtain network buffer descriptors that reference, and provide information about, an Ethernet buffer.

  • Data received from the network is sent into the IP stack by the network interface port layer on a standard RTOS queue.

  • A simple network interface can can be implemented with little or no modification to the Ethernet MAC peripheral drivers. A more efficient network interface can be implemented by modifying the Ethernet MAC peripheral drivers to enable Ethernet buffers to be passed into and out of the IP stack by reference (with no copying of data from one buffer to another).


On this page (intended to be read in order):



Network Buffer Descriptors and Ethernet Buffers

Network buffer descriptors are structures typedef'ed to variables of type xNetworkBufferDescriptor_t. Most of the xNetworkBufferDescriptor_t structure members are for internal use by the IP stack, and must not be modified. Two structure members that can legitimately be modified by the network interface port layer are shown in the code snippet below:


typedef struct xNETWORK_BUFFER
{
    /* When an Ethernet frame is received xDataLength is set to the Ethernet
    frame's length.  The bytes used by the Ethernet frame CRC are not included
    in the length value. */
    size_t xDataLength;

    /* pucEthernetBuffer is set to point to the start of the Ethernet buffer.
    The Ethernet buffer is the buffer that stores the raw Ethernet frame bytes. */
    uint8_t *pucEthernetBuffer;

} xNetworkBufferDescriptor_t;
						
The xNetworkBufferDescriptor_t structure showing just the members used by the network interface port


embedded Ethernet buffers

The pucEthernetBuffer member points to the start of the Ethernet buffer.
The xDataLength member is set to the size of the Ethernet frame (excluding the CRC bytes).


Functions That Must Be Implemented By The Network Interface

The network interface must implement the following three functions (example implementations for both simple and zero copy implementations are provided on this page):


  1. A function called xNetworkInterfaceInitialise()

    xNetworkInterfaceInitialise() is responsible for initialising the Ethernet (or other network) hardware. Typically xNetworkInterfaceInitialise() will make calls into the Ethernet MAC peripheral driver library to configure the Ethernet interface, initialise DMA descriptors, and request the PHY auto-negotiates a network link.

    xNetworkInterfaceInitialise() does not take any parameters, returns pdPASS if the initialisation was successful, and returns pdFAIL if the initialisation fails.

    
    BaseType_t xNetworkInterfaceInitialise( void );
    										
    The xNetworkInterfaceInitialise() function prototype


  2. A function called xNetworkInterfaceOutput()

    xNetworkInterfaceOutput() is responsible for transmitting the data generated by the IP stack.

    xNetworkInterfaceOutput() takes a pointer to a network buffer descriptor as a parameter, returns pdPASS if the Ethernet frame referenced by the network buffer descriptor was successfully sent to the network, and returns pdFAIL if the Ethernet frame referenced by the network buffer descriptor was not successfully sent to the network.

    
    BaseType_t xNetworkInterfaceOutput
                        ( xNetworkBufferDescriptor_t * const pxNetworkBuffer );
    										
    The xNetworkInterfaceOutput() function prototype


  3. A function that sends received data to the IP stack

    A network buffer descriptor must be allocated to reference Ethernet frames that have been filled by the Ethernet MAC hardware. The completed network buffer descriptors must then be posted to an RTOS queue for processing by the UDP/IP stack. Outline examples are provided on this page for reference. Complete examples can be found in the FreeRTOS+UDP distribution.


Interface Functions Provided by the IP Stack

The IP stack allows the creation of simple (as opposed to zero copy) network interface port layers by providing the following functions:


In addition to the functions provided to allow the creation of simple network interfaces, the IP stack provides the following function to allow the creation of more efficient zero copy network interfaces:


Creating a Simple Network Interface Port Layer

Simple network interfaces can be created by using existing Ethernet MAC driver libraries to copy Ethernet frames between buffers allocated by the IP stack and buffers allocated by the embedded Ethernet peripheral drivers. [A more efficient zero copy alternative is provided after the simple example.]

A Simple xNetworkInterfaceInitialise() Implementation


BaseType_t xNetworkInterfaceInitialise( void )
{
BaseType_t xReturn;

    /*
     * Perform the hardware specific network initialisation here.  Typically
     * that will involve using the Ethernet driver library to initialise the
     * Ethernet (or other network) hardware, initialise DMA descriptors, and
     * perform a PHY auto-negotiation to obtain a network link.
     *
     * This example assumes InitialiseNetwork() is an Ethernet peripheral driver
     * library function that returns 0 if the initialisation fails.
     */
    if( InitialiseNetwork() == 0 )
    {
        xReturn = pdFAIL;
    }
    else
    {
        xReturn = pdPASS;
    }

    return xReturn;
}
								
xNetworkInterfaceInitialise() is hardware specific, therefore this example describes what needs to
be done without showing any detail


A Simple xNetworkInterfaceOutput() Implementation


BaseType_t xNetworkInterfaceOutput( xNetworkBufferDescriptor_t * const pxNetworkBuffer )
{
    /* Simple network interfaces (as opposed to more efficient zero copy network
    interfaces) just use Ethernet peripheral driver library functions to copy
    data from the FreeRTOS+UDP buffer into the peripheral driver's own buffer.
    This example assumes SendData() is a peripheral driver library function that
    takes a pointer to the start of the data to be sent and the length of the
    data to be sent as two separate parameters.  The start of the data is located
    by pxNetworkBuffer->pucEthernetBuffer.  The length of the data is located
    by pxNetworkBuffer->xDataLength. */
    SendData( pxNetworkBuffer->pucBuffer, pxNetworkBuffer->xDataLength );

    /* Call the standard trace macro to log the send event. */
    iptraceNETWORK_INTERFACE_TRANSMIT();

    /* It is assumed SendData() copies the data out of the FreeRTOS+UDP Ethernet
    buffer.  The Ethernet buffer is therefore no longer needed, and must
    be returned to the IP stack. */
    vNetworkBufferRelease( pxNetworkBuffer );

    return pdTRUE;
}
								
Example implementation of xNetworkInterfaceOutput() suitable for a simple (rather than zero copy) network interface implementation


Passing Received Data to the IP Stack

Data that is received from the Ethernet (or other network) driver is placed into an Ethernet buffer, then passed into the IP stack on the network event queue called xNetworkEventQueue.

The network event queue is a standard RTOS queue that holds variables of type xIPStackEvent_t.


typedef struct IP_TASK_COMMANDS
{
	/* Specifies the type of event being posted to the queue. */
	eIPEvent_t eEventType;

	/* Points to additional data about the event. */
	void *pvData;
} xIPStackEvent_t;
								
The xIPStackEvent_t type

Messages notifying the IP stack of Ethernet receive events have their eEventType member set to eEthernetRxEvent, and their pvData member set to point to the network buffer descriptor that references the received data.

The IP stack receives data without explicitly calling a receive function. The receive handler can therefore have any name and prototype deemed appropriate.

NOTE 1: If BufferAllocation_2.c is used then network buffer descriptors and Ethernet buffers cannot be allocated from inside an interrupt service routine (ISR). In this case the Ethernet MAC receive interrupt can defer the receive processing to a task. The task should be given a high priority relative to other tasks in the system to ensure it runs immediately after the ISR ends (contiguous in time, just as if all the processing had been performed by the interrupt itself). This is called deferred interrupt processing.

NOTE 2: If ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES is set to 1 in FreeRTOSIPConfig.h and the Ethernet hardware is not already filtering irrelevant Ethernet frames then eConsiderFrameForProcessing() can be called to determine if the received Ethernet frame can be safely dropped (rather than sent to the IP stack for processing).


/* The deferred interrupt handler is a standard RTOS task. */
static void prvEMACDeferredInterruptHandlerTask( void *pvParameters )
{
xNetworkBufferDescriptor_t *pxNetworkBuffer;
size_t xBytesReceived;
extern QueueHandle_t xNetworkEventQueue;
xIPStackEvent_t xRxEvent;

    for( ;; )
    {
        /* Wait for the Ethernet MAC interrupt to indicate that another packet
        has been received.  It is assumed xEMACRxEventSemaphore is a counting
        semaphore (to count the Rx events) and that the semaphore has already
        been created. */
        xSemaphoreTake( xEMACRxEventSemaphore, portMAX_DELAY );

        /* See how much data was received.  Here it is assumed ReceiveSize() is
        a peripheral driver function that returns the number of bytes in the
        received Ethernet frame. */
        xBytesReceived = ReceiveSize();

        if( xBytesReceived > 0 )
        {
            /* Allocate a network buffer descriptor that references an Ethernet
            buffer large enough to hold the received data. */
            pxNetworkBuffer = pxNetworkBufferGet( xBytesReceived, 0 );

            if( pxNetworkBuffer != NULL )
            {
                /* pxNetworkBuffer->pucEthernetBuffer now points to an Ethernet
                buffer large enough to hold the received data.  Copy the
                received data into pcNetworkBuffer->pucEthernetBuffer.  Here it
                is assumed ReceiveData() is a peripheral driver function that
                copies the received data into a buffer passed in as the function's
                parameter. */
                ReceiveData( pxNetworkBuffer->pucEthernetBuffer );

                /* See if the data contained in the received Ethernet frame needs
                to be processed. */
                if( eConsiderFrameForProcessing( pxNetworkBuffer->pucEthernetBuffer )
                                                                      == eProcessBuffer )
                {
                    /* The event about to be sent to the IP stack is an Rx event. */
                    xRxEvent.eEventType = eEthernetRxEvent;

                    /* pvData is used to point to the network buffer descriptor that
                    references the received data. */
                    xRxEvent.pvData = ( void * ) pxNetworkBuffer;

                    /* Send the data to the IP stack. */
                    if( xQueueSendToBack( xNetworkEventQueue, &xRxEvent, 0 ) == pdFALSE )
                    {
                        /* The buffer could not be sent to the IP task so the buffer
                        must be released. */
                        vNetworkBufferRelease( pxNetworkBuffer );

                        /* Make a call to the standard trace macro to log the
                        occurrence. */
                        iptraceETHERNET_RX_EVENT_LOST();
                    }
                    else
                    {
                        /* The message was successfully sent to the 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. */
                    vNetworkBufferRelease( pxNetworkBuffer );
                }
            }
            else
            {
                /* The event was lost because a network buffer was not available.
                Call the standard trace macro to log the occurrence. */
                iptraceETHERNET_RX_EVENT_LOST();
            }
        }
    }
}
								
An example of a simple (rather than more efficient zero copy) receive handler


Creating an Efficient Zero Copy Network Interface Port Layer

Please ensure to read the information about creating a simple network interface before reading this section.

Simple network interfaces copy the data contained in Ethernet frames between buffers allocated by the IP stack and buffers allocated by the Ethernet (or other network) peripheral drivers. Copying the data between buffers in this manner normally allows the Ethernet peripheral driver library to be used with little or no modification. Zero copy network interfaces never copy data from one buffer to another, but instead pass only references to buffers between the peripheral driver library and the IP stack. Creating a zero copy network interface will necessitate edits to most standard Ethernet peripheral driver library implementations.

Most Ethernet MACs transmit data by using a Direct Memory Access (DMA) peripheral to move data from a memory buffer to the Ethernet MAC hardware, and receive data by using a DMA peripheral to move data from the Ethernet MAC hardware into one of a set of pre-allocated memory buffers. Typically the pre-allocated memory buffers will be referenced by a set of chained DMA descriptors (each descriptor points to the next, with the last descriptor pointing back to the first).

Embedded Ethernet DMA buffers

A chain of embedded Ethernet peripheral DMA descriptors pointing to pre-allocated DMA buffers


A Zero Copy xNetworkInterfaceInitialise() Implementation

xNetworkInterfaceInitialise() must ensure the buffers referenced by the received DMA descriptors are allocated using pucEthernetBufferGet(). It is not necessary to allocate buffers to the transmit DMA descriptors until data is actually being transmitted.

Embedded Ethernet Rx and Tx descriptors after initialisation

After initialisation, the Ethernet DMA Rx descriptors reference buffers allocated using pucEthernetBufferGet(),
and the Ethernet DMA Tx descriptors are initialised but don't yet reference any Ethernet buffers.


A Zero Copy xNetworkInterfaceOutput() Implementation

xNetworkInterfaceOutput() updates an Ethernet DMA Tx descriptor so the descriptor points to the Ethernet buffer being transmitted.

NOTE: The Ethernet buffer must be released after the data it contains has been transmitted. If BufferAllocation_2.c is used the Ethernet buffer cannot be released from the Ethernet Transmit End interrupt, so must be release by the xNetworkInterfaceOutput() function the next time the same DMA descriptor is used.


BaseType_t xNetworkInterfaceOutput( xNetworkBufferDescriptor_t * const pxNetworkBuffer )
{
DMADescriptor_t *pxTxDescriptor;

    /* This example assumes GetNextTxDescriptor() is an Ethernet MAC driver library
    function that returns a pointer to a DMA descriptor of type DMADescriptor_t. */
    pxTxDescriptor = GetNextTxDescriptor();

    /* Further, this example assumes the DMADescriptor_t type has a member
    called pucEthernetBuffer that points to the buffer the DMA will transmit, and
    a member called xDataLength that holds the length of the data the DMA will
    transmit.  If the DMA descriptor already references an Ethernet buffer then
    return the Ethernet buffer to the IP stack.  (If a buffer allocation scheme
    other than BufferAllocation_2.c is in use then the Ethernet buffer will
    probably be released in the Ethernet Tx end interrupt handler, in which case
    this step is not be necessary) */
    if( pxTxDescriptor->pucEthernetBuffer != NULL )
    {
        /* Note this is releasing just an Ethernet buffer, not a network buffer
        descriptor. */
        vEthernetBufferRelease( pxTxDescriptor->pucEthernetBuffer );
    }

    /*Configure the DMA descriptor to send the data referenced by the network buffer
    descriptor.  This example assumes SendData() is an Ethernet peripheral driver
    function. */
    pxTxDescriptor->pucEthernetBuffer = pxNetworkBuffer->pucEthernetBuffer;
    pxTxDescriptor->xDataLength = pxNetworkBuffer->xDataLength;
    SendData( pxTxDescriptor );

    /* Call the standard trace macro to log the send event. */
    iptraceNETWORK_INTERFACE_TRANSMIT();

    /* The network buffer descriptor must now be returned to the IP stack, but
    the Ethernet buffer referenced by the network buffer descriptor is still in
    use by the DMA.  Remove the reference to the Ethernet buffer from the network
    buffer descriptor so releasing the network buffer descriptor does not result
    in the Ethernet buffer also being released. */
    pxNetworkBuffer->pucEthernetBuffer = NULL;
    vNetworkBufferRelease( pxNetworkBuffer );

    return pdTRUE;
}
								
An example zero copy implementation of xNetworkInterfaceOutput()


Passing Zero Copy Received Data to the IP Stack

If data that is received from the Ethernet (or other network) driver is DMA'ed into an Ethernet buffer then the data is not copied out of the DMA buffer. Instead the Ethernet Rx DMA descriptor that references the received data is updated to point to a new Ethernet buffer, and the Ethernet buffer that contains the data is passed by reference into the IP stack.

All the notes regarding the implementation of the simple receive handler apply to the zero copy receive handler and are not repeated here.


/* The deferred interrupt handler is a standard RTOS task. */
static void prvEMACDeferredInterruptHandlerTask( void *pvParameters )
{
xNetworkBufferDescriptor_t *pxNetworkBuffer;
size_t xBytesReceived;
extern QueueHandle_t xNetworkEventQueue;
DMADescriptor_t *pxRxDescriptor;
uint8_t *pucTemp;

    for( ;; )
    {
        /* Wait for the Ethernet MAC interrupt to indicate that another packet
        has been received. */
        xSemaphoreTake( xEMACRxEventSemaphore, portMAX_DELAY );

        /* This example assumes GetNextRxDescriptor() is an Ethernet MAC driver
        library function that returns a pointer to the DMA descriptor (of type
        DMADescriptor_t again) that references the Ethernet buffer containing the
        received data. */
        pxRxDescriptor = GetNextRxDescriptor();

        /* Allocate a new network buffer descriptor that references an Ethernet
        frame large enough to hold the maximum network packet size (as defined
        in the FreeRTOSIPConfig.h header file. */
        pxNetworkBuffer = pxNetworkBufferGet( ipTOTAL_ETHERNET_FRAME_SIZE, 0 );

        /* Copy the pointer to the newly allocated Ethernet frame to a temporary
        variable. */
        pucTemp = pxNetworkBuffer->pucEthernetBuffer;

        /* This example assumes that the DMADescriptor_t type has a member
        called pucEthernetBuffer that points to the Ethernet buffer containing
        the received data, and a member called xDataLength that holds the length
        of the received data.  Update the newly allocated network buffer descriptor
        to point to the Ethernet buffer that contains the received data. */
        pxNetworkBuffer->pucEthernetBuffer = pxRxDescriptor->pucEthernetBuffer;
        pxNetworkBuffer->xDataLength = pxRxDescriptor->xDataLength;

        /* Update the Ethernet Rx DMA descriptor to point to the newly allocated
        Ethernet buffer. */
        pxRxDescriptor->puxEthernetBuffer = pucTemp;



        /*
         * The network buffer descriptor now points to the Ethernet buffer that
         * contains the received data, and the Ethernet DMA descriptor now points
         * to a newly allocated (and empty) Ethernet buffer ready to receive more
         * data.  No data was copied.  Only pointers to data were swapped.
         *
         * THE REST OF THE RECEIVE HANDLER FUNCTION FOLLOWS THE EXAMPLE PROVIDED
         * FOR THE SIMPLE ETHERNET INTERFACE IMPLEMENTATION, whereby the network
         * buffer descriptor is sent to the IP stack on the network event queue.
         */
}
								
An example of a zero copy receive handler function


[ Back to the top ]    [ About FreeRTOS ]    [ FreeRTOS+ Sitemap ]    [ Main FreeRTOS Sitemap ]    [ ]


Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.