Network Link Layer (NL2) Quick Start

This guide helps you get started with the NL2 by developing four basic network applications. For comprehensive details on using the NL2, please refer to Creating an Application that Uses the NL2.

BASIC NETWORK APPLICATIONS:

Basic Transmitter Application with Logical Transmit Queues

In this section, we will create an eRTOS application that sends 50 Ethernet packets and exits. It requires two command-line arguments:

This application will use Logical Transmit Queues, which function as virtual channels. With this method, multiple applications can simultaneously use the same Hardware Transmit Queue

  1. Add the necessary header and library files to the eRTOS Application Template in Visual Studio.

Applications that use the NL2 must include Rtnl2Api.h and link the Rtnl2Api.lib library in addition to the usual headers, windows.h, RtApi.h, and RtssApi.h. See Creating a Project.

Copy
#include <windows.h>
                    #include <RtApi.h>
                    #include <RtssApi.h>
                #include <Rtnl2Api.h>

  1. Parse the command line arguments.

We start our program by extracting two command line arguments: the NIC name and the Transmit Queue index.

Copy
if (argc < 3)
                    {
                    RtPrintf("Usage:\n\t%s <NicName> <TxQueueIndex>\n", argv[0]);
                    goto end;
                    }
                    CHAR *NicName = argv[1];
                ULONG TxQueueIndex = RtAtoi(argv[2]);

  1. Attach the process to the NL2.

Then, we attach the current process to the NL2 by calling Rtnl2Init. This is necessary to establish communication with the NL2.

Copy
BOOL Succeeded = Rtnl2Init();
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2Init() failed with error 0x%08x", ErrorCode);
                    goto end;
                }

  1. Open a handle to the specified NIC.

To manipulate the specified NIC, we must open a handle to the Network Interface object with Rtnl2OpenInterface.

Copy
RTNL2_HINTERFACE hInterface = Rtnl2OpenInterface(NicName);
                    if (!hInterface)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2OpenInterface() failed with error 0x%08x", ErrorCode);
                    goto end;
                }

  1. Create a Logical Transmit Queue.

With the handle to the Network Interface object, we can now request the creation of a new Logical Transmit Queue on top of the Transmit Queue specified on the command line. The function to create a Logical Transmit Queue is Rtnl2CreateLogicalTxQueue. It has a BufferCount argument that we hard-code to 64 in this example.

Copy
const ULONG BufferCount = 64;
                    RTNL2_HLOGICAL_TX_QUEUE hLogicalTxQueue = Rtnl2CreateLogicalTxQueue(
                    hInterface,
                    TxQueueIndex,
                    BufferCount);
                    if (!hLogicalTxQueue)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2CreateLogicalTxQueue() failed with error 0x%08x", ErrorCode);
                    goto cleanup_interface;
                }

  1. Build a template of Ethernet frame.

Now, let’s build a template of the Ethernet frame to send. This template is a 60-byte buffer comprising a common 14-byte Ethernet header and 46 bytes of payload filled with zeros. We set the Destination MAC Address to the broadcast address, the Source MAC Address to the NIC's MAC Address (obtained by calling Rtnl2GetMacAddress), and the EtherType to an arbitrary value of 0x1234.

Copy
BYTE EthernetFrameTemplate[60] =
                    {
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Dest Address : broadcast
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Source Address : to be filled
                    0x12, 0x34,                         // EtherType : some arbitrary value
                    0x00,                               // Payload : all zeros          
                    };
                    Succeeded = Rtnl2GetMacAddress(hInterface, &EthernetFrameTemplate[6]);
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2GetMacAddress() failed with error 0x%08x", ErrorCode);
                    goto cleanup_queue;
                }

  1. Transmit 50 Ethernet frames.

Now, we repeat the following action 50 times: update the 4 first bytes of the template's payload (to differentiate each transmitted frame), prepare an RTNL2_TRANSMIT_DESC structure, and call Rtnl2TransmitOverLogicalTxQueue.

Note: For various reasons, the Rtnl2TransmitOverLogicalTxQueue function may not be able to transmit the frame, in which case it returns FALSE. This condition is not necessarily an error and should not cause the application to fail.

Copy
ULONG SequenceNumber = 1;
                    for(ULONG i=0; i < 50; i++)
                    {
                    EthernetFrameTemplate[14] = (BYTE)(SequenceNumber >> 24);
                    EthernetFrameTemplate[15] = (BYTE)(SequenceNumber >> 16);
                    EthernetFrameTemplate[16] = (BYTE)(SequenceNumber >>  8);
                    EthernetFrameTemplate[17] = (BYTE)(SequenceNumber);
                    RTNL2_TRANSMIT_DESC TxDesc = { 0 };
                    TxDesc.pFrameData = EthernetFrameTemplate;
                    TxDesc.FrameLength = sizeof(EthernetFrameTemplate);;
                    Succeeded = Rtnl2TransmitOverLogicalTxQueue(
                    hLogicalTxQueue,
                    &TxDesc,
                    sizeof(TxDesc));
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2TransmitOverLogicalTxQueue() failed with error 0x%08x", ErrorCode);
                    // don't exit (failing to transmit a frame is not a fatal error)
                    }
                    SequenceNumber++;
                }

  1. Clean up.

Before terminating the program, we release the used resources by:

Copy
cleanup_queue:
                    Rtnl2DestroyLogicalTxQueue(hLogicalTxQueue);

                    cleanup_interface:
                Rtnl2CloseInterface(hInterface);

Basic Receiver Application with Logical Receive Queues

In this section, we will create an application that waits to receive an Ethernet packet, extracts one or more packets if a burst is received, and exits. It requires two command-line arguments:

This application will use Logical Receive Queues, which function as virtual channels. Multiple applications can simultaneously use the same Receive Queue.

  1. Add the necessary header and library files to the eRTOS Application Template in Visual Studio.

Applications that use the NL2 must include Rtnl2Api.h and link the Rtnl2Api.lib library in addition to the usual headers, windows.h, RtApi.h, and RtssApi.h. See Creating a Project.

Copy
#include <windows.h>
                    #include <RtApi.h>
                    #include <RtssApi.h>
                #include <Rtnl2Api.h>

  1. Parse the command line arguments.

We start our program by extracting two command line arguments: the NIC name and the Receive Queue index.

Copy
if (argc < 3)
                    {
                    RtPrintf("Usage:\n\t%s <NicName> <RxQueueIndex>\n", argv[0]);
                    goto end;
                    }
                    CHAR *NicName = argv[1];
                ULONG RxQueueIndex = RtAtoi(argv[2]);

  1. Attach the process to the NL2.

Then, we attach the current process to the NL2 by calling Rtnl2Init. This is necessary to establish communication with the NL2.

Copy
BOOL Succeeded = Rtnl2Init();
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2Init() failed with error 0x%08x", ErrorCode);
                    goto end;
                }

  1. Open a handle to the specified NIC.

To manipulate the specified NIC, we must open a handle to the Network Interface object with Rtnl2OpenInterface.

Copy
RTNL2_HINTERFACE hInterface = Rtnl2OpenInterface(NicName);
                    if (!hInterface)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2OpenInterface() failed with error 0x%08x", ErrorCode);
                    goto end;
                }

  1. Create a Logical Receive Queue.

With the handle to the Network Interface object, we can now request the creation of a new Logical Receive Queue on top of the Receive Queue specified on the command line. The function to create a Logical Receive Queue is Rtnl2CreateLogicalRxQueue. It has a BufferCount argument that we hard-code to 64 in this example, and it returns a handle to an events set every time a packet is received.

Copy
const ULONG BufferCount = 64;
                    HANDLE hRxEvent;
                    RTNL2_HLOGICAL_RX_QUEUE hLogicalRxQueue = Rtnl2CreateLogicalRxQueue(
                    hInterface,
                    RxQueueIndex,
                    BufferCount,
                    &hRxEvent);
                    if (!hLogicalRxQueue)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2CreateLogicalRxQueue() failed with error 0x%08x", ErrorCode);
                    goto cleanup_interface;
                }

  1. Start the Logical Receive Queue.

A Logical Receive Queue is not immediately ready to receive after creation. The application may need to configure the Receive filters before starting reception. In our basic application, Receive filters, so we immediately start the Logical Receive Queue with Rtnl2StartLogicalRxQueue.

Copy
Succeeded = Rtnl2StartLogicalRxQueue(hLogicalRxQueue);
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2StartLogicalRxQueue() failed with error 0x%08x", ErrorCode);
                    goto cleanup_queue;
                }

  1. Wait for the reception of an Ethernet frame.

Now, the Logical Receive Queue is capturing the incoming Ethernet frames and signaling the Receive Event every time a new frame is captured. Let’s wait for the event to be signaled.

Copy
DWORD WaitResult = RtWaitForSingleObject(hRxEvent, INFINITE);
                    if (WaitResult == WAIT_FAILED)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("RtWaitForSingleObject() failed with error 0x%08x", ErrorCode);
                    goto cleanup_queue;
                }

  1. Extract all the received frames and print their contents.

At this point, we know that the Logical Receive Queue contains at least one frame. We call Rtnl2ReceiveFromLogicalRxQueue in a loop to extract all the frames received so far, and print their contents. We break the loop as soon as the Rtnl2ReceiveFromLogicalRxQueue function returns FALSE.

Copy
for(;;)
                    {
                    //
                    // Extract the next received frame
                    //
                    static BYTE ReceivedFrameBuffer[1514];
                    RTNL2_RECEIVE_DESC RxDesc = { 0 };
                    RxDesc.pFrameData = ReceivedFrameBuffer;
                    RxDesc.BufferLength = sizeof(ReceivedFrameBuffer);
                    Succeeded = Rtnl2ReceiveFromLogicalRxQueue(hLogicalRxQueue,
                    &RxDesc,
                    sizeof(RxDesc));
                    if (!Succeeded)
                    {
                    break;
                    }

                    //
                    // Print the content of the received frame
                    //
                    RtPrintf("Received a %u-byte Ethernet packet!\n", RxDesc.FrameLength);
                    for(ULONG i=0; i < RxDesc.FrameLength; i+=16)
                    {
                    RtPrintf("%04x ", i);
                    ULONG lim = min(i+16, RxDesc.FrameLength);
                    for(ULONG j=i; j < lim; j++)
                    {
                    RtPrintf(" %02x", RxDesc.pFrameData[j]);
                    }
                    RtPrintf("\n");
                    }
                }

  1. Clean up.

Before terminating the program, we release the used resources by:

Copy
cleanup_queue:
                    Rtnl2DestroyLogicalRxQueue(hLogicalRxQueue);

                    cleanup_interface:
                Rtnl2CloseInterface(hInterface);

Basic Transmitter Application with Physical Transmit Queues

In this section, we will create an application that takes advantage of Physical Transmit Queues. This application will send a burst of Ethernet packets at line speed, wait 1 second, send a second burst at line speed, and terminate. It requires two command-line arguments:

Note: A Physical Queue is an object that allows you to take exclusive control of a hardware queue and read/write directly from/to the NIC's DMA buffers. The APIs available for Physical Queues are less convenient than those for Logical Queues, but Physical Queues are the way to leverage the best performance from the hardware.

  1. Add the necessary header and library files to the eRTOS Application Template in Visual Studio.

Applications that use the NL2 must include Rtnl2Api.h and link the Rtnl2Api.lib library in addition to the usual headers, windows.h, RtApi.h, and RtssApi.h. See Creating a Project.

Copy
#include <windows.h>
                    #include <RtApi.h>
                    #include <RtssApi.h>
                #include <Rtnl2Api.h>

  1. Parse the command line arguments.

We start our program by extracting two command line arguments: the NIC name and the Transmit Queue index.

Copy
if (argc < 3)
                    {
                    RtPrintf("Usage:\n\t%s <NicName> <TxQueueIndex>\n", argv[0]);
                    goto end;
                    }
                    CHAR *NicName = argv[1];
                ULONG TxQueueIndex = RtAtoi(argv[2]);

  1. Attach the process to the NL2.

Then, we attach the current process to the NL2 by calling Rtnl2Init. This is necessary to establish communication with the NL2.

Copy
BOOL Succeeded = Rtnl2Init();
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2Init() failed with error 0x%08x", ErrorCode);
                    goto end;
                }

  1. Open a handle to the specified NIC.

To manipulate the specified NIC, we now have to open a handle to the Network Interface object with Rtnl2OpenInterface.

Copy
RTNL2_HINTERFACE hInterface = Rtnl2OpenInterface(NicName);
                    if (!hInterface)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2OpenInterface() failed with error 0x%08x", ErrorCode);
                    goto end;
                }

  1. Get the current configuration of the Physical Transmit Queue.

With the handle to the Network Interface object, we can query the current configuration of the Transmit Queue specified on the command line. We are especially interested in the NbBuffers field, which indicates the number of fixed DMA Buffers that the driver has allocated for this specific Transmit Queue. The function to get the configuration of a Transmit Queue is Rtnl2GetPhysicalTxQueueConfig.

Copy
RTNL2_PHYSICAL_TX_QUEUE_CONFIG TxQueueConfig;
                    Succeeded = Rtnl2GetPhysicalTxQueueConfig(
                    hInterface,
                    TxQueueIndex,
                    &TxQueueConfig,
                    sizeof(TxQueueConfig));
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2GetPhysicalTxQueueConfig() failed with error 0x%08x", ErrorCode);
                    goto cleanup_interface;
                    }
                ULONG TxQueueNbBuffers = TxQueueConfig.NbBuffers;

  1. Acquire the Physical Transmit Queue.

With the handle to the Network Interface object, we can now request exclusive access to the Transmit Queue specified on the command line. The function to get exclusive access is Rtnl2AcquirePhysicalTxQueue.

Copy
HANDLE hReadyEvent;
                    RTNL2_HPHYSICAL_TX_QUEUE hPhysicalTxQueue = Rtnl2AcquirePhysicalTxQueue(
                    hInterface,
                    TxQueueIndex,
                    sizeof(RTNL2_BUFFER_HEADER),
                    0, // Flags
                    &hReadyEvent);
                    if (!hPhysicalTxQueue)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2AcquirePhysicalTxQueue() failed with error 0x%08x", ErrorCode);
                    goto cleanup_interface;
                }

  1. Wait for the Physical Transmit Queue to become ready.

Because the resources of a Physical Transmit Queue are released asynchronously, there is a period when the queue is not ready, between when an process releases the queue and when another process can start using that queue in exclusive mode. That’s why after each call to Rtnl2AcquirePhysicalTxQueue, we have to wait for the Ready event before we can use the queue.

Copy
DWORD WaitResult = WaitForSingleObject(hReadyEvent, INFINITE);
                    if (WaitResult == WAIT_FAILED)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("RtWaitForSingleObject() failed with error 0x%08x", ErrorCode);
                    goto cleanup_queue;
                }

  1. Create the Transmit event.

When using Physical Transmit Queues, the application has direct access to the hardware DMA Buffers. There is a fixed number of DMA Buffers per queue, and the application must reuse those buffers to send new Ethernet frames. The Transmit event is signaled by the NL2 to notify the application that at least one DMA Buffer has been consumed by the hardware and can be reused to send a new frame. We have to explicitly request the creation of this event by calling Rtnl2CreatePhysicalTxQueueEvent.

Copy
HANDLE hTxEvent;
                    Succeeded = Rtnl2CreatePhysicalTxQueueEvent(
                    hPhysicalTxQueue,
                    &hTxEvent);
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2CreatePhysicalTxQueueEvent() failed with error 0x%08x", ErrorCode);
                    goto cleanup_queue;
                }

  1. Get the list of all the Buffers of the Physical Transmit Queue.

For this basic application, we ask the NL2 to give us all the DMA Buffers which are available for this Transmit Queue. This is done by calling Rtnl2GetPhysicalTxQueueBuffers.

Copy
RTNL2_BUFFER_HEADER *pAvailableBuffers = Rtnl2GetPhysicalTxQueueBuffers(
                    hPhysicalTxQueue,
                    TxQueueConfig.NbBuffers);
                    if (!pAvailableBuffers)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2GetPhysicalTxQueueBuffers() failed with error 0x%08x", ErrorCode);
                    goto cleanup_event;
                    }
                ULONG NbAvailableBuffers = TxQueueConfig.NbBuffers;

  1. Build a template of Ethernet frame.

Now, let’s build a template of the Ethernet frame to send. This template is a 60-byte buffer comprising a common 14-byte Ethernet header and 46 bytes of payload filled with zeros. We set the Destination MAC Address to the broadcast address, the Source MAC Address to the NIC's MAC Address (obtained by calling Rtnl2GetMacAddress), and the EtherType to an arbitrary value of 0x1234.

Copy
BYTE EthernetFrameTemplate[60] =
                    {
                    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Dest Address : broadcast
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Source Address : to be filled
                    0x12, 0x34,                         // EtherType : some arbitrary value
                    0x00,                               // Payload : all zeros
                    };
                    Succeeded = Rtnl2GetMacAddress(hInterface, &EthernetFrameTemplate[6]);
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2GetMacAddress() failed with error 0x%08x", ErrorCode);
                    goto cleanup_buffers;
                }

  1. Fill in all our Buffers.

The next step is to fill in all the Buffers we have for this queue. The content of all our Buffers will be based on the template above, in which we will just set the 4 first bytes of the payload to different values so that each packet appears unique if it gets sniffed by some capture tools.

Copy
ULONG SequenceNumber = 1;
                    RTNL2_BUFFER_HEADER *pBuffer;
                    for(pBuffer=pAvailableBuffers; pBuffer; pBuffer=pBuffer->pNext)
                    {
                    memcpy(pBuffer->pFrameData,
                    EthernetFrameTemplate,
                    sizeof(EthernetFrameTemplate));
                    pBuffer->pFrameData[14] = (BYTE)(SequenceNumber >> 24);
                    pBuffer->pFrameData[15] = (BYTE)(SequenceNumber >> 16);
                    pBuffer->pFrameData[16] = (BYTE)(SequenceNumber >>  8);
                    pBuffer->pFrameData[17] = (BYTE)(SequenceNumber);
                    SequenceNumber++;
                    pBuffer->FrameLength = sizeof(EthernetFrameTemplate);
                }

  1. Transmit the first burst.

Here, we use a unique feature of the Physical Transmit Queues, which is the transmission of bursts of Ethernet packets at line speed. We call the Rtnl2SubmitToPhysicalTxQueue function and provide not a single Buffer but the entire list of Buffers we have just filled. The NL2 will insert all those Buffers in the Hardware Queue, and then trigger the burst.

Note: For various reasons, the Rtnl2SubmitToPhysicalTxQueue function may not be able to insert all the supplied Buffers, in this case, it returns a list of the Buffers that could not be inserted in the queue. This condition is not an error and should not cause the application to fail.

Copy
RTNL2_BUFFER_HEADER *pToBeSubmitted = pAvailableBuffers;
                    RTNL2_BUFFER_HEADER *pNotSubmitted;
                    ULONG NbSubmitted;
                    Succeeded = Rtnl2SubmitToPhysicalTxQueue(
                    hPhysicalTxQueue,
                    pAvailableBuffers,
                    &NbSubmitted,
                    &pNotSubmitted);
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2SubmitToPhysicalTxQueue() failed with error 0x%08x", ErrorCode);
                    goto cleanup_buffers;
                    }
                    pAvailableBuffers = pNotSubmitted;
                NbAvailableBuffers -= NbSubmitted;

  1. Reclaim the Buffers as they are getting consumed.

After the Rtnl2SubmitToPhysicalTxQueue function returns, the hardware starts consuming the supplied Buffers one by one and transmitting Ethernet packets over the wire. For every consumed buffer, the NL2 signals the Transmit event. The application can then call Rtnl2ExtractFromPhysicalTxQueue to reclaim the consumed Buffers, which can be reused to send other frames. In the code below, we wait until all the submitted buffers get reclaimed.

Copy
while (NbAvailableBuffers != TxQueueNbBuffers)
                    {
                    //
                    // Wait for at least one buffer to be consumed
                    //
                    WaitResult = RtWaitForSingleObject(hTxEvent, INFINITE);
                    if (WaitResult == WAIT_FAILED)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("RtWaitForSingleObject-2() failed with error 0x%08x", ErrorCode);
                    goto cleanup_buffers;
                    }

                    //
                    // Extract all the buffers consumed so far
                    //
                    RTNL2_BUFFER_HEADER *pExtracted;
                    ULONG NbExtracted;
                    Succeeded = Rtnl2ExtractFromPhysicalTxQueue(
                    hPhysicalTxQueue,
                    ULONG_MAX,
                    &pExtracted,
                    &NbExtracted);
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2ExtractFromPhysicalTxQueue() failed with error 0x%08x", ErrorCode);
                    goto cleanup_buffers;
                    }

                    //
                    // Add the extracted buffers to our list of available buffers
                    //
                    if (NbExtracted)
                    {
                    for(pBuffer=pExtracted; pBuffer->pNext; pBuffer=pBuffer->pNext);
                    pBuffer->pNext = pAvailableBuffers;
                    pAvailableBuffers = pExtracted;
                    NbAvailableBuffers += NbExtracted;
                    }
                }

  1. Wait one second.

Wait one second so that you can easily separate the two bursts if you are making a capture of the packets sent by this application.

Copy
Sleep(1000);

  1. Fill in all our Buffers with new content.

Now that we have reclaimed all our Buffers, we fill them with new content to send new Ethernet frames.

Copy
for(pBuffer=pAvailableBuffers; pBuffer; pBuffer=pBuffer->pNext)
                    {
                    memcpy(pBuffer->pFrameData,
                    EthernetFrameTemplate,
                    sizeof(EthernetFrameTemplate));
                    pBuffer->pFrameData[14] = (BYTE)(SequenceNumber >> 24);
                    pBuffer->pFrameData[15] = (BYTE)(SequenceNumber >> 16);
                    pBuffer->pFrameData[16] = (BYTE)(SequenceNumber >>  8);
                    pBuffer->pFrameData[17] = (BYTE)(SequenceNumber);
                    SequenceNumber++;
                    pBuffer->FrameLength = sizeof(EthernetFrameTemplate);
                }

  1. Transmit the second burst.

At this point, we are ready to transmit a second burst at line speed, just as we did the first time.

Copy
pToBeSubmitted = pAvailableBuffers;
                    Succeeded = Rtnl2SubmitToPhysicalTxQueue(
                    hPhysicalTxQueue,
                    pAvailableBuffers,
                    &NbSubmitted,
                    &pNotSubmitted);
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2SubmitToPhysicalTxQueue-2() failed with error 0x%08x", ErrorCode);
                    goto cleanup_buffers;
                    }
                    pAvailableBuffers = pNotSubmitted;
                NbAvailableBuffers -= NbSubmitted;

  1. Clean up.

Before terminating the program, we release the used resources by:

Note: We don’t need to worry about the Buffers that were submitted in the previous step, the NL2 will release them asynchronously after we call Rtnl2ReleasePhysicalTxQueue.

Copy
cleanup_buffers:
                    if (pAvailableBuffers)
                    {
                    Rtnl2ReturnPhysicalTxQueueBuffers(hPhysicalTxQueue, pAvailableBuffers);
                    }

                    cleanup_event:
                    Rtnl2DestroyPhysicalTxQueueEvent(hPhysicalTxQueue, &hTxEvent);

                    cleanup_queue:
                    Rtnl2ReleasePhysicalTxQueue(hPhysicalTxQueue);

                    cleanup_interface:
                Rtnl2CloseInterface(hInterface);

Basic Receiver Application with Physical Receive Queues

In this section, we will create an application that takes advantage of Physical Receive Queues. This application will wait to receive an Ethernet packet, wait 10 additional milliseconds, then extract all the packets currently in the queue in one single call. The purpose of waiting 10 ms is to allow the NIC to sufficiently fill up the queue before we call the extract function. The application requires two command-line arguments:

Note: A Physical Queue is an object that allows you to take exclusive control of a hardware queue and read/write directly from/to the NIC's DMA buffers. The APIs available for Physical Queues are less convenient than those for Logical Queues, but Physical Queues are the way to leverage the best performance from the hardware.

  1. Add the necessary header and library files to the eRTOS Application Template in Visual Studio.

Applications that use the NL2 must include Rtnl2Api.h and link the Rtnl2Api.lib library in addition to the usual headers, windows.h, RtApi.h, and RtssApi.h. See Creating a Project.

Copy
#include <windows.h>
                    #include <RtApi.h>
                    #include <RtssApi.h>
                #include <Rtnl2Api.h>

  1. Parse the command line arguments.

We start our program by extracting two command line arguments: the NIC name and the Receive Queue index.

Copy
if (argc < 3)
                    {
                    RtPrintf("Usage:\n\t%s <NicName> <RxQueueIndex>\n", argv[0]);
                    goto end;
                    }
                    CHAR *NicName = argv[1];
                ULONG RxQueueIndex = RtAtoi(argv[2]);

  1. Attach the process to the NL2.

Then, we attach the current process to the NL2 by calling Rtnl2Init. This is necessary to establish communication with the NL2.

Copy
BOOL Succeeded = Rtnl2Init();
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2Init() failed with error 0x%08x", ErrorCode);
                    goto end;
                }

  1. Open a handle to the specified NIC.

To manipulate the specified NIC, we now have to open a handle to the Network Interface object with Rtnl2OpenInterface.

Copy
RTNL2_HINTERFACE hInterface = Rtnl2OpenInterface(NicName);
                    if (!hInterface)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2OpenInterface() failed with error 0x%08x", ErrorCode);
                    goto end;
                }

  1. Acquire the Physical Receive Queue.

With the handle to the Network Interface object, we can now request exclusive access to the Receive Queue specified on the command line. The function to get exclusive access is Rtnl2AcquirePhysicalRxQueue.

Copy
RTNL2_HPHYSICAL_RX_QUEUE hPhysicalTxQueue = Rtnl2AcquirePhysicalRxQueue(
                    hInterface,
                    RxQueueIndex,
                    sizeof(RTNL2_BUFFER_HEADER),
                    0); // Flags
                    if (!hPhysicalRxQueue)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2AcquirePhysicalRxQueue() failed with error 0x%08x", ErrorCode);
                    goto cleanup_interface;
                }

  1. Create the Receive event.

Now, we create the Receive event, which will be signaled by the NL2 every time a new Ethernet packet is received through the queue. This is done by calling Rtnl2CreatePhysicalRxQueueEvent.

Copy
HANDLE hRxEvent;
                    Succeeded = Rtnl2CreatePhysicalRxQueueEvent(
                    hPhysicalRxQueue,
                    &hRxEvent);
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2CreatePhysicalRxQueueEvent() failed with error 0x%08x", ErrorCode);
                    goto cleanup_queue;
                }

  1. Wait for the reception of an Ethernet frame.

Now, we wait for the Receive event to be signaled by the NL2.

Copy
DWORD WaitResult = RtWaitForSingleObject(hRxEvent, INFINITE);
                    if (WaitResult == WAIT_FAILED)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("RtWaitForSingleObject() failed with error 0x%08x", ErrorCode);
                    goto cleanup_queue;
                }

  1. Wait 10 additional milliseconds.

Now, we wait 10 ms to ensure several Ethernet packets are received by the NIC and put into the Receive Queue.

Copy
Sleep(10);

  1. Extract all the received frames in one call.

At this point, we know that the Physical Receive Queue contains at least one frame. We call Rtnl2ExtractFromPhysicalRxQueue just once to extract all the frames received so far, in the form of a list of Buffers.

Copy
RTNL2_BUFFER_HEADER *pExtracted;
                    ULONG NbExtracted;
                    Succeeded = Rtnl2ExtractFromPhysicalRxQueue(hPhysicalRxQueue,
                    ULONG_MAX,
                    &pExtracted,
                    &NbExtracted);
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2ExtractFromPhysicalRxQueue() failed with error 0x%08x", ErrorCode);
                    goto cleanup_event;
                }

  1. Print the contents of all the received frames.

For this example, we go through the list of extracted Buffers and print their contents.

Copy
//
                    // Print the contents of all the received frames
                    //
                    RTNL2_BUFFER_HEADER *pBuffer;
                    for(pBuffer=pExtracted; pBuffer; pBuffer=pBuffer->pNext)
                    {
                    RtPrintf("Received a %u-byte Ethernet packet!\n", pBuffer->FrameLength);
                    for(ULONG i=0; i < pBuffer->FrameLength; i+=16)
                    {
                    RtPrintf("%04x ", i);
                    ULONG lim = min(i+16, pBuffer->FrameLength);
                    for(ULONG j=i; j < lim; j++)
                    {
                    RtPrintf(" %02x", pBuffer->pFrameData[j]);
                    }
                    RtPrintf("\n");
                    }
                }

  1. Return all the extracted Buffers to the NL2.

Now, we return the extracted Buffers to the NL2 so that the NIC can reuse them to hold new incoming frame content. To return all the extracted Buffers to the NL2, we call Rtnl2SubmitToPhysicalRxQueue once and provide the list of all the extracted Buffers. In this case, the call should succeed and submit all the passed Buffers.

Copy
ULONG NbSubmitted;
                    RTNL2_BUFFER_HEADER *pToBeSubmitted = pExtracted;
                    RTNL2_BUFFER_HEADER *pNotSubmitted;
                    Succeeded = Rtnl2SubmitToPhysicalRxQueue(hPhysicalRxQueue,
                    pToBeSubmitted,
                    &NbSubmitted,
                    &pNotSubmitted);
                    if (!Succeeded)
                    {
                    DWORD ErrorCode = GetLastError();
                    RtPrintf("Rtnl2SubmitToPhysicalTxQueue() failed with error 0x%08x", ErrorCode);
                    goto cleanup_event;
                    }
                    if (pNotSubmitted)
                    {
                    RtPrintf("Rtnl2SubmitToPhysicalTxQueue() submitted %u buffers out of %u extracted buffers", NbSubmitted, NbExtracted);
                    goto cleanup_event;
                }

  1. Clean up.

Before terminating the program, we release the used resources by:

Copy
cleanup_event:
                    Rtnl2DestroyPhysicalRxQueueEvent(hPhysicalRxQueue, &hRxEvent);

                    cleanup_queue:
                    Rtnl2ReleasePhysicalRxQueue(hPhysicalRxQueue);

                    cleanup_interface:
                Rtnl2CloseInterface(hInterface);