1.4 Restrictions on Intermediate Drivers

Previous sections have described specific actions an intermediate driver must follow to perform correctly, summarized as follows:

  1. An intermediate driver must set the NDIS_ATTRIBUTE_INTERMEDIATE_DRIVER flag when it calls NdisMSetAttributesEx in its MiniportInitialize function. NDIS identifies that a driver is an intermediate type only through the presence of this flag and takes special steps to ensure that deferred actions, such as passing internally-queued send packets on to the intermediate driver, occur without deadlocks.

  2. An intermediate driver must always, at a minimum, replace an incoming packet descriptor for a packet that it passes down to an underlying driver on a send or up to a higher level driver on a receive with a fresh packet descriptor, allocated by the intermediate driver. It must also then replace its own packet descriptor with the one that originally was associated with the packet when it was passed to the intermediate driver, for instance, when completing a send or completing a receive indication. The intermediate driver must return the resources it has "borrowed" from a higher or lower driver promptly by returning that driver's packet descriptor and the resources it specifies.

  3. An intermediate driver is always a full-duplex driver unless it reports the media type of its virtual NIC as NdisMediumWan. Any other intermediate driver must follow the rules that apply to any full-duplex miniport driver, in particular, the following guideline:

    If any internal driver resources are shared between an intermediate driver’s send function and any other MiniportXxx function, that resource must be protected by a spin lock. The only exception to this is in the MiniportReset path, which is serialized by NDIS with respect to the MiniportSend or MiniportSendPackets function and all other MiniportXxx functions.

    As for any full-duplex miniport driver, an intermediate driver that organizes its per-binding context area into discrete receive-specific, send-specific, and shared ranges, with only the shared resource(s) protected by spin lock(s), will exhibit far better performance than a driver that must overprotect its context area because send-specific, receive-specific, and shared variables are scattered throughout.

  4. When an intermediate driver is performing as a protocol driver, that is, in a ProtocolXxx function, it cannot call any NdisMXxx functions. It can call only the set of NdisXxx functions that are callable by any protocol driver and certain NdisIMXxx functions specific to intermediate drivers. An intermediate driver in a protocol code path that needs to call an NdisMXxx function must call NDIS to switch to a miniport context before making the call. How this is accomplished is described next.

NDIS synchronizes the execution of MiniportXxx functions as already explained in Part 2. To perform this same synchronization for an intermediate driver so that it can act as a miniport, any ProtocolXxx function in the intermediate driver must call NdisIMSwitchToMiniport before the driver calls an NdisMXxx that indicates up to the higher level driver. For instance, before ProtocolReceive calls NdisMIndicateReceivePacket or NdisMXxxIndicateReceive to pass a packet to the next higher driver, it must call NDIS to get into the context of a miniport.

An intermediate driver first calls NdisIMSwitchToMiniport. If NdisIMSwitchToMiniport returns TRUE, ProtocolReceive(Packets) can make its NdisMXxx call followed by a call to NdisIMRevertBack when its miniport-only operations are complete. NdisIMSwitchToMiniport acquires a spin lock that prevents other MiniportXxx functions in the same driver from running while the spin lock is held. Therefore, it is important that the intermediate driver call NdisIMRevertBack as quickly as possible.

If NdisIMSwitchToMiniport returns FALSE, ProtocolReceive(Packets) must call NdisIMQueueMiniportCallback to schedule a callback function in which to forward the receive indication. NDIS calls the driver-supplied callback function when it obtains the necessary spin lock, thereby allowing the callback function to perform the same actions the intermediate driver would have performed if the preceding call to NdisIMSwitchToMiniport had been successful.

When NdisIMQueueMiniportCallback returns, ProtocolXxx can resume protocol operations. If NdisIMQueueMiniportCallback returns NDIS_STATUS_SUCCESS, the miniport-only operations have been completed in the MiniportCallback function before the return.

Note that the general restriction of switching to a miniport context applies to protocol actions other than just indicating up received packets. For instance, an intermediate driver's ProtocolStatus function also must make a context switch to being a miniport before the driver calls NdisMIndicateStatus to forward a status indication from a lower level driver to a higher level driver.

NdisIMSwitchToMiniport, NdisIMRevertBack and NdisIMQueueMiniportCallback must not be called from any MiniportXxx function. It is a fatal error to make such a call.