Creating an Application that Uses the NL2
Topics:
- Creating a Project
- Using the Samples
- Implementing the Application
- Automatic Cleanup after an Attached Application Terminates
- Memory Allocated by the NL2
Creating a Project
To create a project in Visual Studio:
- Create a new project in Visual Studio.
- Select C or C++ as the template type.
- Select the eRTOS Application Template.
- Select the Network Link Layer (NL2) support.
- Select Finish to create the project.
Using the Samples
eRTOS SDK provides source code and project files for NL2 application samples. You can use these samples as a reference when implementing your application.
Implementing the Application
Attaching a process to the NL2
Before using the functionality of the NL2, an application must call Rtnl2Init.
Note: Ensure the NL2 component is started before calling Rtnl2Init, or the call will fail. See Controlling the NL2 Programmatically for more details.
This call attaches the application to the NL2. After a call to Rtnl2Init, all the threads of the calling process are allowed to call the other API functions of the NL2.
Note: There is no such function as Rtnl2End. After a successful call to Rtnl2Init, the process remains attached to the NL2 until it terminates. See Automatic Cleanup after an Attached Application Terminates.
Enumerate Interfaces
At startup, the NL2 builds a list of all the network interfaces it manages, which is not modified afterwards. Each interface is assigned an index at startup and this index doesn’t change over the life of the NL2.
The Rtnl2EnumInterface function allows the application to retrieve the name of an Interface specified by its index.
To get the list of all available Interfaces, the application can call Rtnl2EnumInterface in a loop, starting with an index of 0 and incrementing until the function fails with the ERROR_NO_MORE_ITEMS error code.
Note: The index value associated with an interface is arbitrary and doesn’t represent any meaningful information about the interface.
Open an Interface
Before being able to manipulate an interface, the application must open a handle to it. This is done by calling the Rtnl2OpenInterface function.
This function takes the interface name as a parameter and returns a handle to it.
Once the application no longer needs the interface handle, it must close it by calling Rtnl2CloseInterface. A handle to an interface can't be closed when a Link Status Change event is associated with that handle (see Query and Monitor the Link Status).
Get the MAC Address of an Interface
An application can retrieve the MAC Address of an interface at any time by calling the Rtnl2GetMacAddress function.
Get the Supported Features of an Interface
At startup, the NL2 populates an RTNL2_INTERFACE_FEATURES structure for each interface, containing the list of features supported by that interface.
An application can retrieve the content of this structure at any time by calling the Rtnl2GetInterfaceFeatures function.
Get the Configuration of the Interfaces, the MSI-X Messages, and the Queues
The NL2 reads its static configuration (created using RtConfig.rtreg) and applies it at startup.
It also populates the following data structures for each interface with the applied configuration:
- An RTNL2_INTERFACE_CONFIG structure that contains the interface configuration.
- An RTNL2_MSIX_MESSAGE_CONFIG structure that contains the configuration of each enabled MSI-X Message.
- An RTNL2_PHYSICAL_RX_QUEUE_CONFIG structure that contains the configuration of each enabled Physical Receive Queue.
- An RTNL2_PHYSICAL_TX_QUEUE_CONFIG structure that contains the configuration of each enabled Physical Transmit Queue.
The application can retrieve the contents of all these data structures at any time by calling Rtnl2GetInterfaceConfig, Rtnl2GetMsixMessageConfig, Rtnl2GetPhysicalRxQueueConfig, and Rtnl2GetPhysicalTxQueueConfig.
Query and Monitor the Link Status
The Rtnl2GetLinkStatus function can be called at any time to retrieve the current link status of an interface.
In addition, an application can request the creation of an Event object that will be signaled by the NL2 whenever the link status of a specific interface changes. This is done by calling Rtnl2CreateLinkStatusChangeEvent, which returns a handle to the created event. The application can then use it in subsequent calls to RtWaitForSingleObject or RtWaitForMultipleObjects.
When the application no longer needs to monitor the link status of an interface, it must destroy the Link Status Change event by calling Rtnl2DestroyLinkStatusChangeEvent.
Configure the Hardware Dispatcher
The Hardware Dispatcher decides which Physical Receive Queue each frame received from the wire must go to. At startup, the default behavior of the Hardware Dispatcher is to forward all received frames to the same Physical Receive Queue, which is queue 0 (also known as the Default Receive Queue).
Actual Hardware Dispatchers can generally forward received frames based on various criteria, such as EtherType. Hardware Dispatcher's configuration consists of a set of Dispatch rules that can be dynamically added and removed on demand.
The number of EtherType dispatch rules supported by a given interface is reported in the EtherTypeDispatchRulesCount field of the RTNL2_INTERFACE_FEATURES structure, which can be retrieved by calling Rtnl2GetInterfaceFeatures. A value of 0 for the EtherTypeDispatchRulesCount field indicates that the Hardware Dispatcher of this interface doesn't support EtherType dispatch rules.
To add an EtherType dispatch rule, call Rtnl2AddEtherTypeDispatchRule. This function returns a Rule identifier that the caller must keep to delete the rule later.
To delete an EtherType dispatch rule, call Rtnl2DeleteEtherTypeDispatchRule. The rule to delete is specified by its identifier, which Rtnl2AddEtherTypeDispatchRule returns.
To get the list of all configured EtherType dispatch rules, call Rtnl2GetEtherTypeDispatchRules.
Receive Frames with the Shared Queue Access Method
Introduction
An application can use two methods to receive Ethernet frames through a Physical Receive Queue: the Shared Queue Access method and the Exclusive Queue Access method. In this section, we will discuss the Shared Queue Access method.
Note: A given Physical Receive Queue can't be used simultaneously in Shared and Exclusive Access modes.
With the Shared Queue Access method, multiple applications can simultaneously use the same Physical Receive Queue. The NL2 synchronizes and arbitrates the different requests received in parallel.
Every application that wants to use a given Physical Receive Queue in Shared Access mode needs to create a Logical Receive Queue on top of it. A Logical Receive Queue is like a virtual channel inside a Physical Receive Queue.
The Shared Queue Access method is well suited to applications that don’t require deterministic latency. This typically includes TCP/IP stacks, IEEE Control protocols (such as PTP, LLDP, SRP, and AVDECC), and proprietary control protocols.
Workflow
This workflow outlines how to work with Logical Receive Queues:
- Select the Interface to use and call Rtnl2OpenInterface to get a handle to that Interface.
- Select the Physical Receive Queue to use and call Rtnl2CreateLogicalRxQueue to create a Logical Receive Queue on top of it. This function returns a Receive event handle.
- Configure the operating mode of the Logical Receive Queue with Rtnl2SetLogicalRxQueueMode.
- Configure the Logical Receive Filters of the Logical Receive Queue with Rtnl2SetLogicalRxQueueEtherTypeFilter and Rtnl2SetLogicalRxQueueMulticastFilter.
- If needed, enable the timestamping logic on this Logical Receive Queue by calling Rtnl2EnableLogicalRxQueueTimestamping.
- Start the Logical Receive Queue with Rtnl2StartLogicalRxQueue.
- Repeat the following until the application needs to terminate:
- Wait on the Receive event handle returned by Rtnl2CreateLogicalRxQueue.
- Call Rtnl2ReceiveFromLogicalRxQueue in a loop until it returns ERROR_NO_DATA. This extracts the received frames from the Logical Receive Queue.
- Destroy the Logical Receive Queue with Rtnl2DestroyLogicalRxQueue.
Details about Creating a Logical Receive Queue
A Logical Receive Queue can be created on top of a Physical Receive Queue only if another process has not already acquired it. To create a Logical Receive Queue, call Rtnl2CreateLogicalRxQueue.
The Rtnl2CreateLogicalRxQueue function takes a valid handle to the interface (see Rtnl2OpenInterface), the index of the Physical Receive Queue, and a buffer count as parameters. The buffer count is the number of local buffers the NL2 must allocate for this Logical Receive Queue. These local buffers hold received frames and are dedicated to this Logical Receive Queue, which means they are not shared with other Logical Receive Queues. This guarantees that an application that is slow to extract the received frames from its Logical Receive Queue will not block the reception of frames on another Logical Receive Queue.
The Rtnl2CreateLogicalRxQueue function also returns a handle to an event called the Receive event, which is signaled by the NL2 whenever new frames are available in the Logical Receive Queue.
Note: After getting a Logical Receive Queue handle, the application can safely close the interface handle with Rtnl2CloseInterface if it no longer needs it. This will not invalidate the Logical Receive Queue handle.
Details about Configuring the Operating Mode
The following modes can be enabled or disabled independently for each Logical Receive Queue:
Promiscuous mode:
- When this mode is turned off, incoming frames with the following Destination MAC Addresses are filtered out from this Logical Receive Queue:
- Unicast Addresses which are not equal to the MAC Address of the NIC.
- Multicast Addresses which don’t pass the Logical Receive Multicast Filter (see Details about Configuring the Logical Receive Filters).
- When this mode is turned on, incoming frames are never filtered out based on their Destination MAC Address.
Pass bad frames mode:
- When this mode is turned off, incoming frames marked as “bad frames” by the NIC are filtered out.
- When this mode is turned on, incoming frames marked as “bad frames” by the NIC are not filtered out.
By default, after the creation of the Logical Receive Queue, the operating modes above are turned off.
To configure the operating mode, the Rtnl2SetLogicalRxQueueMode function must be used.
Note: If the NIC hardware has a “Promiscuous” mode and a “Pass bad frames” mode, then the Rtnl2SetLogicalRxQueueMode function not only configures the software filtering but also changes the hardware configuration so that the frames of interest are not filtered out by the NIC.
Details about Configuring the Logical Receive Filters
A Logical Receive Filter is a software filter that takes an Ethernet frame as input and either passes it to the next stage or drops it. Every Logical Receive Queue has 2 Logical Receive Filters that are executed in a row each time the underlying Physical Receive Queue receives an Ethernet frame:
- The Logical Receive EtherType filter takes a decision based on the EtherType of the incoming frame. It can be configured to let all EtherTypes pass or let a finite set of EtherTypes pass.
- The Logical Receive Multicast filter decides based on the Destination MAC Address of the incoming frame, when this MAC Address is a multicast MAC Address. It can be configured to let all Multicast MAC Addresses pass or let a finite set of Multicast MAC Addresses pass.
- To configure the EtherType filter, use the Rtnl2SetLogicalRxQueueEtherTypeFilter function.
- To configure the Multicast filter, use the Rtnl2SetLogicalRxQueueMulticastFilter function.
By default, after a Logical Receive Queue is created, its EtherType filter lets nothing pass, and its Multicast filter doesn’t let any Multicast address pass.
Note: If the NIC hardware has a Multicast Hash Filter, then the Rtnl2SetLogicalRxQueueMulticastFilter function not only configures the software filtering but also changes the configuration of the hardware Multicast Hash Filter so that the requested Multicast Addresses are not filtered out by the NIC.
Details about Enabling the Timestamping Logic on a Logical Receive Queue
To retrieve the ingress timestamps associated with frames received from a Logical Receive Queue, the timestamping logic must be enabled on that Logical Transmit Queue. For that, two conditions must be met:
- The user must enable timestamping on the underlying Physical Receive Queue through the interface configuration in the RtConfig.rtreg file.
- The application must successfully call Rtnl2EnableLogicalRxQueueTimestamping on that Logical Receive Queue.
When these two conditions are met, the NL2 populates the IngressTimestamp field of the RTNL2_RECEIVE_DESC structure for every received frame timestamped by the NIC. You can modify the RtConfig.rtreg file to configure the rules the NIC uses to determine which received frame to timestamp.
Note: After enabling the timestamping logic on a Logical Receive Queue, it remains enabled until it is destroyed. There is no such function as Rtnl2DisableLogicalRxQueueTimestamping.
Details about Starting and Stopping a Logical Receive Queue
After creation, a Logical Receive Queue is stopped by default. This means it is not filled by received frames, even if they pass the Logical Receive Filters.
To start a Logical Receive Queue, the Rtnl2StartLogicalRxQueue function must be used.
To stop a Logical Receive Queue, the Rtnl2StopLogicalRxQueue function must be used.
The recommended workflow to change the configuration of a Logical Receive Queue is as follows:
- Stop the Logical Receive Queue by calling Rtnl2StopLogicalRxQueue.
- If necessary, change the mode settings by calling Rtnl2SetLogicalRxQueueMode.
- If necessary, change the EtherType filter by calling Rtnl2SetLogicalRxQueueEtherTypeFilter.
- If necessary, change the Multicast filter by calling Rtnl2SetLogicalRxQueueMulticastFilter.
- Start the Logical Receive Queue by calling Rtnl2StartLogicalRxQueue.
By following this workflow, there is no risk of receiving frames that don’t fully meet the new requirements for the Logical Receive Queue.
Details about Receiving a Frame from a Logical Receive Queue
As soon as the Logical Receive Queue is started, the NL2 fills the local buffers with the contents of the incoming frames that pass all the logical filters.
To retrieve the contents of the filled local buffers, the application must call the Rtnl2ReceiveFromLogicalRxQueue function.
Before calling Rtnl2ReceiveFromLogicalRxQueue, the application must prepare its buffer (either statically or dynamically allocated) to store the received frame. Then, the application fills in an RTNL2_RECEIVE_DESC structure, which describes the receive operation.
The Rtnl2ReceiveFromLogicalRxQueue function extracts the oldest local buffer from the Logical Receive Queue, copies the frame’s content into the buffer supplied by the caller, and then moves the local buffer back into the set of available local buffers.
The Rtnl2ReceiveFromLogicalRxQueue function also populates the following fields of the RTNL2_RECEIVE_DESC structure before returning:
- RTNL2_RECEIVE_DESC.OriginalFrameLength: length of the frame received from the wire.
- RTNL2_RECEIVE_DESC.FrameLength: number of bytes copied to the user buffer.
If the length of the received frame is greater than the length of the user-supplied buffer, then the frame is truncated; only the part that fits into the user buffer is copied. To know whether a received frame is truncated, the caller must compare RTNL2_RECEIVE_DESC.OriginalFrameLength and RTNL2_RECEIVE_DESC.FrameLength.
If the Logical Receive Queue is empty, then Rtnl2ReceiveFromLogicalRxQueue returns ERROR_NO_DATA.
Relationship Between Logical Receive Queues
The diagram below illustrates how data flows from the Hardware Buffers to the application buffers when several Logical Receive Queues are sitting on top of the same Physical Receive Queue:
Transmit Frames with the Shared Queue Access Method
Introduction
An application can use two methods to transmit Ethernet frames through a Physical Transmit Queue: the Shared Queue Access method and the Exclusive Queue Access method. In this section, we will discuss the Shared Queue Access method.
Note: A given Physical Transmit Queue can't be used both in Shared and Exclusive Access modes simultaneously.
With the Shared Queue Access method, multiple applications can use the same Physical Transmit Queue simultaneously. The NL2 synchronizes and arbitrates the different transmit requests received in parallel.
Every application that wants to use a given Physical Transmit Queue in Shared Access mode needs to create a Logical Transmit Queue on top of it. A Logical Transmit Queue is like a virtual channel inside a Physical Transmit Queue.
The Shared Queue Access method is well suited to applications that don’t require deterministic latency. This typically includes TCP/IP stacks, IEEE Control protocols (such as PTP, LLDP, SRP, and AVDECC), and proprietary control protocols.
Workflow
This workflow outlines how to work with Logical Transmit Queues:
- Select the Interface to use and call Rtnl2OpenInterface to get a handle to that Interface.
- Select the Physical Transmit Queue to use and call Rtnl2CreateLogicalTxQueue to create a Logical Transmit Queue on top of it.
- If needed, call Rtnl2EnableLogicalTxQueueTimestamping to enable the timestamping logic on this Logical Transmit Queue. This function returns an Egress Timestamp event.
- Repeat the following whenever a packet needs to be transmitted, until the application needs to be terminated:
- Fill in a memory block with the content of the packet to be sent (including the Ethernet header, but not the FCS).
- Populate the pFrameData and FrameLength fields of an RTNL2_TRANSMIT_DESC structure.
- If this packet needs to be timestamped on egress, set the EgressTimestampRequired field to TRUE.
- Call Rtnl2TransmitOverLogicalTxQueue.
- If egress timestamping was requested, wait for the Egress Timestamp Event and then call Rtnl2GetLogicalTxQueueTimestamp to retrieve the egress timestamp.
- Destroy the Logical Transmit Queue with Rtnl2DestroyLogicalTxQueue.
Details about Creating a Logical Transmit Queue
A Logical Transmit Queue can be created on top of a Physical Transmit Queue only if another process has NOT already acquired the Physical Transmit Queue. To create a Logical Transmit Queue, call Rtnl2CreateLogicalTxQueue.
The Rtnl2CreateLogicalTxQueue function takes a valid handle to the interface (see Rtnl2OpenInterface), the index of the Physical Transmit Queue, and a buffer count as parameters. The buffer count is the number of local buffers the NL2 allocates for this Logical Transmit Queue. These local buffers hold pending-to-transmit frames and are dedicated to this Logical Transmit Queue, which means they are not shared with other Logical Transmit Queues. This guarantees that an application will not starve another one by using all its buffers. However, this doesn’t prevent an application from slowing others by transmitting many frames and thus using most of the hardware resources (DMA descriptors and link bandwidth).
Note: After getting a Logical Transmit Queue handle, the application can safely close the interface handle with Rtnl2CloseInterface if it no longer needs it. This will not invalidate the Logical Transmit Queue handle.
Details about Enabling the Timestamping Logic on a Logical Transmit Queue
Egress timestamping is done on a per-frame basis upon request from the application. However, before the application can request egress timestamping for a frame transmitted through a Logical Transmit Queue, the timestamping logic must be enabled on that Logical Transmit Queue. For that, two conditions must be met:
- The user must enable timestamping on the underlying Physical Transmit Queue through some static configuration in the RtConfig.rtreg file.
- The application must successfully call Rtnl2EnableLogicalTxQueueTimestamping on that Logical Transmit Queue.
When these two conditions are met, the application can decide whether the hardware should timestamp each frame it transmits through that Logical Transmit Queue.
The Rtnl2EnableLogicalTxQueueTimestamping function returns a handle to an Event. The NL2 signals this event in two different cases:
- Either a frame has been successfully transmitted and timestamped,
- Or the last to-be-timestamped frame has not been timestamped due to the link being down or some other hardware issue, causing the NL2 to abort the operation.
In both cases above, the application should call the Rtnl2GetLogicalTxQueueTimestamp function to determine the reason why the event was signaled, and to retrieve the Egress Timestamp in case there is a valid one.
When using Egress Timestamping through a Logical Transmit Queue, the application should NOT use its timeout because the NL2 already implements one. It will automatically abort the timestamping operation if the hardware doesn't report a valid timestamp within a reasonable amount of time after the corresponding buffer has been consumed.
Note: After enabling the timestamping logic on a Logical Transmit Queue, it remains enabled until the Logical Transmit Queue is destroyed. There is no such function as Rtnl2DisableLogicalTxQueueTimestamping.
Details about Transmitting a Frame over a Logical Transmit Queue
To transmit a frame, the application first prepares the frame’s content (including the Ethernet header and payload, but NOT the final 4-byte FCS) in its buffer, which may be statically or dynamically allocated.
Then, it populates an RTNL2_TRANSMIT_DESC structure, which describes the transmit operation, and calls Rtnl2TransmitOverLogicalTxQueue.
The Rtnl2TransmitOverLogicalTxQueue function behaves differently depending on whether a Hardware Buffer is available when the function is called or not:
- If a Hardware Buffer is available, the function copies the frame’s content into the Hardware Buffer and inserts it into the Hardware Transmit Queue. In this case, only one copy of the user data is made.
- If no Hardware Buffer is available, the function finds an available local buffer, copies the frame’s content into this local buffer, and inserts it into the list of pending local buffers. Later (either the next time an application calls Rtnl2TransmitOverLogicalTxQueue, or asynchronously in a background thread of the NL2), if a Hardware buffer becomes available, the NL2 will copy the content of the pending local buffer into the Hardware Buffer and insert it into the Hardware Transmit Queue. In this case, two copies of the user data are made.
The Rtnl2TransmitOverLogicalTxQueue function fails if, when it is called, there are already BufferCount inflight buffers holding content from this Logical Transmit Queue, where BufferCount is the parameter supplied to Rtnl2CreateLogicalTxQueue.
The NL2 does NOT allow an application to transmit a new to-be-timestamped frame until the previous timestamping operation is finished. The application MUST respect the following sequence:
- If there is no need to timestamp on transmit, transmit frames by calling Rtnl2TransmitOverLogicalTxQueue with RTNL2_TRANSMIT_DESC.EgressTimestampRequired set to FALSE.
- If a frame needs to be timestamped on transmit, transmit it by calling Rtnl2TransmitOverLogicalTxQueue with RTNL2_TRANSMIT_DESC.EgressTimestampRequired set to TRUE.
If the function returns an error, return to step 1 (do NOT wait for the Egress Timestamp event because the NL2 will never signal it in that case). - Wait until the Egress Timestamp event is set by the NL2.
- While waiting for the Egress Timestamp to be signaled, the application can still transmit frames with RTNL2_TRANSMIT_DESC.EgressTimestampRequired set to FALSE.
- Call Rtnl2GetLogicalTxQueueTimestamp.
- Go to step 1.
Note: The NL2 can manage simultaneous Egress Timestamping requests on the same Physical Transmit Queue if they come from multiple Logical Transmit Queues on top of that Physical Transmit Queue. This allows multiple independent applications to use the Egress Timestamping feature on top of the same hardware queue.
However, it is not allowed to make simultaneous Egress Timestamping requests through the same Logical Transmit Queue.
Details about Monitoring and Extracting Egress Timestamps
The application should do the following after every successful call to Rtnl2TransmitOverLogicalTxQueue with RTNL2_TRANSMIT_DESC.EgressTimestampRequired set to TRUE:
- Wait until the Egress Timestamp event is set by the NL2.
- Call Rtnl2GetLogicalTxQueueTimestamp.
It’s up to the application to decide whether to wait for the Egress Timestamp event or handle it asynchronously so that other general frames can be transmitted while waiting for it to be set.
In any case, the NL2 will ALWAYS set the Egress Timestamp after every successful call to Rtnl2TransmitOverLogicalTxQueue with RTNL2_TRANSMIT_DESC.EgressTimestampRequired set to TRUE. Either because the hardware reported a valid timestamp, or because the hardware reported an issue or didn’t report anything within a reasonable amount of time after the corresponding buffer has been consumed by the hardware queue.
If the application calls Rtnl2GetLogicalTxQueueTimestamp before the NL2 signals the Egress Timestamp event, or if the application calls Rtnl2GetLogicalTxQueueTimestamp twice in a row, then the function fails with the ERROR_INVALID_DATA error code. Otherwise, unless some other unexpected errors occur (see Rtnl2GetLogicalTxQueueTimestamp for details), the function will do one of the following:
- Succeed and return a valid Egress Timestamp.
- Fail with the ERROR_IO_DEVICE error code, which means that the Egress Timestamping operation has been aborted by the NL2 due to reasons such as the link being down, expiration of the timeout, or other hardware-specific issues.
Relationship between Logical Transmit Queues
The diagram below illustrates how data flows from the application buffers to the Hardware Buffers when multiple Logical Transmit Queues are sitting on top of the same Physical Transmit Queue:
Note: The diagram shows the data flow when two copies are involved, but there is only one copy when a Hardware Buffer is immediately available when the application calls Rtnl2TransmitOverLogicalTxQueue.
Work with NL2 Buffers
This section is relevant for the Exclusive Queue Access method only (see Receive Frames with the Exclusive Queue Access Method and Transmit Frames with the Exclusive Queue Access Method). In this method, the application exchanges Ethernet frames with the NL2 through NL2 Buffers.
An NL2 Buffer is an area of memory that can be directly used by the underlying hardware to store or load an Ethernet frame.
Every NL2 Buffer is associated with only one Physical Receive Queue or Physical Transmit Queue of an Interface. The NL2 allocates all NL2 Buffers for all Physical Receive Queues and Physical Transmit Queues of all interfaces at startup. The NL2 guarantees that the allocated buffers meet the underlying hardware requirements regarding memory alignment and base address. The NL2 releases NL2 buffers when it terminates.
All the NL2 Buffers allocated for all the Physical Receive Queues of the same interface have the same size, equal to RTNL2_INTERFACE_FEATURES.RxBufferLength.
All the NL2 Buffers allocated for all the Physical Transmit Queues of the same interface have the same size, equal to RTNL2_INTERFACE_FEATURES.TxBufferLength.
Every NL2 Buffer is described by an NL2 Buffer Header structure, which is called RTNL2_BUFFER_HEADER. The RTNL2_BUFFER_HEADER structures are allocated by the NL2. To ensure backward and forward compatibility, the NL2 uses the BufferHeaderStructSize value provided by the application in the call to Rtnl2AcquirePhysicalRxQueue or Rtnl2AcquirePhysicalTxQueue to allocate NL2 Buffer Headers of the right size. The Buffer Header contains the virtual address of the NL2 Buffer, and it can be linked with other Buffer Headers to create lists of NL2 Buffers:
The diagram below shows 3 NL2 Buffers of 2048 bytes each, linked together due to their buffer headers:
Receive Frames with the Exclusive Queue Access Method
Introduction
An application can use two methods to receive Ethernet frames through a Physical Receive Queue: the Exclusive Queue Access method and the Shared Queue Access method. In this section, we will discuss the Exclusive Queue Access method.
Note: A given Physical Receive Queue can't be used in both Shared and Exclusive Access mode simultaneously.
With the Exclusive Queue Access method, only one application can use the Physical Receive Queue at a given time. The operation of getting exclusive access to a Physical Receive Queue is called acquisition.
The Exclusive Queue Access method is well suited to applications that require deterministic latency, typically TSN applications.
Note: If you are implementing a control protocol, you probably don’t need deterministic latency and should consider using the Shared Queue Access method, which is simpler and more flexible than the Exclusive Queue Access method.
General Workflow
This workflow outlines how general network applications should use Physical Receive Queues:
- Select the Interface to use and call Rtnl2OpenInterface to get a handle to that Interface.
- Select the Physical Receive Queue to use and call Rtnl2AcquirePhysicalRxQueue to get exclusive access to that Physical Receive Queue.
- Configure the operating mode of the Physical Receive Queue with Rtnl2SetPhysicalRxQueueMode.
- Configure the Multicast Hash Filter with Rtnl2SetPhysicalRxQueueMulticastFilter.
- If needed, enable the timestamping logic on this Physical Receive Queue by calling Rtnl2EnablePhysicalRxQueueTimestamping.
- Create the Receive event by calling Rtnl2CreatePhysicalRxQueueEvent.
- Repeat the following until the application needs to terminate:
- Wait on the Receive event handle returned by Rtnl2CreatePhysicalRxQueueEvent.
- Call Rtnl2ExtractFromPhysicalRxQueue in a loop until it returns ERROR_NO_DATA. This extracts the filled NL2 Buffers from the Physical Receive Queue.
- Process the extracted NL2 Buffers.
- Call Rtnl2SubmitToPhysicalRxQueue to submit all the extracted NL2 Buffers to the Physical Receive Queue.
- Destroy the Receive event by calling Rtnl2DestroyPhysicalRxQueueEvent.
- Release the Physical Receive Queue by calling Rtnl2ReleasePhysicalRxQueue.
Workflow for Cyclic Applications
Cyclic applications are applications that send packets and process received packets at precise regular intervals.
These applications can be optimized by not using the Receive event but instead polling the state of the Physical Receive Queue every cycle. This saves several context switches that would otherwise occur randomly within each cycle.
The workflow is as follows:
- Select the Interface to use and call Rtnl2OpenInterface to get a handle to that Interface.
- Select the Physical Receive Queue to use and call Rtnl2AcquirePhysicalRxQueue to get exclusive access to that Physical Receive Queue.
- Configure the operating mode of the Physical Receive Queue by calling Rtnl2SetPhysicalRxQueueMode.
- Configure the Multicast Hash Filter with Rtnl2SetPhysicalRxQueueMulticastFilter.
- If needed, enable the timestamping logic on this Physical Receive Queue by calling Rtnl2EnablePhysicalRxQueueTimestamping.
- Repeat the following until the application needs to terminate:
- Wait for the next cycle.
- Call Rtnl2ExtractFromPhysicalRxQueue in a loop until it returns ERROR_NO_DATA. This extracts the filled NL2 Buffers from the Physical Receive Queue.
- Process the extracted NL2 Buffers.
- Call Rtnl2SubmitToPhysicalRxQueue to submit all the extracted NL2 Buffers back to the Physical Receive Queue.
- Release the Physical Receive Queue by calling Rtnl2ReleasePhysicalRxQueue.
Advanced Concepts Related to Physical Receive Queues
NL2 Receive Buffers' State
NL2 Buffers associated to Physical Receive Queues can be in one of the two following states:
- Either owned by the NL2,
- Or owned by the application.
By default, at startup, all allocated NL2 Receive Buffers are owned by the NL2. Later, these buffers are filled by the NIC, and the application takes ownership by calling Rtnl2ExtractFromPhysicalRxQueue. The application then transfers ownership back to the NL2 by calling Rtnl2SubmitToPhysicalTxQueue.
The NL2 enforces strict rules about when the application can pass an NL2 Buffer to an API function. Any call to Rtnl2SubmitToPhysicalRxQueue will fail if the application does not currently own the passed buffer(s).
NL2 Receive Buffers' Flow
The diagram below shows the flow of NL2 Receive Buffers between the application, the NL2, and the NIC hardware:
![]()
FIFOs of Submitted Buffers and Filled Buffers
The NL2 exposes a model of a NIC in which each Physical Receive Queue is composed of a series of two FIFOs of NL2 Buffers:
- A FIFO of submitted buffers, and
- A FIFO of filled buffers
“Filled” buffers are those that the NIC has filled with the content of incoming Ethernet frames. They are ready for the application to extract. If the application has enabled the Receive event, then the NL2 signals this event every time a buffer is inserted in the FIFO of filled buffers.
Submitted buffers are those that the application has processed and returned to the NIC. They are ready for the NIC to fill as soon as new Ethernet frames arrive from the network.
On-Chip Memory
Every NIC has an internal buffer that temporarily stores incoming frame data it is copied to the NL2 Buffers. Some NICs have a small internal buffer (less than the size of an Ethernet frame), while others have an internal buffer that can contain multiple Ethernet frames.
In the NL2 model, the following atomic sequence occurs whenever the internal buffer is not empty, and there is at least one NL2 Buffer in the FIFO of submitted buffers:
- The NIC extracts one NL2 Buffer from the FIFO of submitted buffers.
- The NIC fills the NL2 Buffer with the content of the on-chip memory.
- The NIC inserts the NL2 Buffer into the FIFO of filled buffers.
Details about Acquiring a Physical Receive Queue
To acquire a Physical Receive Queue, the application must have a valid handle to the interface (see Rtnl2OpenInterface) and know the index of the Physical Receive Queue to acquire. Then, the application calls Rtnl2AcquirePhysicalRxQueue. The function fails if another application has already acquired the specified queue or if it is currently used in Shared Queue Access mode. Otherwise, it returns a handle to the Physical Receive Queue, that can be used for all subsequent operations on the queue.
Note: After getting a Physical Receive Queue handle, the application can safely close the interface handle with Rtnl2CloseInterface if it's no longer needed. This will not invalidate the Physical Receive Queue handle.
The Rtnl2AcquirePhysicalRxQueue function not only marks the Physical Receive Queue as acquired by the calling application, but also cleans up the Physical Receive Queue so that all buffers that may be in the FIFO of filled buffers at that point are automatically moved to the FIFO of submitted buffers. This ensures that the application gets the queue in a known state before using it: the FIFO of submitted buffers is full and the FIFO of filled buffers is empty.
Details about Configuring the Operating Mode
Depending on the underlying hardware, some interfaces can operate in a non-nominal mode. The current operating mode is represented by a set of settings that can be turned on or off. In the nominal mode, which is the default when the interface is started, all settings are turned off.
The application can dynamically turn on or off the settings independently and thus make the interface operate in a non-nominal mode. The available settings are as follows:
Promiscuous mode:
- When this mode is turned off, incoming frames with the following Destination MAC Addresses are filtered out by the hardware:
- Unicast Addresses which are not equal to the MAC Address of the NIC.
- Multicast Addresses which don’t pass the Multicast Hash filter (see below).
- When this mode is turned on, incoming frames are never filtered out based on their Destination MAC Address.
Pass bad frames mode:
- When this mode is turned off, the hardware filters out incoming frames marked as “bad frames” by the NIC.
- When this mode is turned on, incoming frames marked as “bad frames” by the NIC are not filtered out.
While these settings apply globally at the interface level, the NL2 exposes them at the queue level. This is because each different application owning a different queue of the same interface may have different requirements. For instance, one application may want to enable promiscuous mode while others may not.
To set the interface mode settings using a Physical Receive Queue handle, the application must call Rtnl2SetPhysicalRxQueueMode.
To set the interface mode settings using a Logical Receive Queue handle, the application must call Rtnl2SetLogicalRxQueueMode.
The NL2 maintains a per-queue copy of each interface mode setting. When an application calls one of the two functions above, the NL2 does not directly update the state of the hardware. It first updates the proper per-queue copy. Then it does a logical OR of the per-queue settings of all the Physical and Logical Receive Queues of the Interface. Finally, it updates the hardware if the result is different than before.
For example, if one application requests that Promiscuous be turned on while another requests that it be turned off, the logical OR will be TRUE, so the NL2 will turn Promiscuous mode on in the hardware. If the two applications request that Promiscuous be turned off, then the NL2 will turn Promiscuous mode off in the hardware.
Note: When the application terminates, the NL2 automatically resets all interface mode settings of the queues owned by that application to their default (i.e. turned off). If this changes the logical OR of all per-queue settings, then the NL2 disables the setting at the hardware level.
In addition to Rtnl2SetPhysicalRxQueueMode and Rtnl2SetLogicalRxQueueMode, the NL2 also provides the Rtnl2GetPhysicalRxQueueMode and Rtnl2GetLogicalRxQueueMode functions, which return two pieces of information:
- The state requested by this application for this setting through this queue handle.
- The currently set state of the hardware (i.e. the logical OR of all per-queue settings).
Details about Configuring the Multicast Hash Filter
Every NIC hardware implements a Multicast Hash Filter, also called an imperfect filter, or inexact filter.
The Multicast Hash Filter is one of the first stages every incoming frame goes through on the Receive path. It checks the first bit of the Destination MAC Address to determine whether it’s a Multicast Address, if so, it calculates a hash value based on the 6 bytes of the Destination MAC Address. This hash value serves as an index in a table of bits. If the bit at the specified index is 0, the Multicast Hash Filter discards the incoming frame immediately. Otherwise, it lets the frame pass to the next stage of the Receive path.
Every hardware has an algorithm, such as Ethernet CRC32, to compute the hash value associated with a Multicast MAC Address. The Ethernet CRC32 is the same algorithm used to compute the FCS of every Ethernet frame. It provides a 32-bit value, but the NIC generally takes only a few bits from that, which are used as the index in the bits table. A common size for the hash index is 5 bits, which allows to address a table of 64 bits. Some hardware can use more bits. The bigger the table, the less probability that undesired Multicast Addresses will pass the filter.
By default, the table of bits of the Multicast Hash Filter is initialized with all 0’s, which means that no Multicast frame is allowed to pass. However, applications can dynamically request the NL2 to update a list of allowed Multicast Addresses, which results in setting some of the bits of that hardware table to 1.
Although one table of bits per interface exists, the NL2 exposes Multicast Filter configuration functions at the queue level. This is because each different application owning a different queue of the same interface may have different requirements; one process may want to receive some Multicast Addresses while others may want to receive different Multicast Addresses, or no Multicast Addresses at all. The NL2 keeps, for each queue, the list of Multicast Addresses that the application owning that queue has requested. Whenever an application updates its list, the NL2 re-builds the list of all Multicast Addresses to let pass by re-aggregating all the lists from all queues, and then gives this aggregated list to the NIC driver, which is responsible for updating the hardware table of bits in the Multicast Hash Filter of the interface.
To set the list of allowed Multicast Addresses using a Physical Receive Queue handle, the application must call Rtnl2SetPhysicalRxQueueMulticastFilter.
To set the list of allowed Multicast Addresses using a Logical Receive Queue handle, the application must call Rtnl2SetLogicalRxQueueMulticastFilter.
Note: When an application terminates, the NL2 automatically destroys the list of allowed Multicast Addresses that was associated with the queues owned by that application, and then calls the NIC driver to potentially update the hardware table of bits of the Multicast Hash Filter.
Details about Enabling the Timestamping Logic on a Physical Receive Queue
The application does not decide whether to enable the hardware timestamping logic in the NIC or what specific rules the hardware applies to determine which incoming frame must be timestamped. The user makes that decision through some static configuration in the RtConfig.rtreg file.
If the user configured hardware timestamping for a given Interface in the RtConfig.rtreg file, then the NL2 enables hardware timestamping and the associated rules at the NIC's startup. The logic runs regardless of whether an application wants to use it at a given time.
If the user didn’t enable hardware timestamping in the RtConfig.rtreg file, then the NL2 does not enable hardware timestamping at the NIC's startup, and the logic remains stopped forever, regardless of whether an application wants to use it at a given time.
Another decision made by the user through interface configuration in the eRTOS Settings is which of the Physical Receive Queues will report the ingress timestamps in the buffers of received frames. The NL2 will enable this only on the Physical Receive Queues that have been marked to be time aware.
At any time, an application can call Rtnl2EnablePhysicalRxQueueTimestamping. The function takes the handle to an acquired Physical Receive Queue as a parameter and does the following:
- Check if the NIC supports hardware timestamping; if not, return an error.
- Check if the NIC has been configured in the RtConfig.rtreg file to enable hardware timestamping; if not, return an error
- Check if the Physical Receive Queue has been configured in the RtConfig.rtreg file to be time-aware; if not, return an error
- If all the verifications above pass, the Physical Receive Queue has timestamping enabled.
When a Physical Receive Queue has timestamping enabled, the NL2 puts the value of the ingress timestamp in the associated RTNL2_BUFFER_HEADER for every incoming frame timestamped by the hardware.
Note: The Rtnl2EnablePhysicalRxQueueTimestamping function does NOT change anything to the hardware; it simply notes that the buffers containing timestamped frames from this Physical Receive Queue object need to contain the timestamp.
Details about Monitoring the FIFO of Filled Buffers
Although not mandatory, a common way of dealing with incoming frames is to enable the hardware Receive interrupt, which the NL2 turns into event notifications.
By calling the Rtnl2CreatePhysicalRxQueueEvent, the application requests the creation of an Event object that will be signaled by the NL2 whenever the NIC triggers the corresponding hardware interrupt. The function returns the handle to the created event, which the application can use in subsequent calls to RtWaitForSingleObject or RtWaitForMultipleObjects.
When the application no longer needs to monitor the filled buffers of a Physical Receive Queue, it must destroy the Receive Event by calling Rtnl2DestroyPhysicalRxQueueEvent.
Note: The NL2 doesn’t enable the hardware interrupt of a given Physical Receive Queue until the application requests the creation of the Receive event.
Note: If interrupt moderation is enabled on the hardware interrupt, the Receive event may not be signaled for every buffer inserted in the FIFO of filled buffers.
Note: The application must NOT assume that the signaling of the Receive event means that new buffers are available in the FIFO of filled buffers. It must gracefully handle the situation where the event is signaled, but there are no newly filled buffers. This can happen because the event is signaled asynchronously.
Reciprocally, when the event is signaled, the application MUST extract all the available filled buffers (in one or more calls to Rtnl2ExtractFromPhysicalRxQueue) because the event is signaled only based on the insertion of new buffers in the FIFO of filled buffers, not based on whether the FIFO of filled buffers is empty or not.
Details about Extracting the Filled Buffers
When an Ethernet frame arrives from the wire (at link speed), its content is stored inside the NIC on-chip memory. The time it takes to complete this operation depends on the length of the frame and the speed of the link. For example, it takes about 120 us to receive a 1500-byte frame over a 100 Mbps link.
Then, the Hardware Dispatcher of the NIC decides which Physical Receive Queue will receive the frame (see Configure the Hardware Dispatcher). If the target FIFO of submitted buffers is not empty, then the following actions occur:
- The frame's content is copied from the NIC's on-chip memory to the FIFO of filled buffers (sitting in system memory); this copy is done in hardware by the NIC's DMA.
- If Receive interrupts are enabled, the NIC generates an interrupt and the NL2 signals the Receive Event.
If a frame is received from the wire and the target submitted FIFO is empty, the hardware silently discards it.
The application can retrieve all filled buffers in one call to the Rtnl2ExtractFromPhysicalRxQueue function. The buffers are returned in the order the NIC filled them.
After retrieving a buffer with Rtnl2ExtractFromPhysicalRxQueue, the NL2 no longer manipulates this buffer, allowing the application to use it freely. In general, the application will process it and resubmit the buffer to the Physical Receive Queue, so that the FIFO of submitted buffers doesn’t get empty and the hardware doesn’t have to discard incoming frames.
If no filled buffer is available when the application calls Rtnl2ExtractFromPhysicalRxQueue, the function returns a NULL pointer for the list of extracted buffers but does NOT fail (it returns TRUE).
Important: The application is NOT allowed to reuse a buffer that has been provided to Rtnl2SubmitToPhysicalRxQueue but has not yet been returned by Rtnl2ExtractFromPhysicalRxQueue. If the application provides the same buffer twice to Rtnl2SubmitToPhysicalRxQueue without getting it back from Rtnl2ExtractFromPhysicalRxQueue, the call to Rtnl2SubmitToPhysicalRxQueue will fail, even if the buffer was filled by the hardware in the meantime.
Note: The Rtnl2ExtractFromPhysicalRxQueue function doesn’t involve any copy. This allows zero copy frame reception.
Details about Submitting Buffers
After processing the extracted buffers, the application returns the ownership to the NL2 by calling Rtnl2SubmitToPhysicalRxQueue. This function inserts the supplied NL2 Buffers in the FIFO of submitted buffers of the Physical Receive Queue, and immediately returns.
Note: A successful return from Rtnl2SubmitToPhysicalRxQueue does NOT mean that any frame has been received. The buffers have been made available for the NIC, which will fill them as soon as frames arrive from the wire.
The application can provide any number of buffers in a single call to Rtnl2SubmitToPhysicalRxQueue. The application provides the address of the first buffer header of a linked list. The NL2 goes through that list and inserts all the buffers in the FIFO of submitted buffers.
Details about Releasing the Physical Receive Queue
After an application has finished using a Physical Receive Queue, it must release it so another application can use it.
To release a Physical Receive Queue, call Rtnl2ReleasePhysicalRxQueue.
The NL2 expects the application to fully terminate all the activities related to that queue before calling Rtnl2ReleasePhysicalRxQueue. In particular, the application must destroy the Receive event associated with that queue, if it created one.
The NL2 also expects the application to submit back all the Receive Buffers that it still owns before calling Rtnl2ReleasePhysicalRxQueue.
Precautions to Take before Processing an Extracted Buffer
About Unfiltered Frames
Unlike Logical Receive Queues, the NL2 does NOT implement software filtering on the Ethernet packets returned to the application by Rtnl2ExtractFromPhysicalRxQueue function. This design choice minimizes the receive latency to the lowest level the hardware allows.
Therefore, the following scenarios can happen:
- The application turned Promiscuous mode off but still receives packets whose Destination MAC Address is not the MAC Address of the NIC.
- This can happen if another application using another Physical/Logical Receive Queue of the same interface has turned Promiscuous mode on.
- Suggested mitigation, if necessary: check the Destination MAC Address in every extracted buffer.
- The application turned PassBadFrames mode off but still receives packets marked as “bad frames” by the NIC.
- This can happen if another application using another Physical/Logical Receive Queue of the same interface has turned the PassBadFrames mode on.
- Suggested mitigation, if necessary: check the BadFrame field of the RTNL2_BUFFER_HEADER of every extracted buffer.
- The application disabled the reception of Multicast packets but still receives Multicast packets.
- This can happen if another application using another Physical/Logical Receive Queue of the same interface has enabled the reception of Multicast packets.
- Suggested mitigation, if necessary: check the Destination MAC Address in every extracted buffer.
- The application has set an EtherType Dispatch rule to receive a given EtherType but still receives different EtherTypes.
- This can happen if the Physical Receive Queue is the default receive queue or if another application has set additional EtherType Dispatch rule(s) to forward other EtherTypes to that queue.
- Suggested mitigation, if necessary: check the EtherType in every extracted buffer.
About Junk Frames
It can also happen that a receive operation fails at the hardware level but still utilizes a submitted buffer and fills it with junk data. In this case, the NL2 does NOT filter out such a buffer. Instead, it sets the Junk field of the RTNL2_BUFFER_HEADER to TRUE and returns the junk buffer to the application in a subsequent call to Rtnl2ExtractFromPhysicalRxQueue.
The application MUST check the Junk field of every extracted NL2 Buffer and immediately re-submit any buffer with the Junk field set to TRUE without processing it.
Transmit Frames with the Exclusive Queue Access Method
Introduction
An application can use two methods to transmit Ethernet frames through a Physical Transmit Queue: the Exclusive Queue Access method and the Shared Queue Access method. In this section, we will discuss the Exclusive Queue Access method.
Note: A given Physical Transmit Queue can't be used in both Shared and Exclusive Access mode simultaneously.
With the Exclusive Queue Access method, only one application can use the Physical Transmit Queue at a given time. The operation of getting exclusive access to a Physical Transmit Queue is called acquisition.
The Exclusive Queue Access method is well suited to applications that require deterministic latency, typically TSN applications.
Note: If you are implementing a control protocol, you probably don’t need deterministic latency and thus should consider using the Shared Queue Access method, which is simpler and more flexible than the Exclusive Queue Access method.
General Workflow
This workflow outlines how general network applications should use Physical Transmit Queues:
- Select the Interface to use and call Rtnl2OpenInterface to get a handle to that Interface.
- Select the Physical Transmit Queue to use and call Rtnl2AcquirePhysicalTxQueue to get exclusive access to that Physical Transmit Queue.
- If needed, enable the timestamping logic on this Physical Transmit Queue by calling Rtnl2EnablePhysicalTxQueueTimestamping; this function returns an Egress Timestamp Event.
- Get a set of available NL2 Buffers by calling Rtnl2GetPhysicalTxQueueBuffers.
- Spawn a thread that does the following in background:
- Create the Transmit event by calling Rtnl2CreatePhysicalTxQueueEvent.
- Repeat the following until the application needs to terminate:
- Wait on the Transmit event handle returned by Rtnl2CreatePhysicalTxQueueEvent.
- Call Rtnl2ExtractFromPhysicalTxQueue in a loop until it returns an empty list of NL2 Buffers. This extracts the consumed NL2 Buffers from the Physical Transmit Queue.
- Insert the extracted NL2 Buffers into the set of available NL2 Buffers.
- Destroy the Transmit event by calling Rtnl2DestroyPhysicalTxQueueEvent.
- Repeat the following whenever a packet needs to be transmitted until the application needs to terminate:
- Check that the set of available NL2 Buffers is not empty and take one of them.
- If the set is empty, drop the packet (or keep it to retry later if the implemented protocol supports this option).
- Fill in the NL2 Buffer with the content of the Ethernet frame to transmit (including the Ethernet header but not the FCS).
- Populate the NL2 Buffer header with the length of the frame.
- If this packet needs to be timestamped on egress, set the EgressTimestampRequired field to TRUE.
- Call Rtnl2SubmitToPhysicalTxQueue.
- If egress timestamping was required, wait on the Egress Timestamp Event and call Rtnl2GetPhysicalTxQueueTimestamp to retrieve the egress timestamp.
- Check that the set of available NL2 Buffers is not empty and take one of them.
- Return the set of available NL2 Buffers by calling Rtnl2ReturnPhysicalTxQueueBuffers.
- Release the Physical Transmit Queue by calling Rtnl2ReleasePhysicalTxQueue.
Workflow for Cyclic Applications
Cyclic applications send packets and process received packets at precise regular intervals.
These applications can be optimized by not using the Transmit event but instead polling the state of the Physical Transmit Queue every cycle. This saves several context switches that would otherwise occur randomly within every cycle.
The workflow is as follows:
- Select the Interface to use and call Rtnl2OpenInterface to get a handle to that Interface.
- Select the Physical Transmit Queue to use and call Rtnl2AcquirePhysicalTxQueue to get exclusive access to that Physical Transmit Queue.
- If needed, enable the timestamping logic on this Physical Transmit Queue by calling Rtnl2EnablePhysicalTxQueueTimestamping; this function returns an Egress Timestamp Event.
- Get a set of available NL2 Buffers by calling Rtnl2GetPhysicalTxQueueBuffers.
- Repeat the following until the application needs to terminate:
- Wait for the next cycle.
- If the set of available NL2 Buffers is not empty, take one of them. Otherwise, call Rtnl2ExtractFromPhysicalTxQueue to reclaim a buffer already consumed by the NIC.
- If Rtnl2ExtractFromPhysicalTxQueue returns an empty list of NL2 Buffers, skip this cycle (this should never happen if the queue has been provisioned with sufficient buffers, except when the link is down).
- Fill in the NL2 Buffer with the content of the Ethernet frame to transmit (including the Ethernet header but not the FCS).
- Populate the NL2 Buffer header with the length of the frame.
- If this packet needs to be timestamped on egress, set the EgressTimestampRequired field to TRUE.
- Call Rtnl2SubmitToPhysicalTxQueue.
- If egress timestamping was requested, wait on the Egress Timestamp Event and call Rtnl2GetPhysicalTxQueueTimestamp to retrieve the egress timestamp.
- Return the set of available NL2 Buffers by calling Rtnl2ReturnPhysicalTxQueueBuffers.
- Release the Physical Transmit Queue by calling Rtnl2ReleasePhysicalTxQueue.
Note: In the sequence above, the application sends one Ethernet packet at every cycle, but real-life applications generally need to send a batch of Ethernet packets at every cycle. In that case, link the NL2 Buffers together in a list and provide the head of the list to the Rtnl2SubmitToPhysicalTxQueue function.
Advanced Concepts Related to Physical Transmit Queues
NL2 Transmit Buffers’ State
Before transmitting frames using the Physical Transmit Queue APIs, the application must get one or more available NL2 Buffers by calling the Rtnl2GetPhysicalTxQueueBuffers function. To return NL2 Buffers to the NL2, the application must call Rtnl2ReturnPhysicalTxQueueBuffers.
The Rtnl2GetPhysicalTxQueueBuffers function returns the address of a buffer header. If the application requested more than one buffer, this buffer header is the head of a linked list. When calling Rtnl2ReturnPhysicalTxQueueBuffers, the application also provides the address of a buffer header. The application does not have to return buffers in the same order as it got them in the first place.
At any given point in time, an NL2 Transmit Buffer is in one of the two following states:
- Either owned by the NL2,
- Or owned by the application.
By default, all freshly allocated NL2 Transmit Buffers are owned by the NL2 at startup. Later, the NL2 transfers ownership to the application when the application calls Rtnl2GetPhysicalTxQueueBuffers. With the NL2 Transmit Buffers that it owns, the application can:
- Either fill them in and call Rtnl2SubmitToPhysicalTxQueue,
- This transfers ownership of the buffers to the NL2 and submits the buffers for transmission.
- To get back the ownership of those buffers, the application must call Rtnl2ExtractFromPhysicalTxQueue.
- Or call Rtnl2ReturnPhysicalTxQueueBuffers.
- This transfers ownership of the buffers to the NL2 but does NOT submit them for transmission.
- To regain ownership of those buffers, the application must call Rtnl2GetPhysicalTxQueueBuffers again.
The NL2 enforces strict rules about when the application can pass an NL2 Buffer to an API function. Any call to Rtnl2SubmitToPhysicalTxQueue or Rtnl2ReturnPhysicalTxQueueBuffers will fail if the application does not currently own the passed buffer(s).
Each application should know in advance how many transmit buffers it will need for proper operation. Thus, it is expected to get all the needed NL2 buffers at startup and return them when the application terminates. This is principally to avoid a lack of resource errors at run time.
NL2 Transmit Buffers' Flow
The diagram below shows the flow of NL2 Transmit Buffers between the application, the NL2, and the NIC hardware:
![]()
FIFOs of Submitted Buffers and Filled Buffers
The NL2 exposes a model of a NIC in which each Physical Transmit Queue is composed of a series of two FIFOs of NL2 Buffers:
- A FIFO of submitted buffers, and
- A FIFO of consumed buffers
Submitted buffers are those the application has filled and given to the NIC for transmission. They are ready to be consumed by the NIC as soon as the NIC is ready to transmit new Ethernet frames onto the network.
Consumed buffers are those consumed by the NIC, meaning that the NIC has already fetched their content and no longer needs them. They are ready to be extracted by the application and reused for another frame transmission. If the application enabled the Transmit event, the NL2 signals that event every time a buffer is inserted in the FIFO of consumed buffers.
On-Chip Memory
Every NIC has an internal buffer that temporarily stores outgoing frame data while it is being output to the wire. Some NICs have a small internal buffer (less than the size of an Ethernet frame), while others have an internal buffer that can contain several Ethernet frames.
In the NL2 model, the following atomic sequence occurs whenever there is at least one NL2 Buffer in the FIFO of submitted buffers and the internal buffer is not full:
- The NIC extracts one NL2 Buffer from the FIFO of submitted buffers.
- The NIC fills the on-chip memory with the content of the NL2 Buffer.
- The NIC inserts the NL2 Buffer into the FIFO of consumed buffers.
Note: The NIC empties the on-chip memory when the medium is free to transmit new Ethernet frames. The speed at which the on-chip memory is emptied is equal to the speed of the link.
Details about Acquiring a Physical Transmit Queue
To acquire a Physical Transmit Queue, the application must have a valid handle to the interface (see Rtnl2OpenInterface) and know the index of the Physical Transmit Queue to acquire. Then, the application calls Rtnl2AcquirePhysicalTxQueue. The function fails if another application already acquired the specified queue, or if it is currently used in Shared Access mode. Otherwise, it returns a handle to the Physical Transmit Queue, that can be used for all subsequent operations on the queue.
The Rtnl2AcquirePhysicalTxQueue function also returns a handle to an Event called the Ready Event. This is a manual-reset event signaled by the NL2 when the Physical Transmit Queue is ready to be used. The application must not use the Physical Transmit Queue before the Ready Event is signaled. The reason why a Physical Transmit Queue may not be ready immediately after being acquired is if the NL2 is still in the process of flushing the buffers that the previous owner of the queue submitted. How long the flushing takes to complete depends on the underlying hardware because:
- Some hardware can dynamically stop their Physical Transmit Queues, in which case the flushing is immediate.
- For other hardware, flushing consists of waiting until the NIC consumes all the submitted buffers.
- Some hardware stops consuming the submitted buffers when the link is down, which can delay the flushing process for an unpredictable time.
Note: After getting a Physical Transmit Queue handle, the application can safely close the interface handle with Rtnl2CloseInterface if it no longer needs it. This will not invalidate the Physical Transmit Queue handle.
Details about Enabling the Timestamping Logic on a Physical Transmit Queue
Egress timestamping is done on a per-frame basis at the request of the application. However, before the application can request egress timestamping for a frame transmitted through a Physical Transmit Queue, the timestamping logic must be enabled on that Physical Transmit Queue. For that, two conditions must be met:
- The user must have enabled timestamping on the Physical Transmit Queue through some static configuration in the RtConfig.rtreg file.
- The application must have successfully called Rtnl2EnablePhysicalTxQueueTimestamping on that Physical Transmit Queue.
When these two conditions are met, the application can decide whether the hardware should timestamp each frame transmitted through that Physical Transmit Queue. See the Details about Requesting Egress Timestamping of Submitted Buffers section.
The Rtnl2EnablePhysicalTxQueueTimestamping function returns a handle to an Event, which is set by the NL2 whenever a new Egress Timestamp is available. The application can read the timestamp through a call to Rtnl2GetPhysicalTxQueueTimestamp.
Note: After enabling the timestamping logic on a Physical Transmit Queue, it remains enabled until the Physical Transmit Queue is released. There is no such function as Rtnl2DisablePhysicalTxQueueTimestamping.
Details about Submitting Buffers for Transmission
With the exclusive access method, frame transmission is done with zero copy of the frame’s content.
The application uses the NL2 Buffers from Rtnl2GetPhysicalTxQueueBuffers, fills in those buffers with the content of the frames to be transmitted, and provides them to the NL2 in a call to Rtnl2SubmitToPhysicalTxQueue. This function inserts the supplied buffers in the FIFO of “submitted buffers” of the Physical Transmit Queue, and immediately returns.
Note: A successful return from Rtnl2SubmitToPhysicalTxQueue does NOT mean the frames have been transmitted. They have simply been made available for the NIC, which will transmit them later.
The application can provide any number of buffers in a single call to Rtnl2SubmitToPhysicalTxQueue. The application provides the address of the first buffer header of a linked list. The NL2 goes through that list and inserts all the buffers in the FIFO of submitted buffers.
Please note that the Physical Transmit Queue has a fixed and predefined number of DMA descriptors in its ring. Each NL2 Buffer present in the FIFO of submitted buffers or in the FIFO of consumed buffers occupies one or more DMA descriptor(s), depending on the underlying hardware. If there are insufficient DMA descriptors to submit all the supplied buffers, the NL2 will insert only a subset of the provided buffers and return the rest to the application.
The Rtnl2SubmitToPhysicalTxQueue function returns TRUE even if not all supplied buffers are submitted due to the full DMA ring. The application is responsible for verifying whether all buffers have been submitted and, if not, performing appropriate actions.
Details about Requesting Egress Timestamping of Submitted Buffers
When the timestamping logic is enabled on a given Physical Transmit Queue, the application must decide whether the associated frame must be timestamped for each buffer submitted to that queue. This is done by setting RTNL2_BUFFER_HEADER.EgressTimestampRequired to TRUE or FALSE.
Egress timestamping is done asynchronously with the submission/consumption/transmission of the frame, so the Transmit event should not be considered a valid indication that an egress timestamp is available. Instead, the application must use the Egress Timestamp event returned by Rtnl2EnablePhysicalTxQueueTimestamping.
To read the Egress Timestamp, call Rtnl2GetPhysicalTxQueueTimestamp.
Important: On a given Physical Transmit Queue, the application must NOT submit two buffers with EgressTimestampRequired set to TRUE in a row, otherwise one of the two Egress Timestamps may be lost.
Also, the application is responsible for implementing a timeout mechanism to handle the situation where the hardware is not able to timestamp an outgoing frame with EgressTimestampRequired set to TRUE.
Please note that implementing the PTP protocol and its different profiles doesn’t require using the Exclusive Queue Access method. If you are implementing PTP, please consider using the Shared Queue Access method, as this method provides much simpler APIs for Egress Timestamping management.
Details about Extracting the Consumed Buffers
The Rtnl2SubmitToPhysicalTxQueue function inserts the supplied buffers in the FIFO of submitted buffers and immediately returns. The actual transmission of the Ethernet frames takes two more steps:
- The buffers are copied from the FIFO of submitted buffers (sitting in system memory) to the NIC on-chip memory; this copy is done in hardware by the NIC's DMA.
- The frames are encoded into their physical signal representation and transmitted over the wire at the link speed.
The time it takes to execute the two steps above depends on several factors:
- The number of buffers already present in the FIFO of submitted buffers when the new buffers are submitted.
- The activity on the other Physical Transmit Queues; depending on the configured priorities and/or traffic shapers, transmission on a queue might be blocked temporarily to let another queue transmit its frames.
- The speed of the link; for example, it takes around 120 us to transmit a 1500-byte frame over a 100 Mbps link.
However, the application doesn’t have to wait until a frame is fully transmitted over the wire to reuse its buffer. After step 1 above, after the DMA has completed copying of the buffer from system memory to NIC on-chip memory, the NIC is no longer accessing the buffer. Therefore, the application can reuse the buffer even if the frame has not been transmitted over the wire yet.
A submitted buffer that passed step 1 above is called a consumed buffer. The application can retrieve all consumed buffers by calling the Rtnl2ExtractFromPhysicalTxQueue function. The buffers are returned in the order they were submitted, which is also the order in which the NIC consumed them.
After retrieving buffers with Rtnl2ExtractFromPhysicalTxQueue, the application can reuse them, fill them with different content, and submit them again to transmit other Ethernet frames. If no buffer is consumed when the application calls Rtnl2ExtractFromPhysicalTxQueue, the function returns a NULL pointer for the list of extracted buffers but does NOT fail (it returns TRUE).
Important: The application is NOT allowed to reuse a buffer that has been provided to Rtnl2SubmitToPhysicalTxQueue but has not yet been returned by Rtnl2ExtractFromPhysicalTxQueue. If the application provides the same buffer twice to Rtnl2SubmitToPhysicalTxQueue without getting it back from Rtnl2ExtractFromPhysicalTxQueue, the call to Rtnl2SubmitToPhysicalTxQueue will fail, even if the frame was fully transmitted over the wire.
Details about Extracting the Last Egress Timestamp
For each Physical Transmit Queue where timestamping is enabled, the hardware keeps track of an independent Egress Timestamp register. Whenever this register gets updated, the hardware triggers an interrupt, which causes the NL2 to wake up to read the value of the hardware register and notify the application through the event returned by Rtnl2EnablePhysicalTxQueueTimestamping.
Later, the application can call Rtnl2GetPhysicalTxQueueTimestamp to read the value of the egress timestamp.
If the application submits a new buffer with EgressTimestampRequired set to TRUE and the associated frame is timestamped before the application calls Rtnl2GetPhysicalTxQueueTimestamp, the new timestamp will be lost.
Details about Monitoring the FIFO of Consumed Buffers
Non-cyclic applications prefer to be notified when consumed buffers are available rather than calling Rtnl2ExtractFromPhysicalTxQueue on a timer basis. Those applications can request the creation of an Event object signaled by the NL2 whenever the NIC triggers the corresponding hardware interrupt.
Calling Rtnl2CreatePhysicalTxQueueEvent creates the Event object. The function returns a handle to the created event, which the application can use in subsequent calls to RtWaitForSingleObject or RtWaitForMultipleObjects.
When the application no longer needs to monitor the buffers consumed by a Physical Transmit Queue, it must destroy the Transmit Event by calling Rtnl2DestroyPhysicalTxQueueEvent.
Note: The NL2 doesn’t enable the hardware interrupt of a given Physical Transmit Queue until the application requests the creation of the Transmit event.
Note: If interrupt moderation is enabled on the hardware interrupt, the Transmit event may not be signaled for every buffer inserted in the FIFO of consumed buffers.
Note: The application must NOT assume that the signaling of the Transmit event means that new buffers are available in the FIFO of consumed buffers. It must gracefully handle the situation where the event is signaled but there are no new consumed buffers. This can happen because the event is signaled asynchronously.
Reciprocally, when the event is signaled, the application MUST extract all the available consumed buffers (in one or more calls to Rtnl2ExtractFromPhysicalTxQueue) because the event is signaled only based on the insertion of new buffers in the FIFO of consumed buffers, not based on whether the FIFO of consumed buffers is empty or not.
Details about Monitoring the Availability of Egress Timestamps
As Egress Timestamping is asynchronous with both submission and buffer consumption, an additional Event is required to monitor it. This Event is returned by Rtnl2EnablePhysicalTxQueueTimestamping.
An application using Egress Timestamping on a Physical Transmit Queue typically has a thread that waits for the Egress Timestamping event and calls Rtnl2GetPhysicalTxQueueTimestamp whenever the event is signaled.
The application should follow the following workflow when using Egress Timestamping on a Physical Transmit Queue:
- Submit a buffer with EgressTimestampRequired set to TRUE.
- Wait until the Egress Timestamp event is signaled; in the meantime, abstain from submitting another buffer with EgressTimestampRequired set to TRUE.
- If too much time elapses without signaling the event, consider that an error and return to step 1.
- When the Egress Timestamp event is signaled, read it with Rtnl2GetPhysicalTxQueueTimestamp and return to step 1.
Important: Implementing the PTP protocol and its different profiles doesn’t require using the Exclusive Queue Access method. Please consider using the Shared Queue Access method if you are implementing PTP, as this method provides much simpler APIs for Egress Timestamping management.
Details about Releasing the Physical Transmit Queue
After an application has finished using a Physical Transmit Queue, it must release it so that another application can use it.
To release a Physical Transmit Queue, call Rtnl2ReleasePhysicalTxQueue.
The NL2 expects the application to fully terminate all the activities related to that queue before calling Rtnl2ReleasePhysicalTxQueue. If it created one, the application must destroy the Transmit event associated with that queue.
The NL2 also expects the application to return all the Transmit Buffers it still owns before calling Rtnl2ReleasePhysicalTxQueue.
Note: It might happen that some buffers are still in the Physical Transmit Queue when Rtnl2ReleasePhysicalTxQueue is called, either consumed but not extracted yet, or even not consumed yet. This is not a problem as the NL2 will take care of reclaiming those buffers on the behalf of the application once they are consumed by the hardware. This flush operation is performed in background and is transparent to the user application.
Configure the Interrupt Moderation
Some interfaces can slow down the generated interrupt rate depending on the underlying hardware. This feature is known as Interrupt moderation, or Interrupt throttling.
Using the Rtnl2SetInterruptModeration function, the application can dynamically turn on or off the interrupt moderation feature and set the value of the interrupt moderation interval in nanoseconds. When the feature is enabled, the hardware ensures that the interval between two consecutive interrupts is always at least equal to the specified interrupt moderation interval.
In MSI-X mode, there is one interrupt moderation interval value for each MSI-X Message. In Line-Based or MSI mode, there is a single interrupt moderation interval value for the whole interface.
By default, at startup, interrupt moderation is turned off (which is equivalent to setting the interrupt moderation interval to 0).
Automatic Cleanup after an Attached Application Terminates
Every application successfully attached to the NL2 (see “Attach the current process to the NL2”) is tracked until it terminates. When the application terminates, which may be the result of a normal process exit, termination by Kill, or a crash, the NL2 automatically performs the following cleanup on behalf of the application:
- Reset the per-queue interface mode settings of all Physical and Logical Receive Queues that the application owned and update the hardware interface mode if needed.
- Reset the per-queue list of allowed Multicast Addresses of all Physical and Logical Receive Queues that the application owned and update the interface's hardware Multicast Hash Filter if needed.
- Destroy any created Link Status Change events.
- Destroy any created Transmit events.
- Destroy any created Receive events.
- Reclaim any NL2 Buffer that was owned by the application.
- Release any acquired Physical Transmit Queues.
- Release any acquired Physical Receive Queues.
- Destroy any created Logical Transmit Queues.
- Destroy any created Logical Receive Queues.
- Close any open Interfaces.
Memory Allocated by the NL2
Introduction
During its lifetime, the NL2 makes dynamic memory allocations. There are two types of memory that the NL2 allocates:
- Local memory: This type of memory is further broken down as follows:
- System external MSpace: This is used when the NL2 creates a Shared Memory region.
- System internal MSpace: This is used when the NL2 creates a kernel object.
- NL2 external MSpace: This is used when the NL2 makes an explicit memory allocation with RtAllocateLocalMemory within the NL2.
- NL2 internal MSpace: This is used when the NL2 opens a handle to a kernel object within the NL2.
- Application external MSpace: This is used when the NL2 makes an explicit memory allocation with RtAllocateLocalMemory within the Application.
- Application internal MSpace: This is used when the NL2 opens a handle to a kernel object within the Application.
- Contiguous physical memory: This is used when the NL2 allocates memory that needs to be shared with the NIC hardware, typically for Transmit/Receive buffers and DMA descriptors rings.
To make the NL2 behavior as deterministic as possible, the occasions when the NL2 makes such dynamic memory allocation are limited to the following:
- At startup of the NL2.
- During the execution of a set of well-identified NL2 APIs.
The NL2 doesn't make any dynamic memory allocation outside of the two cases mentioned above. In particular, the NL2 avoids any dynamic memory allocation during the processing of asynchronous events, such as hardware interrupts and timers.
Memory Allocated at Startup of the NL2
The table below describes the local memory that the NL2 allocates at startup:
Process | External MSpace | Internal MSpace |
---|---|---|
Kernel |
|
|
NL2 |
|
|
The table below describes the contiguous memory that the NL2 allocates at startup:
Contiguous memory | The NL2 itself doesn’t allocate contiguous memory, but the drivers generally do so to store data exchanged with the NICs hardware, such as DMA descriptors rings, Transmit/Receive buffers, etc. |
Note: If you want to disable MSpace auto-expand for the NL2, you will need to know in advance the amount of memory used by the NL2 and its drivers. Follow these steps:
1. Ensure that all your applications have a startup phase when they make all the needed dynamic memory allocations, and no other dynamic memory allocation is done after this phase. In this startup phase, include the calls to all the NL2 API functions that may allocate memory (see Memory Allocated During the Execution of NL2 APIs).
2. Set up all interfaces as they would be in your final application.
3. Enable MSpace auto-expand.
4. Start the NL2.
5. Start all your applications in a mode where they just execute the startup phase and then remain idle.
6. Run RtMspaces.ertos and note the amount of memory used.
7. Disable MSpace auto-expand and set the MSpace size to the amount of memory noted above.
8. Start the NL2 and your applications in normal mode.
Memory Allocated During the Execution of NL2 APIs
Only the following API functions can make dynamic memory allocation:
- Rtnl2Init
- Rtnl2CreateLinkStatusChangeEvent / Rtnl2DestroyLinkStatusChangeEvent
- Rtnl2AcquirePhysicalRxQueue / Rtnl2ReleasePhysicalRxQueue
- Rtnl2CreatePhysicalRxQueueEvent / Rtnl2DestroyPhysicalRxQueueEvent
- Rtnl2AcquirePhysicalTxQueue / Rtnl2ReleasePhysicalTxQueue
- Rtnl2CreatePhysicalTxQueueEvent / Rtnl2DestroyPhysicalTxQueueEvent
- Rtnl2EnablePhysicalTxQueueTimestamping
- Rtnl2CreateLogicalRxQueue / Rtnl2DestroyLogicalRxQueue
- Rtnl2CreateLogicalTxQueue / Rtnl2DestroyLogicalTxQueue
- Rtnl2EnableLogicalTxQueueTimestamping
Other API functions will never make any dynamic memory allocation.
See the Real-Time NL2 API Functions section for details.