Jungo WinDriver
Official Documentation
|
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.
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.
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:
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.
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:
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.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:
Please note:
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 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.
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:
WinDriver\redist
directory.windrvr1630.inf
.Add the following line in the [DriverInstall.NT] section:
Add a config_irq section (where <IRQ>
signifies your selected IRQ number — e.g., 10):
"<path to windrvr1630.inf>"
is the path to your modified WinDriver INF file):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.
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:
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.
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.
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 .
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.
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:
INTCSR
register and save its value.INTCSR
value against the given mask (intrMask
) to verify the source of the interrupt.INTCSR
to acknowledge the interrupt.⚠ Attention
All commands in the example are performed in modes of
DWORD
.
Example:
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:
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.
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:
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.
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().
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
⚠ Attention
Reserving too much space may result in degraded OS performance.
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.
For Example:
Run the following command to view a list of the physical memory ranges on your machine
This produces entries as in the following sample output usable identifies memory sections that are used by Linux:
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:
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:
This instructs Linux to boot with ~1,800MB (“mem=1800M“) starting at address 0x100000 (“@1M“). Reconfigure GRUB to apply the changes:
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:
Comparing the following line from the BIOS-provided mapping section:
with the following line from the user-defined mapping section:
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:
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.
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).
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.
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:
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.
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.
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:
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().
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.
A recommended method would be: In each process that you'd like to enable the Shared Interrupts in:
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().
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:
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.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.
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.
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.
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.
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.
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.