Technical Document #75

Technical Document #75
How do I handle PCI Interrupts from a user-mode WinDriver application?

First, locate the slot to which your card is connected, using WD_PciScanCards(). Then get the card's information by calling WD_PciGetCardInfo(). This information includes the IRQ chosen for the card by the Plug and Play system. Now call WD_CardRegister() to install the interrupt.

Since PCI interrupts are level sensitive, you should verify that the INTERRUPT_LEVEL_SENSITIVE flag is set in cardReg.Card.Item[i].I.Int.dwOptions — where 'i' is the index number of the Interrupt item in the Item array. You would normally not need to set this flag explicitly in your code, since WD_PciGetCardInfo() automatically sets this flag for PCI cards and you can normaly pass the same WD_CARD structure, which was returned from WD_PciGetCardInfo(), to WD_CardRegister() (in cardReg.Card).

Level sensitive interrupts must be acknowledged (i.e., cleared) in the kernel immediately when they are received. To enable this acknowledgment of the interrupts, call InterruptEnable() (/ InterruptThreadEnable() — in versions 4.30–5.22), or the lower level WD_IntEnable() function (which is called by InterruptEnable() (/ InterruptThreadEnable())), passing to it a WD_INTERRUPT structure that holds the interrupt handle returned by the previous call to WD_CardRegister() and the relevant information for clearing the interrupt in the kernel. You should set up this information (i.e., the location of the interrupt status register that needs to be written to/read, the acknowledgment command to be performed — of type enum WD_TRANSFER_COMMAND, defined in windrvr.h — and a buffer with the data to be written\read to\from the register) in the array of WD_TRANSFER structures, which is pointed at by the Cmd member of the WD_INTERRUPT structure. Please note that the information for acknowledging and clearing the interrupt is hardware specific.

NOTE: You can use the DriverWizard to define the relevant register/s for the interrupt acknowledgment (from the "Registers" tab select "New" and define the register) and then assign the register/s to the interrupt (in the 'Interrupt" tab), before generating the code. (Beginning with version 5.2.1 you can assign more than one register to the interrupt.) The generated code would then include transfer commands for acknowledging the interrupt, which comply with the information you have defined in your DriverWizard project.

After passing the interrupt acknowledgment information to WinDriver, you need to physically enable the interrupts. You can do this from your code by writing the relevant information to the interrupt register (this information is also hardware specific).

InterruptEnable() (/ InterruptThreadEnable() — in versions 4.30–5.22) enables the interrupts by calling WD_IntEnable(), and then creates and runs an interrupt handler thread. The interrupt thread calls WD_IntWait() to wait on the interrupt and then activates your interrupt handler routine when the interrupt occurs and WD_IntWait() returns. You can refer to the WinDriver\src\windrvr_int_thread.c file to view the implementation of InterruptEnable() (For version 4.3.0–5.22, refer to the implementation of InterruptThreadEnable() in WinDriver\include\windrvr_int_thread.h). (If you select to use the lower-level WD_IntEnable() and WD_IntWait() functions directly from your code, instead of using the InterruptEnable() (/ InterruptThreadEnable) convenience function, you will need to spawn the thread that calls WD_IntWait() yourself.) When an interrupt occurs, the interrupt acknowledgment commands that you have set up in the WD_INTERRUPT structure, before enabling the interrupts, will be executed by WinDriver at the kernel level, before WD_IntWait() returns and your handler routine is activated.

At the end, remember to call InterruptDisable() (/ InterruptThreadDisable() — in versions 4.30–5.22), or the lower-level WD_IntDisable() function (depending on how you enabled the interrupts), in order to disable the interrupts

Please note that the int.Cmd array can include more than a single WD_TRANSFER command. You can set int.Cmd to point to an array of as many WD_TRANSFER structures as you wish. You can set, for example, the first WD_TRANSFER structure in the array with a read transfer command, in order to read from the relevant register in the kernel before acknowledging the interrupt. The read result will be stored in the Data union field of the WD_TRANSFER structure, and you will be able to access it later from your user mode interrupt handler routine (e.g.: DWORD data = int.Cmd[0].Data.Dword).
It is important to note, that in order to save the data that has been read in the kernel, before acknowledging the interrupt, you will need to set the INTERRUPT_CMD_COPY flag in the dwOptions field of the WD_INTERRUPT structure (int.dwOptions |= INTERRUPT_CMD_COPY;).

The generated DriverWizard code for your card will already implement the relevant WinDriver API calls for scanning the PCI bus, registering your card's resources (including the interrupt) and enabling and handling the interrupt. However, since the interrupt acknowledgment mechanism is hardware specific, you will need to modify the code somewhat, according to the guidelines above, in order to set up the correct acknowledgment information for the interrupt (in accordance with your hardware's specification) and to implement your desired interrupt handler routine.

You can also refer to the WinDriver special library functions for specific PCI chip-sets (such as PLX, Altera, etc.) for examples of specific WinDriver interrupt handling code for these chips. Please note that the interrupt acknowledgment commands in the samples disable all further interrupts. You should therefore re-enable the interrupts from your interrupt handler routine. (You can do this by adding at the end of your XXX_IntHandler() or XXX_IntHandlerRoutine() routine a similar line to that used for enabling the interrupts from XXX_IntEnable()).

For a detailed explanation of the WinDriver interrupt handling mechanism and relevant code samples, please refer to the "Handling Interrupts" section of the WinDriver PCI User's Manual (see specifically the sub-section regarding PCI Interrupts). Please also refer to the function reference in the manual for a better understanding of the relevant functions and structures and their usage.

To increase the flexibility of your interrupt code and improve the interrupt handler rate, consider using WinDriver's Kernel PlugIn feature to acknowledge and handle the interrupts directly in the kernel.