2. Deferred Processing for Interrupts
When the DPC runs, KMDF calls the driver’s EvtInterruptDpc callback. This function performs device-specific interrupt processing and reenables the interrupt for the device. It runs at DISPATCH_LEVEL and therefore must not attempt any operations that might cause a page fault (or wait on any dispatcher objects).
2.1. Code for EvtInterruptDpc Callback
The following is the PCIDRV sample’s EvtInterruptDpc callback:
VOID
NICEvtInterruptDpc(
IN WDFINTERRUPT WdfInterrupt,
IN WDFOBJECT WdfDevice
)
{
PFDO_DATA fdoData = NULL;
fdoData = FdoGetData (WdfDevice);
WdfSpinLockAcquire (fdoData->RcvLock);
NICHandleRecvInterrupt (fdoData);
WdfSpinLockRelease (fdoData->RcvLock);
//
// Handle send interrupt.
//
WdfSpinLockAcquire (fdoData->SendLock);
NICHandleSendInterrupt (fdoData);
WdfSpinLockRelease (fdoData->SendLock);
//
// Check if any queued sends need to be reprocessed.
//
NICCheckForQueuedSends (fdoData);
//
// Start the receive unit if it was stopped
//
WdfSpinLockAcquire (fdoData->RcvLock);
NICStartRecv (fdoData);
WdfSpinLockRelease (fdoData->RcvLock);
//
// Reenable the interrupt.
//
WdfInterruptSynchronize (
WdfInterrupt,
NICEnableInterrupt,
fdoData);
return;
}
Most of the code in this callback is device specific. However, its use of spin locks is worth noting.
When the driver created its I/O queues, it also
created two spin locks and stored their handles in its device context
area. One (RcvLock) protects read-related buffers and operations, and the (SendLock)
protects write-related buffers and operations. In this function, it
uses these spin locks to protect against preemption and concurrent
users while it processes the results of the I/O operation. The driver
calls WdfSpinLockAcquire and WdfSpinLockRelease to acquire and release the locks.
When the driver has completed all
device-specific processing, it reenables the interrupt. The function
that reenables the interrupt (NICEnableInterrupt) must run at DIRQL, so the driver calls WdfInterruptSynchronize to run it. WdfInterruptSynchronize takes a handle to the interrupt object, a pointer to the function to be run (NICEnableInterrupt), and a pointer to the device context area, which it passed as an argument to NICEnableInterrupt. WdfInterruptSynchronize raises IRQL to DIRQL and calls NICEnableInterrupt. When NICEnableInterrupt completes, KMDF lowers the IRQL to DISPATCH_LEVEL and returns.