Technical Document #125

Technical Document #125
Handling shared PCI interrupts in the Kernel PlugIn

PCI interrupts are normally handled at the kernel level. Therefore, the best way to handle PCI interrupts with WinDriver is using the Kernel PlugIn feature, which lets you implement code directly at the kernel level. This is specifically true when handling interrupts that are shared between different hardware devices — a very common phenomenon in the case of PCI interrupts, which are by definition sharable. (For information on how to set the sharable state of the interrupt with WinDriver, see Technical Document #21.)

The Kernel PlugIn consists of two interrupt handler functions:

Replace "KP_" in the function names below with the name of your Kernel PlugIn driver. For example, the Kernel PlugIn interrupt handler functions for the sample KP_PCI driver are KP_PCI_IntAtIrql and KP_PCI_IntAtDpc.
  1. KP_IntAtIrql — This function is executed in the kernel at high IRQ (Interrupt Request) level immediately upon the detection of an interrupt. The function should contain the write/read commands for clearing the source of the interrupt (acknowledging the interrupt) and any additional high-priority interrupt-handling code. If additional deferred kernel- or user-mode interrupt processing is required, KP_IntAtIrql should return TRUE, in which case the deferred KP_IntAtDpc routine (see below) will be executed once the high-level processing is completed. Otherwise, the function should return FALSE and KP_IntAtDpc (as well as any existing user-mode interrupt-handler routine) will not be executed. The generated and sample WinDriver Kernel PlugIn projects, for example, schedule deferred interrupt processing once every five interrupts by implementing code that returns TRUE only for every fifth KP_IntAtIrql call.

  2. KP_IntAtDpc — This function is executed in the kernel at raised execution level, provided KP_IntAtIrql returned TRUE (see above), and should contain any lower-priority kernel interrupt handling that you wish to perform.
    The return value of this function determines how many times, if at all, the user-mode interrupt-handler routine (which can be set in the call to InterruptEnable() from the user-mode) will be executed once the control returns to the user mode.

To handle shared PCI interrupts with WinDriver, perform the following steps:

  1. Generate a Kernel PlugIn project using WinDriver's DriverWizard utility. (When generating C code with the wizard, you will be given the option to generate Kernel PlugIn code — simply check the relevant check-box and proceed to generate the code.) Alternatively, you can also use the generic WinDriver Kernel PlugIn sample — KP_PCI (WinDriver/pci_diag/kp_pci/kp_pci.c) in v7.0.0 and above / KPTEST (WinDriver/kerplug/kermode/kptest.c) in v6.2.3 and below — as the basis for your Kernel PlugIn project. If you are developing a driver for a Xilinx PCI Express card with Bus Master DMA (BMD) design, you can use the sample Kernel PlugIn driver for this design — KP_BMD (xilinx/bmd_design/kp/kp_bmd.c) in v10.3.1 and above / KP_VRTX for Virtex 5 cards (xilinx/virtex5/bmd/kp/kp_vrtx.c) in v10.3.0 and below — or select to generate customized DriverWizard code for this design, including Kernel Plugin code (beginning with v11.3.0).

    The advantage of using the wizard is that the generated code will utilize the specific device configuration information detected for your device, as well as any hardware-specific information that you define with the wizard. When generating Kernel PlugIn code for handling PCI interrupts, in the wizard's Registers tab define the registers that you wish to access upon the detection of an interrupt, and then in the Interrupts tab assign the registers read/write commands that you wish to perform at high IRQ level (in KP_IntAtIrql) to the interrupt. The exact commands for acknowledging the interrupt are hardware-specific and you should therefore consult your hardware specification to determine the correct commands to set.
    For more information on how to define the registers and interrupt commands in the wizard, see Technical Document #105.

  2. The correct way to handle PCI interrupts with the Kernel PlugIn, and shared interrupts in particular, is to include a command in KP_IntAtIrql that reads information from the hardware (normally you would read from the interrupt status register — INTCSR) in order to determine if your hardware generated the interrupt. (You can define a relevant read command in the DriverWizard before generating your Kernel PlugIn code — refer to step #1 above — or manually modify the generated/sample code to add such a command.) If the interrupt was indeed generated by your hardware, the function can set the value of the fIsMyInterrupt parameter to TRUE in order to accept control of the interrupt, and then proceed to write/read the relevant hardware register(s) in order to acknowledge the interrupt, and either return TRUE to perform additional deferred processing or return FALSE if no such processing is required (see above); If, however, you determine that the interrupt was not generated by your hardware, fIsMyInterrupt should be set to FALSE, in order to ensure that the interrupt will be passed on to other drivers in the system, and KP_IntAtIrql should return FALSE. (Note that there is no real harm in setting fIsMyInterrupt to FALSE even if the interrupt belongs to you, as done by default in the generated and sample WinDriver code, since other drivers in the system, assuming they were implemented correctly, should not attempt to handle the interrupt if it was not generated by their hardware.)

    The portion of the code that performs the check whether your hardware generated the interrupt is based on hardware-specific logic that cannot be defined in the wizard. You will therefore need to modify the generated/sample KP_IntAtIrql implementation and add a relevant "if" clause to ensure that you do not accept control of an interrupt that was not generated by your hardware and do not attempt to clear the source of such an interrupt in the hardware.

    Following is a sample KP_IntAtIrql implementation, based on the v6.2.3 generated WinDriver Kernel PlugIn code. The code reads the INTCSR memory register (defined elsewhere in the code) and only proceeds to accept control of the interrupt and acknowledge it if the value read from the INTCSR is 0xFF (which serves as an indication in this sample that our hardware generated the interrupt). The interrupt in this sample is acknowledged by writing back to the INTCSR the value that was read from it.

    Some of the APIs used in the following code were modified in newer versions of WinDriver. For example, in v11.8.0 of WinDriver the WD_ITEMS I.Mem.dwTransAddr field was renamed to pTransAddr and its type changed from DWORD to KPTR.
    BOOL __cdecl KP_XXX_IntAtIrql(PVOID pIntContext, BOOL *pfIsMyInterrupt)
        XXX_HANDLE hXXX = (XXX_HANDLE) pIntContext;
        DWORD data = 0;
        PVOID pData = NULL;
        DWORD addrSpace;
        WD_ITEMS *pItem;
        addrSpace = XXX_INTCSR_SPACE;
        pItem = &hXXX->cardReg.Card.Item[hXXX->addrDesc[addrSpace].index];
        pData = (DWORD*)pItem->I.Mem.dwTransAddr;
        (DWORD)pData += XXX_INTCSR_OFFSET; 
        data = dtoh32(*((DWORD*)pData));
        if (data == 0xFF)
            /* The interrupt was generated by our hardware */
            /* Write 0x0 to INTCSR to acknowledge the interrupt */
            *((DWORD*)pData) = dtoh32(data);
            /* Accept control of the interrupt */
            *pfIsMyInterrupt = TRUE; 
            /* Schedule deferred interrupt processing (XXX_IntAtDpc) */
            return TRUE;
            /* (Do not acknowledge the interrupt) */
            /* Do not accept control of the interrupt */    
            *pfIsMyInterrupt = FALSE;
            /* Do not schedule deferred interrupt processing */
            return FALSE;