- Creating an Interrupt Object
- Supporting Message-signaled Interrupts
- 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
Creating an Interrupt Object
A Windows Driver Frameworks (WDF) driver that handles a device’s hardware interrupts must create a framework interrupt object for each interrupt that each device can support. In framework versions 1.11 and later running on Windows 8 or later versions of the operating system, Kernel-Mode Driver Framework (KMDF) and User-Mode Driver Framework (UMDF) drivers can create interrupt objects requiring passive-level handling. Unless you are writing a driver for a System on a Chip (SoC) platform, however, your driver should use DIRQL interrupt objects.
A driver typically creates framework interrupt objects in its EvtDriverDeviceAdd callback function. A driver can also create interrupt objects from its EvtDevicePrepareHardware callback function.
The framework calls the driver’s EvtDriverDeviceAdd callback function before the Plug and Play (PnP) manager has assigned system resources, such as interrupt vectors, to the device. After the PnP manager assigns resources, the framework stores interrupt resources in the device’s interrupt object. (Drivers that do not support Plug and Play cannot use interrupt objects.)
To create a framework interrupt object, your driver must initialize a WDF_INTERRUPT_CONFIG structure and pass it to the WdfInterruptCreate method.
UMDF supports the following types of interrupts:
- Level-triggered (shared or exclusive)
- Edge-triggered (exclusive only)
- MSI (exclusive by definition)
Note UMDF does not support shared edge-triggered interrupts.
Starting in UMDF version 2.15, UMDF supports interrupts for simple devices like hardware push-buttons, usually backed by GPIO pins, that you cannot enable or disable explicitly using hardware registers. To support such devices, a UMDF driver must use exclusive edge-triggered interrupts.
Starting in KMDF version 1.15, KMDF also supports interrupts for such devices, without the workaround described in Handling Active-Both Interrupts.
Also in WDF_INTERRUPT_CONFIG, your driver supplies pointers to the following driver-supplied event callback functions:
EvtInterruptIsr
Interrupt service routine (ISR) for the interrupt.
EvtInterruptDpc
Deferred procedure call (DPC) for the interrupt.
For drivers using framework version 1.11 or later on Windows 8 or later versions of the operating system, the driver can explicitly set the parent of a framework interrupt object (DIRQL or passive) to either a framework device object or a framework queue object. If the driver specifies a parent, the driver must set the AutomaticSerialization member of the interrupt object’s WDF_INTERRUPT_CONFIG structure to TRUE. (Recall that if AutomaticSerialization is TRUE, the framework synchronizes execution of the interrupt object’s EvtInterruptDpc or EvtInterruptWorkItem callback function with callback functions from other objects that are underneath the interrupt’s parent object.)
As an example, a driver might specify a queue as parent of an interrupt to synchronize the queue’s callbacks with either the interrupt’s EvtInterruptDpc or EvtInterruptWorkItem callback. In this configuration, the framework deletes the queue object when it deletes the device object.
After calling WdfInterruptCreate, the driver can optionally call WdfInterruptSetPolicy or WdfInterruptSetExtendedPolicy to specify additional interrupt parameters. Typically the driver calls these methods from its EvtDriverDeviceAdd callback function.
The framework automatically deletes the interrupt before deleting the interrupt’s parent. Optionally, a driver can call WdfObjectDelete to delete the interrupt at an earlier time.
Supporting Message-signaled Interrupts
Message-signaled interrupts (MSIs) are supported starting with Windows Vista. To enable the operating system to support MSIs for your device, your driver’s INF file must set some values in the registry. For information about how to set these values, see Enabling Message-Signaled Interrupts in the Registry.
Your driver should create a framework interrupt object for each interrupt vector or MSI message that the device can support. If the PnP manager does not grant the device all of the interrupt resources that the device can support, the extra interrupt objects will not be used and their callback functions will not be called.
In Windows 7, the operating system does not support resource requests for more than 910 interrupt messages per device function. In Windows 8, the operating system does not support resource requests for more than 2048 interrupts per device function.
If the device driver exceeds this limit, the device might fail to start. To operate in a computer that contains many logical processors, the driver should not request more than one interrupt per processor.
A driver must tolerate, without failures, system rebalancing of interrupt resources in which the PnP manager assigns to the device any set of alternative interrupt resources from the resource requirements list. For example, the device might be assigned a smaller number of message interrupts than the driver requested. In the worst case, the driver must be prepared to operate the device with just one line-based interrupt.
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() .