next up previous contents
Next: 9.3 Byte Ordering Up: 9. Advanced Issues Previous: 9.1 Performing Direct Memory   Contents


9.2 Handling Interrupts

WinDriver provides you with API, DriverWizard code generation, and samples, in order 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 the 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, 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 describe how to use WinDriver's API to handle PCI, PCMCIA and ISA interrupts. Read these sections in order to understand the sample and generated DriverWizard interrupt code or to write your own interrupt handler.

  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 interrupt routines. To find out how to handle interrupts from the Kernel PlugIn, please refer to section 11.6.5 of the manual.

9.2.1 General - Handling an Interrupt

The interrupt handling sequence using WinDriver is as follows:

  1. When the user selects to enable interrupts on the device, a thread is created to handle incoming interrupts.
  2. The 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 prepared in advance by the user (see section and when the control returns to the user mode, the driver's interrupt handler routine is called.
  4. When the interrupt handler code returns, the wait loop continues.

The low-level WinDriver WD_IntWait() function (described in the WinDriver PCI Low-Level API Reference), which is called from the WinDriver interrupt enable functions (WDC_IntEnable() [B.3.43] / InterruptEnable()) in order to wait for 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.

Since your interrupt thread runs in the user mode, you may call any Windows API from this thread, including file handling and GDI functions. Handling Interrupts with the WDC Library

The WinDriver Card (WDC) library [B.2] provides convenience wrappers to the basic WinDriver PCI/PCMCIA/ISA API, including simplified interrupt handling functions - WDC_IntEnable(), WDC_IntDisable() and WDC_IntIsEnabled(). For a detailed description of these functions, please refer to sections B.3.43 - B.3.45 of the manual.

The sample code below demonstrates how you can use the WDC interrupt APIs to implement a simple interrupt handler. For complete interrupt handler source code that uses these functions, refer to the WinDriver pci_diag (WinDriver/samples/pci_diag/), pcmcia_diag (WinDriver/samples/pcmcia_diag/) and PLX (WinDriver/plx/) samples and to the generated DriverWizard PCI/PCMCIA/ISA code.

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(), which calls WD_IntDisable() */

9.2.2 ISA/EISA and PCI Interrupts

Generally, ISA/EISA interrupts are edge triggered, in contrast to PCI interrupts, which are level sensitive. This has many implications on how the interrupt handler routine is written.

Edge-triggered interrupts
are generated once, when the physical interrupt signal goes from low to high. Therefore, exactly one interrupt is generated. As a result, the operating system calls the WinDriver kernel interrupt handler, which releases the thread waiting for the interrupt (i.e. the thread that called WD_IntWait()). No special action is required in order to acknowledge this type of interrupt.
Level-sensitive 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 WinDriver kernel interrupt handler again, causing the PC to hang! To prevent such a situation, the interrupt must be acknowledged by the WinDriver kernel interrupt handler. Transfer Commands at Kernel Level (Acknowledging the Interrupt)

Usually, interrupt handlers for level-sensitive interrupts, such as PCI interrupts, need to perform transfer commands - i.e. write to and/or read from the device - at the kernel in order to lower the interrupt level (acknowledge the interrupt). The transfer commands typically write to run-time registers on the card, thus clearing the hardware interrupt. However, the exact transfer commands to be performed in order to acknowledge the interrupts are hardware-specific. Therefore, when using WinDriver to handle level-sensitive interrupts, you must inform WinDriver in advance what transfer commands to perform in the kernel when an interrupt is received.

To pass transfer commands to be performed in the WinDriver kernel interrupt handler (before WD_IntWait() returns), you need to prepare an array of commands (defined using a WD_TRANSFER structure), and pass the array to WinDriver when enabling the interrupts using WDC_IntEnable() [B.3.43] (or the lower-level InterruptEnable() or WD_IntEnable() functions - see the WinDriver PCI Low-Level API Reference).

The interrupt enable functions also enable you to define an interrupt mask in order to verify the source of the interrupt. Note that interrupt mask commands must be set directly after a read transfer command in the transfer commands array. If you set an interrupt mask command (trans[i].cmdTrans = CMD_MASK), upon the arrival of an interrupt in the kernel, WinDriver will mask the value read from the card in the preceding read command 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 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 interrupt handler routine will not be invoked.

For example, suppose that 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 (dwAddr), to be intrMask, and that in order to clear the interrupt you need to write 0 to the INTCSR. In this case, you could use the following code to define an array of transfer commands that first reads the INTCSR register, saves its value, then masks it to verify the source of the interrupt and writes 0 to the INTCSR to acknowledge the interrupt (all commands in the example are performed in modes of DWORD):

WD_TRANSFER trans[3]; /* Array of 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].dwPort = dwAddr; /* Assume dwAddr holds the address of 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].dwPort = dwAddr; /* Assume dwAddr 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.

The following code demonstrates how to use the WDC library [B.2] 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 explanation below
    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.3 Interrupts on Windows CE

Windows CE uses a logical interrupt scheme rather than the physical interrupt number. It maintains an internal kernel table that maps the physical IRQ number to the logical IRQ number. Device drivers are expected to use the logical interrupt number when requesting interrupts from Windows CE. In this context, there are three approaches to interrupt mapping:

  1. Use Windows CE Plug-and-Play for Interrupt Mapping (PCI bus driver)
    This is the recommended approach to interrupt mapping on Windows CE. Register the device with the PCI bus driver. Following this method will cause the PCI bus driver to perform the IRQ mapping and direct WinDriver to use it.

    Refer to section 5.3 for an example how to register your device with the PCI bus driver.

  2. Use the Platform Interrupt Mapping (On X86 or ARM)
    In most of the x86 or MIPS platforms, all the physical interrupts except for some reserved ones are statically mapped using this simple mapping: logical interrupt = SYSINTR_FIRMWARE + physical interrupt

    When the device is not registered with Windows CE Plug-and-Play, WinDriver will follow this mapping.

  3. Specify the Mapped Interrupt Value

      This option can only be performed by the Platform Builder.

    Provide the device's mapped logical interrupt value. If unavailable, statically map the physical IRQ to a logical interrupt. Then call WD_CardRegister() with the logical interrupt and with the INTERRUPT_CE_INT_ID flag set. The static interrupt map is in the file CFWPC.C (located in the %_TARGETPLATROOT%$\backslash$KERNEL$\backslash$HAL directory).

    You will then need to rebuild the Windows CE image NK.BIN and download the new executable onto your target platform.

    Static mapping is helpful also in the case of using reserved interrupt mapping. Suppose your platform static mapping is:

    An attempt to initialize and use any of these interrupts will fail. However, you may want to use one or more of these interrupts on occasion, such as when you do not want to use the PPSH, but you want to reclaim the parallel port for some other purpose. To solve this problem, simply modify the file CFWPC.C (located in the %_TARGETPLATROOT%$\backslash$KERNEL$\backslash$HAL directory) to include code, as shown below, that sets up a value for interrupt 7 in the interrupt mapping table:

    Suppose you have a PCI card which was assigned IRQ9. Since Windows CE does not map this interrupt by default, you will not be able to receive interrupts from this card. In this case, you will need to insert a similar entry for IRQ9:
    SETUP_INTERRUPT_MAP(SYSINTR_FIRMWARE+9,9); Improving Interrupt Latency on Windows CE

You can reduce the interrupt latency on Windows CE for PCI devices by making slight changes in the registry and in your code:

  1. When developing your driver on Windows CE platforms, you must first register your device to work with WinDriver, as explained in section 5.3.
    Change the last value in the registry from:

    If you exclude this line, or leave the value 0, the interrupt latency will not be reduced.

  2. Add WD_CE_ENHANCED_INTR to your Preprocessor Definitions of your project and recompile your entire project. When using Microsoft eMbedded Visual C++, the Preprocessor Definitions are found under Project Settings.

  3. When using the low-level WD_xxx API (described in the WinDriver PCI Low-Level API Reference), call CEInterruptEnhance() immediately after calling InterruptEnable().

      When using WinDriver's WDC APIs [B.2] to handle the interrupts, you do not need to call CEInterruptEnhance(), since WDC_IntEnable() [B.3.43] automatically calls CEInterruptEnhance().

    CEInterruptEnhance() receives two parameters:

      void CEInterruptEnhance(HANDLE hThread, DWORD dwSysintr);

next up previous contents
Next: 9.3 Byte Ordering Up: 9. Advanced Issues Previous: 9.1 Performing Direct Memory   Contents