Porting a TCP/IP Driver to the NAL
This topic describes the steps required to port an existing (pre-RTX64 4.0) TCP/IP Stack driver for use with the new network architecture in RTX64 4.0 and later where the RT-TCP/IP Stack is layered on the Network Abstraction Layer (NAL).
NOTE: Please refer to the source code of network drivers installed with RTX64 SDK for details of implementation.
NOTE: If you have written a custom NIC driver and use an existing NIC driver name, the RT64 Control Panel and other RTX64 tools will make assumptions on supported devices and defaults based on the original IntervalZero-provided driver.
Sections in this topic:
- Step 1: Include the NAL header file
- Step 2: Link to the NAL library file
- Step 3: DMA, Transmit DMA, IOCTL locks
- Step 4: Add new fields to the driver structure
- Step 5: Replace function RtndInitialize with RtndInitializeInterface
- Step 6: Modify the RtndReceive, RtndTransmit, RtndIoctl, and driver IST functions
- Step 7: Add Attach and Detach functions
- Step 8: Modify functions to set last error on failure
- Step 9: Support multiple receive queues
Step 1: Include the NAL Header File
- Open the driver’s source code in Visual Studio.
- Add #include
rtNalApi.h
after #includertnapi.h.
Step 2: Link to the NAL Library File
- Open the driver’s project properties in Visual Studio.
- Under Linker/Input, replace
rttcpip.lib
withRTX64Nal.lib
.
Step 3: DMA, Transmit DMA, IOCTL Locks
- Use separate locks for the receive DMA ring, transmit DMA ring, and IOCTLs.
NOTE: If the driver supports multiple receive and transmit queues, separate locks must be used for each receive DMA ring and transmit DMA ring.
Step 4: Add New Fields to the Driver Structure
- Add these new fields to the device driver structure (per queue):
- a field to store the application context pointer.
- a field to store the receive packet callback pointer.
- a field to store the flags indicating that 1) the application is attached to the receive queue, 2) receive notifications are enabled.
- fields to store multicast flags mask (one bit per receive queue).
Step 5: Replace RtndInitialize with RtndInitializeInterface
RtndInitializeInterface uses a pointer to a structure RTNAL_INTERFACE in its third parameter, which is an input/output parameter. The driver should update the structure with the corrected configuration if the driver must change its configuration to run.
For instance, the driver might not support the suggested interrupt type, or it might not monitor the Ethernet link. It might support less than requested number of receive or transmit queues, or a packet size less than the maximum. It might support a smaller number of buffers, or must round up the number of buffers to meet some criteria.
The MaxPacketSize includes 14 byte Ethernet header and does not include the 4 byte FCS. If the driver requires the checksum in transmit or receive buffers, or any other additional space, it must account for FCS when it allocates its buffers. For instance, the 1500 RequestedMtu in RTNINTERFACE in the old driver is equivalent to 1514 MaxPacketSize in RTNAL_INTERFACE.
If the driver supports a single receive queue and transmit queue, it must set NumberOfRxQueues and NumberOfTxQueues to 1 and DefaultRxQueue to 0.
RtndInitializeInterface should set last error if it fails.
Step 6: Change RtndReceive, RtndTransmit, RtndIoctl, and Driver IST Functions
Changes to RtndReceive
- RtndReceive must return 0 if it succeeds, -1 if it fails. It must set last error code on failure. See RtndReceive for a list of error codes.
- The driver must set error code ERROR_NO_DATA when there is no packet waiting to receive.
- RtndReceive must call GetDataLong(ndp, 0) to retrieve the driver interface index.
- If the driver supports more than one receive queue:
- RtndReceive must call GetDataLong(ndp, 1) to retrieve the device queue number.
- RtndReceive must not use network device pointer, stored in RtndConfigure. It must instead use the ndp argument.
Changes to RtndTransmit
- RtndTransmit must return 0 if it succeeds, -1 if it fails. RtndTransmit must set last error on failure. See RtndTransmit for a list of error codes.
- RtndTransmit must obtain the network device pointer by calling RtnDecodePacket. It must then call GetDataLong(ndp, 0) to retrieve the driver interface index.
- If the driver supports more than one transmit queue:
- RtndTransmit must call GetDataLong(ndp, 1) to retrieve the device queue number.
- RtndTransmit must not use network device pointer, stored in RtndConfigure. It must instead use the pointer, provided by RtnDecodePacket.
Changes to RtndIoctl
- RtndIoctl must return 0 if it succeeds, -1 if it fails. RtndIoctl must set last error codes on failure. See RtndIOCTLDriver for a list of error codes.
- Add processing of the following control codes:
- RTNAL_IOCTL_USE_RX_NOTIFICATIONS – Set the flag that the receive notifications are enabled for the receive queue.
- RTNAL_IOCTL_GET_PCI_BUS_LOCATION – The addr argument points to RTNAL_PCI_LOCATION structure to return the device PCI bus location.
- Change the processing of the following control codes ENIOADDMULTI, ENIODELMULTI:
- Remove calls to RtnGetMcastCount(). Enable Multicast Promiscuous mode on ENIOADDMULTI.
- If the driver supports a single receive queue it might disable Multicast Promiscuous mode on ENIODELMULTI with NULL addr argument.
- If the driver supports multiple receive queues, it should use a bit mask to know which queues requested to use multicast. It should disable Multicast Promiscuous mode only when there are no queues using multicast.
See RtndIoctl for the list of additional optional IOCTL control codes.
- If the driver supports more than one receive or transmit queue:
- RtndIoctl must not use the network device pointer stored in RtndConfigure. It must instead use the ndp argument.
- RtndIoctl must use RtnGetDataLong(ndp, 1) to obtain the queue number for control codes, which don’t use a queue number in the structure, pointed by addr.
Changes to the Driver IST Function
- RtnNotifyRev must be replaced by RtnNotifyRecvQueue and called for each queue, waiting receive packets.
- The logic to count newly received packets in IST in old drivers used two receive DMA ring indexes: one was used in RtndReceive to receive packets (NextRxDescriptorToCheck), and the other was used in IST to count received packets (NextRxDescriptorToFill). This logic must be replaced to use NextRxDescriptorToCheck index in RtndReceive and RxDescriptorsNotified count for the packets waiting to be received but not yet notified. This count must be increased in the IST by the number of received packets, since the last RtnNotifyRecvQueue (or RtnNotifyRecv) call. The count must be decremented in RtndReceive.
- The IST should call RtnNotifyRecvQueue only when the receive notification is enabled and the receive application is attached. The driver must discard receive packets for the queue in the IST when the receiving application is not attached.
Step 7: Add Attach and Detach Functions
Add RtndAttachToReceiveQueue
- Lock the receive DMA ring.
- Store the application context pointer.
- Store the receive packet callback pointer.
- Set the flag to attach the receiving application.
- Count the number of waiting packets in the receive queue DMA ring.
- Unlock the receive DMA ring.
NOTE: The function must return the number of packets waiting to be received.
NOTE: If called with an invalid argument, the function must return -1 and set last error to ERROR_BAD_ARGUMENTS.
Add RtndDetachReceiveQueue
- Lock the device IOCTL lock.
- Clear a multicast flag for the queue
- Clear Multicast Promiscuous mode if multicast flags for all queues are 0.
- Clear all receive filters for the queue.
- Unlock the device IOCTL lock.
- Lock the receive DMA ring.
- Clear the application context pointer.
- Clear the receive packet callback pointer.
- Clear receive attach and receive notification flags.
- Discard all packets waiting in the receive DMA ring.
- Unlock the receive DMA ring.
NOTE: The function must return 0 if successful.
NOTE: If called with an invalid argument, the function must return -1 and set last error to ERROR_BAD_ARGUMENTS.
Step 8: Modify Functions to Set Last Error on Failure
Modify these functions to set last error on failure:
- RtndReceive
- RtndTransmit
- RtndIoctl
- RtndInitializeInterface
- RtndConfigure
- RtndUpdown
Step 9: Support Multiple Receive Queues
In order to support multiple receive queues (DMA rings) the device must support receive queue filtering. NAL APIs and RTX64 network drivers support Ethernet type receive filtering. If the driver implements different type of receive filtering, it should add custom IOCTL control codes to enable and disable it.