The following sections take you through the development cycle of a Kernel PlugIn driver.
It is recommended that you first write and debug your entire driver code in the user mode. Then, if you encounter performance problems or require greater flexibility, port portions of your code to a Kernel PlugIn driver.
To build a Kernel PlugIn driver you need the following tools:
![]() | |
|
The WDK is available as part of a Microsoft Development Network (MSDN)
subscription, or from Microsoft Connect. For more information, see http://www.microsoft.com/whdc/devtools/WDK/WDKpkg.mspx. |
![]() | |
| While this is not a minimal requirement, when developing a Kernel PlugIn driver it is highly recommended that you use two computers: set up one computer as your host platform and the other as your target platform. The host computer is the computer on which you develop your driver and the target computer is the computer on which you run and test the driver you develop. |
The functions described in this section are callback functions, implemented in
the Kernel PlugIn driver, which are called when their calling event occurs
– see section 11.5.4 for details. For
example, KP_Init() [B.6.1] is the
callback function that is called when the driver is loaded and should include
any code that you want to execute upon loading.
The name of your driver is given in KP_Init(), which must be
implemented with this name. For the other callback functions, it is the
convention of this reference guide to mark these functions as
KP_xxx() functions (e.g., KP_Open()). However, when
developing your Kernel PlugIn driver you can also select different names for
these callback functions. When generating Kernel PlugIn code with the
DriverWizard, for example, the names of the callback functions (apart from
KP_Init()) conform to the following format:
KP_<Driver Name>_<Callback
Function>. For example, if you named your project
MyDevice the name of your Kernel PlugIn
KP_Open() function will be KP_MyDevice_Open().
Your KP_Init() function [B.6.1] should be
of the following prototype:
BOOL __cdecl KP_Init(KP_INIT {*}kpInit);
where KP_INIT is the following structure:
typedef struct {
DWORD dwVerWD; /* Version of the WinDriver Kernel PlugIn library */
CHAR cDriverName[12]; /* The Kernel PlugIn driver name (up to 8 chars) */
KP_FUNC_OPEN funcOpen; /* The Kernel PlugIn driver's KP_Open() function */
} KP_INIT;
This function is called once, when the driver is loaded. The
KP_INIT structure should be filled with the name of your Kernel
PlugIn and the address of your KP_Open() function [B.6.2] (see example in WinDriver/samples/pci_diag/kp_pci/kp_pci.c).
![]() | |
|
From the KP_PCI sample (WinDriver/samples/pci_diag/kp_pci/kp_pci.c):
/* KP_Init is called when the Kernel PlugIn driver is loaded.
This function sets the name of the Kernel PlugIn driver and the driver's
open callback function. */
BOOL __cdecl KP_Init(KP_INIT *kpInit)
{
/* Verify that the version of the WinDriver Kernel PlugIn library
is identical to that of the windrvr.h and wd_kp.h files */
if (WD_VER != kpInit->dwVerWD)
{
/* Re-build your Kernel PlugIn driver project with the compatible
version of the WinDriver Kernel PlugIn library (kp_nt<version>.lib)
and windrvr.h and wd_kp.h files */
return FALSE;
}
kpInit->funcOpen = KP_PCI_Open;
strcpy (kpInit->cDriverName, KP_PCI_DRIVER_NAME);
return TRUE;
}
Note that the driver name was set using a preprocessor definition. This definition is found in the WinDriver/samples/pci_diag/pci_lib.h header file, which is shared by the pci_diag user-mode application and the KP_PCI Kernel PlugIn driver:
/* Kernel PlugIn driver name (should be no more than 8 characters) */ #define KP_PCI_DRIVER_NAME "KP_PCI"
Your KP_Open() function [B.6.2] should be
of the following prototype:
BOOL __cdecl KP_Open(KP_OPEN_CALL {*}kpOpenCall, HANDLE hWD,
PVOID pOpenData, PVOID {*}ppDrvContext);
This callback is called when the user-mode application calls
WDC_xxxDeviceOpen()
(PCI: [B.3.10],
PCMCIA: [B.3.11],
ISA: [B.3.12]]) with the name of a Kernel
PlugIn driver, or when it calls the low-level
WD_KernelPlugInOpen() function (see the WinDriver PCI Low-Level API Reference), which
is called by the wrapper WDC_xxxDeviceOpen() functions.
In the KP_Open() function, define the callbacks that you wish to
implement in the Kernel PlugIn.
The following is a list of the callbacks that can be implemented:
| Callback | Functionality |
|---|---|
KP_Close() [B.6.3] |
Called when the user-mode application calls
WDC_xxxDeviceClose()
(PCI: [B.3.13],
PCMCIA: [B.3.14],
ISA: [B.3.15] for a device that was
opened with a Kernel PlugIn driver, or when it calls the low-level
WD_KernelPlugInClose() function (see the WinDriver PCI Low-Level API Reference),
which is called by the wrapper WDC_xxxDeviceClose() functions.
|
KP_Call() [B.6.4] |
Called when the user-mode application calls the
WDC_CallKerPlug() function [B.3.18] or the low-level
WD_KernelPlugInCall() function (see the WinDriver PCI Low-Level API Reference),
which is called by the wrapper WDC_CallKerPlug()
function.This function implements a Kernel PlugIn message handler. |
KP_IntEnable() [B.6.6]
|
Called when the user-mode application enables Kernel PlugIn interrupts, by
calling WDC_IntEnable() with the fUseKP parameter
set to TRUE (after having opened the device with a Kernel
PlugIn), or by calling the low-level InterruptEnable() or
WD_IntEnable() functions (see the WinDriver PCI Low-Level API Reference) with a
handle to a Kernel PlugIn driver (set in the hKernelPlugIn
field of the WD_INTERRUPT structure that is passed to the
function). This function should contain any initialization required for your Kernel PlugIn interrupt handling. |
KP_IntDisable() [B.6.7]
|
Called when the user-mode application calls
WDC_IntDisable() [B.3.47], or
the low-level InterruptDisable() or
WD_IntDisable() functions (see the WinDriver PCI Low-Level API Reference), if the
interrupts were previously enabled with a Kernel PlugIn driver (see the
description of KP_IntEnable() above.)This function should free any memory that was allocated by the KP_IntEnable() [B.6.6] callback.
|
KP_IntAtIrql() [B.6.8]
|
Called when WinDriver receives a legacy interrupt, provided the received
interrupt was enabled with a handle to the Kernel PlugIn. This is the
function that will handle your legacy interrupt in the kernel mode. The
function runs at high interrupt request level. Additional deferred
processing of the interrupt can be performed in KP_IntAtDpc()
and also in the user mode (see below.)
|
KP_IntAtDpc() [B.6.9]
|
Called if the KP_IntAtIrql() callback [B.6.8] has requested deferred handling of a legacy
interrupt by returning TRUE.This function should include lower-priority kernel-mode interrupt handler code. The return value of this function determines the amount of times that the application's user-mode interrupt handler routine will be invoked (if at all). |
KP_IntAtIrqlMSI() [B.6.10]
|
Called when WinDriver receives an MSI or MSI-X, provided MSI/MSI-X was enabled
for the received interrupt with a handle to the Kernel PlugIn. This is the
function that will handle your MSI/MSI-X in the kernel mode. The function
runs at high interrupt request level. Additional deferred processing of the
interrupt can be performed in KP_IntAtDpcMSI() and also in the
user mode (see below.)Note: MSI/MSI-X is supported on Linux, Mac OS X, and Windows Vista and higher. |
KP_IntAtDpcMSI() [B.6.11]
|
Called if the KP_IntAtIrqlMSI() callback [B.6.10] has requested deferred handling of an
MSI/MSI-X interrupt by returning TRUE.This function should include lower-priority kernel-mode MSI/MSI-X handler code. The return value of this function determines the amount of times that the application's user-mode interrupt handler routine will be invoked (if at all). Note: MSI/MSI-X is supported on Linux, Mac OS X, and Windows Vista and higher. |
KP_Event() [B.6.5] |
Called when a Plug-and-Play or power management event occurs, provided the
user-mode application previously registered to receive notifications for
this event in the Kernel PlugIn by calling
WDC_EventRegister() [B.3.49]
with the fUseKP parameter set to TRUE (after
having opened the device with a Kernel PlugIn), or by calling the low-level
EventRegister() (see the WinDriver PCI Low-Level API Reference) or
WD_EventRegister() functions with a handle to a Kernel PlugIn
driver (set in the hKernelPlugIn field of the
WD_EVENT structure that is passed to the function).
|
As indicated above, these handlers will be called (respectively) when the
user-mode program opens/closes a Kernel PlugIn driver (using
WDC_xxxDeviceOpen() / WD_KernelPlugInOpen(),
WDC_xxxDeviceClose() / WD_KernelPlugInClose()),
sends a message to the Kernel PlugIn driver (by calling
WDC_CallKerPlug() / WD_KernelPlugInCall()), enables
interrupts with a Kernel PlugIn driver (by calling WDC_IntEnable()
with the fUseKP parameter set to TRUE, after having
opened the device with a Kernel PlugIn / calling InterruptEnable()
or WD_InterruptEnable() with a handle to the Kernel PlugIn set in
the hKernelPlugIn field of the WD_INTERRUPT
structure that is passed to function), or disables interrupts
(WDC_IntDisable()/ InterruptDisable() /
WD_IntDisable()) that have been enabled using a Kernel PlugIn
driver;
The Kernel PlugIn interrupt handlers will be called when an interrupt occurs,
if the interrupts were enabled using a Kernel PlugIn driver (see above.)
The Kernel PlugIn event handler will be called when a Plug-and-Play or power
management event occurs, if the application registered to receive notifications
for the event that occurred using a Kernel PlugIn driver (by calling
WDC_EventRegister() with the fUseKP parameter set to
TRUE, after having opened the device with a Kernel PlugIn /
calling EventRegister() (see the WinDriver PCI Low-Level API Reference) or
WD_EventRegister() with a handle to a Kernel PlugIn driver set in
the hKernelPlugIn field of the WD_EVENT structure
that is passed to the function).
In addition to defining the Kernel PlugIn callback functions, you can implement
code to perform any required initialization for the Kernel PlugIn in
KP_Open() In the sample KP_PCI driver and in the
generated DriverWizard Kernel PlugIn driver, for example,
KP_Open() also calls the shared library's initialization function
and allocates memory for the Kernel PlugIn driver context, which is then used
to store the device information that was passed to the function from the user
mode.
From the KP_PCI sample (WinDriver/samples/pci_diag/kp_pci/kp_pci.c):
/* KP_PCI_Open is called when WD_KernelPlugInOpen() is called from the user mode.
pDrvContext will be passed to the rest of the Kernel PlugIn callbacks. */
BOOL __cdecl KP_PCI_Open(KP_OPEN_CALL *kpOpenCall, HANDLE hWD, PVOID pOpenData,
PVOID *ppDrvContext)
{
PWDC_DEVICE pDev;
WDC_ADDR_DESC *pAddrDesc;
DWORD dwSize, dwStatus;
void *temp;
KP_PCI_Trace("KP_PCI_Open entered\n");
kpOpenCall->funcClose = KP_PCI_Close;
kpOpenCall->funcCall = KP_PCI_Call;
kpOpenCall->funcIntEnable = KP_PCI_IntEnable;
kpOpenCall->funcIntDisable = KP_PCI_IntDisable;
kpOpenCall->funcIntAtIrql = KP_PCI_IntAtIrql;
kpOpenCall->funcIntAtDpc = KP_PCI_IntAtDpc;
kpOpenCall->funcIntAtIrqlMSI = KP_PCI_IntAtIrqlMSI;
kpOpenCall->funcIntAtDpcMSI = KP_PCI_IntAtDpcMSI;
kpOpenCall->funcEvent = KP_PCI_Event;
/* Initialize the PCI library */
dwStatus = PCI_LibInit();
if (WD_STATUS_SUCCESS != dwStatus)
{
KP_PCI_Err("KP_PCI_Open: Failed to initialize the PCI library: %s",
PCI_GetLastErr());
return FALSE;
}
/* Create a copy of device information in the driver context */
dwSize = sizeof(WDC_DEVICE);
pDev = malloc(dwSize);
if (!pDev)
goto malloc_error;
COPY_FROM_USER(&temp, pOpenData, sizeof(void *));
COPY_FROM_USER(pDev, temp, dwSize);
dwSize = sizeof(WDC_ADDR_DESC) * pDev->dwNumAddrSpaces;
pAddrDesc = malloc(dwSize);
if (!pAddrDesc)
goto malloc_error;
COPY_FROM_USER(pAddrDesc, pDev->pAddrDesc, dwSize);
pDev->pAddrDesc = pAddrDesc;
*ppDrvContext = pDev;
KP_PCI_Trace("KP_PCI_Open: Kernel PlugIn driver opened successfully\n");
return TRUE;
malloc_error:
KP_PCI_Err("KP_PCI_Open: Failed allocating %ld bytes\n", dwSize);
PCI_LibUninit();
return FALSE;
}You can use DriverWizard to generate a skeletal Kernel PlugIn driver for your device, and use the generated code as the basis for your Kernel PlugIn driver development (recommended); alternatively, you can use one of the Kernel PlugIn WinDriver samples as the basis for your Kernel PlugIn development.
![]() | |
|
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/pci_diag/kp_pci
directory. If you are using the Xilinx Virtex 5 PCI Express chip with Bus Mastering DMA Validation Design (BMD) firmware, you can also use the specific KP_VRTX5 Kernel PlugIn sample for this chip as the basis for your development. The WinDriver/xilinx/virtex5/bmd directory contains all relevant files for the Virtex 5 BMD sample (see note at the end of 11.6.4.1 regarding this directory's structure). |
The Kernel PlugIn driver is not a stand-alone module. It requires a user-mode application that initiates the communication with the driver. A relevant application will be generated for your driver when using DriverWizard to generate Kernel PlugIn code. The pci_diag application (found under the WinDriver/samples/pci_diag directory) communicates with the sample KP_PCI driver.
Both the KP_PCI sample and the generated wizard code demonstrate communication between a user-mode application (pci_diag / xxx_diag – where xxx is the name you selected for your generated driver project) and a Kernel PlugIn driver (kp_pci.sys/.o/.ko/.kext / kp_xxx.sys/.o/.ko/.kext – depending on the OS).
The sample/generated code demonstrates how to pass data to the Kernel PlugIn's
KP_Open() function and how to use this function to allocate and
store a global Kernel PlugIn driver context, which can be used by other
functions in the Kernel PlugIn.
The sample/generated Kernel PlugIn code implements a message for getting the driver's version number, in order to demonstrate how to initiate specific functionality in the Kernel PlugIn from the user mode and how to pass data between the Kernel PlugIn driver and a user-mode WinDriver application via messages.
The sample/generated code also demonstrates how to handle interrupts in the
Kernel PlugIn. The Kernel PlugIn implements an interrupt counter and interrupt
handlers, including deferred processing interrupt handling, which is used to
notify the user-mode application of the arrival of every fifth incoming
interrupt.
The KP_PCI sample's KP_IntAtIrql() [B.6.8] and KP_IntAtDpc() [B.6.9] functions demonstrate legacy level-sensitive PCI
interrupt handling. As indicated in the comments of the sample
KP_IntAtIrql() function, you will need to modify this function in
order to implement the correct code for acknowledging the interrupt on your
specific device, since interrupt acknowledgment is hardware-specific. The
sample KP_IntAtIrqlMSI() [B.6.10]
and KP_IntAtDpcMSI() [B.6.11]
functions demonstrate handling of Message-Signaled Interrupts (MSI) and Extended Message-Signaled Interrupts (MSI-X) (see
detailed information in
section 9.2).
The generated DriverWizard code will include sample interrupt handler
code for the selected device (PCI/PCMCIA/ISA). The generated
KP_IntAtIrql() function will include code to implement any
interrupt transfer commands defined in the wizard (by assigning registers
read/write commands to the card's interrupt in the
tab). For legacy PCI and PCMCIA interrupts,
which need to be acknowledged in the kernel when the interrupt is received (see
section 9.2), it is recommended that you
use the wizard to define the commands for acknowledging (clearing) the
interrupt, before generating the Kernel PlugIn code, so that the generated code
will already include the required code for executing the commands you defined.
It is also recommended that you prepare such transfer commands when handling
interrupts for hardware that supports MSI/MSI-X, in case enabling of MSI/MSI-X
fails and the interrupt handling defaults to using level-sensitive interrupts
(if supported by the hardware).
Note: Memory allocated for the transfer commands must remain available
until the interrupts are disabled
.
In addition, the sample/generated code demonstrates how to receive notifications of Plug-and-Play and power management events in the Kernel PlugIn.
![]() | |
We recommend that you build and run the sample/generated Kernel PlugIn project
(and corresponding user-mode application) "as-is" before modifying the code
or writing your own Kernel PlugIn driver. Note, however, that you will need to
modify or remove the hardware-specific transfer commands in the sample's
KP_IntAtIrql() function, as explained above.
|
The KP_PCI Kernel PlugIn sample code is implemented in the kp_pci.c file. This sample driver is part of the WinDriver PCI diagnostics sample – pci_diag – which contains, in addition to the KP_PCI driver, a user-mode application that communicates with the driver (pci_diag) and a shared library that includes API that can be utilized by both the user-mode application and the Kernel PlugIn driver. The source files for this sample are implemented in C.
Following is an outline of the files found in the WinDriver/samples/pci_diag directory:
![]() | |
| To use Message-Signaled Interrupts (MSI) or Extended Message-Signaled Interrupts (MSI-X) on Windows Vista and higher (for PCI cards that support MSI/MSI-X) you will need to modify or replace the sample INF file so that your INF file includes specific MSI information; otherwise WinDriver will attempt to use legacy level-sensitive interrupt handling for your card, as explained in section 9.2.6.1 of the manual. |
![]() | |
| The structure of the directory of the Xilinx Virtex 5 PCI Express chip with Bus Mastering DMA Validation Design (BMD) firmware sample – WinDriver/xilinx/virtex5/bmd – is similar to that of the generic PCI sample's pci_diag directory, except for the following issues: the virtex5_diag user-mode application files are located under a diag sub-directory, and the kp sub-directory, which contains the Kernel PlugIn driver's (KP_VRTX5) source files, currently has make files only for Windows. |
The generated DriverWizard Kernel PlugIn code for your device will include a kernel-mode Kernel PlugIn project and a user-mode application that communicates with it. As opposed to the generic KP_PCI and pci_diag sample, the generated wizard code will utilize the resources information detected and/or defined for your specific device, as well as any device-specific information that you define in the wizard before generating the code.
As indicated in section 11.6.3, when using the
driver to handle legacy PCI or PCMCIA interrupts, it is highly recommended that
you define the registers that need to be read/written in order to acknowledge
the interrupt, and set up the relevant read/write commands from/to these
registers in DriverWizard, before generating the code, thus enabling the
generated interrupt handler code to utilize the hardware-specific information
that you defined. It is also recommended that you prepare such transfer
commands when handling interrupts for hardware that supports MSI/MSI-X, in case
enabling of MSI/MSI-X fails and the interrupt handling defaults to using
level-sensitive interrupts (if supported by the hardware).
Note: Memory allocated for the transfer commands must remain available
until the interrupts are disabled
.
Following is an outline of the generated DriverWizard files when selecting to generate Kernel PlugIn code (where xxx signifies the name that you selected for the driver when generating the code). NOTE: The outline below relates to the generated C code, but on Windows you can also generate similar C# code, which includes a C Kernel PlugIn driver (since kernel-mode drivers cannot be implemented in C#), a .NET C# library, and a C# user-mode application that communicates with the Kernel PlugIn driver.
Interrupts will be handled in the Kernel PlugIn driver, if enabled, using a Kernel PlugIn driver, as explained below [11.6.5.2].
If Kernel PlugIn interrupts were enabled, when WinDriver receives a hardware
interrupt, it calls the Kernel PlugIn driver's high-IRQL
handler – KP_IntAtIrql() [B.6.8] (legacy
interrupts) or KP_IntAtIrqlMSI() [B.6.10]
(MSI/MSI-X). If the high-IRQL handler returns
TRUE, the relevant deferred Kernel PlugIn interrupt
handler – KP_IntAtDpc() [B.6.9] (legacy
interrupts) or KP_IntAtDpcMSI() [B.6.11]
(MSI/MSI-X) – will be called after the
high-IRQL handler completes its processing and returns. The return value of the
DPC function determines how many times (if at all) the user-mode interrupt
handler routine will be executed. In the KP_PCI sample, for
example, the Kernel PlugIn interrupt handler code counts five interrupts, and
notifies the user mode on every fifth interrupt; thus WD_IntWait()
(see the WinDriver PCI Low-Level API Reference) will return on only one out of every five incoming
interrupts in the user mode. The high-IRQL handler – KP_IntAtIrql() [B.6.8] or KP_IntAtIrqlMSI() [B.6.10]
– returns TRUE every five interrupts to activate the DPC
handler – KP_IntAtDpc() or KP_IntAtDpcMSI() – and the DPC function
returns the number of accumulated DPC calls from the high-IRQL handler. As a
result, the user-mode interrupt handler will be executed once for every 5
interrupts.
If the Kernel PlugIn interrupt handle is not enabled, then each incoming
interrupt will cause WD_IntWait() to return and your user-mode
interrupt handler routine will be invoked once WinDriver completes the kernel
processing of the interrupts (mainly executing the interrupt transfer commands
passed in the call to WDC_IntEnable() [B.3.46] or the low-level InterruptEnable() or
WD_IntEnable() functions – see the WinDriver PCI Low-Level API Reference)
– see Figure 11.2.
To have the interrupts handled by the Kernel PlugIn, the user-mode application
should open a handle to the device with a Kernel PlugIn driver, by passing the
name of a Kernel PlugIn driver to the WDC_xxxDeviceOpen() function
(PCI: [B.3.10],
PCMCIA: [B.3.11],
ISA: [B.3.12], and then call
WDC_IntEnable() [B.3.46] with the
fUseKP parameter set to TRUE.
If your are not using the WDC_xxx
API [B.2], your application should pass a
handle to the Kernel PlugIn driver to the WD_IntEnable() function
or the wrapper InterruptEnable() function (which calls
WD_IntEnable() and WD_IntWait()). This enables the
Kernel PlugIn interrupt handler. (The Kernel PlugIn handle is passed within the
hKernelPlugIn field of the WD_INTERRUPT structure
that is passed to the functions.) For details regarding the low-level
WD_xxx() API, refer to the WinDriver PCI Low-Level API Reference.
When calling WDC_IntEnable() / InterruptEnable() /
WD_IntEnable() to enable interrupts in the Kernel PlugIn, your
Kernel PlugIn's KP_IntEnable() callback function [B.6.6] is activated. In this function you can set the
interrupt context that will be passed to the Kernel PlugIn interrupt handlers,
as well as write to the device to actually enable the interrupts in the
hardware and implement any other code required in order to correctly enable
your device's interrupts.
If the Kernel PlugIn interrupt handler is enabled, then the relevant high-IRQL
handler, based on the type of interrupt that was enabled –
KP_IntAtIrql() [B.6.8] (legacy
interrupts) or KP_IntAtIrqlMSI() [B.6.10]
(MSI/MSI-X) – will be called for each incoming
interrupt. The code in the high-IRQL handler is executed at high interrupt
request level. While this code is running, the system is halted, i.e., there
will be no context switches and no lower-priority interrupts will be handled.
Code running at high IRQL is limited in the following ways:
WDC_xxx() read/write address or configuration space
functions.
WDC_MultiTransfer() [B.3.25], or the low-level
WD_Transfer(), WD_MultiTransfer(), or
WD_DebugAdd() functions (see the WinDriver PCI Low-Level API Reference).
malloc(), free(), or any
WDC_xxx or WD_xxx API other than those listed
above.
Because of the aforementioned limitations, the code in the high-IRQL handler
(KP_IntAtIrql() [B.6.8] or KP_IntAtIrqlMSI() [B.6.10]) should be kept to a minimum, such as acknowledgment
(clearing) of level-sensitive interrupts. Other code that you want to run in
the interrupt handler should be implemented in the DPC function
(KP_IntAtDpc() [B.6.9] or KP_IntAtDpcMSI() [B.6.11]), which runs at a deferred interrupt level and does not
face the same limitations as the high-IRQL handlers. The DPC function is called
after its matching high-IRQL function returns, provided the high-IRQL handler
returns TRUE.
You can also leave some additional interrupt handling to the user mode. The
return value of your DPC function – KP_IntAtDpc() [B.6.9] or KP_IntAtDpcMSI() [B.6.11] –
determines the amount of times (if any) that your user-mode interrupt handler
routine will be called after the kernel-mode interrupt processing is completed.
The WinDriver architecture enables a kernel-mode function to be activated from the
user mode by passing a message from the user mode to the Kernel PlugIn driver
using WDC_CallKerPlug() [B.3.18]
or the low-level WD_KernelPlugInCall() function (see the
WinDriver PCI Low-Level API Reference).
The messages are defined by the developer in a header file that is common to
both the user-mode and kernel-mode plugin parts of the driver. In the
pci_diag KP_PCI sample and the
generated DriverWizard code, the messages are defined in the shared library
header file – pci_lib.h in the sample or
xxx_lib.h in the generated code.
Upon receiving the message from the user mode, WinDriver will execute the
KP_Call() [B.6.4] Kernel PlugIn callback
function, which identifies the message that has been received and executes the
relevant code for this message (as implemented in the Kernel PlugIn).
The sample/generated Kernel PlugIn code implement a message for getting the
driver's version in order to demonstrate Kernel PlugIn data passing. The
code that sets the version number in KP_Call() is executed in the
Kernel PlugIn whenever the Kernel PlugIn receives a relevant message from
the user-mode application. You can see the definition of the message in the
shared pci_lib.h /
xxx_lib.h shared header file. The user-mode
application (pci_diag.exe /
xxx_diag.exe) sends the message to the Kernel
PlugIn driver via the WDC_CallKerPlug()
function [B.3.18].