3.6.1 Registering and Queueing a DpcForIsr Routine
An NT device driver can register its DpcForIsr by calling IoInitializeDpcRequest when the driver initializes. After the driver is loaded and handling interrupt-driven I/O requests, the ISR calls IoRequestDpc just before it returns control to have the DpcForIsr routine queued for execution. Figure 3.17 illustrates calls to these routines.
Figure 3.17 Using a DPC Object for a DpcForIsr Routine
As Figure 3.17 shows, calling IoInitializeDpcRequest associates a Kernel DPC object with a driver-supplied DpcForIsr routine and a driver-created device object. The I/O Manager allocates memory for the DPC object and calls KeInitializeDpc on the driver's behalf.
When the ISR is called to handle a device interrupt at DIRQL, it should return control to the system as soon as possible for better overall system and driver performance. Usually, an ISR merely stops the device from generating more interrupts, gathers whatever context information the DpcForIsr routine needs to complete the operation that caused the interrupt, calls IoRequestDpc, and returns.
As Figure 3.17 shows, the ISR passes a pointer to the device object, representing the target device for which the operation was carried out, a pointer to the DeviceObject->CurrentIrp, and a pointer to a driver-determined Context for the operation to IoRequestDpc. The I/O Manager calls KeInsertQueueDpc on the driver's behalf, and the corresponding DPC object is queued until IRQL falls below DISPATCH_LEVEL on a processor. Then, the Kernel dequeues the DPC object and the driver's DpcForIsr is run on the processor at IRQL DISPATCH_LEVEL.
On entry, the DpcForIsr is given a pointer to the DPC object and the DeviceObject, current Irp, and Context pointers passed in the ISR's call to IoRequestDpc. The Context-accessible area must be in resident memory. Unless the driver overlaps I/O for the target device, such a context area is usually in the DeviceObject->DeviceExtension, but it 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. The DpcForIsr is responsible for doing whatever is necessary to complete the I/O requested in the current IRP.
The ISR and DpcForIsr can be run concurrently in symmetric multiprocessor machines, so NT device driver writers should follow these guidelines:
·The ISR must call IoRequestDpc just before it returns control. Otherwise, the DpcForIsr might be run on another processor before the ISR has finished setting up the Context-accessible area for the DpcForIsr.
·The ISR could be called again if the device interrupts while or before the DpcForIsr is run. When a driver uses the device extension (see Section 3.2) to maintain context about its device I/O operations, the DpcForIsr should never call IoStartNextPacket for the input DeviceObject (nor dequeue an IRP for the input DeviceObject, if the driver manages its own IRP queueing) until just before it calls IoCompleteRequest with the current Irp. Otherwise, the driver's StartIo or queue-management routine(s) might start a device I/O operation that overwrote the shared context area before the DpcForIsr could complete the current operation.
·The DpcForIsr and any other driver routine that shares a context area with the ISR must call KeSynchronizeExecution with a driver-supplied SynchCritSection routine in order to access any context area shared with the ISR in a multiprocessor-safe manner.
Even in a uniprocessor machine, the ISR could be called again if the device interrupts while or before the DpcForIsr is run. If this occurs, the DpcForIsr routine is run only once. In other words, there is no one-to-one correspondence between an ISR's calls to IoRequestDpc and instantiations of the DpcForIsr routine if an NT driver overlaps I/O operations for its target device objects.
For more information about the functionality required of a DpcForIsr routine, see Chapter 9.