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.
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.
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.
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.
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.