Jungo WinDriver  
Official Documentation
Chapter 10: PCI Advanced Features

This chapter covers advanced driver development issues and contains guidelines for using WinDriver to perform tasks that cannot be fully automated by the DriverWizard. Note that WinDriver's enhanced support for specific chipsets, which we discuss in [Chapter 9] (Chapter 9: Enhanced Support for Specific Chipsets), includes custom APIs for performing hardware-specific tasks like DMA and interrupt handling, thus freeing developers of drivers for these chipsets from the need to implement the code for performing these tasks themselves.

10.1. Handling Interrupts

WinDriver provides you with API, DriverWizard code generation, and samples, 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, 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 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 (if required), 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 provide a general overview of PCI/ISA interrupt handling and explain how to handle interrupts using WinDriver's API. Use this information to understand the sample and generated DriverWizard interrupt code or to write your own interrupt handler.

10.1.1. Interrupt Handling — Overview

PCI and ISA hardware uses interrupts to signal the host.

⚠ Attention

‍In order to handle PCI interrupts correctly with WinDriver on Plug-and-Play (PnP) Windows operating systems, you must first install an INF file for the device, which registers it to work with WinDriver’s PnP driver — windrvr<version>.sys (e.g., windrvr1630.sys).

There are two main methods of PCI interrupt handling:

  • Legacy Interrupts: The traditional interrupt handling, which uses a line-based mechanism. Inthis method, interrupts are signaled by using one or more external pins that are wired "out-of-band", i.e., separately from the main bus lines. Legacy interrupts are divided into two groups:
    • Level-sensitive interrupts: These 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 kernel interrupt handler repeatedly, causing the host platform to hang. To prevent such a situation, the interrupt must be acknowledged (cleared) by the kernel interrupt handler immediately when it is received. Therefore, WinDriver requires you to define an interrupt-status register that will be read/written in order to clear the interrupt. This is a precautionary measurement, because a level sensitive interrupt that is not acknowledged can hang your PC. Legacy PCI interrupts are level sensitive.
    • Edge-triggered interrupts: These are interrupts that are generated once, when the physical interrupt signal goes from low to high. Therefore, exactly one interrupt is generated. No special action is required for acknowledging this type of interrupt. ISA/EISA interrupts are edge triggered.
  • Message-Signaled Interrupts (MSI): Newer PCI bus technologies, available beginning with v2.2 of the PCI bus and in PCI Express, support Message-Signaled Interrupts (MSI). This method uses "in-band" messages instead of pins, and can target addresses in the host bridge. A PCI function can request up to 32 MSI messages. MSI and MSI-X are edge triggered and do not require acknowledgment in the kernel. Among the advantages of MSIs:
    • MSIs can send data along with the interrupt message.
    • As opposed to legacy PCI interrupts, MSIs are not shared; i.e., an MSI that is assigned to a device is guaranteed to be unique within the system.
  • Extended Message-Signaled Interrupts (MSI-X) are available beginning with version 3.0 of the PCI bus. This method provides an enhanced version of the MSI mechanism, which includes the following advantages:
    • Supports 2,048 messages instead of 32 messages supported by the standard MSI.
    • Supports independent message address and message data for each message.
    • Supports per-message masking.
    • Enables more flexibility when software allocates fewer vectors than hardware requests. The software can reuse the same MSI-X address and data in multiple MSI-X slots.

The newer PCI buses, which support MSI/MSI-X, maintain software compatibility with the legacy line-based interrupts mechanism by emulating legacy interrupts through in-band mechanisms. These emulated interrupts are treated as legacy interrupts by the host operating system.

WinDriver supports legacy line-based interrupts, both edge triggered and level sensitive, on all supported operating systems.

WinDriver also supports PCI MSI/MSI-X interrupts (when supported by the hardware).

WinDriver provides a single set of APIs for handling both legacy and MSI/MSI-X interrupts, as described in this manual.

10.1.2. WinDriver Interrupt Handling Sequence

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 enables you to implement kernel-mode interrupt routines. To find out how to handle interrupts from the Kernel PlugIn, please refer to 12.5.5. Handling Interrupts in the Kernel PlugIn.

To listen to PCI interrupts with the DriverWizard, follow these steps:

  • Define the interrupt-status register: In the DriverWizard’s Registers tab, select New and define a new register. You should specify the register’s name, location (i.e., offset into one of the BARs), size, and access mode (read/write). The interrupt-acknowledgment information is hardware specific. You should therefore review your hardware’s specification for the relevant data to set for your specific device.
  • Assign the interrupt-status register to your card’s interrupt: After defining the interrupt-status register, go back to the Interrupts tab and assign the interrupt that you have defined to the card’s interrupt. You should select your interrupt and click on the Edit button to display the Interrupt Information dialog box. Select the register you have defined from the drop-down list in the Access Register box and fill-in the additional information required for acknowledging the interrupt — i.e., read/write mode and the data (if any) to be written to the status register in order to acknowledge and clear the interrupt. You can define several commands for execution upon an interrupt, by simply clicking the More button in the Interrupt Information window.

You should also verify that the interrupt is defined as Level Sensitive and that the Shared box is checked as PCI interrupts should generally be shared. This issue is also explained when clicking the Help button in the Interrupt Information dialog box.

You can now try to listen to the interrupts on your card with the DriverWizard, by clicking the Listen to Interrupts button in the Interrupts tab, and then generating interrupts in the hardware. The interrupts that will be received will be logged in the Log window. To stop listening to the interrupts, click the Stop Listen to Interrupts button in the Interrupts tab.

The interrupt handling sequence using WinDriver is as follows:

  • The user calls one of WinDriver's interrupt enable functions — WDC_IntEnable() or the low-level InterruptEnable() or WD_IntEnable() functions, — to enable interrupts on the device. These functions receive an optional array of read/write transfer commands to be executed in the kernel when an interrupt occurs.

Please note:

  • When using WinDriver to handle level-sensitive interrupts, you must set up transfer commands for acknowledging the interrupt, as explained in 10.1.6. Setting Up Kernel-Mode Interrupt Transfer Commands.
  • Memory allocated for the transfer commands must remain available until the interrupts are disabled.

When WDC_IntEnable() or the lower-level InterruptEnable() function is called, WinDriver spawns a thread for handling incoming interrupts. When using the low-level WD_IntEnable() function you need to spawn the thread yourself. WinDriver must be registered with the OS as the driver of the device before enabling interrupts. For Plug-and-Play hardware (PCI/PCI Express) on Windows platforms, this association is made by installing an INF file for the device. If the INF file is not installed, the interrupt enable function() will fail with a [WD_NO_DEVICE_OBJECT] (WD_NO_DEVICE_OBJECT) error .

  • The interrupt thread runs an infinite loop that waits for an interrupt to occur.
  • When an interrupt occurs, WinDriver executes, in the kernel, any transfer commands that were prepared in advance by the user and passed to WinDriver's interrupt-enable functions
    (see 10.1.6. Setting Up Kernel-Mode Interrupt Transfer Commands).
    When the control returns to the user mode, the driver's user-mode interrupt handler routine (as passed to WinDriver when enabling the interrupts with WDC_IntEnable() or InterruptEnable()) is called.
  • When the user-mode interrupt handler returns, the wait loop continues.
  • When the user no longer needs to handle interrupts, or before the user-mode application exits, the relevant WinDriver interrupt disable function should be called — WDC_IntDisable() or the low-level InterruptDisable() or WD_IntDisable() functions, (depending on the function used to enable the interrupts).

The low-level WD_IntWait() WinDriver function, which is used by the high-level interrupt enable functions to wait on 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, as explained above.

Since your interrupt handler runs in the user mode, you may call any OS API from this function, including file-handling and GDI functions.

10.1.3. Registering IRQs for Non-Plug-and-Play Hardware

On Windows, you may need to register an interrupt request (IRQ) with WinDriver before you can assign it to your non-Plug-and-Play device (e.g., your ISA card).

To register an IRQ with WinDriver on Windows, follow these steps:

  • Open the Device Manager and select View | Resources by type.
  • Select a free IRQ from among those listed in the Interrupt request (IRQ) section.
  • Register the selected IRQ with WinDriver:
    • Back up the files in the WinDriver\redist directory.
    • Edit windrvr1630.inf.

Add the following line in the [DriverInstall.NT] section:

LogConfig=config_irq

Add a config_irq section (where <IRQ> signifies your selected IRQ number — e.g., 10):

[config_irq]
IRQConfig=<IRQ>
  • Reinstall WinDriver by running the following from a command-line prompt (where "<path to windrvr1630.inf>" is the path to your modified WinDriver INF file):
wdreg -inf <path to windrvr1630.inf> install
  • Verify that that the IRQ was successfully registered with WinDriver: Open the Device Manager and locate the WinDriver device. The device properties should have a Resources tab with the registered IRQ.

This procedure registers the IRQ with the virtual WinDriver device. It is recommended that you rename the windrvr1630 driver module, to avoid possible conflicts with other instances of WinDriver that may be running on the same machine (see 17.2. Renaming the WinDriver Kernel Driver).

If you rename your driver, replace references to windrvr1630.inf in the IRQ registration instructions above with the name of your renamed WinDriver INF file.

10.1.4. Determining the Interrupt Types Supported by the Hardware

When retrieving resources information for a Plug-and-Play device using WDC_PciGetDeviceInfo() or the low-level WD_PciGetCardInfo() function, the function returns information regarding the interrupt types supported by the hardware. This information is returned within the dwOptions field of the returned interrupt resource (pDeviceInfo->Card.Item[i].I.Int.dwOptions for the WDC functions pPciCard->Card.Item[i].I.Int.dwOptions for the low-level functions).

The interrupt options bit-mask can contain a combination of any of the following interrupt type flags:

  • [INTERRUPT_MESSAGE_X] (INTERRUPT_MESSAGE_X): Extended Message-Signaled Interrupts (MSI-X).
  • [INTERRUPT_MESSAGE] (INTERRUPT_MESSAGE): Message-Signaled Interrupts (MSI).
  • [INTERRUPT_LEVEL_SENSITIVE] (INTERRUPT_LEVEL_SENSITIVE): Legacy level-sensitive interrupts.
  • [INTERRUPT_LATCHED] (INTERRUPT_LATCHED): Legacy edge-triggered interrupts. The value of this flag is zero and it is applicable only when no other interrupt flag is set.

The WDC_GET_INT_OPTIONS() macro returns a WDC device's interrupt options bitmask. You can pass the returned bit-mask to the WDC_INT_IS_MSI() macro to check whether the bit-mask contains the MSI or MSI-X flags .

The [INTERRUPT_MESSAGE] (INTERRUPT_MESSAGE)
and [INTERRUPT_MESSAGE_X] (INTERRUPT_MESSAGE_X) flags are applicable only to PCI devices.

The Windows APIs do not distinguish between MSI and MSI-X; therefore, on this OS the WinDriver functions set the [INTERRUPT_MESSAGE] (INTERRUPT_MESSAGE) flag for both MSI and MSI-X.

10.1.5. Determining the Interrupt Type Enabled for a PCI Card

When attempting to enable interrupts for a PCI card, WinDriver first tries to use MSI-X or MSI, if supported by the card. If this fails, WinDriver attempts to enable legacy level-sensitive interrupts.

WinDriver's interrupt-enable functions return information regarding the interrupt type that was enabled for the card. This information is returned within the dwEnabledIntType field of the WD_INTERRUPT structure that was passed to the function. When using the high-level WDC_IntEnable() function, the information is stored within the Int field of the WDC device structure referred to by the function's hDev parameter , and can be retrieved using the WDC_GET_ENABLED_INT_TYPE() low-level WDC macro.

10.1.6. Setting Up Kernel-Mode Interrupt Transfer Commands

When handling interrupts you may find the need to perform high-priority tasks at the kernel mode level immediately when an interrupt occurs. For example, when handling level-sensitive interrupts, such as legacy PCI interrupts (see 10.1. Handling Interrupts), the interrupt line must be lowered (i.e., the interrupt must be acknowledged) in the kernel, otherwise the operating system will repeatedly call WinDriver's kernel interrupt handler, causing the host platform to hang. Acknowledgment of the interrupt is hardware-specific and typically involves writing or reading from specific runtime registers on the device.

WinDriver's interrupt enable functions receive an optional pointer to an array of WD_TRANSFER structures , which can be used to set up read/write transfer command from/to memory or I/O addresses on the device. The WDC_IntEnable() function accepts this pointer and the number of commands in the array as direct parameters (pTransCmds and dwNumCmds). The low-level InterruptEnable() and WD_IntEnable() functions receive this information within the Cmd and dwCmds fields of the WD_INTERRUPT structure that is passed to them.

When you need to execute performance-critical transfers to/from your device upon receiving an interrupt — e.g., when handling level-sensitive interrupts — you should prepare an array of WD_TRANSFER structures that contain the required information regarding the read/write operations to perform in the kernel upon arrival of an interrupt, and pass this array to WinDriver's interrupt enable functions.

As explained in 10.1.2. WinDriver Interrupt Handling Sequence, WinDriver's kernel-mode interrupt handler will execute the transfer commands passed to it within the interrupt enable function for each interrupt that it handles, before returning the control to the user mode.

Memory allocated for the transfer commands must remain available until the interrupts are disabled .

10.1.6.1. Interrupt Mask Commands

The interrupt transfer commands array that you pass to WinDriver can also contain an interrupt mask structure, which will be used to verify the source of the interrupt. This is done by setting the transfer structure's cmdTrans field, which defines the type of the transfer command, to CMD_MASK, and setting the relevant mask in the transfer structure's Data field . Interrupt mask commands must be set directly after a read transfer command in the transfer commands array.

When WinDriver's kernel interrupt handler encounters a mask interrupt command, it masks the value that was read from the device in the preceding read transfer command in the array, 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 usermode 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 user-mode interrupt handler routine will not be invoked. Acceptance and rejection of the interrupt is relevant only when handling legacy interrupts; since MSI/MSI-X interrupts are not shared, WinDriver will always accept control of such interrupts.

To correctly handle shared PCI interrupts, you must always include a mask command in your interrupt transfer commands array, and set up this mask to check whether the interrupt handler should claim ownership of the interrupt.

Ownership of the interrupt will be determined according to the result of this mask. If the mask fails, no other transfer commands from the transfer commands array will be executed — including commands that preceded the mask command in the array. If the mask succeeds, WinDriver will proceed to perform any commands that precede the first mask command (and its related read command) in the transfer commands array, and then any commands that follow the mask command in the array.

To gain more flexibility and control over the interrupt handling, you can use WinDriver's Kernel PlugIn feature, which enables you to write your own kernel-mode interrupt handler routines, as explained in 12.5.5. Handling Interrupts in the Kernel PlugIn of the manual.

10.1.6.2. Sample WinDriver Transfer Commands Code

This section provides sample code for setting up interrupt transfer commands using the WinDriver Card (WDC) library API .

The sample code is provided for the following scenario: Assume you have a PCI card that generates level-sensitive interrupts. 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 (pAddr), to be intrMask. In order to clear and acknowledge the interrupt you need to write 0 to the INTCSR.

The code below demonstrates how to define an array of transfer commands that instructs WinDriver's kernel-mode interrupt handler to do the following:

  • Read your card's INTCSR register and save its value.
  • Mask the read INTCSR value against the given mask (intrMask) to verify the source of the interrupt.
  • If the mask was successful, write 0 to the INTCSR to acknowledge the interrupt.

⚠ Attention

‍All commands in the example are performed in modes of DWORD.

Example:

WD_TRANSFER trans[3]; /* Array of 3 WinDriver transfer command structures */
BZERO(trans);
/* 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].pPort = pAddr; /* Assume pAddr holds the address of the 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].pPort = pAddr; /* Assume pAddr holds the address of INTCSR */
/* Set the data to write to the INTCSR IO port: */
trans[2].Data.Dword = 0;
DWORD cmdTrans
Transfer command WD_TRANSFER_CMD.
Definition: windrvr.h:590
UINT32 Dword
Use for 32 bit transfer.
Definition: windrvr.h:601
union WD_TRANSFER::@14 Data
KPTR pPort
I/O port for transfer or kernel memory address.
Definition: windrvr.h:589
@ WP_DWORD
Write port dword.
Definition: windrvr.h:399
@ CMD_MASK
Interrupt Mask.
Definition: windrvr.h:392
@ RP_DWORD
Read port dword.
Definition: windrvr.h:396
#define BZERO(buf)
Definition: windrvr.h:1553

After defining the transfer commands, you can proceed to enable the interrupts. Memory allocated for the transfer commands must remain available until the interrupts are disabled , as explained above.

The following code demonstrates how to use the WDC_IntEnable() function 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 WDC_IntEnable().
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);
#define FALSE
Definition: kpstdlib.h:260
DWORD DLLCALLCONV WDC_IntEnable(_In_ WDC_DEVICE_HANDLE hDev, _In_ WD_TRANSFER *pTransCmds, _In_ DWORD dwNumCmds, _In_ DWORD dwOptions, _In_ INT_HANDLER funcIntHandler, _In_ PVOID pData, _In_ BOOL fUseKP)
Enables interrupt handling for the device.
@ INTERRUPT_CMD_COPY
Copy any data read in the kernel as a result of a read transfer command, and return it to the user wi...
Definition: windrvr.h:611

10.1.7. WinDriver MSI/MSI-X Interrupt Handling

WinDriver supports PCI Message-Signaled Interrupts (MSI) and Extended Message-Signaled Interrupts (MSI-X).

The same APIs are used for handling both legacy and MSI/MSI-X interrupts, including APIs for retrieving the interrupt types supported by your hardware (see 10.1.4. Determining the Interrupt Types Supported by the Hardware) and the interrupt type that was enabled for it (see 10.1.5. Determining the Interrupt Type Enabled for a PCI Card).

When enabling interrupts for a PCI device on an OS that supports MSI/MSIx, WinDriver first tries to enable MSI-X or MSI — if supported by the device — and if this fails, it attempts to enable legacy level-sensitive interrupts.

On Windows, enabling MSI or MSIx interrupts requires that a relevant INF file first be installed for the device, as explained in 10.1.7.1. Windows MSI/MSI-X Device INF Files.

On Linux, you can specify the types of PCI interrupts that may be enabled for your device, via the dwOptions parameter of the WDC_IntEnable() function or of the lowlevel InterruptEnable() function — in which case WinDriver will only attempt to enable interrupts of the specified types (provided they are supported by the device).

WinDriver's kernel-mode interrupt handler sets the interrupt message data in the dwLastMessage field of the WD_INTERRUPT structure that was passed to the interrupt enable/wait function. If you pass the same interrupt structure as part of the data to your user mode interrupt handler routine, as demonstrated in the sample and generated DriverWizard interrupt code, you will be able to access this information from your interrupt handler. When using a Kernel PlugIn driver (see Chapter 12: Understanding the Kernel PlugIn), the last message data is passed to your kernel mode KP_IntAtDpcMSI handler; it is also passed to KP_IntAtIrqlMSI if you have enabled MSI interrupts. You can use the low-level WDC_GET_ENABLED_INT_LAST_MSG() macro to retrieve the last message data for a given WDC device.

10.1.7.1. Windows MSI/MSI-X Device INF Files

The information in this section is relevant only when working on Windows.

To successfully handle PCI interrupts with WinDriver on Windows, you must first install an INF file that registers your PCI card to work with WinDriver's kernel driver, as explained in 17.1. Windows INF Files. To use MSI/MSI-X on Windows, the card's INF file must contain specific [Install.NT.HW] MSI information, as demonstrated below:

[Install.NT.HW]
AddReg = Install.NT.HW.AddReg
[Install.NT.HW.AddReg]
HKR, "Interrupt Management", 0x00000010
HKR, "Interrupt Management\MessageSignaledInterruptProperties", 0x00000010
HKR, "Interrupt Management\MessageSignaledInterruptProperties", MSISupported, 0x10001, 1

Therefore, to use MSI/MSI-X on Windows with WinDriver — provided your hardware supports MSI/MSI-X — you need to install an appropriate INF file. When using DriverWizard on Windows to generate an INF file for a PCI device that supports MSI/MSI-X, the INF generation dialogue allows you to select to generate an INF file that supports MSI/MSI-X (see 6.2. DriverWizard Walkthrough).

In addition, the WinDriver sample code for the Xilinx Bus Master DMA (BMD) design, which demonstrates MSI handling, includes a sample MSI INF file for this design — WinDriver/samples/c/xilinx/bmd_design/xilinx_bmd.inf.

If your card's INF file does not include MSI/MSI-X information, as detailed above, WinDriver will attempt to handle your card's interrupts using the legacy level-sensitive interrupt handling method, even if your hardware supports MSI/MSI-X.

10.1.8. Sample User-Mode WinDriver Interrupt Handling Code

The sample code below demonstrates how you can use the WDC library's interrupt APIs to implement a simple user-mode interrupt handler.

For a complete interrupt handler source code that uses the WDC interrupt functions, refer, for example, to the WinDriver pci_diag (WinDriver/samples/c/pci_diag) and PLX (WinDriver/samples/c/plx) samples, and to the generated DriverWizard PCI/ISA code. For a sample of MSI interrupt handling, using the same APIs, refer to the Xilinx Bus Master DMA (BMD) design sample (WinDriver/samples/c/xilinx/bmd_design), or to the code generated by DriverWizard for PCI hardware that supports MSI/MSI-X.

The following sample code demonstrates interrupt handling for an edge-triggered ISA card. The code does not set up any kernel-mode interrupt transfer commands (see 10.1.6. Setting Up Kernel-Mode Interrupt Transfer Commands), which is acceptable in the case of edge-triggered or MSI/MSI-X interrupts (see 10.1.1. Interrupt Handling — Overview). Note that when using WinDriver to handle level-sensitive interrupts from the user mode, you must set up transfer commands for acknowledging the interrupt in the kernel, as explained above and as demonstrated in 10.1.6. Setting Up Kernel-Mode Interrupt Transfer Commands.

As mentioned in this Chapter, WinDriver provides a single set of APIs for handling both legacy and MSI/MSI-X interrupts. You can therefore also use the following code to handle MSI/MSI-X PCI interrupts (if supported by your hardware), by simply replacing the use of WDC_IsaDeviceOpen() in the sample with WDC_PciDeviceOpen().

VOID DLLCALLCONV interrupt_handler (PVOID pData)
{
PWDC_DEVICE pDev = (PWDC_DEVICE)pData;
/* Implement your interrupt handler routine here */
printf("Got interrupt %d\n", pDev->Int.dwCounter);
}
...
int main()
{
DWORD dwStatus;
...
...
hDev = WDC_PciDeviceOpen(...);
...
/* 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));
}
else
{
printf("Press Enter to uninstall interrupt\n");
fgets(line, sizeof(line), stdin);
/* WDC_IntDisable() calls InterruptDisable();
InterruptDisable() calls WD_IntDisable(). */
}
...
...
}
#define NULL
Definition: kpstdlib.h:268
const char *DLLCALLCONV Stat2Str(_In_ DWORD dwStatus)
Retrieves the status string that corresponds to a status code.
WD_INTERRUPT Int
Interrupt information.
Definition: wdc_defs.h:60
Device information struct.
Definition: wdc_defs.h:46
DWORD dwCounter
Number of interrupts received.
Definition: windrvr.h:658
struct WDC_DEVICE * PWDC_DEVICE
DWORD DLLCALLCONV WDC_PciDeviceOpen(_Outptr_ WDC_DEVICE_HANDLE *phDev, _In_ const WD_PCI_CARD_INFO *pDeviceInfo, _In_ const PVOID pDevCtx)
Allocates and initializes a WDC PCI device structure, registers the device with WinDriver,...
#define WDC_DRV_OPEN_DEFAULT
Definition: wdc_lib.h:69
DWORD DLLCALLCONV WDC_DriverClose(void)
Closes the WDC WinDriver handle (acquired and stored by a previous call to WDC_DriverOpen()) and unin...
DWORD DLLCALLCONV WDC_PciDeviceClose(_In_ WDC_DEVICE_HANDLE hDev)
Uninitializes a WDC PCI device structure and frees the memory allocated for it.
DWORD DLLCALLCONV WDC_IntDisable(_In_ WDC_DEVICE_HANDLE hDev)
Disables interrupt interrupt handling for the device, pursuant to a previous call to WDC_IntEnable()
DWORD DLLCALLCONV WDC_DriverOpen(_In_ WDC_DRV_OPEN_OPTIONS openOptions, _In_ const CHAR *pcLicense)
Opens and stores a handle to WinDriver's kernel module and initializes the WDC library according to t...
void * WDC_DEVICE_HANDLE
Handle to device information struct.
Definition: wdc_lib.h:33
@ WD_STATUS_SUCCESS
[0] Operation completed successfully
Definition: windrvr.h:1066
#define DLLCALLCONV
Definition: windrvr.h:32

10.2. Reserving and locking physical memory on Windows and Linux

This chapter explains how to reserve a segment of the physical memory (RAM) for exclusive use, and then access it using WinDriver, on Windows or Linux.

⚠ Attention

‍In most cases, there is no need to resort to this method in order to reserve segments of memory for exclusive use. Normally, you can lock a safe Direct Memory Access (DMA) buffer (e.g., using WinDriver’s DMA APIs) and then access the buffer from your driver. For more info, see 11.2. Performing Direct Memory Access (DMA) or 11.3. Performing Direct Memory Access (DMA) transactions.

The method described in this document should be used only in rare cases of “memory-intensive” driver projects and only when the required memory block cannot be locked using standard methods, such as allocation of a contiguous DMA buffer. When using this method, take special care not to write to the wrong memory addresses, so as to avoid system crashes, etc. The relevant device must have an INF file installed.

This chapter is organized as follows:

  • Reserving the desired amount of RAM: Windows and Linux
  • On Windows – Calculating the base address of the reserved memory
  • Using WinDriver to access reserved memory

Reserving the desired amount of RAM

⚠ Attention

‍Reserving too much space may result in degraded OS performance.

  • Windows

Run the command line as an administrator, and use the BCDEdit utility to set the value of removememory to the number of MB you wish to reserve. Upon successful completion, BCDEdit will display a success message. To complete reservation, reboot the PC.

bcdedit /set removememory specify_size_in_MB

For Example:

bcdedit /set removememory 10
  • Linux

Run the following command to view a list of the physical memory ranges on your machine

dmesg | grep BIOS

This produces entries as in the following sample output usable identifies memory sections that are used by Linux:

[ 0.000000] BIOS-e820: 0000000000000000 - 000000000009f800 (usable)
[ 0.000000] BIOS-e820: 000000000009f800 - 00000000000a0000 (reserved)
[ 0.000000] BIOS-e820: 00000000000f0000 - 0000000000100000 (reserved)
[ 0.000000] BIOS-e820: 0000000000100000 - 000000007dce0000 (usable)
[ 0.000000] BIOS-e820: 000000007dce0000 - 000000007dce3000 (ACPI NVS)
[ 0.000000] BIOS-e820: 000000007dce3000 - 000000007dcf0000 (ACPI data)
[ 0.000000] BIOS-e820: 000000007dcf0000 - 000000007dd00000 (reserved)
[ 0.000000] BIOS-e820: 00000000c0000000 - 00000000d0000000 (reserved)
[ 0.000000] BIOS-e820: 00000000fec00000 - 0000000100000000 (reserved)

Edit the boot-loader command line to instruct the Linux kernel to boot with less memory in the desired address range. For example, the following line from the sample dmesg output shown above:

[ 0.000000] BIOS-e820: 0000000000100000 - 000000007dce0000 (usable)

indicates that there is a ~1.95GB address range, starting at address 0x100000, that is used by Linux. To reserve ~150MB of memory at the end of this range for DMA, on a machine with a GRUB boot loader, add the following to the grub file:

GRUB_CMDLINE_LINUX="${GRUB_CMDLINE_LINUX} mem=1800M memmap=1800M@1M"

This instructs Linux to boot with ~1,800MB (“mem=1800M“) starting at address 0x100000 (“@1M“). Reconfigure GRUB to apply the changes:

sudo grub-mkconfig -o /boot/grub/grub.cfg
  • Reboot Linux
  • Run the command
dmesg | grep BIOS\|user

to see the available physical memory ranges. The output should include a BIOS-provided physical RAM mappings section with the BIOS-.... lines from the original dmesg output, and a new user-defined RAM mappings section with user: ... lines indicating the actual available physical memory ranges (based on user definitions). The user entry for the memory range you modified in the previous steps should be missing the portion you reserved. For example, for the original BIOS mappings and boot-loader changes examples provided in the previous steps, the new output should look like similar to this:

[ 0.000000] BIOS-provided physical RAM map:
[ 0.000000] BIOS-e820: 0000000000000000 - 000000000009f800 (usable)
[ 0.000000] BIOS-e820: 000000000009f800 - 00000000000a0000 (reserved)
[ 0.000000] BIOS-e820: 00000000000f0000 - 0000000000100000 (reserved)
[ 0.000000] BIOS-e820: 0000000000100000 - 000000007dce0000 (usable)
[ 0.000000] BIOS-e820: 000000007dce0000 - 000000007dce3000 (ACPI NVS)
[ 0.000000] BIOS-e820: 000000007dce3000 - 000000007dcf0000 (ACPI data)
[ 0.000000] BIOS-e820: 000000007dcf0000 - 000000007dd00000 (reserved)
[ 0.000000] BIOS-e820: 00000000c0000000 - 00000000d0000000 (reserved)
[ 0.000000] BIOS-e820: 00000000fec00000 - 0000000100000000 (reserved) [ 0.000000] user-defined physical RAM map:
[ 0.000000] user: 0000000000000000 - 000000000009f800 (usable)
[ 0.000000] user: 000000000009f800 - 00000000000a0000 (reserved)
[ 0.000000] user: 00000000000f0000 - 0000000000100000 (reserved)
[ 0.000000] user: 0000000000100000 - 0000000070900000 (usable)
[ 0.000000] user: 000000007dce0000 - 000000007dce3000 (ACPI NVS)
[ 0.000000] user: 000000007dce3000 - 000000007dcf0000 (ACPI data)
[ 0.000000] user: 000000007dcf0000 - 000000007dd00000 (reserved)
[ 0.000000] user: 00000000c0000000 - 00000000d0000000 (reserved)

Comparing the following line from the BIOS-provided mapping section:

[ 0.000000] BIOS-e820: 0000000000100000 - 000000007dce0000 (usable)

with the following line from the user-defined mapping section:

[ 0.000000] user: 0000000000100000 - 0000000070900000 (usable)

shows that the original ~1.95GB memory range — 0x100000 – 0x7dce0000 was reduced to a ~1.75GB range — 0x100000–0x70900000. The memory in address range 0x70900000 – 0x7dce0000 is no longer available because it has been reserved in the previous steps, allowing you to use this range for DMA.

Calculating the base address

To acquire the base address of the reserved memory segment on Windows, you must first determine the physical memory mapping on your PC and retrieve the base address and length (in bytes) of the highest address space used by the operating system. Then add the length of this address space to its base address to receive the base address of your reserved memory segment:

Reserved memory base address = Highest OS physical memory base address + length of the highest OS memory base address

To verify the size of your reserved memory block, compare the length of the highest OS address space, before and after modifying boot configuration to reserve the memory. This can be done as follows:

  • Open the registry (Start –> Run –> regedit.exe)
  • Navigate to the registry key: HKEY_LOCAL_MACHINE\HARDWARE\RESOURCEMAP\System Resources\Physical Memory\.Translated

This key is of type REG_RESOURCE_LIST and holds information regarding the physical memory mapping on your PC. To view a parsed version of the mapped addresses, double-click on the Translated key, select the relevant resource from the Resource Lists dialog, and double-click on the resource (or select Display…) in order to display the resources dialog, which contains a list of all memory address ranges for the selected resource. The base address for your reserved physical memory block is calculated by locating the highest base address in the list and adding to it the length of the relevant address space.

For example, for the following Resources dialog, the highest base address is 0x1000000 and the length of the address space that begins at this address is 0x1eff0000, so the base address of the reserved memory is 0x1fff0000.

reserved memory base address = 0x1000000 + 0x1eff0000 = 0x1fff0000

RAM Reserved

Using WinDriver with reserved memory

Once you acquire the physical base address of the memory segment that you reserved, you can easily access it using WinDriver, as you would any memory on an ISA card. You can use WinDriver’s DriverWizard to test the access to the memory you reserved: use the wizard to create a new ISA project, define the new memory item according to the information you acquired in the previous step(s), then proceed to access the memory with the wizard. You can also use DriverWizard to generate a sample diagnostics application that demonstrates how to lock and access the reserved memory region using WinDriver’s API. The following code segment demonstrates how you can define and lock the physical memory using WinDriver’s WDC API. The handle returned by the sample LockReservedMemory() function can be used to access the memory using WinDriver’s WDC memory access APIs, defined in the WinDriver/include/wdc_lib.h header file.

⚠ Attention

‍There may be differences between the API in your version of WinDriver and that used in the following example (such as differences in field names and/or types).

/* LockReservedMemory: Returns a WDC handle for accessing
the reserved memory block.
Parameters:
pReservedRegionBase: The physical base address of
the reserved memory region (as calculated in the
previous step)
qwReservedRegionLength: The length (in bytes) of the
reserved memory region, i.e.,:
<size_in_MB> (as configured in Step 1) * 0x100000
Note:
The function uses the high-level WDC APIs.
You can implement similar code using the low-level
WD_CardRegister() API — see the WinDriver User's
Manual for details.
*/
WDC_DEVICE_HANDLE LockReservedMemory(PHYS_ADDR pReservedRegionBase,
UINT64 qwReservedRegionLength)
{
DWORD dwStatus;
WD_CARD deviceInfo;
/* Set the reserved memory's resources information */
BZERO(deviceInfo);
SetMemoryItem(&deviceInfo, pReservedRegionBase,
qwReservedRegionLength);
/* Get a handle to the reserved memory block */
dwStatus = WDC_IsaDeviceOpen(&hDev, &deviceInfo, NULL,
if (WD_STATUS_SUCCESS != dwStatus)
{
printf( "Failed opening a WDC device handle.
Error 0x%lx — %s\n", dwStatus,
Stat2Str(dwStatus));
return NULL;
}
/* Return the handle to the reserved memory */
return hDev;
}
/* SetMemoryItem: Initializes a WDC device information
structure for a specified memory region.
Parameters:
pDeviceInfo: Pointer to a resources information structure
pPhysAddr: The memory region's physical base address
qwBytes: The memory region's length, in bytes
*/
void SetMemoryItem(WD_CARD *pDeviceInfo, PHYS_ADDR pPhysAddr, UINT64 qwBytes)
{
WD_ITEMS *pItem = pDeviceInfo->Item;
pDeviceInfo->dwItems = 2;
/* 1st item: Bus */
pItem[0].item = ITEM_BUS;
pItem[0].I.Bus.dwBusType = WD_BUS_ISA;
/* 2nd item: Memory */
pItem[1].item = ITEM_MEMORY;
/* Lock the memory for exclusive use */
pItem[1].fNotSharable = 1;
/* Set the reserved memory's base address */
pItem[1].I.Mem.pPhysicalAddr = pPhysAddr;
/* Set the reserved memory's size */
pItem[1].I.Mem.qwBytes = qwBytes;
/* Set the reserved memory's address space */
pItem[1].I.Mem.dwBar = 0;
/* Map physical memory as cached (applicable only to RAM). */
}
WD_BUS_TYPE dwBusType
Bus Type: WD_BUS_PCI/ISA/EISA.
Definition: windrvr.h:689
WD_ITEMS Item[WD_CARD_ITEMS]
Definition: windrvr.h:773
DWORD dwItems
Definition: windrvr.h:771
PHYS_ADDR pPhysicalAddr
Physical address on card.
Definition: windrvr.h:724
UINT64 qwBytes
Address range.
Definition: windrvr.h:725
DWORD dwBar
PCI Base Address Register number.
Definition: windrvr.h:735
DWORD dwOptions
Bitmask of WD_ITEM_MEM_OPTIONS flags.
Definition: windrvr.h:736
DWORD fNotSharable
Definition: windrvr.h:718
union WD_ITEMS::I I
DWORD item
ITEM_TYPE.
Definition: windrvr.h:717
struct WD_ITEMS::I::Mem Mem
WD_BUS Bus
ITEM_BUS.
Definition: windrvr.h:763
DWORD DLLCALLCONV WDC_IsaDeviceOpen(_Outptr_ WDC_DEVICE_HANDLE *phDev, _In_ const WD_CARD *pDeviceInfo, _In_ const PVOID pDevCtx)
Allocates and initializes a WDC ISA device structure, registers the device with WinDriver,...
@ ITEM_BUS
Bus.
Definition: windrvr.h:701
@ ITEM_MEMORY
Memory.
Definition: windrvr.h:699
@ WD_ITEM_MEM_ALLOW_CACHE
Map physical memory as cached; applicable only to host RAM, not to local memory on the card.
Definition: windrvr.h:708
UINT64 PHYS_ADDR
Definition: windrvr.h:382
unsigned __int64 UINT64
Definition: windrvr.h:314
@ WD_BUS_ISA
ISA.
Definition: windrvr.h:681

10.3. Buffer sharing between multiple processes

This section describes how to share a contiguous DMA buffer or a kernel buffer between multiple processes.

The buffer sharing mechanism can be used to improve the work of multiple processes by allowing the developer avoiding unnecessary buffer copying. Coupled with the WinDriver IPC mechanism it might also be used as a way for two processes to convey large messages/data. Currently WinDriver supports sharing DMA contiguous buffer (See WDC_DMAContigBufLock() ) and Kernel buffer (See WDS_SharedBufferAlloc() ).

In order to share a buffer the developer must first pass its global handle to the other process(es). See Macros WDS_SharedBufferGetGlobalHandle() and WDC_DMAGetGlobalHandle() for getting a buffer's global handle.

Than, the handle should be passed to the other process(es). E.g. by WinDriver IPC calls: WDS_IpcUidUnicast(), WDS_IpcSubGroupMulticast() or WDS_IpcMulticast().

After a process gets the global buffer handle, it should use WDC_DMABufGet() or WDS_SharedBufferGet() to retrieve the buffer. WinDriver will re-map the buffer to the new process virtual memory (I.e. user-space) and will fill all the necessary information as though the buffer was allocated from the calling process.

Once the process no longer needs the shared buffer it should use the regular unlock/free calls - WDC_DMABufUnlock() appropriately.

WinDriver manages all system resources, hence the call to WDC_DMABufUnlock() will not remove system resources until all processes no longer use the buffer.

10.4. Single Root I/O Virtualization (SR-IOV)

10.4.1. Introduction

Single Root I/O Virtualization (SR-IOV) is a PCIe extended capability which makes one physical device appear as multiple virtual devices. The physical device is referred to as Physical Function (PF) while the virtual devices are referred to as Virtual Functions (VF).

Allocation of VF can be dynamically controlled by the PF via registers encapsulated in the capability. By default, SR-IOV is disabled and the PF behaves as a regular PCIe device. Once it is turned on, each VF's PCI configuration space can be accessed by its own domain, bus, slot and function number (Routing ID). Each VF also has a PCI memory space, which is used to map its register set.A VF device driver operates on the register set so it can be functional and appear as a real PCI device.

At the moment, WinDriver only supports SR-IOV in Linux.

Important notes:

  • VFs have to be the same type of device as the PF.
  • SR-IOV requires hardware support, as the number of VFs that can be presented depends upon the device.
  • The PCI SIG SR-IOV specification indicates that each device can have up to 256 VFs, but practical limits are usually lower, as each VF requires actual hardware resources.
  • VFs can't be used to configure the device.

10.4.2. Using SR-IOV with WinDriver

The following steps were required to operate the Intel 82599ES 10-Gigabit SFI/SFP+ Network Adaptor. Other cards might require different adjustments.

Make sure that prior to installing WinDriver you have run ./configure --enable-sriov-support from the WinDriver/redist directory.

The pci_diag application may be run from the WinDriver/samples/c/pci_diag directory, in order to show relevant SR-IOV options.

It is possible that upon boot it will be required to add pci=assign-busses to the boot arguments in the kernel command line. This tells the kernel to always assign all PCI bus numbers, overriding whatever the firmware may have done.

Otherwise, assigning VF to a device may result in "bus number is out of range" error. This argument can be added by editing the file /etc/default/grub/, and adding it to the GRUB_CMDLINE_LINUX_DEFAULT variable. Then run update-grub to make this change permanent. Under Ubuntu, a temporary change of this variable can be done by pressing 'e' when the GRUB menu, and appending the argument to the 'linux' line. WinDriver provides you with the API for enabling, disabling and getting the number of assigned virtual functions - see the description of WDC_PciSriovEnable(), WDC_PciSriovDisable(), and WDC_PciSriovGetNumVFs() respectively.

10.5. Using WinDriver's IPC APIs

This section describes how to use WinDriver to perform Inter-Process Communication.

WinDriver IPC allows several WinDriver-based user applications to send and receive user defined messages.

The pci_diag sample found in WinDriver/util (source code found in WinDriver/samples/c/pci_diag) examplifies usage of this ability. Using IPC with WinDriver requires having a license that includes IPC support. IPC APIs will fully work during the Evaluation period.

10.5.1 IPC Overview

You should define a group ID that will be used by all your applications, and optionally you can also define several sub-group IDs to differentiate between different types of applications. For example, define one sub-group ID for operational applications, and another sub-group ID for monitoring or debug applications. This will enable you to send messages only to one type of application.

An IPC message can be sent in three ways:

  • Multicast - The message will be sent to all processes that were registered with the same group ID.
  • Sub-group Multicast - The message will be sent to all processes that were registered with the same sub-group ID.
  • Unicast (By WinDriver IPC unique ID) - The message will be sent to one specific process.

In order to start working with WinDriver IPC each user application must first register by calling WDS_IpcRegister(). It must provide a group ID and a sub-group ID and can optionally provides callback function to receive incoming messages. WinDriver IPC unique ID is received either in the result of WDS_IpcScanProcs() or in the message received by the callback function. Multicast messages may be sent by calling WDS_IpcMulticast() or WDS_IpcSubGroupMulticast(), while unicast messages may be sent by WinDriver IPC unique ID by calling WDS_IpcUidUnicast().

Query of current registered processes can be done using WDS_IpcScanProcs().

10.5.2. Shared Interrupts via IPC

A method of acknowledging interrupts from more than one process is by using the Shared Interrupts via IPC API provided by WinDriver.

This mechanism allows creating a special IPC "process" that sends IPC messages to other processes that are registered to WinDriver IPC.

10.5.2.1. Enabling Shared Interrupts:

A recommended method would be: In each process that you'd like to enable the Shared Interrupts in:

  • Register the process to IPC using WDS_IpcRegister().
  • Write your own callback function that is relevant to what you wish to occur when a Shared Interrupt occurs. Send a pointer to that function as an argument to WDS_SharedIntEnable(). You can write a different callback for each process to acheive a different handling of the interrupt in different contexts.

10.5.2.2. Disabling Shared Interrupts:

You can either disable Shared Interrupts locally (for the current process only) using WDS_SharedIntDisableLocal() or disable Shared Interrupts for all processes by using WDS_SharedIntDisableGlobal().

10.6. FAQ

10.6.1. What is the significance of marking a resource as 'shared' with WinDriver, and how can I verify the 'shared' status of a specific resource on my card?

The "shared" status determines whether the resource is locked for exclusive use. When a memory, I/O, or interrupt item is defined as non-sharable, the device registration function — WDC_PciDeviceOpen() / WDC_IsaDeviceOpen(), or the low-level WD_CardRegister() function — checks whether the resource is already locked for exclusive use. If it is, the registration fails; otherwise, the resource is locked exclusively, and any subsequent attempt to lock the resource — either from within the same application, or from another WinDriver application — will fail, until the resource is released using WDC_PciDeviceClose() / WDC_IsaDeviceClose(), or the low-level WD_CardUnregister() function.

⚠ Attention

‍Device registers cannot be defined as shared.

PCI resources, and specifically interrupts, should generally be defined as sharable.

For memory and I/O, the share status defined with WinDriver affects only WinDriver-based applications. However, for interrupts there is further significance to the share status, since it also determines whether other drivers (not necessarily WinDriver drivers) can lock the same interrupt. For an explanation of how to read the value of the PCI interrupt status register, please see futher FAQ questions.

Use one of the following methods to check whether a resource is defined as sharable:

  • From your code, print the value of the fNotSharable flag for the memory/IO/interrupt item that you wish to check — deviceInfo.Card.Item[i].fNotSharable when using the WDC API, or cardReg.Card.Item[i].fNotSharable when using the low-level WinDriver API. A value of 1 indicates that the resources is not sharable. A value of 0 indicates that the resource is sharable.
  • If you have used the DriverWizard to generate your code, you can run the DriverWizard, open the xxx.wdp DriverWizard project file, which served as the basis for your application, select the Memory, I/O, or Interrupt tab (respectively) and click on Edit. Then look to see if the Shared box in the Resource Information window is checked, to signify that the resource is defined as shared for this device. Note that this test is only relevant when using the original xxx.wdp project file for your application code, and assuming your code does not modify the "shared" status — since any changes made in the xxx.wdp file, via the DriverWizard, will only affect subsequent code generation, and will not affect the existing application code. Therefore, checking the “shared” values from the code is more accurate.

As a general guideline, avoid locking the same resource twice. It is always recommend to release a previously locked resource before trying to lock it again (either from the same application, or from different applications). If you need to access the resources from several applications, you can share the handle received from a single call to the device registration function — WDC_PciDeviceOpen() / WDC_IsaDeviceOpen(), or the low-level WD_CardRegister() function.

10.6.2. Can I access the same device, simultaneously, from several WinDriver applications?

It is possible to create several WinDriver-based driver processes that will access the same card, although we do not recommend this, and it should not generally be required.

Should you decide to implement such a design, consider the synchronization issues carefully. To bypass the synchronization problems, we recommend you use only one point of access to your hardware. Use a single process to directly access your hardware, while the other processes should access the hardware only via this process. The advantage of this design is that only one point requires synchronization.

Please note, however, that we will not be able to provide technical support specifically related to the implementation of such designs and accompanying issues (such as synchronization problems that might occur). You should therefore carefully consider if this is indeed the desired design in your case.

For USB, note that while multiple calls to WDU_Init() for the same device may succeed, after the first attach callback accepts control of the device no other attach notifications will be received until WDU_Uninit() is called for the relevant WDU_Init() call. Using a single process to perform a single WDU_Init() call with a single attach callback function, as suggested above, will eliminate problems resulting from multiple WDU_Init() calls.

You may consider accessing the device from one application, and communicate with other apps using the IPC API.

10.6.3. How can I read the value of the PCI interrupt status register from my WinDriver ISR, in order to determine, for example, which card generated the interrupt when the IRQ is shared between several devices?

When handling the interrupts from the user mode you must acknowledge (clear) the interrupt on the card whether your card generated the interrupt or not (in case of shared IRQs). However, this does not necessarily present a problem.

To determine which card generated the interrupt, and activate your ISR only in case the interrupt was generated by your card, you can set up the transfer commands of the WD_INTERRUPT structure, which is passed to WDC_IntEnable() / InterruptEnable(), or to the lower level WD_IntEnable() function — int.Cmds — to include a command to read from the interrupt register before clearing the interrupt. The register will then be read in the kernel, immediately when an interrupt is received and before it is cleared, and you will be able to access the read value from your user mode interrupt handler routine when it is activated.

⚠ Attention

‍In order to save the read value you must set the INTERRUPT_CMD_COPY flag in the dwOptions field of the WD_INTERRUPT structure (int.dwOptions |= INTERRUPT_CMD_COPY).

When using WinDriver’s Kernel PlugIn feature to handle the interrupts directly in the kernel, you can read the value of the interrupt status register on your card directly when an interrupt is received, from within your [KP_IntAtIrql()] (KP_PCI_IntAtIrql) routine, and proceed to acknowledge (clear) the interrupt and execute your ISR only if the value of the status register indicates that the interrupt was indeed generated by your card. For more information on how to handle shared PCI interrupts from the Kernel PlugIn, please refer to the Kernel PlugIn chapter of this manual.

10.6.4. I need to be able to count the number of interrupts occurring and possibly call a routine every time an interrupt occurs. Is this possible with WinDriver?

Yes. You can use the DriverWizard to generate a diagnostics code for your device, which includes a skeletal interrupt handling mechanism that also monitors the number of interrupts received. You may need to modify the code slightly to suit your hardware’s specification with regard to interrupt handling.

You can also refer to the int_io sample, which is available under the WinDriver/samples/c/int_io/ directory, for an example of handling ISA interrupts with WinDriver.

Both the sample and the generated code will install an interrupt and spawn a thread that waits on it. The number of interrupts received is returned in the dwCounter field of the WD_INTERRUPT structure.

10.6.5. Does WinDriver poll the interrupt (Busy Wait)?

No. When calling WD_IntWait() (which is called from the high-level WDC_IntEnable() and InterruptEnable() APIs) the calling thread is put to sleep. CPU cycles are not wasted on waiting. When an interrupt occurs the thread is awaken and you can perform your interrupt handling.

10.6.6. Can I write to disk files during an interrupt routine?

Yes. WinDriver enables you to implement your interrupt service routine (ISR) in the user mode, thereby allowing you to perform all library function calls from within your ISR. Just remember to keep it short, so you don’t miss the next interrupt.

If you select to implement your ISR in the kernel, using WinDriver’s Kernel PlugIn feature, you will not be able to use the standard user mode file I/O functions. You can either leave the file I/O handling in the user-mode ISR, and implement your interrupt code so that the user-mode interrupt routine is executed only once every several interrupts. You can refer to the generated DriverWizard Kernel PlugIn code and/or to the generic WinDriver Kernel PlugIn sample — WinDriver/samples/c/pci_diag/kp_pci/kp_pci.c — for an example of monitoring the interrupt count); or replace the user-mode file I/O code with calls to OS-specific functions, which can be called at the kernel level (for example, the WDK ZwCreateFile(), ZwWriteFile() and ZwReadFile() functions). Please note, however, that this will diminish your driver’s cross-platform portability.