7.3.4 Managing Interlocked Queues with a Driver-Created Thread
Like the system floppy controller driver mentioned in Chapter 2, an NT driver with a device-dedicated thread, rather than a StartIo routine, usually manages its own queueing of IRPs in an interlocked queue. Such a driver's thread pulls IRPs from its interlocked queue when there is work to be done on the device.
In general, such a driver must manage synchronization with its thread to any resources shared between the thread and other driver routines. Such a driver also must have some way to notify its driver-created thread that IRPs are queued. Usually, such a thread waits on a dispatcher object, stored in the device extension, until the driver's Dispatch routines set the dispatcher object to the Signaled state after inserting an IRP into the interlocked queue.
When such a device driver's Dispatch routines are called, each checks the parameters in the I/O stack location of the input IRP and, if they are valid, queues the request for further processing. For each IRP queued to a driver-dedicated thread, the Dispatch routine should set up whatever context its thread needs to process that IRP before it calls ExInterlockedInsert..List. The driver's I/O stack location in each IRP gives the driver's thread access to the device extension of the target device object, where the driver can share context information with its thread, as the thread removes each IRP from the queue.
Any driver-created thread runs at IRQL PASSIVE_LEVEL and at a base runtime priority previously set when the driver called PsCreateSystemThread. Such a thread's call to ExInterlockedRemoveHeadList temporarily raises the IRQL to DISPATCH_LEVEL on the current processor while the IRP is being removed from the driver's internal queue, and the original IRQL is restored to PASSIVE_LEVEL on return from this call.
Any driver thread (or driver-supplied worker-thread callback) must manage the IRQLs at which it runs carefully. For example, consider the following:
·Because system threads generally run at IRQL PASSIVE_LEVEL, it is possible for a driver thread to wait on Kernel-defined dispatcher objects.
For example, a device-dedicated thread might wait on an event for other driver routines to satisfy and complete some number of partial-transfer IRPs that the thread sets up with IoBuildSynchronousFsdRequest.
·On the other hand, such a device-dedicated thread must raise IRQL on the current processor before it calls certain support routines.
For example, if a driver uses DMA, its device-dedicated thread must nest its calls to IoAllocateAdapterChannel and IoFreeAdapterChannel between calls to KeRaiseIrql and KeLowerIrql because the Io..AdapterXxx routines and other support routines for DMA operations must be called at IRQL DISPATCH_LEVEL.
Note that, as mentioned in Section 7.2, StartIo routines are run at DISPATCH_LEVEL, so NT drivers that use DMA need not make calls to the the Ke..Irql routines from their StartIo routines.
·A driver-created thread can access pageable memory because it runs in a nonarbitrary thread context (its own) at IRQL PASSIVE_LEVEL, but many other standard NT driver routines run at raised IRQL.
Consequently, if a driver-created thread allocates memory that can be accessed by other driver routines, it must allocate the memory from nonpaged pool. For example, if a device-dedicated thread allocates any buffer that will be accessed later by the driver's ISR or SynchCritSection, AdapterControl, ControllerControl, DpcForIsr, CustomDpc, IoTimer, CustomTimerDpc, or, in a higher-level driver, IoCompletion routine, the thread-allocated memory cannot be pageable.
·Like a StartIo routine, a driver thread must synchronize its access to a physical device and to any shared state information or resources that the driver maintains in the device extension with the driver's other routines that access the same device, memory location, or resources.
If the thread shares the device or state with the ISR, it must call KeSynchronizeExecution with a driver-supplied SynchCritSection routine to program the device or to access the shared state. For more information about SynchCritSection routines, see Chapter 10.
If the thread shares state or resources with routines other than the ISR, the driver must protect the shared state or resources with a driver-initialized executive spin lock for which the driver provides the storage. For more information about using spin locks, see Chapter 16.
For more information about managing IRQLs, and about the design tradeoffs of a using a driver thread for a slow device, see Chapter 16. For specific information about the required IRQL(s) for calling any particular support routine, see the Kernel-mode Driver Reference.