1.6 Transmitting Packets Through an Intermediate Driver

As discussed in Section 1.1.1, an intermediate driver should always provide a MiniportSendPackets function when it registers with NdisIMRegisterLayeredMiniport. If the intermediate driver is layered between two NDIS drivers that support multipacket sends, the MiniportSendPackets function can forward incoming packet arrays by calling NdisSendPackets. If the intermediate driver is layered under a transport that sends one packet at a time at NdisSend, the MiniportSendPackets function can transmit the single packet with NdisSend or NdisSendPackets without any negative performance impact.

Such an intermediate driver does not become a performance bottleneck. If the intermediate driver is layered between a transport that sends a packet array to MiniportSendPackets and a miniport that only handles a single packet at a time, MiniportSendPackets can send the array of packets it receives with NdisSendPackets without regard to the capabilities of the underlying miniport. NDIS, transparently to the intermediate driver, queues the packets in the array and sends each individual packet to the underlying miniport’s MiniportSend function when the miniport is able to accept the send request.

Because calls down to the MiniportXxx functions originate in NDIS, synchronization is guaranteed, so an intermediate driver need not make any NdisIMXxx calls as described in Section 1.4, when forwarding requests that originate in higher level drivers to underlying drivers.

An intermediate driver will, at a minimum, replace the packet descriptor for each incoming send with its own. It must retain the original descriptor (and chained buffers if it copies them to new buffers) from the higher level driver and, when the send completes, the intermediate driver must return the original packet descriptor and the data buffers of the as-sent packet before completing the send back to the higher level driver. For more information on how to allocate packet resources and copy information from one packet to another, see Section 1.3.

MiniportSendPackets receives an array of packet descriptors arranged in an order determined by the caller of NdisSendPackets. In most cases, the intermediate driver should maintain this ordering as it passes an incoming array of packets on to the underlying NIC driver. Only an intermediate driver that adds out-of-band information to incoming packets before passing them on to the underlying driver is likely to reorder an incoming array.

NDIS always preserves the ordering of packet descriptor pointers as passed as an array to NdisSendPackets. The underlying miniport NIC driver also assumes that an array of packet pointers passed in to its MiniportSendPackets function implies the packets should be transmitted in the same order.

An intermediate driver can transmit a single packet by calling NdisSend with a pointer to the packet descriptor. An intermediate driver can transmit several packets or just one using NdisSendPackets and passing a pointer to an array of pointer(s) to one or more packet descriptors.

An intermediate driver developer, in general, should determine whether to use NdisSend or NdisSendPackets based upon the driver’s own requirements and any known characteristics of the underlying NIC driver. An intermediate driver can call NdisRequest with a RequestType of NdisQueryInformation and an OID_GEN_MAXIMUM_SEND_PACKETS to obtain the maximum number of send packets that the underlying driver will accept in a packet array. If the underlying driver returns a value of one or NDIS_STATUS_NOT_SUPPORTED, the intermediate driver can choose to use NdisSend rather than NdisSendPackets. If the underlying driver returns a value greater than one, both drivers' performance will be better if the intermediate driver sends an array of packets with NdisSendPackets.

If any underlying NIC driver returns a value greater than one, the intermediate driver also should have a MiniportSendPackets function. In addition, such an miniport should respond to an OID_GEN_MAXIMUM_SEND_PACKETS query with a comparable value to that of the underlying NIC driver.

If OOB information is passed between the intermediate driver and the NIC driver, either send function can be called, since in either case the underlying driver can read the OOB data associated with the packet descriptor using NDIS-supplied macros.

If an intermediate driver sends more packets to an underlying NIC driver then that driver has the internal resources to transmit:

When an intermediate driver calls NdisSend from MiniportSend, it relinquishes ownership of the packet descriptor and of all the resources it describes until the send completes, either synchronously or asynchronously. If the status returned by NdisSend is other than NDIS_STATUS_PENDING, the call completes synchronously, and ownership of the packet resources reverts to the intermediate driver on return from NdisSend. The intermediate driver should return any send resources allocated by a higher level driver and propagate the result of its call to NdisSend as the result of MiniportSend.

If the status returned by NdisSend is NDIS_STATUS_PENDING, when the send subsequently completes, the final status of the send and the packet descriptor will be returned to ProtocolSendComplete. The intermediate driver should call NdisSendComplete from its ProtocolSendComplete function to propagate the send status up to the next higher driver. Since ProtocolSendComplete must call NdisIMSwitchToMiniport before completing the send to the next higher driver, it should call NdisIMRevertBack as quickly as possible.

When an intermediate driver transmits one or more packet(s) by calling NdisSendPackets, the send operation is implicitly asynchronous. The caller relinquishes ownership of its packet descriptor(s) and all the resources they describe until each packet descriptor and the final status of the send for that packet is returned to ProtocolSendComplete. ProtocolSendComplete must propagate the status of the send of each packet back to the next higher driver as described in the previous paragraph.

As a consequence, if an intermediate driver sends packets in an array using NdisSendPackets, the intermediate driver must not attempt to read the Status member(s) of the associated OOB block(s) on return from NdisSendPackets. This member is in use by NDIS to track the progress of an in-transition send request and is volatile. An intermediate driver can only obtain the status of a transmit request by examining the Status argument passed to its ProtocolSendComplete function.

If an intermediate driver requests the transmission of an array of packets of different priorities by rearranging the packets it receives from an overlying driver before transmitting them, it should place the highest priority packets at the beginning of the array. NDIS retains this ordering when it passes the packets to either the MiniportSend or to the MiniportSendPackets function of the underlying NIC driver, even if NDIS queues some of the packets internally. This ordering is maintained by NDIS for each call to NdisSendPackets.

NDIS never attempts to examine and make queuing decisions based on the OOB block associated with a packet descriptor. Unless an intermediate driver has special knowledge of the manner in which a NIC driver handles packet priority, it should assume that the NIC driver transmits the packets in the order in which it receives them, preserving the as-received order.

The structure of the Private member of an NDIS_PACKET-type descriptor is opaque to an intermediate driver and is accessed to read and, in some cases, to write using NDIS-supplied macros or functions. For example, before sending a packet, an intermediate driver can call NdisSetPacketFlags to set the intermediate-defined flags in the NDIS-private portion of the descriptor. Such flags are not defined by NDIS, but are defined by a pair of cooperating protocol and underlying NIC drivers.

Passing Media-Specific Information

More media-specific information can be passed by an intermediate driver in the OOB block associated with each NDIS_PACKET descriptor. The definition of the OOB block is:

typedef struct _NDIS_PACKET_OOB_DATA {
    union {
        ULONGLONG TimeToSend;
        ULONGLONG TimeSent;
    };
    ULONGLONG TimeReceived;
    UINT HeaderSize;
    UINT SizeMediaSpecificInfo;
    PVOID MediaSpecificInformation;
    NDIS_STATUS Status;
} NDIS_PACKET_OOB_DATA, *PNDIS_PACKET_OOB_DATA;

The structure of individual records within the buffer at MediaSpecificInformation is defined as follows:

typedef struct MediaSpecificInformation {
    UINT NextEntryOffset;
    NDIS_CLASS_ID ClassId;
    UINT Size;
    UCHAR ClassInformation[1];
} MEDIA_SPECIFIC_INFORMATION;

The ClassId member is an NDIS-defined enumeration that defines the type of information found at ClassInformation[1]. Currently, there are two class IDs in use across Microsoft operating systems that support Win32: NdisClass802_3Priority and NdisClassWirelessWanMbxMailbox. See the Network Driver Reference for details.

If the intermediate driver knows that the underlying NIC driver to which it is sending packets uses OOB data, it can set certain OOB structure members. For instance, an intermediate driver can: