Interrupts and Interrupt Handling


Some devices that attach to a PCI bus, to a PCI Express bus, or to other backplane buses generate interrupts to signal the processor-and thus, the operating system-that they require service. The function driver for such a device must include code that enables and disables interrupts in the device hardware and that responds to interrupts when they occur during device operation-typically to signal that the device has completed a requested operation.

Devices that attach to protocol buses such as USB, IEEE 1394, and Bluetooth do not generate interrupts, so their drivers do not require interrupt-handling code.

Line-Based and Message-Based Interrupts

Most devices generate line-based interrupts by sending an electrical signal on a dedicated pin called an interrupt line. Some newer PCI devices generate MSIs instead by writing a data value to a particular address. Versions of Microsoft Windows earlier than Windows Vista support only line-based interrupts. Windows Vista and later operating systems support both line-based and message-based interrupts.

Tip 

Microsoft made several enhancements to the interrupt architecture in Windows Vista, as described in "Interrupt Architecture Enhancements in Windows Vista" on the WHDC Web site-online at http://go.microsoft.com/fwlink/?LinkId=81584.

Driver Support for Handling Interrupts

Function drivers require the same objects and callback functions to handle interrupts regardless of the type of interrupts that the device generates. To support interrupt handling in a driver:

  • Create an interrupt object for each line-based or message-based interrupt that the device can generate.

  • Provide optional EvtInterruptEnable and EvtInterruptDisable callback functions that enable and disable interrupts in the device hardware.

  • Provide optional EvtDeviceD0EntryPostInterruptsEnabled and EvtDeviceD0ExitPreInterruptsDisabled callback functions if the device requires any tasks to be performed during power transitions while its interrupts are enabled.

  • Provide an EvtInterruptIsr callback function to service the interrupt at DIRQL.

  • Provide an optional EvtInterruptDpc callback function if the driver requires additional interrupt-servicing tasks at DISPATCH_LEVEL.

The EvtInterruptIsr, EvtInterruptEnable, and EvtInterruptDisable callbacks run at DIRQL. Interrupts at DIRQL and lower are masked off-and thus cannot occur-while any of these functions is running. Therefore, each of these functions should do only the work that is absolutely necessary. Extended operations in any of these callbacks can slow the system.

Interrupt Objects

An interrupt object (that is, WDFINTERRUPT) represents an interrupt vector or an individual MSI. A driver creates interrupt objects during EvtDriverDeviceAdd processing. Each interrupt object must include pointers to the EvtInterruptIsr and EvtInterruptDpc event callback functions, and the object can also include additional information. To create an interrupt object, a driver fills in an attributes structure and a configuration structure and then calls a creation method.

If your device generates volatile interrupt data that the EvtInterruptIsr callback retrieves and the EvtInterruptDpc function uses, you should initialize an object attributes structure and create an object context area for the interrupt object. The EvtInterruptIsr callback can retrieve the data at DIRQL and store it in the object context area, where the EvtInterruptDpc function and other EvtInterruptXxx callbacks can access it.

Interrupt Object Configuration Structure

The driver calls the WDF_INTERRUPT_CONFIG_INIT function to initialize the WDF_INTERRUPT_CONFIG structure with pointers to the EvtInterruptIsr and EvtInterruptDpc callback functions that the driver implements for the interrupt. The driver can also set additional information in the structure before creating the interrupt object. The following fields are the most commonly used:

SpinLock

An optional handle to a WDF spin lock object.

AutomaticSerialization

A Boolean value to enable or disable framework serialization of the EvtInterruptDpc callback.

EvtInterruptEnable and EvtInterruptDisable

Pointers to the driver's callbacks to enable and disable interrupts in the device hardware.

Spinlock

If your driver creates several interrupt objects and must synchronize its handling of several interrupts, as might be necessary to support MSI, you can use a single, driver-supplied spin lock for all of the interrupt objects. The driver creates the lock by calling WdfSpinLockCreate and then supplies the handle to the spin lock object in the SpinLock field of the interrupt configuration structure for each interrupt object that the spin lock will protect. The framework determines the highest DIRQL among the interrupts and passes this DIRQL when it calls the system to connect the interrupt. The system always acquires the lock at this DIRQL so that none of the associated interrupts can interrupt the synchronized code.

Automaticserialization

The AutomaticSerialization field indicates whether the framework serializes calls to the EvtInterruptDpc callback functions with calls to the I/O event callbacks for the device object and with callbacks for any other objects for which the driver set automatic serialization. If your driver must always synchronize the execution of its EvtInterruptDpc callback with another EvtDpcFunc callback or with the execution of any of the I/O event callback functions to which framework synchronization applies, you should enable automatic serialization. If the driver requires such synchronization only occasionally, use a spin lock instead.

Interrupt Object Attributes

In addition to initializing an interrupt object configuration structure, the driver initializes an object attributes structure. Some drivers can avoid this step and simply pass WDF_NO_ATTRIBUTES. However, if your driver requires interrupt context data in several of its EvtInterruptXxx callbacks, it should create a context area and initialize the ContextTypeInfo field of the attributes structure by calling WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE.

For example, the AMCC5933 sample driver defines the INTERRUPT_DATA type for the interrupt context area and initializes the object attributes structure with this information as follows:

 WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&interruptAttributes, INTERRUPT_DATA); 

The SynchronizationScope and ExecutionLevel fields of the attributes structure do not apply to interrupt objects. A driver must not set the Parent field because the framework sets the parent of every interrupt object to the device object; the driver cannot change this setting.

Interrupt Object Creation

To create the interrupt object, the driver calls the WdfInterruptCreate method and passes a handle to the device object, a pointer to the interrupt configuration structure, and a pointer to an attribute configuration structure. The method returns status and a handle to the newly created interrupt object.

If the device can generate more than one interrupt vector or message, the driver must configure and create an interrupt object for each one. The PnP manager attempts to assign all of the interrupt vectors or messages that the device can support. For MSI messages, if the PnP manager cannot assign all of the messages, it instead assigns only one message. The framework does not use any remaining interrupt objects and does not call their callback functions.

A KMDF driver is not required to connect and disconnect interrupts. The framework automatically connects and disconnects interrupts for the driver as part of entering or leaving the D0 state. Drivers are required only to create interrupt objects and provide callbacks to enable, disable, and service interrupts.

The Pcidrv sample creates an interrupt object in the NICAllocateSoftwareResources function, which is called by the driver's EvtDriverDeviceAdd callback. Listing 16-3 shows how the Pcidrv sample creates its interrupt object. This code appears in the Pcidrv\sys\hw\nic_init.c file.

Listing 16-3: Creating an interrupt object

image from book
 WDF_INTERRUPT_CONFIG_INIT(&interruptConfig,                           NICEvtInterruptIsr,                           NICEvtInterruptDpc); interruptConfig.EvtInterruptEnable = NICEvtInterruptEnable; interruptConfig.EvtInterruptDisable = NICEvtInterruptDisable; status = WdfInterruptCreate(FdoData->WdfDevice,                           &interruptConfig,                           WDF_NO_OBJECT_ATTRIBUTES,                           &FdoData->WdfInterrupt); if (!NT_SUCCESS (status)) {     return status; } 
image from book

The Pcidrv sample initializes the interrupt configuration structure by specifying pointers to the EvtInterruptIsr and EvtInterruptDpc callbacks, which are named NICEvtInterruptIsr and NICEvtInterruptDpc, respectively. The driver also sets pointers to EvtInterruptEnable and EvtInterruptDisable callbacks named NICEvtInterruptEnable and NICEvtInterruptDisable, respectively. Then the driver calls WdfInterruptCreate, passing a pointer to the interrupt configuration structure, the constant WDF_NO_ATTRIBUTES and a pointer to a location to receive the handle to the interrupt object. The driver stores the handle in the context area of its device object.

How to Enable and Disable Interrupts

If your device can generate an interrupt, the driver must enable and disable the interrupts in the hardware. Most drivers should implement EvtInterruptEnable and EvtInterruptDisable to enable and disable interrupts. The framework invokes EvtInterruptEnable and EvtInterruptDisable at DIRQL while holding the interrupt spin lock and calls each of them once for each interrupt that the device can generate. These two callbacks provide the safest approach and minimize the risk of error.

However, for a few drivers, enabling and disabling each interrupt separately is inconvenient because of the design of the device. If your device has such a design, you can instead perform these tasks in EvtDeviceD0EntryPostInterruptsEnabled and EvtDeviceD0ExitPreInterruptsDisabled. These functions run at PASSIVE_LEVEL, so the driver must protect its access to the device hardware by acquiring an interrupt spin lock and performing any other synchronization that the design of the device requires.

The framework calls the EvtInterruptEnable callback at the following times:

  • During a device transition to D0 after EvtDeviceD0Entry has returned.

  • In response to the driver's call to WdfInterruptEnable.

The EvtInterruptEnable callback runs at DIRQL, so it should quickly enable the interrupt and return. If the driver requires additional processing after enabling its interrupt, it should set an EvtDeviceD0EntryPostInterruptsEnabled callback, which the framework calls at PASSIVE_LEVEL.

EvtInterruptEnable is called with two parameters: a handle to the interrupt object and a handle to the associated device object. If the driver has stored information about its device registers in the device object context area, the driver can use the device object handle to call the accessor function for the context area. With the returned context area pointer, the driver can access the hardware registers as required to enable the interrupt.

The EvtInterruptDisable callback disables interrupts for its device. The framework calls this function whenever the driver calls WdfInterruptDisable and during a device transition out of the D0 state, but before it calls EvtDeviceD0Exit. EvtInterruptDisable is called at DIRQL for the device and with the interrupt spin lock held; therefore, it should quickly disable the interrupt and return. If the driver requires additional processing before disabling its interrupt, it should set an EvtDeviceD0ExitPreInterruptsDisabled callback, which the framework calls at PASSIVE_LEVEL.

The EvtInterruptDisable callback is passed the same two parameters as the EvtInterruptEnable callback and proceeds to undo the actions that were performed in that callback.

Listing 16-4 is a slightly modified version of the code that enables interrupts for the Pcidrv sample in Isrdpc.c. The sample driver uses the NICEnableInterrupt macro to set the device registers. Listing 16-4 includes the relevant statements from the expanded macro instead of the call to the macro itself.

Listing 16-4: Enabling interrupts

image from book
 NTSTATUS NICEvtInterruptEnable(     IN WDFINTERRUPT Interrupt,     IN WDFDEVICE AssociatedDevice     ) {     PFDO_DATA           fdoData;     fdoData = FdoGetData(AssociatedDevice);     fdoData->CSRAddress->ScbCommandHigh = 0;     return STATUS_SUCCESS; } 
image from book

The driver uses the device object handle to get a pointer to the device object context area where it has stored information about the device registers. It then sets the PCI device register to enable interrupts.

The EvtInterruptDisable callback in the same file is similar, as Listing 16-5 shows. This listing includes the relevant statements from the expanded NICDisableInterrupt macro.

Listing 16-5: Disabling interrupts

image from book
 NTSTATUS NICEvtInterruptDisable(     IN WDFINTERRUPT Interrupt,     IN WDFDEVICE AssociatedDevice     ) {     PFDO_DATA fdoData;     fdoData = FdoGetData(AssociatedDevice);     fdoData->CSRAddress->ScbCommandHigh = SCB_INT_MASK;     return STATUS_SUCCESS; } 
image from book

In the Pcidrv sample, the EvtInterruptDisable callback sets the PCI device register to disable interrupts.

Post-Interrupt Enable and Pre-Interrupt Disable Processing

Some devices cause interrupt storms if they are initialized after their interrupts are enabled. The driver for such a device must therefore be able to perform initialization after the device enters D0 but before its interrupt is enabled. Other devices, however, cannot be initialized until after the interrupt is enabled. To enable correct operation of both types of devices, the framework supplies post-interrupt-enable and pre-interrupt-disable events for which drivers can register.

When powering up a device, the framework invokes a driver's callbacks in the following order:

  1. EvtDeviceD0Entry

  2. EvtInterruptEnable

  3. EvtDeviceD0EntryPostInterruptsEnabled

The framework calls EvtDeviceD0Entry first at IRQL PASSIVE_LEVEL. Drivers that must initialize their devices before the interrupt is connected-for example, to prevent interrupt storms-should do so in this callback. Next, the framework calls EvtInterruptEnable. Drivers should enable their interrupts and do little or nothing else in this function because it is called at DIRQL. Finally, the framework calls EvtDeviceD0EntryPostInterruptsEnabled at PASSIVE_LEVEL. Drivers that must initialize their devices after the interrupt is connected should do so in this callback.

At power-down, the framework calls the corresponding functions in the opposite order:

  1. EvtDeviceD0ExitPreInterruptsDisabled

  2. EvtInterruptDisable

  3. EvtDeviceD0Exit

To undo work done by EvtDeviceD0EntryPostInterruptsEnabled, a driver registers an EvtDeviceD0ExitPreInterruptsDisabled function. Like the post-enable function, the pre-disable function does work at PASSIVE_LEVEL in preparation for disabling the interrupt.

A driver registers the post-interrupt enable and pre-interrupt disable callbacks in the WDF_PNPPOWER_EVENT_CALLBACKS structure before creating the device object. The Pcidrv sample fills this structure in its EvtDriverDeviceAdd callback, as follows:

 pnpPowerCallbacks.EvtDeviceD0EntryPostInterruptsEnabled =         NICEvtDeviceD0EntryPostInterruptsEnabled; pnpPowerCallbacks.EvtDeviceD0ExitPreInterruptsDisabled =         NICEvtDeviceD0ExitPreInterruptsDisabled; 

In the current version of the Pcidrv sample, both of these functions are placeholders.

Interrupt Service Routines

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 or MSI. Internally, Windows keeps a list of the ISRs 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.

The framework intercepts the call from the operating system and calls the EvtInterruptIsr callback that the driver registered. The interrupt spin lock prevents additional interrupts at DIRQL or lower so that the EvtInterruptIsr callback can retrieve volatile, interrupt-specific data from the device.

The EvtInterruptIsr callback runs at DIRQL, so it 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.

  • Copy any volatile data from the device to a shared storage location, typically the interrupt object context area.

  • Queue a DPC to perform any work related to the interrupt.

The EvtInterruptIsr callback 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. The EvtInterruptIsr function runs on the same processor on which its device interrupted; in turn, its EvtInterruptDpc or EvtDpcFunc function runs on the same processor as the EvtInterruptIsr function that queued it.

To determine whether its device is interrupting, the driver must access the device hardware, so the driver must previously have stored a pointer to the hardware registers in a location that it can access at DIRQL from the EvtInterruptIsr callback. The context area of the interrupt object is a good choice because the EvtInterruptIsr callback receives a handle to the interrupt object from the framework. A driver could also use the context area of the device object. The device object is accessible at DIRQL by a call to the WdfInterruptGetDevice method, so the sample PCIDRV driver stores the pointer in the device object context area.

After the driver retrieves the hardware register pointer, it checks the hardware to find out whether its device is the source of the interrupt. If the interrupt did not come from the driver's device, the driver returns FALSE immediately. The EvtInterruptIsr function must not return FALSE if its device generated the interrupt. Such "unclaimed" interrupts can eventually hang or crash the system.

If the device generated the interrupt, the driver stops the device from interrupting and copies any volatile data to the context area of the interrupt object or to some other location that its EvtInterruptIsr and EvtInterruptDpc functions can share. It then calls WdfInterruptQueueDpcForIsr to queue the DPC and returns TRUE.

Important 

Your driver's interrupt service callback must be written so that it can share interrupt vectors or MSIs with another device. The function must not assume that it is called only for interrupts that its device generated. It should return TRUE only if its device actually did generate the interrupt. Returning TRUE under any other conditions can crash the system.

Listing 16-6 shows the Pcidrv sample's EvtInterruptIsr callback, which is defined in the Pcidrv\sys\hw\isrdpc.c file.

Listing 16-6: Servicing an interrupt in an EvtInterruptIsr callback

image from book
 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 re-enabled in NICEvtInterruptDpc.         NICDisableInterrupt(FdoData);         // Acknowledge the interrupt(s) and get status.         NIC_ACK_INTERRUPT(FdoData, IntStatus);         WdfInterruptQueueDpcForIsr(Interrupt);      }     return InterruptRecognized; } 
image from book

The Pcidrv sample's first step is to determine whether its device is interrupting. To do so, the driver 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 it 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.

For most drivers, checking whether device interrupts have been disabled is unnecessary. However, this driver defines the NIC_INTERRUPT_DISABLED and NIC_INTERRUPT_ACTIVE macros in the Nic_def.h header file. The macros 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 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.

However, if interrupts have not been disabled and an interrupt is currently active, the device must have generated the interrupt. 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, the driver queues the EvtInterruptDpc callback by calling WdfInterruptQueueDpcForIsr and then returns.

Deferred Processing for Interrupts

When the DPC runs, the framework calls the driver's EvtInterruptDpc callback. This function performs device-specific interrupt processing and reenables the interrupt for the device. The callback function runs at DISPATCH_LEVEL and therefore must neither attempt any operations that might cause a page fault nor wait on any dispatcher objects.

Listing 16-7 shows the Pcidrv sample's EvtInterruptDpc callback, also from Isrdpc.c.

Listing 16-7: Deferred interrupt processing in an EvtInterruptDpc callback

image from book
 VOID NICEvtInterruptDpc(     IN WDFINTERRUPT WdfInterrupt,     IN WDFOBJECT    WdfDevice     ) {     PFDO_DATA fdoData = NULL;     fdoData = FdoGetData(WdfDevice);     . . . //Device-specific code omitted.     // Re-enable the interrupt.     // This driver is a port from WDM, so it uses     // WdfInterruptSynchronize. It could instead acquire     // the interrupt spin lock by calling WdfInterruptAcquireLock.     WdfInterruptSynchronize (WdfInterrupt,                              NICEnableInterrupt,                              fdoData);     return; } 
image from book

The Pcidrv driver's EvtInterruptDpc callback processes the results of the I/O operation and then reenables the interrupt. The driver must reenable the interrupt at DIRQL while holding the interrupt spin lock.

This sample driver was ported from WDM, so it uses the WdfInterruptSynchronize method. The framework includes this method primarily for compatibility with existing WDM drivers. WdfInterruptSynchronize takes a handle to the interrupt object, a pointer to an EvtInterruptSynchronize function to be run at DIRQL (that is, NICEnableInterrupt), and a pointer to driver-defined context data. WdfInterruptSynchronize calls the system to acquire the interrupt spin lock and then calls the EvtInterruptSynchronize function, passing the context pointer. When the EvtInterruptSynchronize function completes, the system releases the spin lock and WdfInterruptSynchronize returns.

A better way to synchronize processing at DIRQL is to acquire the interrupt spin lock directly, as the next section describes.

Synchronized Processing at DIRQL

To synchronize processing at DIRQL, a driver can call WdfInterruptAcquireLock to acquire the interrupt spin lock immediately before the code that requires synchronization and then call WdfInterruptReleaseLock immediately after the synchronized code. WdfInterruptAcquireLock raises IRQL on the current processor to DIRQL and acquires the interrupt spin lock. WdfInterruptReleaseLock releases the lock and lowers IRQL.

Listing 16-8 shows how the PLX9x5x driver uses this lock. This sample code is from the Sys\IsrDpc.c file.

Listing 16-8: Using the interrupt spin lock

image from book
 WdfInterruptAcquireLock(Interrupt); if ((devExt->IntCsr.bits.DmaChan0IntActive) && (devExt->Dma0Csr.bits.Done)) {     // If Dma0 channel 0 (write) is interrupting and     // the Done bit is set in the Dma0 CSR, a WRITE is complete.     // Clear the done bit and channel interrupting bit.     devExt->IntCsr.bits.DmaChan0IntActive = FALSE;     devExt->Dma0Csr.uchar = 0;     writeInterrupt = TRUE; } . . . //Additional device-specific code omitted WdfInterruptReleaseLock(Interrupt); 
image from book

Listing 16-7 shows code from the PLX9x5x driver's EvtInterruptDpc callback. The PLX device has two DMA channels: one channel for read, and one channel for write. These two channels can handle concurrent DMA transactions. Every time a DMA transaction completes, the device generates an interrupt. The driver's EvtInterruptIsr function determines which channel produced the interrupt, disables the interrupt on that channel, and queues the EvtInterruptDpc callback. Thus, it is possible that the DPC could be running for one channel while the ISR is servicing an interrupt on the other channel. The DPC acquires the interrupt lock to synchronize its access to the hardware registers with that of the ISR. The DPC checks the registers in the device hardware to determine why the device interrupted and then clears the registers to reenable the interrupt.




Developing Drivers with the Microsoft Windows Driver Foundation
Developing Drivers with the Windows Driver Foundation (Pro Developer)
ISBN: 0735623740
EAN: 2147483647
Year: 2007
Pages: 224

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net