- 9.2 Handling Interrupts
- 9.2.1 General — Handling an Interrupt
- 9.2.1.1 Handling Interrupts with the WDC Library
- 9.2.2 ISA/EISA and PCI Interrupts
- 9.2.2.1 Transfer Commands at Kernel Level (Acknowledging the Interrupt)
- 9.2.3 Improving the Interrupt Handling Rate on VxWorks
- 9.2.4 Interrupts in Windows CE
- 9.2.4.1 Improving Interrupt Latency in Windows CE
- Handling Active-Both Interrupts
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.
****************************************************************************************
NOTE |
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:
- When the user selects to enable interrupts on the device, a thread is created to handle incoming interrupts.
- The thread runs an infinite loop that waits for an interrupt to occur.
- When an interrupt occurs, WinDriver executes, in the kernel, any transfer commands prepared in advance by the user (see section 9.2.2.1) and when the control returns to the user mode, the driver’s interrupt handler routine is called.
- 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() [A.2.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.
9.2.1.1 Handling Interrupts with the WDC Library
The WinDriver Card (WDC) library [A.1] 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 A.2.43 — A.2.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.
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.
9.2.2.1 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() [A.2.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):
After defining the transfer commands, you can proceed to enable the interrupts.
The following code demonstrates how to use the WDC library [A.1] to enable the interrupts, using the transfer commands prepared above:
9.2.3 Improving the Interrupt Handling Rate on VxWorks
WinDriver for VxWorks (a.k.a. DriverBuilder) implements a call-back routine, which, if set by the user, is executed immediately when an interrupt is received, thus enabling you to speed up interrupt acknowledgment and processing. This routine is not needed on Windows 98/Me/NT/2000/XP/Server 2003, Linux and Solaris, since you can use the Kernel PlugIn feature to improve the interrupt handling rate on these platforms. See section 11 or http://www.jungo.com/kpi.html for more information about the Kernel PlugIn.
To use the windrvr_isr() routine:
Include the following declaration in your code:
int (__cdecl *windrvr_isr)(void);
Set windrvr_isr() to point to the interrupt handler routine that you wish to have performed immediately upon the arrival of an interrupt. For example:
9.2.4 Interrupts in 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:
Use Windows CE Plug-and-Play for Interrupt Mapping (PCI bus driver)
This is the recommended approach to interrupt mapping in 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.
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.
Specify the Mapped Interrupt Value
****************************************************************************************
NOTE |
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% KERNEL 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:
- IRQ0 : Timer Interrupt
- IRQ2 : Cascade interrupt for the second PIC
- IRQ6 : The floppy controller
- IRQ7 : LPT1 (because the PPSH does not use interrupts)
- IRQ9
- IRQ13 : The numeric coprocessor
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% KERNEL HAL directory) to include code, as shown below, that sets up a value for interrupt 7 in the interrupt mapping table:
SETUP_INTERRUPT_MAP(SYSINTR_FIRMWARE+7,7);
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);
9.2.4.1 Improving Interrupt Latency in Windows CE
You can reduce the interrupt latency in Windows CE for PCI devices by making slight changes in the registry and in your code:
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:
«WdIntEnh»=dword:0
to:
«WdIntEnh»=dword:1
If you exclude this line, or leave the value 0, the interrupt latency will not be reduced.
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.
When using the low-level WD_xxx API (described in the WinDriver PCI Low-Level API Reference ), call CEInterruptEnhance() immediately after calling InterruptEnable() .
****************************************************************************************
NOTE |
When using WinDriver’s WDC APIs [A.1] to handle the interrupts, you do not need to call CEInterruptEnhance() , since WDC_IntEnable() [A.2.43] automatically calls CEInterruptEnhance() . |
CEInterruptEnhance() receives two parameters:
hThread : A handle to the interrupt thread, as received from InterruptEnable() .
Handling Active-Both Interrupts
NoteВ В This topic applies only to Kernel-Mode Driver Framework (KMDF) version 1.13 and earlier.
Many devices have hardware registers that control interrupt generation and masking. Typically, KMDF and UMDF drivers for such devices use the framework’s built-in interrupt support.
However, a simple device on a System on a Chip (SoC) hardware platform might not have hardware registers for interrupts. As a result, a driver for such a device might not be able to control when the interrupt gets generated, or be able to mask the interrupt in hardware. If the device interrupts immediately upon connection, and the driver is using the framework’s interrupt support, it is possible for the interrupt to fire before the framework has fully initialized the framework interrupt object. As a result, a KMDF driver must call WDM routines directly to connect and disconnect interrupts. Because a UMDF driver cannot call these methods, you cannot write a UMDF driver for such a device.
This topic describes how a KMDF driver might handle interrupts for such a device.
On SoC hardware platforms, active-both interrupts are typically used for very simple devices like hardware push-buttons. When a user presses a push-button, the interrupt signal line from the device transitions from low to high, or from high to low. When the user releases the push-button, the interrupt line transitions in the opposite direction. A GPIO pin configured as an active-both interrupt input generates interrupts on both low-to-high and high-to-low transitions, resulting in the system calling the peripheral device driver’s interrupt service routine (ISR) in both cases. However, the driver does not receive an indication whether the transition is low-to-high or high-to-low.
To distinguish between low-to-high and high-to-low transitions, the driver must track the state of each interrupt. To do so, your driver might maintain a Boolean interrupt state value that is FALSE when interrupt line state is low and TRUE when line state is high.
Consider an example in which the line state defaults to low when the system starts. The driver initializes the state value to FALSE in its EvtDevicePrepareHardware callback function. Then each time the driver’s ISR is called, signaling a change in state, the driver inverts the state value in its ISR.
If the line state is high when the system starts, the interrupt fires immediately after it is enabled. Because the driver calls the IoConnectInterruptEx routine directly, instead of calling WdfInterruptCreate, it is ensured of receiving a possible immediate interrupt.
This solution requires that the GPIO controller support active-both interrupts in hardware, or that the driver for the GPIO controller emulate active-both interrupts in software. For information about emulating active-both interrupts, see the description of the EmulateActiveBoth member of the CONTROLLER_ATTRIBUTE_FLAGS structure.
The following code example shows how a KMDF driver for a peripheral device can track interrupt polarity.
In the preceding code example, the driver’s EvtDriverDeviceAdd callback function configures the device context and then calls IoInitializeDpcRequest to register a DpcForIsr routine.
The driver’s InterruptService routine inverts the interrupt state value and then calls IoRequestDpc to queue the DPC.
In its EvtDevicePrepareHardware callback function, the driver initializes the state value to FALSE and then calls IoConnectInterruptEx. In its EvtDeviceReleaseHardware callback function, the driver calls IoDisconnectInterruptEx to unregister its ISR.