9.2. Handling Interrupts

WinDriver provides you with API, DriverWizard code generation, and samples, to simplify the task of handling interrupts from your driver.

If you are developing a driver for a device based on one of the enhanced-support WinDriver chipsets [7], we recommend that you use the custom WinDriver interrupt APIs for your specific chip in order to handle the interrupts, since these routines are implemented specifically for the target hardware.

For other chips, we recommend that you use DriverWizard to detect/define the relevant information regarding the device interrupt (such as the interrupt request (IRQ) number, its type and its shared state), define commands to be executed in the kernel when an interrupt occurs (if required), and then generate skeletal diagnostics code, which includes interrupt routines that demonstrate how to use WinDriver's API to handle your device's interrupts, based on the information that you defined in the wizard.

The following sections provide a general overview of PCI/ISA interrupt handling and explain how to handle interrupts using WinDriver's API. Use this information to understand the sample and generated DriverWizard interrupt code or to write your own interrupt handler.

9.2.1. Interrupt Handling — Overview

PCI and ISA hardware uses interrupts to signal the host.
There are two main methods of PCI interrupt handling:

  • Legacy Interrupts: The traditional interrupt handling, which uses a line-based mechanism. In this method, interrupts are signaled by using one or more external pins that are wired "out-of-band", i.e., separately from the main bus lines.

    Legacy interrupts are divided into two groups:

    • Level-sensitive interrupts: These interrupts are generated as long as the physical interrupt signal is high. If the interrupt signal is not lowered by the end of the interrupt handling in the kernel, the operating system will call the kernel interrupt handler repeatedly, causing the host platform to hang. To prevent such a situation, the interrupt must be acknowledged (cleared) by the kernel interrupt handler immediately when it is received.

      Legacy PCI interrupts are level sensitive.

    • Edge-triggered interrupts: These are interrupts that are generated once, when the physical interrupt signal goes from low to high. Therefore, exactly one interrupt is generated. No special action is required for acknowledging this type of interrupt.

      ISA/EISA interrupts are edge triggered.

  • MSI/MSI-X: Newer PCI bus technologies, available beginning with v2.2 of the PCI bus and in PCI Express, support Message-Signaled Interrupts (MSI). This method uses "in-band" messages instead of pins, and can target addresses in the host bridge. A PCI function can request up to 32 MSI messages.
    Note: MSI and MSI-X are edge triggered and do not require acknowledgment in the kernel.

    Among the advantages of MSIs:

    • MSIs can send data along with the interrupt message.
    • As opposed to legacy PCI interrupts, MSIs are not shared; i.e., an MSI that is assigned to a device is guaranteed to be unique within the system.

    Extended Message-Signaled Interrupts (MSI-X) are available beginning with version 3.0 of the PCI bus. This method provides an enhanced version of the MSI mechanism, which includes the following advantages:

    • Supports 2,048 messages instead of 32 messages supported by the standard MSI.
    • Supports independent message address and message data for each message.
    • Supports per-message masking.
    • Enables more flexibility when software allocates fewer vectors than hardware requests. The software can reuse the same MSI-X address and data in multiple MSI-X slots.

    The newer PCI buses, which support MSI/MSI-X, maintain software compatibility with the legacy line-based interrupts mechanism by emulating legacy interrupts through in-band mechanisms. These emulated interrupts are treated as legacy interrupts by the host operating system.

WinDriver supports legacy line-based interrupts, both edge triggered and level sensitive, on all supported operating systems: Windows, and Linux.

WinDriver also supports PCI MSI/MSI-X interrupts (when supported by the hardware) on Linux and Windows 7 and higher (earlier versions of Windows do not support MSI/MSI-X), as detailed in Section 9.2.7.

WinDriver provides a single set of APIs for handling both legacy and MSI/MSI-X interrupts, as described in this manual.

9.2.2. WinDriver Interrupt Handling Sequence

This section describes how to use WinDriver to handle interrupts from a user-mode application. Since interrupt handling is a performance-critical task, it is very likely that you may want to handle the interrupts directly in the kernel. WinDriver's Kernel PlugIn [11] enables you to implement kernel-mode interrupt routines.
To find out how to handle interrupts from the Kernel PlugIn, please refer to Section 11.6.5 of the manual.

The interrupt handling sequence using WinDriver is as follows:

  1. The user calls one of WinDriver's interrupt enable functions — WDC_IntEnable() [B.3.48] or the low-level InterruptEnable() or WD_IntEnable() functions, described in the WinDriver PCI Low-Level API Reference — to enable interrupts on the device.
    These functions receive an optional array of read/write transfer commands to be executed in the kernel when an interrupt occurs (see Step 3).


    • When using WinDriver to handle level-sensitive interrupts, you must set up transfer commands for acknowledging the interrupt, as explained in Section 9.2.6.
    • Memory allocated for the transfer commands must remain available until the interrupts are disabled .

    When WDC_IntEnable() [B.3.48] or the lower-level InterruptEnable() function is called, WinDriver spawns a thread for handling incoming interrupts.
    When using the low-level WD_IntEnable() function you need to spawn the thread yourself.

    WinDriver must be registered with the OS as the driver of the device before enabling interrupts. For Plug-and-Play hardware (PCI/PCI Express) on Windows platforms, this association is made by installing an INF file for the device [15.1]. If the INF file is not installed, the interrupt enable function() will fail with a WD_NO_DEVICE_OBJECT error [B.11].
  2. The interrupt thread runs an infinite loop that waits for an interrupt to occur.
  3. When an interrupt occurs, WinDriver executes, in the kernel, any transfer commands that were prepared in advance by the user and passed to WinDriver's interrupt-enable functions (see Section 9.2.6).
    When the control returns to the user mode, the driver's user-mode interrupt handler routine (as passed to WinDriver when enabling the interrupts with WDC_IntEnable() or InterruptEnable()) is called.
  4. When the user-mode interrupt handler returns, the wait loop continues.
  5. When the user no longer needs to handle interrupts, or before the user-mode application exits, the relevant WinDriver interrupt disable function should be called — WDC_IntDisable() [B.3.49] or the low-level InterruptDisable() or WD_IntDisable() functions, described in the WinDriver PCI Low-Level API Reference (depending on the function used to enable the interrupts).
  • The low-level WD_IntWait() WinDriver function (described in the WinDriver PCI Low-Level API Reference), which is used by the high-level interrupt enable functions to wait on interrupts from the device, puts the thread to sleep until an interrupt occurs. There is no CPU consumption while waiting for an interrupt. Once an interrupt occurs, it is first handled by the WinDriver kernel, then WD_IntWait() wakes up the interrupt handler thread and returns, as explained above.
  • Since your interrupt handler runs in the user mode, you may call any OS API from this function, including file-handling and GDI functions.

9.2.3. Registering IRQs for Non-Plug-and-Play Hardware on Windows 7 and Higher

On Windows 7 and higher, you may need to register an interrupt request (IRQ) with WinDriver before you can assign it to your non-Plug-and-Play device (e.g., your ISA card).
To register an IRQ with WinDriver on Windows, follow these steps:

  1. Open the Device Manager and select View --> Resources by type.
  2. Select a free IRQ from among those listed in the Interrupt request (IRQ) section.
  3. Register the selected IRQ with WinDriver:
    1. Back up the files in the WinDriver\redist directory.
    2. Edit windrvr1411.inf:
      1. Add the following line in the [DriverInstall.NT] section:
      2. Add a config_irq section (where "<IRQ>" signifies your selected IRQ number — e.g., 10):
    3. Reinstall WinDriver by running the following from a command-line prompt (where "<path to windrvr1411.inf>" is the path to your modified WinDriver INF file):
      wdreg -inf <path to windrvr1411.inf> install
    4. Verify that that the IRQ was successfully registered with WinDriver: Open the Device Manager and locate the WinDriver device. The device properties should have a Resources tab with the registered IRQ.

This procedure registers the IRQ with the virtual WinDriver device. It is recommended that you rename the windrvr1411 driver module, to avoid possible conflicts with other instances of WinDriver that may be running on the same machine [15.2].
If you rename your driver, replace references to windrvr1411.inf in the IRQ registration instructions above with the name of your renamed WinDriver INF file.

9.2.4. Determining the Interrupt Types Supported by the Hardware

When retrieving resources information for a Plug-and-Play device using WDC_PciGetDeviceInfo() [B.3.16] or the low-level WD_PciGetCardInfo() function (described in the WinDriver PCI Low-Level API Reference), the function returns information regarding the interrupt types supported by the hardware. This information is returned within the dwOptions field of the returned interrupt resource (pDeviceInfo->Card.Item[i].I.Int.dwOptions for the WDC functions pPciCard->Card.Item[i].I.Int.dwOptions for the low-level functions). The interrupt options bit-mask can contain a combination of any of the following interrupt type flags:

  • INTERRUPT_MESSAGE_X: Extended Message-Signaled Interrupts (MSI-X).*
  • INTERRUPT_MESSAGE: Message-Signaled Interrupts (MSI).*
  • INTERRUPT_LEVEL_SENSITIVE: Legacy level-sensitive interrupts.
  • INTERRUPT_LATCHED: Legacy edge-triggered interrupts. The value of this flag is zero and it is applicable only when no other interrupt flag is set.

The WDC_GET_INT_OPTIONS macro returns a WDC device's interrupt options bit-mask [B.4.9]. You can pass the returned bit-mask to the WDC_INT_IS_MSI macro to check whether the bit-mask contains the MSI or MSI-X flags [B.4.10].

  • The INTERRUPT_MESSAGE and INTERRUPT_MESSAGE_X flags are applicable only to PCI devices [9.2.7].
  • * The Windows APIs do not distinguish between MSI and MSI-X; therefore, on this OS the WinDriver functions set the INTERRUPT_MESSAGE flag for both MSI and MSI-X.

9.2.5. Determining the Interrupt Type Enabled for a PCI Card

When attempting to enable interrupts for a PCI card on Linux or Windows 7 and higher, WinDriver first tries to use MSI-X or MSI, if supported by the card. If this fails, WinDriver attempts to enable legacy level-sensitive interrupts.
WinDriver's interrupt-enable functions return information regarding the interrupt type that was enabled for the card. This information is returned within the dwEnabledIntType field of the WD_INTERRUPT structure that was passed to the function. When using the high-level WDC_IntEnable() function, the information is stored within the Int field of the WDC device structure referred to by the function's hDev parameter [B.3.48], and can be retrieved using the WDC_GET_ENABLED_INT_TYPE low-level WDC macro [B.4.8].

9.2.6. Setting Up Kernel-Mode Interrupt Transfer Commands

When handling interrupts you may find the need to perform high-priority tasks at the kernel-mode level immediately when an interrupt occurs. For example, when handling level-sensitive interrupts, such as legacy PCI interrupts [9.2.1], the interrupt line must be lowered (i.e., the interrupt must be acknowledged) in the kernel, otherwise the operating system will repeatedly call WinDriver's kernel interrupt handler, causing the host platform to hang. Acknowledgment of the interrupt is hardware-specific and typically involves writing or reading from specific runtime registers on the device.

WinDriver's interrupt enable functions receive an optional pointer to an array of WD_TRANSFER structures [B.7.10], which can be used to set up read/write transfer command from/to memory or I/O addresses on the device.
The WDC_IntEnable() function [B.3.48] accepts this pointer and the number of commands in the array as direct parameters (pTransCmds and dwNumCmds).
The low-level InterruptEnable() and WD_IntEnable() functions receive this information within the Cmd and dwCmds fields of the WD_INTERRUPT structure that is passed to them (see the WinDriver PCI Low-Level API Reference).

When you need to execute performance-critical transfers to/from your device upon receiving an interrupt — e.g., when handling level-sensitive interrupts — you should prepare an array of WD_TRANSFER structures that contain the required information regarding the read/write operations to perform in the kernel upon arrival of an interrupt, and pass this array to WinDriver's interrupt enable functions. As explained in Section 9.2.2, Step 3, WinDriver's kernel-mode interrupt handler will execute the transfer commands passed to it within the interrupt enable function for each interrupt that it handles, before returning the control to the user mode. Note: Memory allocated for the transfer commands must remain available until the interrupts are disabled . Interrupt Mask Commands

The interrupt transfer commands array that you pass to WinDriver can also contain an interrupt mask structure, which will be used to verify the source of the interrupt. This is done by setting the transfer structure's cmdTrans field, which defines the type of the transfer command, to CMD_MASK, and setting the relevant mask in the transfer structure's Data field [B.7.10]. Note that interrupt mask commands must be set directly after a read transfer command in the transfer commands array.

When WinDriver's kernel interrupt handler encounters a mask interrupt command, it masks the value that was read from the device in the preceding read transfer command in the array, with the mask set in the interrupt mask command. If the mask is successful, WinDriver will claim control of the interrupt, execute the rest of the transfer commands in the array, and invoke your user-mode interrupt handler routine when the control returns to the user mode. However, if the mask fails, WinDriver will reject control of the interrupt, the rest of the interrupt transfer commands will not be executed, and your user-mode interrupt handler routine will not be invoked. (Note: acceptance and rejection of the interrupt is relevant only when handling legacy interrupts; since MSI/MSI-X interrupts are not shared, WinDriver will always accept control of such interrupts.)

  • To correctly handle shared PCI interrupts, you must always include a mask command in your interrupt transfer commands array, and set up this mask to check whether the interrupt handler should claim ownership of the interrupt.
  • Ownership of the interrupt will be determined according to the result of this mask. If the mask fails, no other transfer commands from the transfer commands array will be executed — including commands that preceded the mask command in the array. If the mask succeeds, WinDriver will proceed to perform any commands that precede the first mask command (and its related read command) in the transfer commands array, and then any commands that follow the mask command in the array.

  • To gain more flexibility and control over the interrupt handling, you can use WinDriver's Kernel PlugIn feature, which enables you to write your own kernel-mode interrupt handler routines, as explained in Section 11.6.5 of the manual. Sample WinDriver Transfer Commands Code

This section provides sample code for setting up interrupt transfer commands using the WinDriver Card (WDC) library API [B.2].
The sample code is provided for the following scenario: Assume you have a PCI card that generates level-sensitive interrupts. When an interrupt occurs you expect the value of your card's interrupt command-status register (INTCSR), which is mapped to an I/O port address (pAddr), to be intrMask.
In order to clear and acknowledge the interrupt you need to write 0 to the INTCSR.

The code below demonstrates how to define an array of transfer commands that instructs WinDriver's kernel-mode interrupt handler to do the following:

  1. Read your card's INTCSR register and save its value.
  2. Mask the read INTCSR value against the given mask (intrMask) to verify the source of the interrupt.
  3. If the mask was successful, write 0 to the INTCSR to acknowledge the interrupt.

Note: all commands in the example are performed in modes of DWORD.

WD_TRANSFER trans[3]; /* Array of 3 WinDriver transfer command structures */

/* 1st command: Read a DWORD from the INTCSR I/O port */
trans[0].cmdTrans = RP_DWORD;
/* Set address of IO port to read from: */
trans[0].pPort = pAddr; /* Assume pAddr holds the address of the INTCSR */

/* 2nd command: Mask the interrupt to verify its source */
trans[1].cmdTrans = CMD_MASK;
trans[1].Data.Dword = intrMask; /* Assume intrMask holds your interrupt mask */

/* 3rd command: Write DWORD to the INTCSR I/O port.
   This command will only be executed if the value read from INTCSR in the
   1st command matches the interrupt mask set in the 2nd command. */
trans[2].cmdTrans = WP_DWORD;
/* Set the address of IO port to write to: */
trans[2].pPort = pAddr; /* Assume pAddr holds the address of INTCSR */
/* Set the data to write to the INTCSR IO port: */
trans[2].Data.Dword = 0;

After defining the transfer commands, you can proceed to enable the interrupts.
Note that memory allocated for the transfer commands must remain available until the interrupts are disabled , as explained above.
The following code demonstrates how to use the WDC_IntEnable() function to enable the interrupts using the transfer commands prepared above:

/* Enable the interrupts:
   hDev: WDC_DEVICE_HANDLE received from a previous call to WDC_PciDeviceOpen().
   INTERRUPT_CMD_COPY: Used to save the read data - see WDC_IntEnable().
   interrupt_handler: Your user-mode interrupt handler routine.
   pData: The data to pass to the interrupt handler routine. */
WDC_IntEnable(hDev, &trans, 3, INTERRUPT_CMD_COPY, interrupt_handler,
    pData, FALSE);

9.2.7. WinDriver MSI/MSI-X Interrupt Handling

As indicated in Section 9.2.1, WinDriver supports PCI Message-Signaled Interrupts (MSI) and Extended Message-Signaled Interrupts (MSI-X) on Linux and Windows 7 and higher (earlier versions of Windows do not support MSI/MSI-X).

The same APIs are used for handling both legacy and MSI/MSI-X interrupts, including APIs for retrieving the interrupt types supported by your hardware [9.2.4] and the interrupt type that was enabled for it [9.2.5].

When enabling interrupts for a PCI device on an OS that supports MSI/MSIx, WinDriver first tries to enable MSI-X or MSI — if supported by the device — and if this fails, it attempts to enable legacy level-sensitive interrupts.

On Windows, enabling MSI or MSIx interrupts requires that a relevant INF file first be installed for the device, as explained in Section
On Linux, you can specify the types of PCI interrupts that may be enabled for your device, via the dwOptions parameter of the WDC_IntEnable() function [B.3.48] or of the low-level InterruptEnable() function (described in the WinDriver PCI Low-Level API Reference) — in which case WinDriver will only attempt to enable interrupts of the specified types (provided they are supported by the device).

WinDriver's kernel-mode interrupt handler sets the interrupt message data in the dwLastMessage field of the WD_INTERRUPT structure that was passed to the interrupt enable/wait function. If you pass the same interrupt structure as part of the data to your user-mode interrupt handler routine, as demonstrated in the sample and generated DriverWizard interrupt code, you will be able to access this information from your interrupt handler. When using a Kernel PlugIn driver [11], the last message data is passed to your kernel-mode KP_IntAtDpcMSI [B.8.11] handler; on Windows 7 and higher, it is also passed to KP_IntAtIrqlMSI [B.8.10].
You can use the low-level WDC_GET_ENABLED_INT_LAST_MSG macro to retrieve the last message data for a given WDC device [B.4.11]. Windows MSI/MSI-X Device INF Files

The information in this section is relevant only when working on Windows.

To successfully handle PCI interrupts with WinDriver on Windows, you must first install an INF file that registers your PCI card to work with WinDriver's kernel driver, as explained in Section 15.1.

To use MSI/MSI-X on Windows, the card's INF file must contain specific [Install.NT.HW] MSI information, as demonstrated below:

AddReg = Install.NT.HW.AddReg

HKR, "Interrupt Management", 0x00000010
HKR, "Interrupt Management\MessageSignaledInterruptProperties", 0x00000010
HKR, "Interrupt Management\MessageSignaledInterruptProperties", MSISupported, \
    0x10001, 1

Therefore, to use MSI/MSI-X on Windows 7 and higher with WinDriver — provided your hardware supports MSI/MSI-X — you need to install an appropriate INF file.

When using DriverWizard on Windows 7 and higher to generate an INF file for a PCI device that supports MSI/MSI-X, the INF generation dialogue allows you to select to generate an INF file that supports MSI/MSI-X (see Section 4.2, Step 3).

In addition, the WinDriver sample code for the Xilinx Bus Master DMA (BMD) design, which demonstrates MSI handling, includes a sample MSI INF file for this design — WinDriver/xilinx/bmd_design/xilinx_bmd.inf.

If your card's INF file does not include MSI/MSI-X information, as detailed above, WinDriver will attempt to handle your card's interrupts using the legacy level-sensitive interrupt handling method, even if your hardware supports MSI/MSI-X.

9.2.8. Sample User-Mode WinDriver Interrupt Handling Code

The sample code below demonstrates how you can use the WDC library's [B.2] interrupt APIs (described in Sections B.3.48–B.3.50 of the manual) to implement a simple user-mode interrupt handler.
For complete interrupt handler source code that uses the WDC interrupt functions, refer, for example, to the WinDriver pci_diag (WinDriver/samples/pci_diag) and PLX (WinDriver/plx) samples, and to the generated DriverWizard PCI/ISA code. For a sample of MSI interrupt handling, using the same APIs, refer to the Xilinx Bus Master DMA (BMD) design sample (WinDriver/xilinx/bmd_design), or to the code generated by DriverWizard for PCI hardware that supports MSI/MSI-X on the supported operating systems (Linux or Windows 7 and higher).

  • The following sample code demonstrates interrupt handling for an edge-triggered ISA card. The code does not set up any kernel-mode interrupt transfer commands [9.2.6], which is acceptable in the case of edge-triggered or MSI/MSI-X interrupts [9.2.1]. Note that when using WinDriver to handle level-sensitive interrupts from the user mode, you must set up transfer commands for acknowledging the interrupt in the kernel, as explained above and as demonstrated in Section 9.2.6.
  • As mentioned above [9.2.7], WinDriver provides a single set of APIs for handling both legacy and MSI/MSI-X interrupts. You can therefore also use the following code to handle MSI/MSI-X PCI interrupts (if supported by your hardware), on Linux or Windows 7 and higher, by simply replacing the use of WDC_IsaDeviceOpen() in the sample with WDC_PciDeviceOpen() [B.3.17].
VOID DLLCALLCONV interrupt_handler (PVOID pData)

    /* Implement your interrupt handler routine here */

    printf("Got interrupt %d\n", pDev->Int.dwCounter);


int main()
    DWORD dwStatus;

    hDev = WDC_IsaDeviceOpen(...);
    /* Enable interrupts. This sample passes the WDC device handle as the data
        for the interrupt handler routine */
    dwStatus = WDC_IntEnable(hDev, NULL, 0, 0,
        interrupt_handler, (PVOID)hDev, FALSE);
    /* WDC_IntEnable() allocates and initializes the required WD_INTERRUPT
        structure, stores it in the WDC_DEVICE structure, then calls
        InterruptEnable(), which calls WD_IntEnable() and creates an interrupt
        handler thread. */
    if (WD_STATUS_SUCCESS != dwStatus)
       printf ("Failed enabling interrupt. Error: 0x%x - %s\n",
           dwStatus, Stat2Str(dwStatus));
        printf("Press Enter to uninstall interrupt\n");
        fgets(line, sizeof(line), stdin);

        /* WDC_IntDisable() calls InterruptDisable();
           InterruptDisable() calls WD_IntDisable(). */


9.2.9. Shared Interrupts via IPC

A method of acknowledging interrupts from more than one process is by using the Shared Interrupts via IPC API provided by WinDriver. This mechanism allows creating a special IPC "process" that sends IPC messages to other processes that are registered to WinDriver IPC. You can use the pci_diag sample code to further learn and test about this ability.

This method requires having a license that includes IPC support. Enabling Shared Interrupts:

A recommended method would be: In each process that you'd like to enable the Shared Interrupts in:
  1. Register the process to IPC using WDS_IpcRegister() [C.2.2].
  2. Write your own callback function that is relevant to what you wish to occur when a Shared Interrupt occurs. Send a pointer to that function as an argument to WDS_SharedIntEnable() [C.2.9].
You can write a different callback for each process to acheive a different handling of the interrupt in different contexts. Disabling Shared Interrupts:

You can either disable Shared Interrupts locally (for the current process only) using WDS_SharedIntDisableLocal() [C.2.10] or disable Shared Interrupts for all processes by using WDS_SharedIntDisableGlobal() [C.2.11].