When a device interrupts, Windows calls the
driver to service the interrupt. However, more than one device can be
connected to a single interrupt vector. Internally, the operating
system keeps a list of the InterruptService
routines of the drivers for devices that interrupt at the same level.
When an interrupt signal arrives, Windows traverses the list and calls
the drivers in sequence until one of them acknowledges and services the
interrupt.
KMDF intercepts the call from the operating system and, in turn, calls the EvtInterruptIsr callback that the driver registered. Like traditional InterruptService functions, this callback runs at DIRQL. Because EvtInterruptIsr runs at such a high IRQL,
it should do its work quickly and then return. Remember that it cannot
take any action that would cause a page fault (or wait on any
dispatcher objects) because it runs at an IRQL greater than APC_LEVEL.
The EvtInterruptIsr callback should perform the following tasks, and nothing more:
Determine whether its device is interrupting, and if not, return FALSE immediately.
Stop the device from interrupting.
Queue a DPC to perform any work related to the interrupt.
The EvtInterruptIsr function is called with a handle to the interrupt object for the driver’s device and a ULONG value that specifies the message ID if the device is configured for MSIs and zero otherwise.
1. Code for EvtInterruptIsr Callback
The following is the PCIDRV sample’s EvtInterruptIsr callback (which is defined in pcidrv\sys\hw\isrdpc.c):
BOOLEAN
NICEvtInterruptIsr(
IN WDFINTERRUPT Interrupt,
IN ULONG MessageID
)
{
BOOLEAN InterruptRecognized = FALSE;
PFDO_DATA FdoData = NULL;
USHORT IntStatus;
UNREFERENCED_PARAMETER (MessageID);
FdoData = FdoGetData (WdfInterruptGetDevice (Interrupt));
//
// Process the interrupt if it is enabled
// and active.
//
If (!NIC_INTERRUPT_DISABLED (FdoData) &&
NIC_INTERRUPT_ACTIVE (FdoData))
{
InterruptRecognized = TRUE;
//
// Disable the interrupt. It will be reenabled in
// NICEvtInterruptDpc.
//
NICDisableInterrupt (FdoData);
//
// Acknowledge the interrupt (s) and get status
//
NIC_ACK_INTERRUPT (FdoData, IntStatus);
WdfInterruptQueueDpcForIsr (Interrupt);
}
return InterruptRecognized;
}
The PCIDRV
sample’s first step is to determine whether its device is interrupting.
To do so, it must check its device registers. It gets a handle to the
device object that is associated with the interrupt object by calling
the WdfInterruptGetDevice method and then passes that handle to FdoGetData to get a pointer to the device context area. In the context area, the driver stored a pointer to its mapped hardware registers.
The NIC_INTERRUPT_DISABLED and NIC_INTERRUPT_ACTIVE macros (defined in pcidrv\sys\Hw\Nic_def.h)
check the hardware registers to determine whether interrupts have been
disabled for the device and whether they are currently active. If
interrupts have been disabled, the device cannot possibly have
generated the interrupt. The same is true if the device’s interrupt is
enabled but not currently active. In either case, the driver returns
with InterruptRecognized set to FALSE. (For most drivers, checking whether device interrupts have been disabled is unnecessary.)
However, if interrupts have not been disabled and an
interrupt is currently active, the device must have generated the
interrupts. In this case, the driver sets InterruptRecognized to TRUE.
To stop the device from interrupting, the driver calls NICDisableInterrupt and then uses the driver-defined NIC_ACK_INTERRUPT macro to acknowledge the interrupt in the hardware. Finally, it queues a DPC by calling WdfInterruptQueueDpcForIsr and then returns.