Chapter 1 NDIS Intermediate Drivers

An NDIS intermediate driver usually exports MiniportXxx functions at its upper edge and ProtocolXxx functions at its lower edge. Less commonly, an intermediate driver can export MiniportXxx functions at its upper edge and a private interface to an underlying non-NDIS driver at its lower edge.

An intermediate driver is typically layered over one or more NDIS NIC drivers and under a transport driver (possibly multilayered) that supports TDI at its upper edge. Theoretically, an intermediate driver could be layered above or below another intermediate driver, although such an arrangement is unlikely to exhibit good performance.

Two examples of intermediate drivers are:

Figure 1.1 shows these two types of intermediate drivers.

Figure 1.1 Supported Intermediate Driver Configurations

An NDIS intermediate driver interfaces to NDIS to forward packets sent by a driver above and to pass them to a driver below. When an intermediate driver receives packets from an underlying driver, it indicates them to the driver above either by calling a filter-specific NdisMXxxIndicateReceive function or NdisMIndicateReceivePacket.

An intermediate driver calls NDIS to open and establish a binding to an underlying NIC driver or intermediate NDIS driver that exports a set of MiniportXxx functions at its upper edge. An intermediate driver provides MiniportSetInformation and MiniportQueryInformation functions to process set and query requests from higher level driver(s) and, perhaps, to pass them through to a lower level NDIS driver by calling NdisRequest.

An intermediate driver calls NDIS-provided functions to send packets on to still lower level NDIS drivers to the net. For instance, an intermediate driver must call NdisSend or NdisSendPackets to send a packet or array of packets. If the intermediate driver is layered over a nonNDIS NIC driver, the send interface is opaque to NDIS after it calls the MiniportSend or MiniportSendPackets function of the intermediate driver.

NDIS provides a set of NdisXxx functions and macros that hides the details of the underlying operating system. For instance, an intermediate driver can call NdisMInitializeTimer to create a timer for synchronization purposes and NdisInitializeListHead to create a linked list. Intermediate drivers use NDIS functions in order to be more portable across Microsoft operating systems that support the Win32 interface.

Pageable and Discardable Code

As explained in Part 1, every MiniportXxx or ProtocolXxx function runs at a particular IRQL. The possible IRQLs for these functions range between PASSIVE_LEVEL up to and including DISPATCH_LEVEL in intermediate drivers.

Intermediate driver functions that always run at IRQL PASSIVE_LEVEL can be marked as pageable using the NDIS_PAGABLE_FUNCTION macro. Driver developers are encouraged to designate code as pageable whenever possible, freeing system space for code that must be memory-resident. A driver function that runs at IRQL PASSIVE_LEVEL can be made pageable as long as it neither calls nor is called by any function that runs at IRQL >= DISPATCH_LEVEL, for instance a function that acquires a spin lock. Acquiring a spin lock causes the IRQL of the acquiring thread to be raised to IRQL DISPATCH_LEVEL. A function, such as ProtocolBindAdapter, that runs at IRQL PASSIVE_LEVEL, must not call NDIS functions that run at IRQL >= DISPATCH_LEVEL if ProtocolBindAdapter is marked as pageable code. For more information about NDIS functions that run at raised IRQL, see the Network Driver Reference, which specifies the IRQL for each of the NdisXxx functions.

The DriverEntry function of an intermediate driver should be specified as initialization-only code, using the NDIS_INIT_FUNCTION macro. Code identified with this macro is assumed to only run once at system initialization time, and as a result, is only mapped during that time. After DriverEntry returns, code marked with the NDIS_INIT_FUNCTION macro is discarded.

Synchronizing Access to Shared Resources

Access to any driver-allocated shared resource must be synchronized if the resource can be simultaneously shared by two driver functions or if the intermediate driver can run on an SMP machine such that the same driver function can be attempting to simultaneously access the resource from more than one processor. For instance, if a driver maintains a shared queue, a spin lock can be used to serialize access to that queue. The spin lock should be initialized when the queue is created by calling NdisAllocateSpinLock.

However, care should be taken not to overprotect a shared resource, such as a queue. For example, some read operations can be done without serialization. Any operation that manipulates the queue links however, must be serialized. Spin locks always should be used sparingly and held for as short a time as possible. See the Kernel-Mode Driver Design Guide for an in-depth discussion of spin locks.