2.4 Receiving Data in a Protocol Driver

Underlying NIC drivers can indicate packets in two ways:

Every protocol driver must have at least one of two possible receive handlers and can have both of the following:

  1. ProtocolReceive is a required function that is called with a pointer to a lookahead buffer.

    After ProtocolReceive examines the lookahead data and determines that the packet is intended for one or more of its clients, it must copy the lookahead data into a protocol-allocated buffer, possibly to be chained to a protocol-allocated packet descriptor. If the size of the lookahead buffer is less than the total size of the received packet, ProtocolReceive must call NdisTransferData with the protocol-allocated packet descriptor to obtain the rest of the received data, which the underlying driver copies into the protocol-supplied buffer.

  2. ProtocolReceivePacket is an optional function that receives a pointer to packet descriptor specifying a buffered full network packet.

    ProtocolReceivePacket also examines the packet data and determines whether the packet is intended for one or more of its clients. If so, the protocol can give its TDI client(s) ownership of the indicated packet resources, including direct read-only access to the buffered net packet data, by returning a nonzero value (a count of clients to which the protocol forwarded the receive indication) from ProtocolReceivePacket. The protocol driver's client(s) must subsequently return the packet descriptor and all the resources it specifies to the underlying driver by calling TdiReturnChainedReceives with a pointer to the packet descriptor. Each client must make this call with the packet descriptor until all clients' calls total the nonzero value returned from ProtocolReceivePacket for that receive indication.

    When the protocol driver's client(s) call TdiReturnChainedReceives the required number of times, they relinquish ownership of the returned packet resources to the underlying NIC driver that originally indicated the receive.

    On the other hand, if a protocol driver returns zero from ProtocolReceivePacket, it relinquishes ownership of the packet resources when ProtocolReceivePacket returns. Relinquishing such a received packet immediately could occur, for instance, if the client for the packet has closed the connection or in some manner become unavailable, or if the protocol copies the indicated data into buffers of its own and processes the data internally before indicating up to its clients.

Implementing a ProtocolReceivePacket Handler

When an underlying NIC driver indicates an array of one or more packet descriptors with NdisMIndicateReceivePacket, NDIS will usually call a bound protocol driver’s ProtocolReceivePacket function with each packet descriptor, thereby allowing the protocol driver (or its TDI clients) to retain the packet descriptor and all the resources it specifies until the protocol or its client(s) have consumed the data and returned the packet descriptor. Two kinds of NIC drivers typically call NdisMIndicateReceivePacket with an array of packet descriptors:

  1. A NIC driver managing a busmaster DMA adapter that is capable of receiving several packets at a time into a ring of buffers

  2. A NIC driver that provides out-of-band data containing media-specific information, such as packet priority, in the NDIS_PACKET_OOB_DATA block associated with the packet descriptor

    Such a driver need not be the driver of a busmaster DMA device.

If a protocol driver is aware that it is (or might be) bound to such a NIC driver, it should have a ProtocolReceivePacket function. This allows the protocol driver to do all of the following:

Even when a protocol driver provides a ProtocolReceivePacket handler, there are cases when a call by a NIC driver to NdisMIndicateReceivePacket results in a call to the protocol driver’s ProtocolReceive function. Since a NIC driver temporarily relinquishes ownership of driver-allocated resources when it calls NdisMIndicateReceivePacket, the underlying driver depends on the consumers of those packets to return them in a timely manner. Otherwise, the NIC driver can run short of receive resources, such as receive buffer space in the NIC. When it does, the NIC driver sets NDIS_STATUS_RESOURCES in the OOB block associated with a packet descriptor in the packet array it passes to NdisMIndicateReceivePacket. Indicating a packet with this status causes NDIS to call the overlying driver's ProtocolReceive function with such a packet and with any subsequent packets in the array, thus forcing the protocol driver to copy the packet data immediately, rather than retaining the NIC-driver-allocated packet resources.

If the protocol driver requires the OOB data associated with a packet descriptor but is called at ProtocolReceive, it must call NdisQueryReceiveInformation to copy the media-specific information into a protocol-supplied buffer and possibly the TimeSent and TimeReceived values if the underlying NIC driver provides these timestamps.

Implementing a ProtocolReceive Handler

If the underlying NIC driver calls a filter-specific NdisMXxxIndicateReceive function, NDIS always calls the ProtocolReceive function of each bound protocol driver. If one or more of such a protocol driver's clients are the target of the packet and the lookahead buffer size is less than the total packet size, ProtocolReceive must do the following:

  1. Copy the lookahead data into an internal buffer mapped by a protocol-allocated packet descriptor.

  2. Chain buffer descriptor(s) mapping sufficient protocol-allocated buffers to contain the rest of the network packet data to a protocol-allocated packet descriptor.

  3. Call NdisTransferData with the packet descriptor, so the underlying driver copies the rest of the received data into the protocol's buffer(s).

When NdisTransferData returns STATUS_SUCCESS or the ProtocolTransferDataComplete function is called, the lookahead buffer can be chained to the packet descriptor containing this transferred data and indicated up to any interested client(s).

NdisTransferData can only be called once for each receive indication. It is the responsibility of the protocol driver to set up its packet descriptor with chained buffers of a sufficient size to contain the full network packet. After NdisTransferData returns, the received data is no longer available from the underlying NIC driver.

The size of the lookahead buffer passed to ProtocolReceive is the minimum of the size returned by a call to NdisRequest with OID_GEN_CURRENT_LOOKAHEAD and the total packet size. All data in the lookahead buffer is read-only to the ProtocolReceive.

If the call to ProtocolReceive occurred because the underlying NIC driver set the status for one or more packets in a packet array to NDIS_STATUS_RESOURCES before calling NdisMIndicateReceivePacket, the size of the lookahead buffer will always be equal to the size of the full network packet. In these circumstances, the protocol driver never calls NdisTransferData because ProtocolReceive can copy the full indication into an internal buffer immediately.

If the NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA bit is set in response to an OID_GEN_MAC_OPTIONS request, a protocol driver can use any means to copy lookahead data into an internal buffer, such as calling NdisMoveMemory. If this flag was not set, the protocol driver must call TdiCopyLookaheadData to copy the indicated data; otherwise, the results from a copy operation are indeterminate.

ProtocolReceive must execute as quickly as possible. The protocol driver must insure that it has protocol-allocated packet resources available before it receives incoming indications. After the protocol driver examines the packet and determines that the packet is not one it will copy, ProtocolReceive should simply return NDIS_STATUS_NOT_ACCEPTED.

ProtocolReceive must not process the received data as soon as it is copied since that would adversely impact system performance, as well as possibly causing dropped receives in the underlying NIC driver. Instead, the protocol driver processes the received data later in its ProtocolReceiveComplete function. Following one or more receive indications, ProtocolReceiveComplete is called when the underlying NIC driver calls NdisMXxxIndicateReceiveComplete. Typically, this occurs when the underlying NIC driver has received and indicated a miniport-determined number of packets or before it exits its DPC-level receive handler. The protocol driver must queue the received data in ProtocolReceive so that it is available to ProtocolReceiveComplete for postprocessing.

Accessing OOB Information

When a received network packet is indicated to ProtocolReceive, it forces the driver to copy the received data into a protocol-supplied buffer. If such an indication has associated media-specific and/or timestamp information in the OOB block associated with the packet descriptor, a protocol driver can call NdisQueryReceiveInformation to copy the media-specific information, as well as any TimeSent and TimeReceived timestamps.

If a received packet is passed to ProtocolReceivePacket, the protocol driver can copy the information from the associated OOB block using NDIS-supplied macros as follows:

TimeSent is the time a packet was sent by the NIC driver on the remote node, and is retrieved and stored by the underlying NIC driver on the local node if available. TimeReceived is the time that the incoming packet was received on the underlying NIC on the local node.

The current system time can be determined using NdisGetCurrentSystemTime or KeQuerySystemTime if the protocol converts these timestamps into another format.