Jungo WinDriver  
Official Documentation
Chapter 13: Creating a Kernel PlugIn Driver

The easiest way to write a Kernel PlugIn driver is to use DriverWizard to generate the Kernel PlugIn code for your hardware (see 12.5.3. Sample/Generated Kernel PlugIn Driver Code Overview and
12.5.4.3. The Generated DriverWizard Kernel PlugIn Directory).

Alternatively, you can use one of the WinDriver Kernel PlugIn samples as the basis for your Kernel PlugIn development. You can also develop your code "from scratch", if you wish.

As indicated in 12.5.3. Sample/Generated Kernel PlugIn Driver Code Overview, the Kernel PlugIn documentation in this manual focuses on the generated DriverWizard code, and the generic PCI Kernel PlugIn sample — KP_PCI, located in the WinDriver/samples/c/pci_diag/kp_pci directory. If you are using the a PCI Express card with the Xilinx Bus Master DMA (BMD) design, you can also use the KP_BMD Kernel PlugIn sample as the basis for your development; the WinDriver/samples/c/xilinx/bmd_design directory contains all the relevant sample files — see the Xilinx BMD Kernel PlugIn directory structure note at the end of (12.5.4.1. pci_diag and kp_pci Sample Directories).

The following is a step-by-step guide to creating your Kernel PlugIn driver.

13.1. Determine Whether a Kernel PlugIn is Needed

The Kernel PlugIn should be used only after your driver code has been written and debugged in the user mode. This way, all of the logical problems of creating a device driver are solved in the user mode, where development and debugging are much easier.

Determine whether a Kernel PlugIn should be written by consulting Chapter 11: Improving PCI Performance, which explains how to improve the performance of your driver. In addition, the Kernel PlugIn affords greater flexibility, which is not always available when writing the driver in the user mode (specifically in regard to the interrupt handling).

13.2. What programming languages can be used with a Kernel PlugIn?

The Kernel PlugIn is a kernel module, and therefore must be written in the C language. The user application that calls the Kernel PlugIn can be of any langauge supported by WinDriver: C, C#.NET, Visual Basic.NET, Python, Java.

When generating code in the DriverWizard in languages other than C with Kernel PlugIn, the Kernel PlugIn code will still be generated in C.

13.3. Prepare the User-Mode Source Code

Please follow this step:

  • Isolate the functions you need to move into the Kernel PlugIn.
  • Remove any platform-specific code from the functions. Use only functions that can also be used from the kernel.
  • Recompile your driver in the user mode.
  • Debug your driver in user mode again to see that your code still works after changes have been made.

Keep in mind that the kernel stack is relatively limited in size. Therefore, code that will be moved into the Kernel PlugIn should not contain static memory allocations. Use the malloc() function to allocate memory dynamically instead. This is especially important for large data structures.

If the user-mode code that you are porting to the kernel accesses memory addresses directly using the user-mode mapping of the physical address returned from the low-level WD_CardRegister() function — note that in the kernel you will need to use the kernel mapping of the physical address instead (the kernel mapping is also returned by WD_CardRegister() ).

When using the API of the WDC library to access memory, you do not need to worry about this, since this API ensures that the correct mapping of the memory is used depending on whether the relevant APIs are used from the user mode or from the kernel mode.

13.4. Create a New Kernel PlugIn Project

As indicated above, you can use DriverWizard to generate a new Kernel PlugIn project (and a corresponding user-mode project) for your device (recommended), or use one of the WinDriver Kernel PlugIn samples as the basis for your development.

⚠ Attention

‍To successfully build a Kernel PlugIn project using MS Visual Studio, the path to the project directory must not contain any spaces.

If you select to start your development with the KP_PCI sample, follow these steps:

  • Make a copy of the WinDriver/samples/c/pci_diag/kp_pci directory. For example, to create a new Kernel PlugIn project called KP_MyDrv, copy WinDriver/samples/c/pci_diag/kp_pci to WinDriver/samples/c/mydrv.
  • Change all instances of "KP_PCI" and "kp_pci", in all the Kernel PlugIn files in your new directory, to "KP_MyDrv" and "kp_mydrv" (respectively).

The names of the KP_PCI_xxx() functions in the kp_pci.c files do not have to be changed, but the code will be clearer if you use your selected driver name in the function names.

  • Change all occurrences of "KP_PCI" in file names to "kp_mydrv".
  • To use the shared pci_lib library API from your Kernel PlugIn driver and user-mode application, copy the pci_lib.h and pci_lib.c files from the WinDriver/samples/c/pci_diag directory to your new mydrv directory.

You can change the names of the library functions to use your driver's name (MyDrv) instead of "PCI", but note that in this case you will also need to modify the names in all calls to these functions from your Kernel PlugIn project and usermode application. If you do not copy the shared library to your new project, you will need to modify the sample Kernel PlugIn code and replace all references to the PCI_xxx library APIs with alternative code.

  • Modify the files and directory paths in the project and make files, and the #include paths in the source files, as needed (depending on the location in which you selected to save your new project directory).
  • To use the pci_diag user-mode application, copy WinDriver/samples/c/pci_diag/pci_diag.c and the relevant pci_diag project, solution, or make files to your mydrv directory, rename the files (if you wish), and replace all pci_diag references in the files with your preferred usermode application name.

To use the solution files, also replace the references to "KP_PCI" in the files with your new Kernel PlugIn driver, e.g., "KP_MyDrv". Then modify the sample code to implement your desired driver functionality.

For a general description of the sample and generated Kernel PlugIn code and its structure,
see 12.5.3. Sample/Generated Kernel PlugIn Driver Code Overview and
12.5.4. Kernel PlugIn Sample/Generated Code Directory Structure (respectively).

13.5. Open a Handle to the Kernel PlugIn

To open a handle to a Kernel PlugIn driver, WD_KernelPlugIn() needs to be called from the user mode. This low-level function is called from WDC_KernelPlugInOpen() — when it is called with the name of a Kernel PlugIn driver.

When using the high-level WDC API you should use the following method to open a Kernel PlugIn handle:

  • First open a regular device handle — by calling the relevant WDC_PciDeviceOpen() / WDC_IsaDeviceOpen() function without the name of a Kernel PlugIn driver. Then call WDC_KernelPlugInOpen(), passing to it the handle to the opened device. WDC_KernelPlugInOpen() opens a handle to the Kernel PlugIn driver, and stores it within the kerPlug field of the provided device structure .
  • Open a handle to the device, using the relevant WDC_PciDeviceOpen() / WDC_IsaDeviceOpen() function, and pass the name of a Kernel PlugIn driver within the function's pcKPDriverName parameter. The device handle returned by the function will also contain (within the kerPlug field) a Kernel PlugIn handle opened by the function.

⚠ Attention

‍This method cannot be used to open a handle to a 64-bit Kernel PlugIn driver from a 32-bit application, or to open a Kernel PlugIn handle from a .NET application.

To ensure that your code works correctly in all the supported configurations, use the first method described above.

The generated DriverWizard and the sample pci_diag shared library (xxx_lib.c / pci_lib.c) demonstrate how to open a handle to the Kernel PlugIn — see the generated/sample XXX_DeviceOpen() / PCI_DeviceOpen() library function (which is called from the generated/sample xxx_diag/pci_diag user-mode application).

The handle to the Kernel PlugIn driver is closed when the WD_KernelPlugInClose() function is called from the user mode.

When using the low-level WinDriver API, this function is called directly from the user-mode application.

When using the high-level WDC API , the function is called automatically when calling WDC_PciDeviceClose() / WDC_IsaDeviceClose() ) with a device handle that contains an open Kernel PlugIn handle. The function is also called by WinDriver as part of the application cleanup, for any identified open Kernel PlugIn handles.

13.6. Set Interrupt Handling in the Kernel PlugIn

Kindly follow these instructions:

  • When calling WDC_IntEnable() (after having opened a handle to the Kernel PlugIn driver, as explained in 13.5. Open a Handle to the Kernel PlugIn), set the fUseKP function parameter to TRUE to indicate that you wish to enable interrupts in the Kernel PlugIn driver with which the device was opened.

The generated DriverWizard and the sample pci_diag shared library (xxx_lib.c / pci_lib.c) demonstrate how this should be done — see the generated/sample XXX_IntEnable()/PCI_IntEnable() library function (which is called from the generated/sample xxx_diag/pci_diag user-mode application).

If you are not using the WDC_xxx API , in order to enable interrupts in the Kernel PlugIn call WD_IntEnable() or InterruptEnable() (which calls WD_IntEnable()), and pass the handle to the Kernel PlugIn driver that you received from WD_KernelPlugInOpen() (within the hKernelPlugIn field of the WD_KERNEL_PLUGIN structure that was passed to the function).

You can implement this function to set the interrupt context that will be passed to the high-IRQL and DPC Kernel PlugIn interrupt handler routines, as well as write to the device to actually enable the interrupts in the hardware, for example, or implement any other code required in order to correctly enable your device's interrupts.

  • Move the implementation of the user-mode interrupt handler, or the relevant portions of this implementation, to the Kernel PlugIn's interrupt handler functions. High-priority code, such as the code for acknowledging (clearing) level-sensitive interrupts, should be moved to the relevant high-IRQL handler — KP_IntAtIrql (legacy interrupts) or KP_IntAtIrqlMSI (MSI/MSI-X) — which runs at high interrupt request level.

Deferred processing of the interrupt can be moved to the relevant DPC handler — KP_IntAtDpc — which will be executed once the high-IRQL handler completes it processing and returns TRUE. You can also modify the code to make it more efficient, due to the advantages of handling the interrupts directly in the kernel, which provides you with greater flexibility (e.g., you can read from a specific register and write back the value that was read, or toggle specific register bits). For a detailed explanation on how to handle interrupts in the kernel using a Kernel PlugIn, refer to 12.5.5. Handling Interrupts in the Kernel PlugIn of the manual.

13.7. Set I/O Handling in the Kernel PlugIn

Perform the next steps:

  • Move your I/O handling code (if needed) from the user mode to the Kernel PlugIn message handler — KP_Call() .
  • To activate the kernel code that performs the I/O handling from the user mode, call WDC_CallKerPlug() or the low-level WD_KernelPlugInCall() function with a relevant message for each of the different functionality that you wish to perform in the Kernel PlugIn. Implement a different message for each functionality.
  • Define these messages in a header file that is shared by the user-mode application (which will send the messages) and the Kernel PlugIn driver (that implements the messages).

In the sample/generated DriverWizard Kernel PlugIn projects, the message IDs and other information that should be shared by the user-mode application and Kernel PlugIn driver are defined in the pci_lib.h/xxx_lib.h shared library header file.

13.8. Compile Your Kernel PlugIn Driver

The Kernel PlugIn is not backward compatible. Therefore, when switching to a different version of WinDriver, you need to rebuild your Kernel PlugIn driver using the new version.

13.8.1. Windows Kernel PlugIn Driver Compilation

The sample WinDriver\samples\c\pci_diag\kp_pci Kernel PlugIn directory and the generated DriverWizard Kernel PlugIn <project_dir>\kermode directory (where <project_dir> is the directory in which you selected to save the generated driver project) contain the following Kernel PlugIn project files (where xxx is the driver name — pci for the sample / the name you selected when generating the code with the wizard):

  • x86 — 32-bit project files:
    • msdev_<version>\kp_xxx.vcxproj — 32-bit MS Visual Studio project file (where <version> signifies the IDE version — e.g., "2019")
    • win_gcc/makefile — 32-bit Windows GCC (MinGW/Cygwin) makefile
  • amd64 — 64-bit project files:
    • msdev_<version>\kp_xxx.vcxproj — 64-bit MS Visual Studio project file (where <version> signifies the IDE version — e.g., "2019")
    • win_gcc/makefile — 64-bit Windows GCC (MinGW/Cygwin) makefile
  • arm64 — 64-bit ARM64 project files:
    • msdev_<version>\kp_xxx.vcxproj — 64-bit ARM64 MS Visual Studio project file (where <version> signifies the IDE version — e.g., "2019")
    • win_gcc/makefile — 64-bit ARM64 Windows GCC (MinGW/Cygwin) makefile

The sample WinDriver\samples\c\pci_diag directory and the generated <project_dir> directory contain the following project files for the user-mode application that drives the respective Kernel PlugIn driver (where xxx is the driver name — pci for the sample / the name you selected when generating the code with the wizard):

  • x86 — 32-bit project files:
    • msdev_<version>\xxx_diag.vcxproj — 32-bit MS Visual Studio project file (where <version> signifies the IDE version — e.g., "2019")
    • win_gcc/makefile — 32-bit Windows GCC (MinGW/Cygwin) makefile
  • amd64 — 64-bit project files:
    • msdev_<version>\xxx_diag.vcxproj — 64-bit MS Visual Studio project file (where <version> signifies the IDE version — e.g., "2019")
    • win_gcc/makefile — 64-bit Windows GCC (MinGW/Cygwin) makefile
  • arm64 — 64-bit ARM64 project files:
    • msdev_<version>\xxx_diag.vcxproj — 64-bit ARM MS Visual Studio project file (where <version> signifies the IDE version — e.g., "2019")
    • win_gcc/makefile — 64-bit ARM64 Windows GCC (MinGW/Cygwin) makefile

The msdev_<version> MS Visual Studio directories listed above also contain xxx_diag.sln solution files that include both the Kernel PlugIn and user-mode projects.

If you used DriverWizard to generate your code and you selected to generate a dynamic link library (DLL), the generated <project_dir> directory will also have a libproj DLL project directory. This directory has x86 (32-bit) and/or amd64 (64-bit) and/or arm64 (64-bit ARM64) directories that contain msdev_<version> directories for your selected IDEs, and each IDE directory has an xxx_libapi.vcproj project file for building the DLL. The DLL is used from the wizard-generated user-mode diagnostics project (xxx_diag.vcxproj).

To build your Kernel PlugIn driver and respective user-mode application on Windows, follow these steps:

  • Verify that the Windows Driver Kit (WDK) is installed.
  • Build the Kernel PlugIn SYS driver (kp_pci.sys — sample / kp_xxx.sys — wizard generated code):
    • Using MS Visual Studio — Start Microsoft Visual Studio, and do the following:

From your driver project directory, open the Visual Studio Kernel PlugIn solution file — <project_dir>\msdev_<version>\xxx_diag.sln, where <project_dir> is your driver project directory (pci_diag for the sample code / the directory in which you selected to save the generated DriverWizard code), msdev_<version> is your target Visual Studio directory (e.g., msdev_2019), and xxx is the driver name (pci for the sample / the name you selected when generating the code with the wizard).

When using DriverWizard to generate code for MS Visual Studio, you can use the IDE to Invoke option to have the wizard automatically open the generated solution file in your selected IDE, after generating the code files.

  1. To successfully build a Kernel PlugIn project using MS Visual Studio, the path to the project directory must not contain any spaces.
    • Set the Kernel PlugIn project (kp_pci.vcxproj / kp_xxx.vcxproj) as the active project.
    • Select the active configuration for your target platform: From the Build menu, choose Configuration Manager..., and select the desired configuration.
  2. To build the driver for multiple operating systems, select the lowest OS version that the driver must support from the Project Properties -> Driver Settings -> Target OS Version, select Windows 10 as it is the lowest version currently supported by WinDriver.
    • Build your driver: Build the project from the Build menu or using the relevant shortcut key (e.g. CTRL+SHIFT+B in Visual Studio).
  3. Build the user-mode application that drives the Kernel PlugIn driver (pci_diag.exe — sample / xxx_diag.exe — wizard-generated code):
    • Using MS Visual Studio —
  4. Set the user-mode project (pci_diag.vcxprojsample / xxx_diag.vcxproj — wizard generated code) as the active project. Then build the application: Build the project from the Build menu or using the relevant shortcut key (e.g., CTRL+SHIFT+B in Visual Studio 2017).
    • Using Windows GCC —

Do the following from your selected Windows GCC development environment (MinGW/Cygwin):

Change directory to your target Windows GCC application directory — <project_dir>/<CPU>/win_gcc, where <project_dir> is your driver project directory (pci_diag for the sample code / the directory in which you selected to save the generated DriverWizard code), and <CPU> is the target CPU architecture (x86 for x86 platforms, amd64 for x64 platforms and arm64 for ARM64 platforms).

For example:

  • When building a 32-bit version of the sample pci_diag application, which drives the sample KP_PCI driver —
$ cd WinDriver/samples/c/pci_diag/x86/win_gcc
  • When building a 64-bit wizard-generated user-mode application that drivers a wizard-generated Kernel PlugIn driver —
$ cd <project_dir>/amd64/win_gcc
  • When building a 64-bit ARM64 wizard-generated user-mode application that drivers a wizard-generated Kernel PlugIn driver —
$ cd <project_dir>/arm64/win_gcc

— where <project_dir> signifies the path to your generated DriverWizard project directory (for example, ~/WinDriver/wizard/my_projects/my_kp).

  • Build the application using the make command.

13.8.2. Linux Kernel PlugIn Driver Compilation

To build your Kernel PlugIn driver and respective user-mode application on Linux, follow these steps:

  • Open a shell terminal.
  • Change directory to your Kernel PlugIn directory.

For example:

  • When building the sample KP_PCI driver —
$ cd WinDriver/samples/c/pci_diag/kp_pci
  • When building a wizard-generated Kernel PlugIn driver —
$ cd <project_dir>/kermode/linux/

— where <project_dir> signifies the path to your generated DriverWizard project directory (for example, ~/WinDriver/wizard/my_projects/my_kp).

  • Generate the makefile using the configure script:
$ ./configure

If you have renamed the WinDriver kernel module, be sure to uncomment the following line in your Kernel PlugIn configuration script (by removing the pound sign — "#"), before executing the script, in order to build the driver with the -DWD_DRIVER_NAME_CHANGE flag (see 17.2.2. Linux Driver Renaming):

# ADDITIONAL_FLAGS="-DWD_DRIVER_NAME_CHANGE"

The configuration script creates a makefile based on the running kernel. You may select to use another installed kernel source, by executing the script with the --with-kernel-source=<path> option, where <path> is the full path to the kernel source directory — e.g., /usr/src/linux. If the Linux kernel version is 2.6.26 or higher, the configuration script generates makefiles that use kbuild to compile the kernel modules.

./configure --help
  • Build the Kernel PlugIn module using the make command.

This command creates a new LINUX.<kernel version>.<CPU> directory, which contains the created kp_xxx_module.o/.ko driver.

  • Change directory to the directory that holds the makefile for the sample user-mode diagnostics application.

For the KP_PCI sample driver —

$ cd ../LINUX/

For the generated DriverWizard Kernel PlugIn driver —

$ cd ../../linux/
  • Compile the sample diagnostics program using the make command.

13.8.3. Porting a Kernel PlugIn project developed prior to version 10.3.0, to support working with a 32-bit user-mode application and a 64-bit Kernel PlugIn driver

Beginning with version 10.3.0 of WinDriver, you can communicate with a 64-bit Kernel PlugIn driver from a 32-bit user-mode application. To support such a configuration, you need to keep in mind the following points:

The handle cannot be opened using one of the WDC device-open functions, e.g. WDC_PciDeviceOpen(), by calling it with the name of a Kernel PlugIn driver. In all Kernel PlugIn configurations, except for a 64-bit driver that interacts with a 32-bit application, either of these methods can be used to open a handle to the Kernel PlugIn from the application.

  • The Kernel PlugIn driver must implement a KP_Open callback that correctly translates the 32-bit data received from the user mode, into 64-bit kernel data.

The Kernel PlugIn driver’s KP_Init() function must set this callback in the funcOpen_32_64 field of the KP_INIT structure that it initializes. This field was added in version 10.3.0 of WinDriver.

The callback set in the KP_INIT funcOpen_32_64 field, is called by WinDriver when a 32-bit application opens a handle to a Kernel PlugIn driver. This callback will hereby be referred to as the funcOpen_32_64 callback. The callback set in the KP_INIT funcOpen field is the standard KP_Open callback, which is called by WinDriver whenever an application opens a Kernel PlugIn handle, except for the specific configuration in which the funcOpen_32_64 callback is used. This callback will hereby be referred to as the funcOpen callback. When writing your Kernel PlugIn driver, you can select whether to provide both of these callback types or just one of them — depending on how you plan to build the driver and how you expect it to be used. For example, if you are not planning to use the driver on a 64-bit operating system, you do not need to implement and set funcOpen_32_64.

In version 10.3.0 and above of WinDriver, the generated DriverWizard Kernel PlugIn projects, and the sample PCI Kernel PlugIn project (kp_pci), are written so as to support all types of user-mode and Kernel PlugIn configurations, including using a 32-bit application with a 64-bit Kernel PlugIn.

To modify code developed with earlier versions of WinDriver to afford the same support, follow these steps:

In the user-mode code, do the following:

  • Separate the steps of opening a handle to the device and opening a handle to the Kernel PlugIn. In version 10.2.1 and below of WinDriver, the sample and generated code used the WDC_PciDeviceOpen() / WDC_IsaDeviceOpen() functions (e.g., WDC_PciDeviceOpen()) to open both of these handles. As explained above, this method cannot be used if you also wish to work with a 32-bit application and a 64-bit Kernel PlugIn driver. If your code uses this method, modify the call to WDC_PciDeviceOpen() / WDC_IsaDeviceOpen() so that you pass NULL for the last two parameters — pcKPDriverName and pKPOpenData.
  • Use WDC_KernelPlugInOpen() to open the handle to the Kernel PlugIn driver.

For the function’s hDev parameter, pass the same handle whose address was passed as the phDev parameter of the device-open function.

⚠ Attention

‍You are passing hDev — WDC_DEVICE_HANDLE — and not &hDev — WDC_DEVICE_HANDLE* — like in the first parameter of WDC_PciDeviceOpen() / WDC_IsaDeviceOpen().

For the pcKPDriverName parameter, pass the name of your Kernel PlugIn driver (as previously done for the equivalent device-open function parameter, if you used it to also open the Kernel PlugIn handle).

For the pKPOpenData parameter, pass the data you wish to pass down from the application to the Kernel PlugIn (similar to the device-open function parameter of the same name).

In the older WinDriver sample and generated code, the device handle was passed as the open-data. However, to reduce the work that would need to be done in the Kernel PlugIn to convert the 32-bit data to64-bit, the newer code now passes only the necessary information to the Kernel PlugIn, we recommend that you do the same:

  • Define the following types, to be used both by the user-mode and Kernel PlugIn code (in the sample and generated code, these types are defined in the xxx_lib.h file):
/* Device address description struct */
typedef struct {
DWORD dwNumAddrSpaces; /* Total number of device address spaces */
WDC_ADDR_DESC *pAddrDesc; /* Array of device address spaces information */
} PCI_DEV_ADDR_DESC;
Address space information struct.
Definition: wdc_defs.h:27
  • Allocate a variable of type PCI_DEV_ADDR_DESC and set its fields according to the information returned by the previous call to the WDC_PciDeviceOpen() / WDC_IsaDeviceOpen() function. In the sample and generated code, this is done in the xxx_lib.c file as shown below:
PCI_DEV_ADDR_DESC devAddrDesc;
...
dwStatus = WDC_PciDeviceOpen(&hDev, pDeviceInfo, pDevCtx, NULL, NULL, NULL);
...
pDev = hDev;
devAddrDesc.dwNumAddrSpaces = pDev->dwNumAddrSpaces;
devAddrDesc.pAddrDesc = pDev->pAddrDesc;
WDC_KernelPlugInOpen(hDev, KP_PCI_DRIVER_NAME, &devAddrDesc);
#define NULL
Definition: kpstdlib.h:268
DWORD dwNumAddrSpaces
Total number of device's address spaces.
Definition: wdc_defs.h:50
WDC_ADDR_DESC * pAddrDesc
Array of device's address spaces information.
Definition: wdc_defs.h:53
Device information struct.
Definition: wdc_defs.h:46
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,...
DWORD DLLCALLCONV WDC_KernelPlugInOpen(_In_ WDC_DEVICE_HANDLE hDev, _In_ const CHAR *pcKPDriverName, _In_ PVOID pKPOpenData)
Opens a handle to a Kernel PlugIn driver.

In the Kernel PlugIn driver code, do the following:

  • Modify your funcOpen callback.

If you have changed the type of the open-data that is passed from the user mode to the driver, you need to adjust the implementation of your open-callback accordingly. If your existing code was based on the sample or generated Kernel PlugIn code in earlier versions of WinDriver, and you have selected to pass the address of a PCI_DEV_ADDR_DESC struct as your Kernel PlugIn open-data modify your funcOpen callback as follows:

  • Replace the PWDC_DEVICE hDev; definition with PCI_DEV_ADDR_DESC *pDevAddrDesc; and replace instances of WDC_DEVICE and pDev in the function with PCI_DEV_ADDR_DESC and pDevAddrDesc respectively.
  • Remove the temp variable definition and replace the two COPY_FROM_USER calls (which use the temp variable) with the following call: COPY_FROM_USER(pDevAddrDesc, pOpenData, dwSize).
  • As an additional unrelated fix to previous sample and generated WinDriver funcOpen implementations, move the call to the library initialization function (PCI_LibInit() in the PCI sample / XXX_LibInit in the generated DriverWizard code — where XXX is the name of your driver project) to the beginning of the function — right after the variable declarations and before the first call to the Kernel PlugIn trace messages function (KP_PCI_Trace() in the PCI sample /KP_XXX_Trace() in the wizard-generated code).
  • Create your funcOpen_32_64 callback.

Copy your funcOpen callback function, and rename it. The renamed function will be modified to implement your funcOpen_32_64 callback — for opening a handle to a64-bit Kernel PlugIn driver from a 32-bit application. In the WinDriver/samples/c/pci_diag/kp_pci/kp_pci.c sample, the funcOpen function is named KP_PCI_Open(), and the funcOpen_32_64 function is named KP_PCI_Open_32_64(). The generated DriverWizard Kernel PlugIn open functions are named in a similar manner, except that “PCI” is replaced with your selected driver project name.

⚠ Attention

‍If you only plan to support a 32-bit application and 64-bit Kernel PlugIn configuration, you can skip this step, and in the next step modify your KP_Open callback to use it as your funcOpen_32_64 callback, and then assign this callback to the KP_INIT funcOpen_32_64 field, instead of to the funcOpenfield.

However, to keep your code portable, it’s best to implement both types of callbacks.

  • Edit your Kernel PlugIn code to handle the translation of the 32-bit data passed from the user mode, into 64-bit kernel data:
  • Add a 32-bit definition of the Kernel PlugIn open-data type.
  • In your funcOpen_32_64 callback, translate the 32-bit open-date received from the user mode, into the equivalent 64-bit type.

For example, if your original code was based on the sample or generated Kernel PlugIn code in earlier versions of WinDriver, and you have selected to pass the address of a PCI_DEV_ADDR_DESC struct as your Kernel PlugIn open-data modify the code as follows:

  1. Add the following definitions at the beginning of the file:
#define PTR32 DWORD
typedef struct WDC_DEV_ADDR_DESC_32B {
DWORD dwNumAddrSpaces; /* Total number of device address spaces */
PTR32 pAddrDesc; /* Array of device address spaces information */
} WDC_DEV_ADDR_DESC_32B, *PWDC_DEV_ADDR_DESC_32B;
#define PTR32
Definition: kp_pci.c:46
  1. Modify your funcOpen_32_64 callback as follows: Add the following definition:
WDC_DEV_ADDR_DESC_32B devAddrDesc_32;

Then add the following lines after the kpOpenCall field assignments, in order to copy to the kernel mode the 32-bit data that was received from the user mode:

/* Copy device information sent from a 32-bit user application */
COPY_FROM_USER(&devAddrDesc_32, pOpenData, sizeof(PCI_DEV_ADDR_DESC_32B));

Then replace the following line — COPY_FROM_USER(pDevAddrDesc, pOpenData, dwSize) — with these lines, in order to copy the 32-bit data into a 64-bit structure:

/* Copy the 32-bit data to a 64-bit struct */
pDevAddrDesc->dwNumAddrSpaces = devAddrDesc_32.dwNumAddrSpaces;

Replace the second COPY_FROM_USER() call — COPY_FROM_USER(pAddrDesc, pDevAddrDesc->pAddrDesc, dwSize) — with the following:

COPY_FROM_USER(pAddrDesc, (PVOID)(KPTR)devAddrDesc_32.pAddrDesc, dwSize);
UINT32 KPTR
Definition: windrvr.h:372

Notify WinDriver of your funcOpen_32_64 callback, by assigning your funcOpen_32_64 callback to the funcOpen_32_64 field of the KP_INIT struct that is initialized in KP_Init():

kpInit->funcOpen_32_64 = <Your funcOpen_32_64 callback >;

For example, this is the assignment line from the updated kp_pci.c sample:

kpInit->funcOpen_32_64 = KP_PCI_Open_32_64;
BOOL __cdecl KP_PCI_Open_32_64(KP_OPEN_CALL *kpOpenCall, HANDLE hWD, PVOID pOpenData, PVOID *ppDrvContext)
KP_PCI_Open_32_64 is called when WD_KernelPlugInOpen() is called from a 32-bit user mode application ...
Definition: kp_pci.c:234

⚠ Attention

‍If you select to only implement a funcOpen_32_64 callback, and not to implement a standard funcOpencallback, remove the assignment to the KP_INIT funcOpen field in KP_Init().

  • Edit all remaining device-open type handling.

If you have changed the type of the open-data that is passed from the user mode to the driver, you need to edit the related type handling in your Kernel PlugIn code. For example, if you previously passed PWDC_DEVICE as the open-data type, and you now pass PCI_DEV_ADDR_DESC, and your code was previously developed using the a WinDriver sample or wizard-generated Kernel PlugIn driver, you would need to make the following additional changes:

  • In KP_Close, replace this line — free(((PWDC_DEVICE)pDrvContext)->pAddrDesc) — with the following, to free the correct type:
if (((PCI_DEV_ADDR_DESC *)pDrvContext)->pAddrDesc)
free(((PCI_DEV_ADDR_DESC *)pDrvContext)->pAddrDesc);
void __cdecl free(void *buf)
  • In KP_IntAtIrql, replace this line — PWDC_DEVICE pDev = (PWDC_DEVICE)pIntContext; — with the following:
PCI_DEV_ADDR_DESC *pDevAddrDesc = (PCI_DEV_ADDR_DESC *)pIntContext;

and edit any remaining pDev instances in the function. For example, replace this line — pAddrDesc = &pDev->pAddrDesc[INTCSR_ADDR_SPACE]; — with the following:

pAddrDesc = &pDevAddrDesc->pAddrDesc[INTCSR_ADDR_SPACE];

13.9. Install Your Kernel PlugIn Driver

13.9.1. Windows Kernel PlugIn Driver Installation

Verify that the WinDriver kernel module is installed: Before installing your Kernel PlugIn driver, verify that the WinDriver driver — windrvr<version>.sys/.o/.ko in the newer WinDriver versions (e.g., windrvr1630.sys/.o/.ko) — is installed, since the Kernel PlugIn module depends on the WinDriver module for its successful operation. You can run the Debug Monitor to verify that WinDriver is loaded.

Install your Kernel PlugIn driver:

  • Remember to copy your Kernel PlugIn driver (my_kp.sys/.kext/.o/.ko) to the operating system’s drivers/modules directory before attempting to install the driver.
  • Don’t forget to install your Kernel PlugIn driver before running your application:On Windows: Use the wdreg installation utility (or wdreg_gui/wdreg_frontend — depending on the WinDriver version and OS that you are using) to install the driver. To install a my_kp.sys driver, run this command:
    wdreg -name MY_KP install

⚠ Attention

‍The Kernel PlugIn is not backward compatible. Therefore, when switching to a different version of WinDriver, you need to rebuild your Kernel PlugIn driver using the new version.

Driver installation on Windows requires administrator privileges.

The following steps can also be performed with the wdreg_frontend.exe GUI application. For additional information, refer to 16.3. The wdreg_frontend utility.

  • Copy the driver file (xxx.sys) to the target platform's drivers directory: windir%\system32\drivers (e.g., C:\WINDOWS\system32\drivers).
  • Register/load your driver, using the wdreg.exe or wdreg_gui.exe utility:

In the following instructions, KP_NAME stands for your Kernel PlugIn driver's name, without the .sys extension. To install your driver, run this command:

WinDriver\util\wdreg -name KP_NAME install

Kernel PlugIn drivers are dynamically loadable — i.e., they can be loaded and unloaded without reboot. For additional information, refer to 16.2.3. Dynamically Loading/Unloading Your Kernel PlugIn Driver.

13.9.2. Linux Kernel PlugIn Driver Installation

On Linux, you do not need to copy the driver file, since the driver installation (make install) will handle this for you:

  • Change directory to your Kernel PlugIn driver directory.

For example, when installing the sample KP_PCI driver, run

$ cd WinDriver/samples/c/pci_diag/kp_pci

When installing a driver created using the Kernel PlugIn files generated by DriverWizard, run the following command, where <path> signifies the path to your generated DriverWizard project directory (e.g., ~/WinDriver/wizard/my_projects/my_kp):

$ cd <path>/kermode/
  • Execute the following command to install your Kernel PlugIn driver:

The following command must be executed with root privileges.

# make install

Kernel PlugIn drivers are dynamically loadable — i.e., they can be loaded and unloaded without reboot. For additional information, refer to 16.4.1. Dynamically Loading/Unloading Your Kernel PlugIn Driver.

13.10. FAQ

13.10.1. I would like to execute in the kernel some pieces of code written in languages other than C/C++ (Python/Java/C#/Visual Basic.NET), using the Kernel PlugIn. Is it possible?

The Kernel PlugIn is a kernel module, and therefore must be written in the C language. However, the user application that calls the Kernel PlugIn can be of any langauge supported by WinDriver: C, C#.NET, Visual Basic.NET, Java, Python. When generating code in the DriverWizard in languages other than C with Kernel PlugIn, the Kernel PlugIn code will still be generated in C. This user application would communicate with the C-language Kernel PlugIn.

If you select to implement such a design, in order to ensure the correct interaction between the user mode and Kernel PlugIn applications, you will need to implement a file in your programming language that contains common definitions for the Kernel PlugIn and the user mode applications — as done in the library header files of the sample and DriverWizard generated Kernel PlugIn C projects (e.g. WinDriver\samples\pci_diag\pci_lib.h, used by the sample KP_PCI driver). WinDriver implements these files for you when you generate code with DriverWizard, and they should be generated in your project directory, for you to start as fast and easy as possible.

For further examples check: WinDriver/include/windrvr.h C header file and the corresponding WinDriver/samples/python/wdlib/windrvr.py (Python), WinDriver/src/wdapi_dotnet/windrvr.h (.NET), WinDriver/lib/wdapi_javaXXX.jar (Java) as examples of implementing the same code both in C and other supported/ languages.

13.10.2. How do I allocate locked memory in the kernel that can be used inside the interrupt handler?

WinDriver implements malloc() and free() in its Kernel PlugIn library (kp_nt1630.lib/kp_linux1630.o_shipped), to which your Kernel PlugIn code is linked.

These functions are implemented to allocate locked memory when called from the kernel mode, so you can use that memory in your interrupt handler as well.

13.10.3. How do I handle shared PCI interrupts in the Kernel PlugIn?

PCI interrupts are normally handled at the kernel level. Therefore, the best way to handle PCI interrupts with WinDriver is using the Kernel PlugIn feature, which lets you implement code directly at the kernel level. This is specifically true when handling interrupts that are shared between different hardware devices — a very common phenomenon in the case of PCI interrupts, which are by definition sharable.

The Kernel PlugIn consists of two interrupt handler functions:

⚠ Attention

‍Replace “KP_” in the function names below with the name of your Kernel PlugIn driver. For example, the Kernel PlugIn interrupt handler functions for the sample KP_PCI driver are KP_PCI_IntAtIrql and KP_PCI_IntAtDpc.

  • KP_IntAtIrql — this function is executed in the kernel at high IRQ (Interrupt Request) level immediately upon the detection of an interrupt. The function should contain the write/read commands for clearing the source of the interrupt (acknowledging the interrupt) and any additional high-priority interrupt-handling code.

If additional deferred kernel- or user-mode interrupt processing is required, KP_IntAtIrql should return TRUE, in which case the deferred KP_IntAtDpc routine (see below) will be executed once the high-level processing is completed. Otherwise, the function should return FALSE and KP_IntAtDpc (as well as any existing user-mode interrupt-handler routine) will not be executed. The generated and sample WinDriver Kernel PlugIn projects, for example, schedule deferred interrupt processing once every five interrupts by implementing code that returns TRUE only for every fifth KP_IntAtIrql call.

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

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

  • Generate a Kernel PlugIn project using WinDriver’s DriverWizard utility. When generating C code with the DriverWizard, you will be given the option to generate Kernel PlugIn code — simply check the relevant check-box and proceed to generate the code. Alternatively, you can also use the generic WinDriver Kernel PlugIn sample — KP_PCI(WinDriver\samples\c\pci_diag\kp_pci\kp_pci.c) — as the basis for your Kernel PlugIn project. If you are developing a driver for a Xilinx PCI Express card with Bus Master DMA (BMD) design, you can use the sample Kernel PlugIn driver for this design — KP_BMD (WinDriver\samples\c\xilinx\bmd_design\kp\kp_bmd.c) — or select to generate customized DriverWizard code for this design, including Kernel Plugin code.

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

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

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

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

BOOL __cdecl KP_XXX_IntAtIrql(PVOID pIntContext, BOOL *pfIsMyInterrupt)
{
XXX_HANDLE hXXX = (XXX_HANDLE) pIntContext;
DWORD data = 0;
PVOID pData = NULL;
DWORD addrSpace;
WD_ITEMS *pItem;
addrSpace = XXX_INTCSR_SPACE;
pItem = &hXXX->cardReg.Card.Item[hXXX->addrDesc[addrSpace].index];
pData = (DWORD*)pItem->I.Mem.dwTransAddr;
(DWORD)pData += XXX_INTCSR_OFFSET;
data = dtoh32(*((DWORD*)pData));
if (data == 0xFF)
/* The interrupt was generated by our hardware */
{
/* Write 0x0 to INTCSR to acknowledge the interrupt */
*((DWORD*)pData) = dtoh32(data);
/* Accept control of the interrupt */
*pfIsMyInterrupt = TRUE;
/* Schedule deferred interrupt processing (XXX_IntAtDpc) */
return TRUE;
}
else
{
/* (Do not acknowledge the interrupt) */
/* Do not accept control of the interrupt */
*pfIsMyInterrupt = FALSE;
/* Do not schedule deferred interrupt processing */
return FALSE;
}
}
#define TRUE
Definition: kpstdlib.h:264
#define FALSE
Definition: kpstdlib.h:260
union WD_ITEMS::I I
struct WD_ITEMS::I::Mem Mem

13.10.4. What is pIntContext in the Kernel PlugIn interrupt functions?

pIntContext is private context data that is passed from KP_IntEnable to the Kernel PlugIn interrupt handler functions — KP_IntAtIrql() and KP_IntAtDpc().

13.10.5. I need to call WD_Transfer() in the Kernel PlugIn. From where do I get hWD to pass to these functions?

Jungo strongly recommends using the high-level WDC (PCI) API instead of using the low-level API.

To obtain the handle to WinDriver’s kernel module (hWD) from the Kernel PlugIn, you can call WD_Open() directly from the Kernel PlugIn.

For PCI/ISA, you can also use the hWD handle, which is passed from the user mode via WD_KernelPlugInOpen(), and received as the second argument of the Kernel PlugIn callback function KP_Open(). Alternatively, you can also pass the handle from the user mode to the Kernel PlugIn using the pData field of the WD_KERNEL_PLUGIN_CALL struct, which is used in the call to WD_KernelPlugInCall() in the user mode and results in a callback to KP_Call() in the Kernel PlugIn.

After obtaining the handle to WinDriver, the call to WD_Transfer() (or any other WinDriver API that receives a handle to WinDriver as a parameter) is the same as in the user mode.

13.10.6. A restriction in KP_IntAtIrql is to use only non-pageable memory. What does this mean?

Variables defined in the Kernel PlugIn project (such as global and local variables) are non paged. WinDriver also implements malloc() to allocate non-paged memory when used from within the kernel.

If you are using a pointer to the memory in the user mode from within the Kernel PlugIn, you need to make a copy of its contents (allocate memory and copy the data to the non-paged memory) in order to access it from your KP_IntAtIrql function, in order to ensure safe access to the data at all times.

13.10.7. How do I call WD_Transfer() in the Kernel PlugIn interrupt handler?

You can call WD_Transfer() from within the KP_IntAtIrql or KP_IntAtDpc functions (replace “KP” in the function names with your Kernel PlugIn driver name — e.g., “KP_PCI”) in order to access the hardware.

⚠ Attention

‍You can also access the memory or I/O directly from within the Kernel PlugIn interrupt functions. For direct memory access, use the kernel mapping of the memory, returned from WD_CardRegister() in cardReg.Card.Item[i].I.Mem.pTransAddr.

When calling WD_Transfer() you will need to pass as the first argument a handle to WinDriver (hWD). You can refer to the FAQ questions in this chapter to find out how to obtain a handle to WinDriver from within the Kernel PlugIn.

As specified in the aforementioned FAQ questions, you can store the handle to WinDriver in a global Kernel PlugIn variable (recommended), or pass it from one function to another. Below is an example of passing a handle to WinDriver from KP_Open() to KP_IntAtIrql:

Add the following line to KP_Open()

ppDrvContext = (PVOID) hWD;

Add the following line to KP_IntEnable()

ppIntContext = pDrvContext;

You can now use WD_Transfer() to access memory/IO from within KP_IntAtIrql. For example (IO access):

HANDLE hWD = (HANDLE) pIntContext;
BZERO(trans);
trans.cmdTrans = WP_BYTE;
trans.dwPort = 0x378;
trans.Data.Byte = 0x65;
WD_Transfer(hWD, &trans);
DWORD cmdTrans
Transfer command WD_TRANSFER_CMD.
Definition: windrvr.h:590
BYTE Byte
Use for 8 bit transfer.
Definition: windrvr.h:599
union WD_TRANSFER::@14 Data
@ WP_BYTE
Write port byte.
Definition: windrvr.h:397
#define BZERO(buf)
Definition: windrvr.h:1553
#define WD_Transfer(h, pTransfer)
Executes a single read/write instruction to an I/O port or to a memory address.
Definition: windrvr.h:1912

This will write 0x65 to port 0x378 upon interrupt. The hWD handle is passed from KP_Open() to KP_IntEnable() to KP_IntAtIrql via the context.

You can also view the generated DriverWizard Kernel PlugIn code for an example of calling WD_MultiTransfer() from KP_IntAtIrql, provided you have used the DriverWizard to define and assign the register/s for the interrupt acknowledgment before generating the code.

13.10.8. How do I share a memory buffer between Kernel PlugIn and user-mode projects for DMA or other purposes?

From your user application, use the WDS_SharedBufferAlloc() function to allocate a shared memory buffer, which can be used both from a user-mode application and a Kernel PlugIn driver.

Pass the returned ppKerBuf->pKernelAddr value from the above function to the Kernel PlugIn using the pData paramter of the function WDC_CallKerPlug(). Note that WDS_SharedBufferAlloc() is part of the Server APIs of WinDriver, these APIs require a special license.

ℹ️ Note

‍If you would like to get more information about Server APIs licensing, please send an e-mail to WinDr.nosp@m.iver.nosp@m.@jung.nosp@m.o.co.nosp@m.m, and our team will assist you.

13.10.9. If I write a new function in my SYS Kernel PlugIn driver, must it also be declared with __cdecl?

No. Only the callbacks used by WinDriver need to be declared as __cdecl.