Copyright © 2003 by Printing Communications Assoc., Inc. (PCAUSA). All rights reserved
The Microsoft® Windows® Driver Development Kit (DDK) PassThru NDIS Intermediate (IM) driver sample provides an excellent introduction to the mechanics of implementing a skeleton NDIS IM filter driver. (Thanks, NDIS Development Team!!!). However, the PassThru sample stops short of actually illustrating any observable function. To be of any actual use you must take the next step and add functionality of your own to the skeleton driver. If you are new to Windows driver development and/or NDIS drivers, then the next step can be a big one.
This article starts with the Microsoft PassThru NDIS IM driver sample from the Windows DDK Build 3790 (Windows Server 2003) and extends it by adding these functions:
Later articles in this series will describe additional extensions to PassThru.
Our starting point is the Microsoft PassThru NDIS IM driver sample from the Windows DDK Build 3790 (Windows Server 2003). The baseline PassThru consists of these key files:
In addition, there is the PassThru.htm file that provides important information including:
This series of articles add functionality to PassThru in s series of fairly small steps. Each step includes a description of the functionality being added and a description of the driver modifications needed to implement the new functionality. We will also develop a companion Win32 application to illustrate use of the extended functions.
New modules and header files will be added to house code the implements new functions. Most of the new driver code will be added in a module named PTExtend.c. A companion Win32 console application named "PassThru User I/O" (PTUserIo) is developed to illustrate user-mode functionality. The overall structure of the completed PassThruEx project looks like this:
Sources to the modified PassThru driver and the companion test application are available for download. I would like to thank Microsoft for providing permission to include sources to the PassThru to driver for your benefit.
Hopefully most readers will already be familiar with the IRP-based interface that is most commonly used to implement user/driver programming interfaces. Applications use the basic Win32 functions CreateFile, DeviceIoControl, ReadFile, WriteFile and CloseHandle for the user-mode end of the interface.
Drivers create a device object and a Win32-visible symbolic link name that can be opened in user-mode using CreateFile. The driver registers IRP-based functions that are (eventually) called to implement the kernel-mode end of the interface.
The foundation for the device I/O control interface has already been laid in the baseline PassThru driver sample. The NdisMRegisterDevice function is called from the PtRegisterDevice routine in PassThru.c to create a device object and a Win32-visible symbolic link name and to register I/O request handlers.
The code fragments below are from the baseline PassThru driver.
DispatchTable[IRP_MJ_CREATE] = PtDispatch; DispatchTable[IRP_MJ_CLEANUP] = PtDispatch; DispatchTable[IRP_MJ_CLOSE] = PtDispatch; DispatchTable[IRP_MJ_DEVICE_CONTROL] = PtDispatch;
NdisInitUnicodeString(&DeviceName, NTDEVICE_STRING); NdisInitUnicodeString(&DeviceLinkUnicodeString, LINKNAME_STRING);
// // Create a device object and register our dispatch handlers // Status = NdisMRegisterDevice( NdisWrapperHandle, &DeviceName, &DeviceLinkUnicodeString, &DispatchTable[0], &ControlDeviceObject, &NdisDeviceHandle );
In the extended PassThru driver we eliminate use of the PtDispatch function (delete PtDispatch code from PassThru.c and remove its prototype from PassThru.h). In its place we call four explicit dispatch functions: DevOpen, DevCleanup, DevClose and DevIoControl.
// BEGIN_PTUSERIO DispatchTable[IRP_MJ_CREATE] = DevOpen; DispatchTable[IRP_MJ_CLEANUP] = DevCleanup; DispatchTable[IRP_MJ_CLOSE] = DevClose; DispatchTable[IRP_MJ_DEVICE_CONTROL] = DevIoControl; // END_PTUSERIO
NdisInitUnicodeString(&DeviceName, NTDEVICE_STRING); NdisInitUnicodeString(&DeviceLinkUnicodeString, LINKNAME_STRING);
// // Create a device object and register our dispatch handlers // Status = NdisMRegisterDevice( NdisWrapperHandle, &DeviceName, // \\Device\\Passthru &DeviceLinkUnicodeString, // \\DosDevices\\Passthru &DispatchTable[0], &ControlDeviceObject, &NdisDeviceHandle );
The new dispatch functions are implemented in PTExtend.c module under the \PassThruEx\Driver folder. At this point the dispatch handlers are simple stubs.
Click here to view the skeleton I/O dispatch handlers.
Although the driver skeleton I/O dispatch handlers are simple stubs, they are sufficiently functional to allow us to start building and testing the Win32 application. At this point we can add code that open and closes a handle on the PassThru device symbolic link.
The Win32 test application is called "PassThru User I/O". It is a MFC console application implemented in the PTUserIo.cpp module under the \PassThruEx\Test folder.
The key user-mode function added at this step is the PtOpenControlChannel function. It uses CreateFile to open a handle on the "\\.\PassThru" filespec name. A handle on a PassThruEx "control channel" is an ordinary handle to the device that is not associated with a specific adapter binding. Control channel handles are used to access global information such as the driver's binding list.
The skeleton _tmain function calls PtOpenControlChannel. If the PassThruEx driver with the new dispatch handlers is installed, then PtOpenControlChannel should succeed. If a valid handle is returned it is simply closed.
Click here to view Win32 code to open and close a handle on the PassThru device.
The first useful function to be added to PassThru is a call to query the driver for a list of its current bindings. The binding names that are returned by this query identify the bindings that can be used in other binding-specific functions.
Implementation of this function is straightforward. A DeviceIoControl call will be made on the handle returned by PtOpenControlChannel. The call will pass a user-mode output buffer to the PassThru driver on IOCTL_PTUSERIO_ENUMERATE (defined in IOCommon.h). The driver's DevIoControl dispatcher will call a function that will fill the user's output buffer with an array of binding name strings.
When the DeviceIoControl call returns the user-mode application will have the driver's binding names.
The essential work of saving binding names is already done in the baseline PassThru driver. All we need to do is to add a handler for IOCTL_PTUSERIO_ENUMERATE that fills the user's buffer with the binding names.
The binding names are saved in the ADAPT structure that is created by the PtBindAdapter function for each successfully opened binding. The virtual adapter name (name passed to NdisIMInitializeDeviceInstanceEx) is saved in the DeviceName field of the ADAPT structure.
The ADAPT structures themselves are kept in a singly-linked list whose list head is the global variable pAdaptList.
All we have to do to implement the PassThruEx binding enumeration function is fill a user-provided buffer with DeviceName strings fetched from the list of ADAPT structures. In the PTExtend module we add a function called DevEnumerateBindings to fill the user's buffer with binding names. This function is called from the DevIoControl IOCTL dispatcher for the case IOCTL_PTUSERIO_ENUMERATE.
The buffer will be filled with an array of null-terminated Unicode strings with the end of the list being identified by an empty Unicode string.
There are a few additional details to attend to. It is necessary to account for the possibility that the adapter list could change while we were walking the list and copying binding names into the user's buffer. The baseline PassThru driver already has a spinlock for this purpose. We simply hold the GlobalLock while we are examining the adapter list. In addition, add appropriate sanity checks and __try/__except handlers to head off potential problems.
To examine the driver's binding enumeration code, follow the links below.
Click here to view DevEnumerateBindings handler
Now we need to write user-mode code that illustrates how to call the driver for this information and how to display the binding information.
Implementation of this function is straightforward. A DeviceIoControl call will be made on the handle returned by PtOpenControlChannel. The call will pass a user-mode output buffer to the PassThru driver on IOCTL_PTUSERIO_ENUMERATE (defined in IOCommon.h). The driver's DevIoControl dispatcher will call a function that will fill the user's output buffer with an array of binding name strings..
First we make a trivial function PtEnumerateBindings that is a wrapper around DeviceIoControl. This function makes the IOCTL_PTUSERIO_ENUMERATE request to pass the user's output buffer to the driver. If successful, it returns a buffer that has been filled with the driver binding names.
The binding name buffer is an array of null-terminated Unicode strings with the end of the list being identified by an empty Unicode string.
The skeleton _tmain function calls PtEnumerateBindings. If the call is successful it loops through the buffer and displays binding names on the console.
If all is well running the PTUserIo application will display the binding names on the console:
PassThru User I/O Test Application Copyright (c) 2003 Printing Communications Assoc., Inc. (PCAUSA) All rights reserved.Driver Bindings: "\Device\{67A4853E-1940-43A3-A442-74701B5133B0}" "\Device\{0611AD65-41D8-4BB1-8A8F-43008BB362A3}" "\Device\{8DA82E8E-D091-4FB2-902A-673FBEC2DA7C}"
This display is hardly human readable. However, it does confirm that the binding enumeration function works.
This test was run on a Windows XP SP1 system with three NDIS miniports installed. Using other APIs (IOCTL_NDIS_QUERY_GLOBAL_STATS) we can display additional information describing each binding:
PassThru User I/O Test Application Copyright (c) 2003 Printing Communications Assoc., Inc. (PCAUSA) All rights reserved.Driver Bindings: "\Device\{67A4853E-1940-43A3-A442-74701B5133B0}" Description: " Intel 8255x-based Integrated Fast Ethernet" Medium: 802.3 Mac address = 00-00-39-14-92-A9 Media Connect Status: Disconnected"\Device\{0611AD65-41D8-4BB1-8A8F-43008BB362A3}" Description: " NdisWan Adapter" Medium: 802.3 Mac address = C0-F2-20-52-41-53 Media Connect Status: Connected"\Device\{8DA82E8E-D091-4FB2-902A-673FBEC2DA7C}" Description: "3CRWE737A AirConnect Wireless LAN PC Card" Medium: 802.3 Mac address = 00-50-DA-03-4E-6C Media Connect Status: Connected
Click here to view Win32 code to query and display binding names.
At this point we really want to move on and add the logic to open a handle to a specific PassThru binding. However, there is a problem that must we must anticipate and deal with before we can proceed.
One of the key objects managed in the PassThru driver is the ADAPT structure, defined in the PassThru.h header file. The ADAPT structure contains essential information about each binding that as been successfully opened. In the baseline PassThru driver the "life cycle" of the ADAPT structure is controlled by the NDIS wrapper. The ADAPT structure is allocated and initialized in the PtBindAdapter function in the Protocol.c module. It can be freed in the PtUnbindAdapter or in the MPHalt handler. Since NDIS is responsible for calling each of these three functions, the creation and destruction of each ADAPT structure is accomplished safely.
When we create our user-mode handle to a specific PassThru binding we will actually be creating a mapping between the handle and a specific ADAPT structure. We will need to access information contained in the ADAPT structure whenever we use our new API to perform operations on an adapter handle.
Hopefully the problem that must be dealt with is becoming clear. If the associated ADAPT structure is freed before the user-mode handle is closed, then the system can crash.
We must
provide a mechanism to insure that the ADAPT structure exists as long as the
user-mode handle is open.
The most common method for controlling the "life cycle" of a temporary object is called "reference counting". When the ADAPT structure is created in PtBindAdapter the ADAPT reference count is set to 1. Once the reference count falls to zero, the ADAPT structure is freed.
First we add a RefCount member variable to the ADAPT structure.
Then we provide two functions that manipulate the RefCount:
Here are the basic implementations of these two functions:
VOID PtRefAdapter( PADAPT pAdapt ) { NdisInterlockedIncrement( &pAdapt->RefCount ); }VOID PtDerefAdapter( PADAPT pAdapt ) { if( NdisInterlockedDecrement( &pAdapt->RefCount) == 0 ) { NdisFreeMemory(pAdapt, 0, 0); } }
We modify PtBindAdapter to set the initial ADAPT RefCount to 1. And finally we replace calls to NdisFreeMemory with calls to PtDerefAdapter in PtUnbindAdapter and MPHalt.
From a logical perspective at this point it looks like we have simply provided a more complicated way to call NdisFreeMemory. However, when we add the logic to open a handle to a specific PassThru binding we can use PtRefAdapter and PtDerefAdapter to insure that the AD APT structure persists as long as the user-mode handle is open.
It is also worthwhile to note that PtDerefAdapter can do more then simply free the memory being referenced. In the actual implementation of PtDerefAdapter we consolidate some duplicate code from PtUnbindAdapter and MPHalt into PtDerefAdapter.
Click here to view ADAPT reference counting code fragments
Actually, we'll address more then simply "opening an adapter handle" here. We'll also address the "life cycle" of a the handle - including ways to deal with the case that NDIS unbinds the adapter after the user-mode handle has been opened.
From the Win32 API perspective the process of opening an adapter handle will be simple and familiar. We'll provide a function PtOpenAdapter that takes a Unicode binding name (returned from PtEnumerateBindings) and returns a valid handle if successful. Adapter-specific operations (which we haven't yet invented...) can be performed on the adapter handle. When we're done we'll call PtCloseAdapter to close the adapter handle.
The basic steps for opening a handle within the driver are:
- Save pointer to OPEN_CONTEXT in the ADAPT structure.
- Save pointer to ADAPT structure in OPEN_CONTEXT structure. Increment the adapter reference count.
- Associate handle with OPEN_CONTEXT by saving pointer to OPEN_CONTEXT in the FileObject FsContext field.
In subsequent calls to the driver I/O dispatcher the OPEN_CONTEXT structure can be recovered from the FileObject FsContext field.
The simplicity of the implementation introduces one restriction that should be noted:
The driver enforces exclusive access on a per-binding basis. Only one adapter handle can be opened at a time on each PassThru binding.
Closing the handle involves adding logic in DevCleanup to cancel pending I/O on the handle and logic in DevClose to free (actually, dereference) the OPEN_CONTEXT structure.
There is one additional problem concerning adapter handles that must be dealt with.
After the adapter handle has been opened it is entirely possible that NDIS can unbind the adapter associated with the handle.
This situation must be dealt with carefully.
At this point it is worthwhile to crank up the NDIS Tester to insure that we haven't screwed up to badly. Focus on the Load/Unload tests for now. If you aren't familiar with NDIS Tester, see Stephan Wolf's article Testing Network Drivers with the NDIS Test Tool.
It is not possible to write bug-free code (at least the author has this weakness...). However, we can invent tests that can help flush out at least some faults. One test that we ran on the open adapter logic was:
We won't dwell on the details of this test except to say that 1.) eventually it passed with flying colors and 2.) initially it didn't.
The user-mode PtOpenAdater function will eventually call the driver's DevIoControl dispatcher on IOCTL_PTUSERIO_OPEN_ADAPTER (defined in IOCommon.h). The DevOpenAdapter function is then called to do the work.
The first thing that DevOpenAdapter does is to call a new function PtLookupAdapterByName to find the ADAPT structure whose DeviceName matching the binding name provided by the user in the input buffer. There are two important things to notice about this function:
- Case Sensitive Name Comparison
- The NdisEqualMemory function is used to compare the user's binding name with the ADAPT DeviceName field. It would be desirable to use case-insensitive string comparison functions. However, since the comparison is being performed with a spin lock held (IRQL == DISPATCH_LEVEL) string comparison functions are not allowed.
- Reference Count Added To ADAPT Structure
- Notice that a reference count is added to the ADAPT structure before NdisReleaseSpinLock is called. If the ref count was not added, it is entirely possible that NDIS could cause the binding to be unbound (and the ADAPT structure to be freed) before the ADAPT pointer could be returned to the caller.
If PtLookupAdapterByName succeeds in finding a matching ADAPT binding, then an OPEN_CONTEXT structure is allocated to manage information about the specific open context (i.e., open handle). OPEN_CONTEXT is a allocated and initialized by the DevAllocateOpenContext function and reference counting is done using DevRefOpenContext and DevDerefOpenContext.
At this point we need to make an association between the user-mode handle and specific PassThru binding that we have found. Here are the two items to be associated:
The standard DDK doesn't actually say much about the use of the FILE_OBJECT in the I/O stack location. However, its use is vital in many situations. If you have ever written a file system driver then you can appreciate how precious the two FsContext and FsContext2 fields really are.
Simply put, the FILE_OBJECT in the I/O stack location represents an open instance of a PassThru device object. There is a one-to-one correspondence between the FILE_OBJECT and the user-mode handle (at least in simple cases...). Of most significance is the fact that the driver is free to assign whatever values it desires to the FsContext and FsContext2 fields and these values will be returned to the driver in the FILE_OBJECT for subsequent calls to the I/O dispatcher on the same handle.
So, to make the handle-binding association we set the FsContext field to be a pointer to our OPEN_CONTEXT structure. On subsequent calls to the I/O dispatcher we can examine FsContext. If it is not NULL, then it is a pointer to an OPEN_CONTEXT structure identifying the binding to be used.
When a user-mode handle is closed there will be some work to do in the DevCleanup and DevClose routines. The DevClose routine will, of course, make a call to DevDerefOpenContext.
Finally we must add the ability to deal with the case that NDIS unbinds the adapter after the user-mode handle has been successfully opened. To accommodate this we add function DevOnUnbindAdapter that notifies the open handle logic about the unbind. The code in this function must wait for all outstanding NDIS operations on the adapter to complete. Then it should cancel all pending I/O on the handle. The assumption must be that as soon as DevOnUnbindAdapter returns the adapter will be closed with a call to NdisCloseAdapter.
We add calls to DevOnUnbindAdapter from MPHalt and from PtUnbindAdapter.
Click here to view driver open handle code fragments
Implementation of the user-mode code for opening and closing a PassThru adapter handle is straightforward. A DeviceIoControl call is made on a handle returned by PtOpenControlChannel. The call will pass a user-mode input buffer containing the binding name to the PassThru driver on IOCTL_PTUSERIO_OPEN_ADAPTER (defined in IOCommon.h). The driver's DevIoControl dispatcher will call the DevOpenAdapter function that does the kernel-mode work.
These are the user-mode functions that are used to open a binding-specific handle to the PassThru driver.
HANDLE PtOpenAdapter( PWSTR pszAdapterName ) { HANDLE hAdapter; BOOLEAN bRc; ULONG bytesReturned, nBufferLength;hAdapter = INVALID_HANDLE_VALUE;// // Open A Control Channel Handle On The PassThru Device // hAdapter = PtOpenControlChannel();if( hAdapter == INVALID_HANDLE_VALUE ) { return( INVALID_HANDLE_VALUE ); }// // Determine Length (Bytes) Of The Adapter Name // nBufferLength = wcslen( (PWSTR )pszAdapterName ) * sizeof( WCHAR );// // Call Driver To Make Open The Adapter Context // bRc = DeviceIoControl( hAdapter, (DWORD)IOCTL_PTUSERIO_OPEN_ADAPTER, pszAdapterName, nBufferLength, NULL, 0, &bytesReturned, NULL );// // Check Results // if ( !bRc ) { CloseHandle( hAdapter ); return( INVALID_HANDLE_VALUE ); }return( hAdapter ); // Success }BOOL PtCloseAdapter( HANDLE hAdapter ) { // // Close The Handle // ---------------- // Future versions may pereform additional work in this routine... // return( CloseHandle( hAdapter ) ); }
Just when you thought we had exhausted the topic of opening and closing Win32 handles to the PassThru driver we find that there is another major issue to consider.
The Win32 Plug-and-Play (PnP) mechanism can halt a miniport or unbind a protocol binding at any time. Indeed, PnP can initiate unloading of the PassThru driver at any time.
The bottom line here is that PnP can cause events that invalidate a Win32 handle to the PassThru driver after it has been successfully opened.
The reference counting logic helps deal with this situation to a limited extent. It is at least "safe" for NDIS to unbind adapters while a Win32 handle is open to an adapter. A little memory is not released as soon as desired, but the system should not crash. However, at this point there is no mechanism to inform the application that the adapter handle is invalid.
Of more significance is that if NDIS unbinds all adapters on the PassThru driver and attempts to unload the driver, the unload will not happen until all handles on the PassThru device are closed. It is important to notify the application of this situation to allow the PassThru driver to be unloaded.
There are two aspects of this issue that must be accommodated:
A fairly frequent topic on the newsgroups is: "My driver can't be unloaded". One possible reason could be that a Win32 handle remains open; as long as the Win32 handle to a driver remains open, the driver can't be unloaded.
One trivial approach to this problem is to only open Win32 handles to the PassThru driver for brief intervals. In this case, the handles are closed most of the time and the process of re-opening the handles becomes the notification mechanism (re-opening the handle will fail...). In its current state the PTUserIo application uses this approach; Win32 handles are only opened only briefly and are closed when the application exits. No problem.
On the other hand, there are applications (such as packet monitoring applications) which necessarily must keep handles opened for extended intervals. These types of applications need a mechanism that allows the driver to inform the application of events that invalidate the Win32 handle.
We are not going to develop the handle-invalidating notification mechanism in Part 1 of this series of articles. Instead we will deal with it in later topics when we actually add features that require a PassThru handle to remain open for extended periods.
Now that we have developed a mechanism to open a binding-specific handle on the PassThru driver it's time to do something with it. Adding the capability to make NDIS requests to query information is first on the list. Once we can query information on a handle then we can fetch and display human-readable information about the adapter.
Conceptually the Win32 API to query information is a simple DeviceIoControl call on the handle opened by PtOpenAdapter. The input buffer is used to pass the NDIS object identifier (OID) of interest to the driver. The driver's protocol section will then make an NdisRequest call to query information on the specified OID and fill the user's output buffer with the returned information.
However, we must realize that the baseline PassThru driver functionality is already making NdisRequests as part of the miniport pass-through logic. Our Win32-initiated NdisRequests must not break the existing functionality.
In addition, we need to anticipate that a fully functional NDIS IM driver will actually have three independent NdisRequest initiators:
Miniport MPQueryInformation/MPSetInformation pass-through (exists in baseline driver)
Win32-Initiated Requests (subject of this topic)
Autonomous Driver-Initiated Requests (future topic)
The simplicity of the implementation introduces restrictions that should be noted:
Queries made on an open adapter handle on the PassThru driver are synchronous and must be serialized by the Win32 application.
When we are done we can modify the PTUserIo application to fetch human-readable information using the new PtQueryInformation API. This test was run on a Windows XP SP1 system with three NDIS miniports installed:
PassThru User I/O Test Application Copyright (c) 2003 Printing Communications Assoc., Inc. (PCAUSA) All rights reserved.Driver Bindings: "\Device\{67A4853E-1940-43A3-A442-74701B5133B0}" Description: " Intel 8255x-based Integrated Fast Ethernet" Medium: 802.3 Mac address = 00-00-39-14-92-A9 Media Connect Status: Disconnected"\Device\{0611AD65-41D8-4BB1-8A8F-43008BB362A3}" Description: " NdisWan Adapter" Medium: 802.3 Mac address = C0-F2-20-52-41-53 Media Connect Status: Connected"\Device\{8DA82E8E-D091-4FB2-902A-673FBEC2DA7C}" Description: "3CRWE737A AirConnect Wireless LAN PC Card" Medium: 802.3 Mac address = 00-50-DA-03-4E-6C Media Connect Status: Connected
Before we can add our Win32-initiated NDIS query information extensions we need to understand how the baseline PassThru driver handles calls to the MPQueryInformation handler.
The baseline PassThru driver implementation depends on the fact that NDIS serializes calls to this function. This means that there can never be more then one call to MPQueryInformation in progress on any PassThru binding at any given time. Based on this fact the PassThru driver provides a single NDIS_REQUEST structure in the ADAPT structure for each binding. Calls to MPQueryInformation use the per-adapter NDIS_REQUEST structure to make the NdisRequest that passes the query to the lower-level miniport. The NdisRequest call is made synchronously waiting on a NDIS_EVENT until the PtRequestComplete handler is called.
Making an appropriate modification to PtRequestComplete is the key change that must be made to the baseline code to add our Win32-initiated NDIS query information implementation.
Fortunately, all that is needed in PtRequestComplete to distinguish between miniport-initiated requests and Win32-initiated requests is a simple test on the NDIS_REQUEST pointer, as illustrated below:
VOID PtRequestComplete( IN NDIS_HANDLE ProtocolBindingContext, IN PNDIS_REQUEST NdisRequest, IN NDIS_STATUS Status ) { PADAPT pAdapt = (PADAPT)ProtocolBindingContext; if( NdisRequest != &(pAdapt->Request) ) { // // Not A Miniport Request // ---------------------- // Handle completion of this request differently.... // return; } ... }
The mechanism that we will add to make our Win32-initiated call to NdisRequest will add a second level of indirection for completing requests. This is done by adding a containing structure around the basic NDIS_REQUEST structure:
typedef struct _NDIS_REQUEST_EX { NDIS_REQUEST Request; LOCAL_REQUEST_COMPLETE_HANDLER RequestCompleteHandler; PVOID RequestContext; NDIS_STATUS RequestStatus; NDIS_EVENT RequestEvent; } NDIS_REQUEST_EX, *PNDIS_REQUEST_EX;
The extended NDIS_REQUEST_EX structure includes fields for request-specific request completion routine and request-specific completion context. It would be used in the PtRequestComplete handler like this:
VOID PtRequestComplete( IN NDIS_HANDLE ProtocolBindingContext, IN PNDIS_REQUEST NdisRequest, IN NDIS_STATUS Status ) { PADAPT pAdapt = (PADAPT)ProtocolBindingContext; NDIS_OID Oid = pAdapt->Request.DATA.SET_INFORMATION.Oid;// BEGIN_PTUSERIO // // Handle Local NDIS Requests // -------------------------- // Here we handle NDIS requests that do not originate from the miniport. // // Typically, these are requests that were initiated from user-mode but // could also be requests initiated autonomously by the NDIS IM driver. // if( NdisRequest != &(pAdapt->Request) ) { PNDIS_REQUEST_EX pLocalRequest = (PNDIS_REQUEST_EX )NdisRequest;(*pLocalRequest->RequestCompleteHandler )( pAdapt, pLocalRequest, Status );return; } // END_PTUSERIO// // Since our request is not outstanding anymore // ASSERT(pAdapt->OutstandingRequests == TRUE);...}
Now that handling of request completion is taken care of we can implement the rest of the driver code for making the Win32-initiated requests:
Click here to view driver query-handling code fragments
Implementation of the user-mode code for querying NDIS information on an open adapter handle is straightforward. A DeviceIoControl call is made on a handle returned by PtOpenAdapter. The call will pass a user-mode input buffer containing the OID value to be queried to the PassThru driver on IOCTL_PTUSERIO_QUERY_INFORMATION (defined in IOCommon.h). The driver's DevIoControl dispatcher will call the DevQueryInformation function that does the kernel-mode work.
This the user-mode function that is used to to open a binding-specific handle to the PassThru driver.
DWORD PtQueryInformation( HANDLE hAdapter, ULONG OidCode, PVOID InformationBuffer, UINT InformationBufferLength, PULONG pBytesWritten ) { DWORD nResult = ERROR_SUCCESS;
*pBytesWritten = 0;
// // Make The DeviceIoControl Call // if( !DeviceIoControl( hAdapter, IOCTL_PTUSERIO_QUERY_INFORMATION, &OidCode, sizeof(OidCode), InformationBuffer, InformationBufferLength, pBytesWritten, NULL ) ) { // // DeviceIoControl returned an error // nResult = GetLastError(); }
return( nResult ); }
Frankly, writing this article took quite a bit longer then I expected...
Because I have been working with NDIS since the days of NDIS 2.0, TSRs and "Windows for Workgroups" (how many remember that?) I thought that the introductory topic of adding a DeviceIoControl API to PassThru and at least getting to the point of making synchronous NDIS requests would be "a walk in the park". However, the additional task of explaining each step really slowed me down - and that was actually a good thing. As I wrote some of the explanations I noticed faults in the code that really should be corrected.
I hope that the material presented in this series of articles so far is fairly robust and of interest to at least some novice NDIS developers.
This article and the companion code are intended to be used for the benefit of the reader of this article. The companion code is intended to help the reader derive and improve their own NDIS Intermediate driver for Windows platforms.
Here is the general form of the Copyright Notice found in the companion code for this article:
Companion Sample Code for the Article "Extending the Microsoft PassThru NDIS Intermediate Driver" Portions Copyright (c) 1992-2000 Microsoft Corporation; used by permission. Portions Copyright (c) 2003 Printing Communications Associates, Inc. (PCAUSA) The right to use this code in your own derivative works is granted so long as 1.) your own derivative works include significant modifications of your own, 2.) you retain the above copyright notices and this paragraph in its entirety within sources derived from this code. This product includes software developed by PCAUSA. The name of PCAUSA may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
This article is:
Copyright (c) 2003 Printing Communications Associates, Inc. (PCAUSA). All rights reserved.
PCAUSA does not grant the right to redistribute or publish this article without written permission.
It is certainly intended that you should be able to incorporate the ideas and code presented in this article into your own code. At the same time, for a free code like this it is essential that you understand that the author does not provide any warranty whatsoever that the sample code is fit for any purpose whatsoever.
In other words: This article and the accompanying sample code are provided for educational purposes only. Use at your own risk.
[ Click here to download PassThruEx source code... ]
Thomas F. Divine is founder of PCAUSA, a company which has been serving the Windows device driver community since 1992. PCAUSA licenses network device driver samples that illustrate specialized kernel mode programming technologies such an NDIS Intermediate drivers, TDI Clients and a variety of network data filtering techniques.