3.6.2 Registering and Queueing a CustomDpc Routine
An NT device driver can register a CustomDpc routine by calling KeInitializeDpc when the driver initializes. Just before it returns control, the ISR can call KeInsertQueueDpc to have the CustomDpc routine queued for execution. Figure 3.18 illustrates calls to these routines.
Figure 3.18 Using a DPC Object for a CustomDpc Routine
As Figure 3.18 shows, a driver that has a CustomDpc routine must provide the storage for a DPC object. Because the driver must pass a pointer to the DPC object from its ISR, the storage must be in resident system-space memory. Most NT device drivers with CustomDpc routines provide storage for their DPC objects in the device extension of a driver-created device object (see Section 3.2), but the storage can be in a controller extension if the driver uses a controller object (see Section 3.4) or in nonpaged pool allocated by the driver.
When the DriverEntry routine calls KeInitializeDpc, it must pass the entry point for its CustomDpc routine and pointers to the driver-allocated storage for the DPC object and to the driver-defined DeferredContext area, which is passed to the CustomDpc routine when it is called. Because it must be accessible at raised IRQL, the DeferredContext-accessible area also must be in resident memory.
Note that a CustomDpc routine is not associated with a given device object as the DpcForIsr routine is when the driver calls IoInitializeDpcRequest, as described in Section 3.6.1. Nevertheless, the DeferredContext area for a CustomDpc routine almost always includes pointers to the target device object and current IRP, because a CustomDpc routine has the same functional requirements as a DpcForIsr routine: to complete an interrupt-driven I/O operation at a lower IRQL than the ISR.
As Figure 3.18 shows, the ISR passes pointers to the DPC object and to two additional parameters, which are caller-determined, to KeInsertQueueDpc. If all processors in the machine currently have code running at IRQL greater than or equal to DISPATCH_LEVEL, the DPC object is queued until the IRQL falls below DISPATCH_LEVEL on a processor. Then, the Kernel dequeues the DPC object and the driver's CustomDpc routine is run on the processor at IRQL DISPATCH_LEVEL.
Only a single instantiation of a given DPC object can be queued at any given moment. That is, if an ISR calls KeInsertQueueDpc more than once with the same Dpc pointer before the driver's CustomDpc routine is run, the CustomDpc routine runs only once after IRQL falls below DISPATCH_LEVEL on a processor.
A CustomDpc routine is responsible for doing whatever is necessary to complete the I/O operation(s) that caused the interrupt(s). For more information about the functionality required of a CustomDpc routine, see Chapter 9.
Note that the ISR and CustomDpc can be run concurrently in a symmetric multiprocessor machine, so NT driver writers who implement CustomDpc routines should follow the guidelines set out in Section 3.6.1.