作者:Thomas F. Divine
译:feikoo
微软的Windows驱动开发工具(DDK) 附带的NDIS中间层驱动PassThru源代码给我们提供了一个极好的示例,它向我们展示了实现NDIS 中间层过滤驱动框架一种方法。(感谢NDIS开发团队)。但是,PassThru中缺乏实际的功能。要想让它具备实际的用途,我们必须进行下一步的工作,就是向这个框架中添加自己的功能。如果你是Windows驱动开发或NDIS驱动开发的新手,接下来的工作可是一件苦差。
本文接下来以微软Windows DDK Build 3790(Windows Server 2003)附带的PassThru NDIS中间层驱动示例为起点,并在此基础上按下列步骤来添加自己的功能:
1. 基本的DeviceIoControl接口:提供Win32应用程序与PassThru驱动进行通信的一种基本方法。2.绑定枚举函数:允许Win32应用程序查询PassThru驱动的绑定信息;
2. ADAPT结构引用计数:添加对ADAPT结构的逻辑计数;
3. 适配器名柄(Adapter Handle)打开/关闭功能:建立与某一具体命名的PassThru绑定的用户模式句柄的方法。该句柄可以用来在某一适配器上请求,读写I/O和进行其它的操作。
4. 处理事件通知:在这节我们将处理已经打开的驱动句柄变为无效的情况(如Pnp)。
5. 在一个打开的适配器句柄上查询信息:增加一种用Win32初始化的NDIS请求在一个打开的适配器句柄上查询信息的方法。 接下来的一系列文章将介绍对PassThru的另一些扩展。
一 . 扩展PassThru驱动(PassThruEx)
我们由DDK(以下提及DDK均指Windows DDK Build 3790 Windows Server 2003)中的驱动程序源代码开始。PassThru包含以下所列的这些关键文件:
/PassThru - Windows DDK Build 3790 PassThru工程文件夹;
/Driver – PassThru驱动源程序
PassThru.c – DriveEntry函数和其它PassThru小端口驱动程序与协议驱动程序共用的代码部分。
PassThru.h – PassThru的头文件;
Miniport.c – PassThru中与Miniport相关的函数;
Protocol.c – PassThru中与Protocol相关的函数;
Precomp.h – 预编译的头文件;
Sources – 编译工具所用的源文件列表文件;
另外,在PassThru.htm中包含了下列重要信息:
1. 编译该示例的方法;2. 安装编译好的驱动程序的方法;3. 代码说明
文章由易渐难,一步一步地向PassThru中添加功能。每一步中都包含了所添加部分的功能描述和需要修改的代码部分的说明。并且,我们还开发一个Win32应用程序来演示所添加函数的功能。
我们将新添中的代码尽量放在新的.C或.H文件中,大部分的新增代码被放在了PTExtend.c文件中。其中PassThru 用户I/O(PTUUserIo)这个Win32控制台程序用来演示用户态下功能。整个完整的PassThru工程结构如下:
/PassThru - Windows DDK Build 3790 PassThru工程文件夹;
/Driver – PassThru驱动源程序
PassThru.c – DriveEntry函数和其它PassThru小端口驱动程序与协议驱动程序共用的代码部分。
PassThru.h – PassThru的头文件;
Miniport.c – PassThru中与Miniport相关的函数;
Protocol.c – PassThru中与Protocol相关的函数;
Precomp.h – 预编译的头文件;
Sources – 编译工具所用的源文件列表文件;
IOCommon.h – 驱动和用户态下程序所共用的头文件;
PTExtend.c – 包含新加代码的.c文件
/Test – PassThruEX Win32控制台测试程序;
PTUserIo.cpp – Win32控制台测试程序;
PTUtils.cpp – 支持文件(次重要)。
修改后的PassThru驱动程序和测试程序源代码均可以下载。在此感谢微软件公司提供了使用源代码的许可。
二.添加基本的DeviceIoControl接口
我们期望读者在看这篇文章之前已经熟悉基于IRP接口的用户/驱动程序编程。应用程序使用基本的为终端用户提供的Win32接口函数:CreateFile,DeviceIoControl,ReadFile,WriteFile和CloseHandle。
驱动程序创建一个设备对象和一个在Win32用户态下可用CreateFile打开并访问的符号链接名,并注册一些基于IRP的函数,通过这些函数来实现驱动程序内核态的终端用户接口。
1. 驱动程序代码:
设备I/O控制接口的代码在PassThru驱动示例中已经列出。其中NdisMRegisterDevice函数被PassThru.c中的PtRegisterDevice方法调用,通过该函数创建了设备对象和Win32用户态下可见的符号链接名字以及注册了用处理I/O请求的函数。
(1)原PassThru中的代码:PassThru.c中的PtRegisterDevice函数
以下的代码片断摘自PassThru驱动程序PassThru.c:
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
);
(2)修改后的代码:PassThru.c中的PtRegisterDevice函数
在扩展后的PassThru驱动程序中,我们删除了PtDispatch 函数(在PassThru.c中删除PtDispatch 的代码并在PassThru.h中删除其原型),并在该处用分发函数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
);
以上所用的函数在PTExtend.c中实现,在文件夹/PassThruEx/Driver中可找到。以下所列为添加的函数的全部代码:
These are the skeleton I/O dispatch handlers that are implemented in PTExtend.c. These are sufficient for a quick-and-dirty test of opening and closing a handle on the PassThru device. More functionality will be added.
NTSTATUS
DevOpen(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
UNREFERENCED_PARAMETER(pDeviceObject);
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
pIrpSp->FileObject->FsContext = NULL;
pIrpSp->FileObject->FsContext2 = NULL;
DBGPRINT(("==>Pt DevOpen: FileObject %p/n", pIrpSp->FileObject));
pIrp->IoStatus.Information = 0;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
DBGPRINT(("<== Pt DevOpen/n"));
return NtStatus;
}
NTSTATUS
DevCleanup(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
UNREFERENCED_PARAMETER(pDeviceObject);
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
DBGPRINT(("==>Pt DevCleanup: FileObject %p/n", pIrpSp->FileObject ));
pIrp->IoStatus.Information = 0;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
DBGPRINT(("<== Pt DevCleanup/n"));
return NtStatus;
}
NTSTATUS
DevClose(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
UNREFERENCED_PARAMETER(pDeviceObject);
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
DBGPRINT(("==>Pt DevClose: FileObject %p/n", pIrpSp->FileObject ));
pIrpSp->FileObject->FsContext = NULL;
pIrpSp->FileObject->FsContext2 = NULL;
pIrp->IoStatus.Information = 0;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
DBGPRINT(("<== Pt DevClose/n"));
return NtStatus;
}
NTSTATUS
DevIoControl(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
ULONG BytesReturned = 0;
ULONG FunctionCode;
PUCHAR ioBuffer = NULL;
ULONG inputBufferLength;
ULONG outputBufferLength;
UNREFERENCED_PARAMETER(pDeviceObject);
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
ioBuffer = pIrp->AssociatedIrp.SystemBuffer;
inputBufferLength = pIrpSp->Parameters.DeviceIoControl.InputBufferLength;
outputBufferLength = pIrpSp->Parameters.DeviceIoControl.OutputBufferLength;
FunctionCode = pIrpSp->Parameters.DeviceIoControl.IoControlCode;
DBGPRINT(("==>Pt DevIoControl: FileObject %p/n", pIrpSp->FileObject ));
switch (FunctionCode)
{
case IOCTL_PTUSERIO_ENUMERATE:
case IOCTL_PTUSERIO_OPEN_LOWER_ADAPTER:
case IOCTL_PTUSERIO_OPEN_VIRTUAL_ADAPTER:
case IOCTL_PTUSERIO_QUERY_INFORMATION:
case IOCTL_PTUSERIO_SET_INFORMATION:
default:
NtStatus = STATUS_NOT_SUPPORTED;
break;
}
if (NtStatus != STATUS_PENDING)
{
pIrp->IoStatus.Information = BytesReturned;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
}
DBGPRINT(("<== Pt DevIoControl/n"));
return NtStatus;
}
2.测试用的应用程序代码:
尽管I/O分发处理函数较简单,但是它足以让我们开始编译并测试它。在此,我们添加代码打开并关闭在符号链接名上的句柄。
测试程序叫作“PassThru用户I/O”,它是一个MFC控制台程序,在PTUserIO.cpp中实现,放在/PassThruEx/Test文件夹中。
在这一步中,我们添加的用户态函数主要是PtOpenControlChannel ,它通过CreateFile函数在标准文件名//./PassThru上打开一个句柄,这个用于控制的句柄是一个普通的句柄类型,它具体的适配器绑定没有关系。控制渠道(Control Channel)用于访问全局信息如驱动绑定列表等。
_tMain函数主要功能是调用PtOpenControlChannel ,当安装上我们修改后的驱动程序后,PtOpenControlChannel 函数应该能成功返回;当成功得到一个句柄后,此处不做任何事情,仅仅将其关闭即可。
/////////////////////////////////////////////////////////////////////////////
//// PtOpenControlChannel
//
// Purpose
// Open a "control channel" handle on the PassThru device.
//
// Parameters
// None.
//
// Return Value
// The INVALIE_HANDLE_VALUE if unsuccessful. Otherwise, a valid handle
// to the passthru device.
//
// Remarks
// There are no parameters to this function because the PassThru filespec
// name is already known. For PassThru this is "//./PassThru" or
// "//./Global/PassThru"
//
// This call opens a "control channel". That is, a handle that can be
// used for DeviceIoControl calls but is not associated with a specific
// adapter.
//
// Notice that the FILE_FLAG_OVERLAPPED attribute is not specified. The
// returned handle is used for synchronous operations only.
//
// A more sophisticated API would employ asynchronous I/O. However, a
// sample of that complexity is beyond the scope of this article.
//
HANDLE
PtOpenControlChannel( void )
{
DWORD DesiredAccess;
DWORD ShareMode;
LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL;
DWORD CreationDistribution;
DWORD FlagsAndAttributes;
HANDLE TemplateFile;
HANDLE Handle;
//
// Use CreateFile to Open the Handle
//
DesiredAccess = GENERIC_READ|GENERIC_WRITE;
ShareMode = 0;
CreationDistribution = OPEN_EXISTING;
FlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
TemplateFile = (HANDLE)INVALID_HANDLE_VALUE;
Handle = CreateFile(
"////.//PassThru",
DesiredAccess,
ShareMode,
lpSecurityAttributes,
CreationDistribution,
FlagsAndAttributes,
TemplateFile
);
if( Handle == INVALID_HANDLE_VALUE )
{
// See Microsoft KB Article 259131
Handle = CreateFile(
"////.//Globals//PassThru",
DesiredAccess,
ShareMode,
lpSecurityAttributes,
CreationDistribution,
FlagsAndAttributes,
TemplateFile
);
}
return (Handle);
}
/////////////////////////////////////////////////////////////////////////////
//// _tmain
//
// Purpose
// PTUserIo MFC console application MAIN entry point.
//
// Parameters
//
// Return Value
//
// Remarks
//
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
//
// Open A Handle On The PassThru Device
//
HANDLE PtHandle = PtOpenControlChannel();
if( PtHandle == INVALID_HANDLE_VALUE )
{
cout << "PassThru Handle Open Failed" << endl;
nRetCode = 1;
return nRetCode;
}
cout << "PassThru Handle Open Successful" << endl;
CloseHandle( PtHandle );
return nRetCode;
}
三 添加一个枚举绑定信息的函数
添加到PassThru驱动程序中的第一个有用的函数是用于查询当前适配器绑定关系的。返回的绑定关系名称用于区分适配器上的绑定关系,并可用于其它基于绑定关系的函数中。
这个函数可直接实现,我们在PtOpenControlChannel 返回的句柄 基础上调用DeviceIoControl 的IOCTL_PTUSERIO_ENUMERATE 命令(在IoPrecommon.h中定义)向驱动程序传递一个输出缓冲区指针,相应的分发函数(前面添加的四个之一)就会在缓冲中填入相应的绑定信息字符串。
当DeviceIoControl 返回时,用户就在缓冲区中得到了绑定关系信息。
1.驱动代码:
保存绑定关系名的必要工作在原来的PassThru代码中已经实现,我们要做是添加一个处理IOCTL_PTUSERIO_ENUMERATE 命令的函数来向用户缓冲区填写数据。
绑定关系名保存在由PtBindAdapter 成功调用时创建的ADAPT结构中,虚拟适配器名(传递给NdisIMInitializeDeviceInstanceEx 的名字)保存在ADAPT结构中的DeviceName字段中。
ADAPT结构本身保存在由pAdaptList 指向的单链表中。
我们要做的工作就是将从ADAPT结构中DeviceName字段提取的字符串放到用户提供的缓冲区中。在PTExtend.c中,添加一个DevEnumerateBindings 来完成此工作。此函数由DevIoControl IOCTL处理IOCTL_PTUSERIO_ENUMERATE命令时调用分发函数时调用它。 缓冲区中最终内容为NULL结尾的UNICODE字符串,LIST则以空UNICODE字符串结尾。
有一点需要注意:当我们在遍历pAdaptList和向缓冲区中拷贝绑定关系名时,pAdaptList列表中的内容可能会发生变化,原PassThru代码中的Spinlock就是用于解决此问题,当我们要检查列表时,只需要先获取ClobalLock这个互斥变量即可(用于互斥)。另外,可添加错误检查___try….__except…..语句来阻止错误发生。
<点击下面的链接查看枚举绑定关系的代码>
以下是这部分的完整代码:
NTSTATUS
DevEnumerateBindings(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
ULONG BytesReturned = 0;
PUCHAR ioBuffer = NULL;
ULONG inputBufferLength;
ULONG outputBufferLength, Remaining;
PADAPT *ppCursor;
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
ioBuffer = pIrp->AssociatedIrp.SystemBuffer;
inputBufferLength = pIrpSp->Parameters.DeviceIoControl.InputBufferLength;
outputBufferLength = pIrpSp->Parameters.DeviceIoControl.OutputBufferLength;
Remaining = outputBufferLength;
//
// Sanity Check On Length
//
if( sizeof( UNICODE_NULL ) > Remaining
)
{
BytesReturned = 0;
NtStatus = NDIS_STATUS_BUFFER_OVERFLOW;
goto CompleteTheIRP;
}
//
// Walk The Adapter List
//
NdisAcquireSpinLock( &GlobalLock );
__try
{
//
// Insert List-Terminating NULL
//
*((PWCHAR )ioBuffer) = UNICODE_NULL;
BytesReturned = sizeof( UNICODE_NULL );
Remaining -= sizeof( UNICODE_NULL );
for( ppCursor = &pAdaptList; *ppCursor != NULL; ppCursor = &(*ppCursor)->Next )
{
//
// Sanity Check On Length
//
if( (*ppCursor)->DeviceName.Length sizeof( UNICODE_NULL) > Remaining ) {
BytesReturned = 0;
NtStatus = NDIS_STATUS_BUFFER_OVERFLOW;
break;
}
//
// Add The Virtual DeviceName To The Buffer
// ----------------------------------------
// This name passed to NdisIMInitializeDeviceInstanceEx.
//
NdisMoveMemory(ioBuffer, (*ppCursor)->DeviceName.Buffer, (*ppCursor)->DeviceName.Length );
//
// Move Past Virtual DeviceName In Buffer
//
Remaining -= (*ppCursor)->DeviceName.Length;
BytesReturned = (*ppCursor)->DeviceName.Length;
ioBuffer = (*ppCursor)->DeviceName.Length;
//
// Add Name-Terminating NULL
//
*((PWCHAR )ioBuffer) = UNICODE_NULL;
Remaining -= sizeof( UNICODE_NULL );
BytesReturned = sizeof( UNICODE_NULL );
ioBuffer = sizeof( UNICODE_NULL );
//
// Add List-Terminating NULL
// -------------------------
// Space is already accomodated for this.
//
*((PWCHAR )ioBuffer) = UNICODE_NULL;
}
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
BytesReturned = 0;
NtStatus = STATUS_INVALID_PARAMETER;
}
NdisReleaseSpinLock( &GlobalLock );
CompleteTheIRP:
if (NtStatus != STATUS_PENDING)
{
pIrp->IoStatus.Information = BytesReturned;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
}
return NtStatus;
}
NTSTATUS
DevIoControl(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
ULONG BytesReturned = 0;
ULONG FunctionCode;
PUCHAR ioBuffer = NULL;
ULONG inputBufferLength;
ULONG outputBufferLength;
UNREFERENCED_PARAMETER(pDeviceObject);
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
ioBuffer = pIrp->AssociatedIrp.SystemBuffer;
inputBufferLength = pIrpSp->Parameters.DeviceIoControl.InputBufferLength;
outputBufferLength = pIrpSp->Parameters.DeviceIoControl.OutputBufferLength;
FunctionCode = pIrpSp->Parameters.DeviceIoControl.IoControlCode;
DBGPRINT(("==>Pt DevIoControl: FileObject %p/n", pIrpSp->FileObject ));
switch (FunctionCode)
{
case IOCTL_PTUSERIO_ENUMERATE:
return( DevEnumerateBindings(pDeviceObject, pIrp) );
case IOCTL_PTUSERIO_OPEN_LOWER_ADAPTER:
case IOCTL_PTUSERIO_OPEN_VIRTUAL_ADAPTER:
case IOCTL_PTUSERIO_QUERY_INFORMATION:
case IOCTL_PTUSERIO_SET_INFORMATION:
default:
NtStatus = STATUS_NOT_SUPPORTED;
break;
}
if (NtStatus != STATUS_PENDING)
{
pIrp->IoStatus.Information = BytesReturned;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
}
DBGPRINT(("<== Pt DevIoControl/n"));
return NtStatus;
}
2. 测试程序代码
这里需要写一个用户程序用于演示如何通过驱动得到相关信息以及获得相关的绑定信息。
这个函数可直接实现,我们在PtOpenControlChannel 返回的句柄基础上调用DeviceIoControl ,通过命令IOCTL_PTUSERIO_ENUMERATE(在precommon.h中定义) 将用户缓冲区指针传递给驱动程序。DeviceIoControl 分发函数会调用相应的函数向用户缓冲区中填写数据。
第一步我们先写一个DeviceIoControl简单的Wrapper函数PtEnumerateBindings ,这个函数使命令IOCTL_PTUSERIO_ENUMERATE将用户缓冲区指针传递给驱动程序。如果调用成功,则返回填满绑定关系信息的缓冲区指针。
缓冲区是一个以NULL结尾的字符串,字符串列表的结尾以UNICODE空字符串结尾。
函数_tmain()调用PtEnumerateBindings 。如果成功,则它遍历整个缓冲区并在控制台中显示出来。
如果程序一切OK的话,则将显示以下的信息:
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}"
虽然以上的信息可读性很差,但是我可以断定我们的程序工作得很好。
以上程序在装有NDIS小端口驱动程序(网卡驱动)的Windows XP sp1上正常运行。如果调用其它的API(IOCTL_NDIS_QUERY_GLOBAL_STATS) 则可以显示其它的信息(具体内容跟测试用的机器的配置有关系):
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
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 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
/////////////////////////////////////////////////////////////////////////////
//// PtEnumerateBindings
//
// Purpose
// Use DeviceIoControl to query the PassThru device for a list of its
// current bindings.
//
// Parameters
// PtHandle - Handle returned from a successful call to PtOpenControlChannel.
// Buf - Pointer to a unsigned character array to be filled with
// the PassThru driver's binding information.
// BufLength - Pointer to a DWORD. On input this variable must be filled with
// the size (in bytes) of the buffer. On successful completion the
// variable is used to return the number of bytes written to the
// buffer.
//
// Return Value
// Returns TRUE if the I/O operation was successful. In this case the
// variable pointed to by BufLength is used to return the number of
// bytes written to Buff.
//
// Returns FALSE if the operation was unsuccessful. In this case additional
// error information can be fetched by calling GetLastError. The
//
// Remarks
// If successful the buffer is filled with multiple wide-character strings
// with the end of the buffer identified by an empty string.
//
// Each binding made in the driver is represented by two strings (a tuple)
// in the buffer:
//
// Virtual Adapter Name - The name passed to NdisIMInitializeDeviceInstanceEx
// for the binding.
// Lower Adapter Name - The name passed to NdisOpenAdapter for the binding.
//
BOOL
PtEnumerateBindings(
HANDLE PtHandle,
PCHAR Buf,
DWORD *BufLength
)
{
BOOL bResult;
//
// Use DeviceIoControl to Call The Device
//
bResult = DeviceIoControl(
PtHandle,
IOCTL_PTUSERIO_ENUMERATE,
NULL,
0,
Buf,
*BufLength,
BufLength,
NULL
);
return( bResult );
}
/////////////////////////////////////////////////////////////////////////////
//// _tmain
//
// Purpose
// PTUserIo MFC console application MAIN entry point.
//
// Parameters
//
// Return Value
//
// Remarks
//
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
//
// Open A Handle On The PassThru Device
//
HANDLE PtHandle = PtOpenControlChannel();
if( PtHandle == INVALID_HANDLE_VALUE )
{
cout << "PassThru Handle Open Failed" << endl;
nRetCode = 1;
return nRetCode;
}
cout << "PassThru Handle Open Successful" << endl;
//
// Enumerate The PassThru Bindings
//
WCHAR BindingList[ 2048 ];
DWORD BufLength = sizeof( BindingList );
if( PtEnumerateBindings( PtHandle, (PCHAR )BindingList, &BufLength ) )
{
PWCHAR pWStr = BindingList;
UINT nWCHARsRead;
INT nBytesUnread = BufLength;
if( !BufLength )
{
cout << "Binding List Is Empty" << endl;
}
else
{
cout << endl << "Driver Bindings:" << endl;
while( pWStr && *pWStr && nBytesUnread > 0 )
{
//
// Display Virtual Adapter Name
// ----------------------------
// This is the name passed to NdisIMInitializeDeviceInstanceEx.
// We can call this our "virtual adapter name".
//
_tprintf( " /042%ws/042/n", pWStr );
//
// Advance In Buffer
//
nWCHARsRead = wcslen( pWStr ) 1;
nBytesUnread -= nWCHARsRead * sizeof( WCHAR );
if( nBytesUnread <= 0 )
{
pWStr = NULL;
}
else
{
pWStr = nWCHARsRead;
}
}
}
}
else
{
cout << endl << "Binding Enumeration Failed" << endl;
}
CloseHandle( PtHandle );
return nRetCode;
}
四 ADAPT结构引用计数
在继续下一步之前,我们先解决一个问题:在PassThru中维护的一个重要对象就是ADAPT结构,该结构在PassThru.h中定义。在ADAPT结构中,包含了所有已成功打开的绑定关系的相关信息。在原PassThru代码中,ADAPT结构的生命周期是由NDIS Wrapper函数决定的。它由Protocol.c中的PtBindAdapter 分配并初始化的,在PtUnbindAdapter 或者在MPHalt 处理函数释放。因为这三个函数的调用都是由NDIS负责的,所以创建和销毁工作都能安全的完成。
当我们在一个特定的绑定的关系中创建句柄时,实际上我们创建的是一种句柄和ADAPT结构之间的映身关系。当我们在适配器句柄上进行操作时,就需要访问ADAPT结构中的相关信息。
现在问题已经摆在我们面前了:当一个已经关联的ADAPT结构在用户句柄关闭前被子释放了,这时系统有可能崩溃。
警告:我们必须提供一种机制保证这种情况不会发生。
控制这种临时对象生命周期的通用方法就是“引用计数”。当在PtBindAdapter 中创建ADAPT结构时,ADAPT的引用计数被加1,当计数变成0时,ADAPT结构就可以安全释放了。
1.驱动代码
首先在ADAPT结构中添加一个成员变量:RefCount ,然后添加两个函数用来操作该变量。
PtRefAdapter 安全的将RefCount加1
PtDerefAdapter 将RefCount减1并作以下判断: 如果 RefCount为0则调用 NdisFreeMemory
下面是两个函数的实现:
VOID PtRefAdapter( PADAPT pAdapt )
{
NdisInterlockedIncrement( &pAdapt->RefCount );
}
VOID PtDerefAdapter( PADAPT pAdapt )
{
if( NdisInterlockedDecrement( &pAdapt->RefCount) == 0 )
{
NdisFreeMemory(pAdapt, 0, 0);
}
}
然后,我们修改PtBindAdapter,使其设置ADAPT中的RefCount为1,并且在PtUnbindAdapter and MPHalt 用PtDerefAdapter代替NdisFreeMemory。
从逻辑上看,我们似乎用了一种复杂的方法来调用NdisFreeMemory;但是,当添加了在特定的PassThru绑定关系上打开句柄的代码后,我们就可以用PtRefAdapter和PtDerefAdapter来保证ADAPT结构不会先于用户句柄被释放。
值得注意的是,PtDerefAdapter不仅可以释放被引用的内存(变量),在实际实现的时候,还可将PtUnbindAdapter和MPHalt中的部分代码放在PtDerefAdapter中。
Modified Code: PassThru.h Module, ADAPT Structure
typedef struct _ADAPT
{
...
NDIS_STRING DeviceName; // For initializing the miniport edge
// BEGIN_PTUSERIO
ULONG RefCount; // Used For ADAPT Reference Counting
// END_PTUSERIO
NDIS_EVENT MiniportInitEvent; // For blocking UnbindAdapter while
// an IM Init is in progress.
...
} ADAPT, *PADAPT;
New Code: PTExtend.c Module, PtRefAdapter and PtDerefAdapter Functions
These are the two ADAPT structure reference counting functions. In addition to managing when the ADAPT structure is freed we have absorbed additional redundant code from MPHalt and PtUnbindAdapter into PtDerefAdapter.
VOID
PtRefAdapter( PADAPT pAdapt )
{
NdisInterlockedIncrement( &pAdapt->RefCount );
}
VOID
PtDerefAdapter( PADAPT pAdapt )
{
if( !pAdapt )
{
return;
}
if( NdisInterlockedDecrement( &pAdapt->RefCount) == 0 )
{
DBGPRINT(( "PtDerefAdapter: Adapter: 0x%8.8X/n", pAdapt ? (ULONG )pAdapt : 0 ));
//
// Free all resources on this adapter structure.
//
if (pAdapt->RecvPacketPoolHandle != NULL)
{
//
// Free the packet pool that is used to indicate receives
//
NdisFreePacketPool(pAdapt->RecvPacketPoolHandle);
pAdapt->RecvPacketPoolHandle = NULL;
}
if (pAdapt->SendPacketPoolHandle != NULL)
{
//
// Free the packet pool that is used to send packets below
//
NdisFreePacketPool(pAdapt->SendPacketPoolHandle);
pAdapt->SendPacketPoolHandle = NULL;
}
NdisFreeMemory(pAdapt, 0, 0);
}
}
Modified Code: Protocol.c Module, PtBindAdapter Function
These modifications add the initial reference count to the ADAPT structure, In addition, the new PtDerefAddapter function is called instead of NdisFreeMemory in one place.
VOID
PtBindAdapter(
OUT PNDIS_STATUS Status,
IN NDIS_HANDLE BindContext,
IN PNDIS_STRING DeviceName,
IN PVOID SystemSpecific1,
IN PVOID SystemSpecific2
)
{
...
do
{
...
//
// Initialize the adapter structure. We copy in the IM device
// name as well, because we may need to use it in a call to
// NdisIMCancelInitializeDeviceInstance. The string returned
// by NdisReadConfiguration is active (i.e. available) only
// for the duration of this call to our BindAdapter handler.
//
NdisZeroMemory(pAdapt, TotalSize);
pAdapt->DeviceName.MaximumLength = Param->ParameterData.StringData.MaximumLength;
pAdapt->DeviceName.Length = Param->ParameterData.StringData.Length;
pAdapt->DeviceName.Buffer = (PWCHAR)((ULONG_PTR)pAdapt sizeof(ADAPT));
NdisMoveMemory(pAdapt->DeviceName.Buffer,
Param->ParameterData.StringData.Buffer,
Param->ParameterData.StringData.Length);
// BEGIN_PTUSERIO
//
// Add Initial Reference To Adapter
//
PtRefAdapter( pAdapt );
// END_PTUSERIO
NdisInitializeEvent(&pAdapt->Event);
NdisAllocateSpinLock(&pAdapt->Lock);
...
} while(FALSE);
...
if (*Status != NDIS_STATUS_SUCCESS)
{
if (pAdapt != NULL)
{
if (pAdapt->BindingHandle != NULL)
{
NDIS_STATUS LocalStatus;
//
// Close the binding to the adapter
//
NdisResetEvent(&pAdapt->Event);
NdisCloseAdapter(&LocalStatus, pAdapt->BindingHandle);
pAdapt->BindingHandle = NULL;
if (LocalStatus == NDIS_STATUS_PENDING)
{
NdisWaitEvent(&pAdapt->Event, 0);
LocalStatus = pAdapt->Status;
}
ASSERT (LocalStatus == NDIS_STATUS_SUCCESS);
}
// BEGIN_PTUSERIO
//
// Remove Protocol's Reference To The Adapter
//
PtDerefAdapter( pAdapt );
// END_PTUSERIO
pAdapt = NULL;
}
}
}
Modified Code: Protocol.c Module, PtUnbindAdapter Function
These modifications show the call to the new PtDerefAddapter function is called instead of NdisFreeMemory. In addition, some common code shared between PtUnbindAdapter and MPHalt has been absorbed into PtDerefAdapter.
VOID
PtUnbindAdapter(
OUT PNDIS_STATUS Status,
IN NDIS_HANDLE ProtocolBindingContext,
IN NDIS_HANDLE UnbindContext
)
{
...
if (pAdapt->MiniportHandle != NULL)
{
*Status = NdisIMDeInitializeDeviceInstance(pAdapt->MiniportHandle);
if (*Status != NDIS_STATUS_SUCCESS)
{
*Status = NDIS_STATUS_FAILURE;
}
}
else
{
//
// We need to do some work here.
// Close the binding below us
// and release the memory allocated.
//
if(pAdapt->BindingHandle != NULL)
{
NdisResetEvent(&pAdapt->Event);
NdisCloseAdapter(Status, pAdapt->BindingHandle);
//
// Wait for it to complete
//
if(*Status == NDIS_STATUS_PENDING)
{
NdisWaitEvent(&pAdapt->Event, 0);
*Status = pAdapt->Status;
}
pAdapt->BindingHandle = NULL;
}
else
{
//
// Both Our MiniportHandle and Binding Handle should not be NULL.
//
*Status = NDIS_STATUS_FAILURE;
ASSERT(0);
}
// BEGIN_PTUSERIO
//
// Free the memory here, if was not released earlier(by calling the HaltHandler)
//
PtDerefAdapter( pAdapt );
// END_PTUSERIO
}
DBGPRINT(("<== PtUnbindAdapter: Adapt %p/n", pAdapt));
}
Modified Code: Miniport.c Module, MPHalt Function
These modifications show the call to the new PtDerefAddapter function is called instead of NdisFreeMemory. In addition, some common code shared between PtUnbindAdapter and MPHalt has been absorbed into PtDerefAdapter.
VOID
MPHalt(
IN NDIS_HANDLE MiniportAdapterContext
)
{
...
//
// Delete the ioctl interface that was created when the miniport
// was created.
//
(VOID)PtDeregisterDevice();
if (pAdapt->BindingHandle != NULL)
{
NDIS_STATUS LocalStatus;
//
// Close the binding to the adapter
//
NdisResetEvent(&pAdapt->Event);
NdisCloseAdapter(&LocalStatus, pAdapt->BindingHandle);
pAdapt->BindingHandle = NULL;
if (LocalStatus == NDIS_STATUS_PENDING)
{
NdisWaitEvent(&pAdapt->Event, 0);
LocalStatus = pAdapt->Status;
}
ASSERT (LocalStatus == NDIS_STATUS_SUCCESS);
}
// BEGIN_PTUSERIO
//
// Remove Miniport's Reference To The Adapter
//
PtDerefAdapter( pAdapt );
// END_PTUSERIO
DBGPRINT(("<== MiniportHalt: pAdapt %p/n", pAdapt));
}
五.适配器名柄(Adapter Handle)打开/关闭功能
在这部分,我们不仅会介绍打开一个适配器句柄,而且会讲述句柄的生命周期:包括如何处理NDIS在用户句柄关闭之前取消绑定关系情况的方法。
从Win32用户角度来看,打开适配器句柄的过程简单而且类似。这里将提供PtOpenAdapter函数,它以UNICODE格式的绑定关系名(从PtEnumerateBindings函数中返回)为参数,成功调用则返回一个有效句柄。跟适配器相关的操作就可在这个句柄上操作。当所有工作都结束后,调用PtCloseAdapter函数来关闭句柄。
打开句柄在驱动程序内部的工作步骤如下:
1) 搜索pAdapterList链表,在其中找DeviceName与所指定的名字相匹配的ADAPT结构。
2) 如找到,则分配一个OPEN_CONTEXT结构,用来管理句柄的相关信息。
3) 将适配器(Adapter)与句柄关联起来:A 将指向OPEN_CONTEXT的指针保存在ADAPT结构中。B 将指向ADAPT结构的指针保存在OPEN_CONTEXT中,并将适配器的引用计数增一。C 关联句柄与OPEN_CONTEXT结构,即将指向OPEN_CONTEXT结构的指针保存在FileObject中的FsContext字段中。
在接下来的一系列的I/O分发函数调用中,OPEN_CONTEXT结构可以从FileObject结构的Fscontext字段中恢复。
以上的过程也决定了实现方式上的一个限制:驱动程序只能互斥访问每一个绑定关系,一个PassThru绑定关系上一次只能打开一个句柄。
关闭一个句柄的工作句括:在DevCleanup中添加取消在该句柄上的未完成I/O操作的代码和在DevClose中释放OPEN_CONTEXT结构。
另外一个需要处理的与句柄有关的问题是:在打开了适配器句柄后,NDIS可能随时取消与句柄相关的绑定关系。这种情况必须小心处理。
基于此,我们必须在测试程序中做好相关的处理,从而不致于弄得一团糟。现在将焦点集中到Load/Unload测试上。如果你对NDIS Tester不熟悉的话,请参考:Stephan Wolf's article Testing Network Drivers with the NDIS Test Tool.
当然,要写出没有BUG的程序是不可能的(至少本文的作者有这个缺点)。但是,可以通过一些测试来消除一些错误。一种可以用来测试打开适配器代码的方法是:
1) 在PassThru小端口中连续运行NDIS Tester Load/Unload测试。
2) 当前重复打开一个PassThru适配器,等待5秒钟后又关闭。返复运行。
3) 用PoolTag工具检查是否有内存泄漏。
我们在此不详细描述测试过程,结果只有两种:(1)成功通过;(2)一开始根本就没法运行。
1.驱动程序代码
用户态的PtOpenAdapter函数最终会调用驱动程序的DevIoControl函数对命令IOCTL_PTUSERIO_OPEN_ADAPTER(在IOCommon.h中定义的)的分发函数,DevOpenAdapter做要的工作。
DevOpenAdapter做的第一件事就是调用一个新函数PtLookuAdapterByName去查找ADAPT结构,在这个结构中,DeviceName与用户提供的绑定关系名相匹配。对这个函数有两个需要注意的地方:
名字比较对大小写敏感:函数NdisEqualMemory 用来比较用户的绑定关系名与ADAPT结构中的DeviceName。用户可能想使用不区分大小的字符串比较函数,但是,由于这种比较是在拥有Spinlock(IRQL==DISPATCH_LEVEL)的条件下进行的,所以其它字符比较不能使用。
引用计数添加到ADAPT结构中:注意,引用计数应在NdisReleaseSpinLock调用前添加到ADAPT结构中。如果引用计数还没有增加,则完全可能导致NDIS无限绑定(或者是ADAPT结构指针在返回到调用者之前被错误释放掉)。
如果PtLookupAdapterByName 成功找到一个与之匹配的ADAPT绑定,此时NDIS会分配一个OPEN_CONTEXT结构来管理特定的OPEN CONTEXT信息(例如:打开句柄)。OPEN_CONTEXT的分配与初始化由DevAllocateOpenContext函数来完成,计数工作则通过DevRefOpenContext 和DevDerefOpenContext。
这里我们需要对已经找到的用户句柄和特定的PassThru绑定关系建立关联。以下是要关联两个实体:
1) 用户态句柄:由I/O栈中FILE_OBJECT。
2) 特定的绑定关系:由OPEN_CONTEXT结构表示(pAdapt字段指向ADAPT结构)。
标准的DDK并没有讲述太多的关于I/O栈中的FILE_OBJECT的用途。但是,在很多情况下,它的用途还是很大的。如果你曾经写过文件系统驱动,你就会发现FsContext和FsContext2字段是多么重要。
简单地将FILE_OBJECT放置在I/O栈中代表PassThru的一个打开实例。在FILE_OBJECT与用户态句柄之间用一种一对一的联系(至少在简单情况下是这样)。最重要的是驱动程序可以自由的指定你所想要的值给FsContext和FsContext2字段,这些值将会在FILE_OBJECT中被返回给驱动程序以备后来在同一句柄上对I/O分发函数的调用。
因此,为了建立句柄与绑定的关系,我们将FsContext字段设为指向OPEN_CONTEXT结构的指针。在接下来对I/O分发函数的调用中我们检查FsContext。如果它非空,那么就是我们正在使用的绑定关系的指针。
六 关闭适配器句柄
当一个用户态句柄关闭时,需要在DevCleanup和DevClose过程中做一些工作。当然,DevClose会调用DevDerefOpenContext。
七 处理在一个打开的适配器上意外取消绑定的情况:
最后,我们必须添加这样一种功能:处理在一个用户态句柄已经打开的情况下NDIS取消适配器绑定的情况。为了应付这种情况,我们添加了函数DevOnUnbindAdapter ,用来通知已经打开句柄的用户。此函数必须等待所有的在该适配器上重要的NDIS操作完成,然后取消该句柄上的所有pending I/O。这里有个假设:当函数DevOnUnbindAdapter 一返回,适配就会调用NdisCloseAdapter 来关闭适配器。
我们在MPHalt和PtUnbindAdapter函数调用DevOnUnbindAdapter 。
<点击链接查看相关代码>
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.
PADAPT
PtLookupAdapterByName(
IN PUCHAR pNameBuffer,
IN USHORT NameBufferLength,
IN BOOLEAN bUseVirtualName
)
{
PADAPT *ppCursor, pAdapt = NULL;
//
// Sanity Checks
//
if( !pNameBuffer || !NameBufferLength )
{
return( NULL );
}
//
// Walk The Adapter List
// ---------------------
// Hold the global lock while walking. Otherwise, the adapter list could be altered at any point in
// the list processing sequence.
//
NdisAcquireSpinLock( &GlobalLock );
for( ppCursor = &pAdaptList; *ppCursor != NULL;
ppCursor = &(*ppCursor)->Next
)
{
__try
{
if( bUseVirtualName )
{
//
// Check For Match Against Virtual Adapter Name
//
if( ( (*ppCursor)->DeviceName.Length == NameBufferLength) &&
NdisEqualMemory( (*ppCursor)->DeviceName.Buffer, pNameBuffer, NameBufferLength ))
{
//
// Return Pointer To Found Adapter
//
pAdapt = (*ppCursor);
break;
}
}
else
{
//
// Check For Match Against Lower Adapter Name
//
if( ( (*ppCursor)->LowerDeviceName.Length == NameBufferLength) &&
NdisEqualMemory( (*ppCursor)->LowerDeviceName.Buffer, pNameBuffer, NameBufferLength))
{
//
// Return Pointer To Found Adapter
//
pAdapt = (*ppCursor);
break;
}
}
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
pAdapt = NULL;
break;
}
}
//
// Add Reference To Adapter Memory
// -------------------------------
// As soon as the spinlock is released (below) and before returning to the caller it is possible
// for NDIS to unbind the selected adapter from the PassThru protocol. The reference counting scheme
// insures that the memory pointed to by pAdapt will remain valid until the last call to
// PtDerefAdapter.
//
if( pAdapt )
{
PtRefAdapter( pAdapt );
}
NdisReleaseSpinLock( &GlobalLock );
return( pAdapt );
}
New Code: PTExtend.h Module, OPEN_CONTEXT Structure
Used by the driver to manage information about a specific open context (i.e., open handle).
typedef
struct _OPEN_CONTEXT
{
ULONG RefCount;
NDIS_SPIN_LOCK Lock;
BOOLEAN bAdapterClosed;
PADAPT pAdapt;
}
OPEN_CONTEXT, *POPEN_CONTEXT;
Modified Code: PassThru.h Module, ADAPT Structure
Modify the ADAPT structure to add a member variable that points to the OPEN_CONTEXT structure associated with the adapter..
typedef struct _ADAPT
{
...
NDIS_STRING DeviceName; // For initializing the miniport edge
// BEGIN_PTUSERIO
ULONG RefCount; // Used For ADAPT Reference Counting
POPEN_CONTEXT pOpenContext;
// END_PTUSERIO
NDIS_EVENT MiniportInitEvent; // For blocking UnbindAdapter while
// an IM Init is in progress.
...
} ADAPT, *PADAPT;
New Code: PTExtend.c Module, DevRefOpenContext and DevDerefOpenContext Functions
These are the two OPEN_CONTEXT structure reference counting functions. It manages when the OPEN_CONTEXT structure is freed..
VOID
DevRefOpenContext( POPEN_CONTEXT pOpenContext )
{
PtRefAdapter( pOpenContext->pAdapt );
NdisInterlockedIncrement( &pOpenContext->RefCount );
}
VOID
DevDerefOpenContext( POPEN_CONTEXT pOpenContext )
{
PADAPT pAdapt = NULL;
if( !pOpenContext )
{
return;
}
pAdapt = pOpenContext->pAdapt;
if( NdisInterlockedDecrement( &pOpenContext->RefCount) == 0 )
{
NdisFreeSpinLock( &pOpenContext->Lock );
NdisFreeMemory(pOpenContext, 0, 0);
}
PtDerefAdapter( pAdapt );
}
New Code: PTExtend.c Module, DevAllocateOpenContext Function
This function allocates and initializes an OPEN_CONTEXT structure.
POPEN_CONTEXT
DevAllocateOpenContext( PADAPT pAdapt )
{
POPEN_CONTEXT pOpenContext = NULL;
NdisAllocateMemoryWithTag( &pOpenContext, sizeof( OPEN_CONTEXT ), TAG );
if( !pOpenContext )
{
return( NULL );
}
//
// Initialize The Open Context Structure
//
NdisZeroMemory( pOpenContext, sizeof( OPEN_CONTEXT ) );
NdisAllocateSpinLock( &pOpenContext->Lock );
NdisInitializeEvent( &pOpenContext->LocalRequest.RequestEvent );
//
// Add Initial Reference To Open Context
// -------------------------------------
// Note that we already have added an implicit reference to the adapter
// because of the PtLookupAdapterByName call.
//
pOpenContext->RefCount = 1;
pOpenContext->pAdapt = pAdapt;
return( pOpenContext );
}
New Code: PTExtend.c Module, DevOpenAdapter Function
The DevIoControl dispatcher calls the DevOpenAdapter function to do the work related to opening an adapter handle.
NTSTATUS
DevOpenAdapter(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp,
IN BOOLEAN bUseVirtualName
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
ULONG BytesReturned = 0;
PUCHAR pNameBuffer = NULL;
ULONG NameBufferLength;
PADAPT pAdapt;
POPEN_CONTEXT pOpenContext;
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
pNameBuffer = pIrp->AssociatedIrp.SystemBuffer;
NameBufferLength = pIrpSp->Parameters.DeviceIoControl.InputBufferLength;
//
// Lookup Adapter By Name
// ----------------------
// If successful the lookup function has added a ref count to the found ADAPT
// structure.
//
pAdapt = PtLookupAdapterByName( pNameBuffer, (USHORT )NameBufferLength, bUseVirtualName );
if( !pAdapt )
{
NtStatus = STATUS_OBJECT_NAME_NOT_FOUND;
goto CompleteTheIRP;
}
//
// Fail Open If Unbind Is In Progress
//
NdisAcquireSpinLock(&pAdapt->Lock);
if( pAdapt->UnbindingInProcess )
{
NdisReleaseSpinLock(&pAdapt->Lock);
PtDerefAdapter( pAdapt );
NtStatus = STATUS_INVALID_DEVICE_STATE;
goto CompleteTheIRP;
}
NdisReleaseSpinLock(&pAdapt->Lock);
if( pAdapt->pOpenContext )
{
PtDerefAdapter( pAdapt );
NtStatus = STATUS_DEVICE_BUSY;
goto CompleteTheIRP;
}
pOpenContext = DevAllocateOpenContext( pAdapt );
if( !pOpenContext )
{
PtDerefAdapter( pAdapt );
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
goto CompleteTheIRP;
}
//
// Sanity Check For Concurrent Open Race Condition
// -----------------------------------------------
// At this point we enforce exclusive access on a per-binding basis.
//
// This logic deals with the situation where two concurrent adapter
// opens could be in progress. We want an atomic mechanism that insures
// that only one of the opens will be successful.
//
// This InterlockedXXX function performs an atomic operation: First it
// compares pAdapt->pOpenContext with NULL, if they are equal, the function
// puts pOpenContext into pAdapt->pOpenContext, and return NULL. Otherwise,
// it return existing pAdapt->pOpenContext without changing anything.
//
// NOTE: This implementation is borrowed from the NDISPROT sample from
// the Windows DDK.
//
if ( InterlockedCompareExchangePointer (& (pAdapt->pOpenContext), pOpenContext, NULL) != NULL)
{
PtDerefAdapter( pAdapt );
NtStatus = STATUS_DEVICE_BUSY;
goto CompleteTheIRP;
}
//
// Associate This Handle With The Open Context
//
pIrpSp->FileObject->FsContext = pOpenContext;
//
// Complete The IRP
//
CompleteTheIRP:
pIrp->IoStatus.Information = BytesReturned;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return NtStatus;
}
Modified Code: PTExtend.c Module, DevIoControl Function
In DevIoControl we add a call to dispatch IOCTL_PTUSERIO_OPEN_ADAPTER (defined in IOCommon.h) to the DevOpenAdapter handler
NTSTATUS
DevIoControl(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
ULONG BytesReturned = 0;
ULONG FunctionCode;
PUCHAR ioBuffer = NULL;
ULONG inputBufferLength;
ULONG outputBufferLength;
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
ioBuffer = pIrp->AssociatedIrp.SystemBuffer;
inputBufferLength = pIrpSp->Parameters.DeviceIoControl.InputBufferLength;
outputBufferLength = pIrpSp->Parameters.DeviceIoControl.OutputBufferLength;
FunctionCode = pIrpSp->Parameters.DeviceIoControl.IoControlCode;
switch (FunctionCode)
{
case IOCTL_PTUSERIO_ENUMERATE:
return( DevEnumerateBindings(
pDeviceObject,
pIrp
)
);
case IOCTL_PTUSERIO_OPEN_ADAPTER:
return( DevOpenAdapter(
pDeviceObject,
pIrp,
FALSE // Is Lower Adapter
)
);
case IOCTL_PTUSERIO_QUERY_INFORMATION:
case IOCTL_PTUSERIO_SET_INFORMATION:
default:
NtStatus = STATUS_NOT_SUPPORTED;
break;
}
if (NtStatus != STATUS_PENDING)
{
pIrp->IoStatus.Information = BytesReturned;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
}
return NtStatus;
}
Modified Code: PTExtend.c Module, DevClose Function
Here we null some pointers and call DefDerefOpenContext.
NTSTATUS
DevClose(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
POPEN_CONTEXT pOpenContext;
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
pOpenContext = pIrpSp->FileObject->FsContext;
//
// Undo IRP_MJ_CREATE Operations
//
pIrpSp->FileObject->FsContext = NULL;
pIrpSp->FileObject->FsContext2 = NULL;
if( pOpenContext )
{
if( pOpenContext->pAdapt )
{
(pOpenContext->pAdapt)->pOpenContext = NULL;
}
DevDerefOpenContext( pOpenC ontext );
}
pIrp->IoStatus.Information = 0;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return NtStatus;
}
Modified Code: PTExtend.c Module, DevOnUnbindAdapter Function
This function is called from MPHalt or PtUnbindAdapter to notify the open handle logic of a "surprise unbind" event.
VOID
DevOnUnbindAdapter( POPEN_CONTEXT pOpenContext )
{
PADAPT pAdapt = NULL;
if( !pOpenContext )
{
return;
}
DBGPRINT(("==>Pt DevOnUnbindAdapter: Context %p/n", pOpenContext ));
//
// Set Flag That Will Cause Subsequent I/O To Be failed
//
pOpenContext->bAdapterClosed = TRUE;
//
// Wait For Outstanding NDIS Operations On The Handle To Complete
//
//
// Cancel Pending I/O On The Handle
//
DBGPRINT(("<== Pt DevOnUnbindAdapter/n"));
}
Modified Code: Miniport.c Module, MPHalt Function
This function is modified to call DevOnUnbindAdapter to notify the open handle logic of a "surprise unbinding" event.
VOID
MPHalt(
IN NDIS_HANDLE MiniportAdapterContext
)
{
...
NdisReleaseSpinLock(&GlobalLock);
// BEGIN_PTUSERIO
//
// Notify Open Handle Logic About Surprise Inbind
//
DevOnUnbindAdapter( pAdapt->pOpenContext );
// END_PTUSERIO
//
// Delete the ioctl interface that was created when the miniport
// was created.
//
(VOID)PtDeregisterDevice();
...
}
Modified Code: Protocol.c Module, PtUnbindAdapter Function
This function is modified to call DevOnUnbindAdapter to notify the open handle logic of a "surprise unbinding" event.
VOID
PtUnbindAdapter(
OUT PNDIS_STATUS Status,
IN NDIS_HANDLE ProtocolBindingContext,
IN NDIS_HANDLE UnbindContext
)
{
...
else
{
NdisReleaseSpinLock(&pAdapt->Lock);
}
// BEGIN_PTUSERIO
//
// Notify Open Handle Logic About Surprise Inbind
//
DevOnUnbindAdapter( pAdapt->pOpenContext );
// END_PTUSERIO
#ifndef WIN9X
//
// Check if we had called NdisIMInitializeDeviceInstanceEx and
// we are awaiting a call to MiniportInitialize.
//
...
}
应用程序代码:
打开/关闭一个适配器句柄的代码实现很简单。我们在PtOpenControlChannel 返回的句柄基础上调用DeviceIoControl 函数,通过命令IOCTL_PTUSERIO_OPEN_ADAPTER 将存有绑定关系名的用户态缓冲区指针传给PassThru驱动程序,紧接着,驱动程序的DevIoControl 分发函数会调用DevOpenAdapter 函数来在内核中完成相关的工作。
新加代码:PTUserIo.cpp中的PtOpenAdapter和PtCloseAdapter函数。
以下就是用来在PassThru中打开基于绑定关系句柄的用户态函数:
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 ) );
}
八 处理事件通知(Event Notifications)
你可能以为有关在驱动程序上打开/关闭Win32句柄的话题已经讲完了,其实还没有完。接下来我们讲下一个重要的话题:
注意:Win32 PnP(即插即用)机制可以随时停止一个miniport驱动或者取消协议驱动的绑定。事实上,PnP可以在任何时候开始卸载PassThru驱动。
那就是说PnP可以引起任何一个已经打开的Win32句柄无效。
引用计数机制只能在有限的范围内解决这个问题。它至少可让NDIS在一个已经适配器句柄已经打开时安全地取消它。虽然有部分内存没有象我们预期的那样快被释放掉,但是系统也不会因此而崩溃。但是,这里还没有一种机制来通知上层应用程序该适配器句柄已经无效了。
需要注意的一点是:如果在PassThru设备上还有未关闭的句柄,那么NDIS就不会卸载这个驱动,即便是它已经取消了所有的该驱动程序上的绑定关系。
这问题有点需要提及:(1)在PassThru驱动上有win32句柄打开的前提下,部分PnP事件无法完成。(2)为了相对温和地处理这些事件,应该通知Win32应用程序。
在新闻组上经常问到的一个问题是:无法卸载驱动程序。可能的原因之一就是有一个未关闭的句柄存在。只要该句柄未关闭就无法卸载。
一个小技巧来处理这种问题就是:仅仅在需要打开它,即让打开的句柄在很快就关闭。这种重复打开方式就变成了处理问题的通知机制(注意:当未关闭该句柄时不能再次打开)。我们就是在PTUserIo程序中使用的这种方法。
另一情况是:有些程序需要长时间保持该打开的句柄(如数据包监控程序),这些程序就不能用上述方法,只能用其它的通知机制。
在这篇文章中,我们不打开实现这种通知机制,而是留到后续章节。当我们向驱动程序中添加需长时间保持句柄的程序时,才实现它。
九 在一个适配器句柄上查询信息
既然我们已经实现了一种在PassThru驱动中打开基于绑定的句柄的机制,接下来添加在其上通过NDIS请求查询信息的功能。
理论上,Win32 API查询信息也是简单在PtOpenAdapter 返回的句柄基础上调用DeviceIoControl 。输入缓冲区主要用来向传递相关的NDIS对象标识(OID)给驱动程序。驱动程序的协议驱动部分则调用NdisRequest 来查询对应OID并将结果写入用户提供 的输出缓冲区中。
但是,在原来的PassThru驱动程序中,NdisRequest 已经作为MINIPORT驱动的一部分了,因此,在Win32相关的NdisRequest 中,我们就不能改变原业存在的功能关系。
另外,我们须期望,一个功能完备的NDIS IM驱动实际有三个独的NdisRequest initiators。
1. Miniport:MPQueryInformation/MPSetInformation Passthrough。
2. Win32-Initiated Requests:这是本节讨论的问题
3. Autonomous Driver-Initiated Requests:这是以后讨论的问题。
要想实现简单,我们须注意以下约定:
在PassThru驱动适配器句柄上进行的查询是同步的,并且必须被Win32应用程序序列化。
如果我们做到了,则可以在PTUserIo程序中使用新的PtQueryInformation API函数来获取具有可读性的信息。以下测试在Windows XP sp1,3个NDIS 小端口驱动程序上运行。
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
1 驱动程序代码:
在添加Win32-initiated NDIS查询功能之前,先看看原来的驱动程序中是如何处理对PQueryInformation 的调用的。
原驱动程序是基于这种事实的:NDIS会序列化对这个函数的调用。这就意味着在任一个绑定关系上同时只有对MPQueryInformation的调用。基于此,PassThru驱动在ADAPT结构中对每一个绑定关系提供了一个NDIS_REQUEST结构,对MPQueryInformation 的调用则通过NDIS_REQUEST结构来向下层的miniport传递查询。NdisRequest 是在等待NDIS_EVENT(同步信号量)上调用的,直到PtRequestComplete 处理函数被调用。
注意:要添加我们的Win32-initiated NDIS信息查询功能,只要对PtRequestComplete 做适当的修改即可。
幸运的是,要区分miniport-initiated Requests和Win32-initiated Requests,所要做的工作就是简单的判断NDIS_REQUEST指针。如下所示:
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;
}
...
}
我们对NdisRequest 的Win32-initiated 调用机制使得完成请求增加了一个Second level of indirection,通过向NDIS_REQUEST结构添加外包结构来实现,如下所示。
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;
NDIS_REQUEST_EX结构包含了request-specific request completion routine相关的字段和request-specific completion context相关的字段。它这样使用:
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);
...
}
到此为止,我们实现了如何处理Request completion,接下来将实现Win32-initiated Requests的驱动程序代码。
(1)向OPEN_CONTEXT结构中添加一个NDIS_REQUEST_EX成员。这个成员用来在打开的适配器句柄上调用NdisRequests。
(2)修改DevAllocateOpenContext ,初始化NDIS_REQUEST_EX NDIS_EVENT。
(3)添加一个新的DevRequestComplete处理函数,它是一个Win32-initiated Requests的Request-specific处理函数
(4)修改DevIoControl用来将IOCTL_PTUSERIO_QUERY_INFORMATION 命令转发给DevQueryInformation.。
(5)实现DevQueryInformation. 用来调用NdisRequest和等待NDIS_EVENT上的完成事件。
见 “扩展微软DDK中的NDIS IM驱动的功能:添加一个DeviceIoControl接口(八).”
2 应用程序代码
新代码:PTUserIo.cpp中的PtQueryInformation函数
以下是打开PassThru驱动上的基于绑定关系句柄的用户态函数:
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 );
}
原作者的脚注:
<略>
版权信息:
1 代码版权:
2 文章版权:
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.
3. 下载原文的代码:
http://www.wd-3.com/downloads/ExtendingPassthru.zip
4. 关于作者
About the author:
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.
//
// 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);
...
}
New Code: PTExtend.c Module, DevQueryInformation Function
The DevIoControl dispatcher calls the DevQueryinformation function to do the work related to making a NdisRequest to query information on a PassThru adapter handle.
NTSTATUS
DevQueryInformation(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp,
IN BOOLEAN bUseVirtualName
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
ULONG BytesReturned = 0;
PUCHAR ioBuffer = NULL;
ULONG inputBufferLength;
ULONG outputBufferLength;
NDIS_OID Oid;
PADAPT pAdapt;
POPEN_CONTEXT pOpenContext;
PNDIS_REQUEST_EX pLocalRequest;
NDIS_STATUS NdisStatus;
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
ioBuffer = pIrp->AssociatedIrp.SystemBuffer;
inputBufferLength = pIrpSp->Parameters.DeviceIoControl.InputBufferLength;
outputBufferLength = pIrpSp->Parameters.DeviceIoControl.OutputBufferLength;
pOpenContext = pIrpSp->FileObject->FsContext;
if( !pOpenContext )
{
NtStatus = STATUS_INVALID_HANDLE;
goto CompleteTheIRP;
}
pAdapt = pOpenContext->pAdapt;
if( !pAdapt )
{
NtStatus = STATUS_INVALID_HANDLE;
goto CompleteTheIRP;
}
//
// Sanity Check On Input Buffer/OID
//
if( inputBufferLength != sizeof( NDIS_OID ) )
{
NtStatus = STATUS_INVALID_PARAMETER;
goto CompleteTheIRP;
}
Oid = *(PNDIS_OID )ioBuffer;
//
// Fail Open If Unbind Is In Progress
//
NdisAcquireSpinLock(&pAdapt->Lock);
if( pAdapt->UnbindingInProcess )
{
NdisReleaseSpinLock(&pAdapt->Lock);
NtStatus = STATUS_INVALID_DEVICE_STATE;
goto CompleteTheIRP;
}
//
// All other queries are failed, if the miniport is not at D0,
//
if (pAdapt->MPDeviceState > NdisDeviceStateD0)
{
NdisReleaseSpinLock(&pAdapt->Lock);
NtStatus = STATUS_INVALID_DEVICE_STATE;
goto CompleteTheIRP;
}
//
// This is in the process of powering down the system, always fail the request
//
if (pAdapt->StandingBy == TRUE)
{
NdisReleaseSpinLock(&pAdapt->Lock);
NtStatus = STATUS_INVALID_DEVICE_STATE;
goto CompleteTheIRP;
}
NdisReleaseSpinLock(&pAdapt->Lock);
//
// Now (Finally) Make The NDIS Request...
//
//
// May need to add ref counts to adapt and open context. Also, bump
// a counter of outstanding requests...
//
DevRefOpenContext( pOpenContext );
pLocalRequest = &pOpenContext->LocalRequest;
pLocalRequest->Request.RequestType = NdisRequestQueryInformation;
pLocalRequest->Request.DATA.QUERY_INFORMATION.Oid = Oid;
pLocalRequest->Request.DATA.QUERY_INFORMATION.InformationBuffer = ioBuffer;
pLocalRequest->Request.DATA.QUERY_INFORMATION.InformationBufferLength = outputBufferLength;
pLocalRequest->Request.DATA.QUERY_INFORMATION.BytesNeeded = 0;
pLocalRequest->Request.DATA.QUERY_INFORMATION.BytesWritten = 0;
pLocalRequest->RequestCompleteHandler = DevRequestComplete;
pLocalRequest->RequestContext = pOpenContext;
NdisResetEvent( &pLocalRequest->RequestEvent );
NdisRequest(
&NdisStatus,
pAdapt->BindingHandle,
(PNDIS_REQUEST )pLocalRequest
);
if( NdisStatus != NDIS_STATUS_PENDING )
{
DevRequestComplete( pAdapt, pLocalRequest, NdisStatus );
}
NdisWaitEvent( &pLocalRequest->RequestEvent, 0 );
NdisStatus = pLocalRequest->RequestStatus;
if( NdisStatus == NDIS_STATUS_SUCCESS )
{
BytesReturned = pLocalRequest->Request.DATA.QUERY_INFORMATION.BytesWritten;
if( BytesReturned > outputBufferLength )
{
BytesReturned = outputBufferLength;
}
NtStatus = STATUS_SUCCESS;
}
else
{
NDIS_STATUS_TO_NT_STATUS( NdisStatus, &NtStatus);
}
DevDerefOpenContext( pOpenContext );
//
// Complete The IRP
//
CompleteTheIRP:
pIrp->IoStatus.Information = BytesReturned;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return NtStatus;
}
Modified Code: PTExtend.c Module, DevIoControl Function
In DevIoControl we add a call to dispatch IOCTL_PTUSERIO_QUERY_INFORMATION (defined in IOCommon.h) to the DevQueryInformation handler
NTSTATUS
DevIoControl(
IN PDEVICE_OBJECT pDeviceObject,
IN PIRP pIrp
)
{
PIO_STACK_LOCATION pIrpSp;
NTSTATUS NtStatus = STATUS_SUCCESS;
ULONG BytesReturned = 0;
ULONG FunctionCode;
PUCHAR ioBuffer = NULL;
ULONG inputBufferLength;
ULONG outputBufferLength;
pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
ioBuffer = pIrp->AssociatedIrp.SystemBuffer;
inputBufferLength = pIrpSp->Parameters.DeviceIoControl.InputBufferLength;
outputBufferLength = pIrpSp->Parameters.DeviceIoControl.OutputBufferLength;
FunctionCode = pIrpSp->Parameters.DeviceIoControl.IoControlCode;
switch (FunctionCode)
{
case IOCTL_PTUSERIO_ENUMERATE:
return( DevEnumerateBindings(
pDeviceObject,
pIrp
)
);
case IOCTL_PTUSERIO_OPEN_ADAPTER:
return( DevOpenAdapter(
pDeviceObject,
pIrp,
FALSE // Is Lower Adapter
)
);
case IOCTL_PTUSERIO_QUERY_INFORMATION:
return( DevQueryInformation(
pDeviceObject,
pIrp,
FALSE // Is Lower Adapter
)
);
case IOCTL_PTUSERIO_SET_INFORMATION:
default:
NtStatus = STATUS_NOT_SUPPORTED;
break;
}
if (NtStatus != STATUS_PENDING)
{
pIrp->IoStatus.Information = BytesReturned;
pIrp->IoStatus.Status = NtStatus;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
}
return NtStatus;
}
New Code: PTExtend.c Module, DevRequestComplete Function
This is the second-level NdisRequest completion handler called from PtRequestComplete for completion of non-local requests.
VOID
DevRequestComplete(
IN PADAPT pAdapt,
IN PNDIS_REQUEST_EX pLocalRequest,
IN NDIS_STATUS Status
)
{
POPEN_CONTEXT pOpenContext;
DBGPRINT(("<== Pt DevRequestComplete/n"));
pOpenContext = (POPEN_CONTEXT )pLocalRequest->RequestContext;
pLocalRequest->RequestStatus = Status;
NdisSetEvent( &pLocalRequest->RequestEvent );
DBGPRINT(("<== Pt DevRequestComplete/n"));