NDIS小端口驱动实现IOCTL分发

关键思路:为驱动创建一个设备对象

usbnwifi 例程中,MPInitialize函数通过NICRegisterDevice函数,注册了NICDispatch函数入口(该函数实现IRP的分发处理),从而为小端口驱动注册了一个IOCTL接口。这一过程根本上是调用了NDIS的NdisRegisterDeviceEx函数,传入了包含分发函数入口的_NDIS_DEVICE_OBJECT_ATTRIBUTES数据结构。

最近在编写 NDIS 小端口驱动程序时,需要利用 IOCTL 对网卡进行配置,但翻遍 NDIS 文档也没有找到 NDIS 的 IOCTL 实现。有幸在 WDK 7600 提供的 usbnwifi 例程中看到了实现思路,看起来基本上也就是 WDM 框架下 IOCTL 的简单封装。现在把思路写下来供思考,不全面的地方,建议直接参考 usbnwifi 例程的实现。

1. 创建设备对象

NDIS框架提供的NdisRegisterDeviceEx函数,可以为 NDIS 小端口驱动创建一个设备对象。函数原型如下:

NDIS_STATUS NdisRegisterDeviceEx(
  _In_  NDIS_HANDLE                    NdisHandle,
  _In_  PNDIS_DEVICE_OBJECT_ATTRIBUTES DeviceObjectAttributes,
  _Out_ PDEVICE_OBJECT                 *pDeviceObject,
  _Out_ PNDIS_HANDLE                   NdisDeviceHandle
);

第一个参数是 NDIS 句柄不提。

第二个参数是 IRP 的关键参数。NDIS_DEVICE_OBJECT_ATTRIBUTES结构定义了设备对象的若干属性。

先创建一个分发表,将分发函数入口点传入:

DispatchTable[IRP_MJ_CREATE] = NICDispatch_Create;
DispatchTable[IRP_MJ_CLEANUP] = NICDispatch_Cleanup;
DispatchTable[IRP_MJ_CLOSE] = NICDispatch_Close;
DispatchTable[IRP_MJ_DEVICE_CONTROL] = NICDispatch_IOCTL; // 也可将这四种传入同一个分发函数,在函数内做区分

将分发表和一些其他属性一起传给NDIS_DEVICE_OBJECT_ATTRIBUTES 结构:

DeviceObjectAttributes.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; // type implicit from the context
DeviceObjectAttributes.Header.Revision = NDIS_DEVICE_OBJECT_ATTRIBUTES_REVISION_1;
DeviceObjectAttributes.Header.Size = sizeof(NDIS_DEVICE_OBJECT_ATTRIBUTES);
DeviceObjectAttributes.MajorFunctions = &DispatchTable[0];
DeviceObjectAttributes.ExtensionSize = sizeof(CONTROL_DEVICE_EXTENSION);
DeviceObjectAttributes.DefaultSDDLString = NULL;
DeviceObjectAttributes.DeviceClassGuid = 0;

有两个属性需要特别留意,DeviceNameSymbolicName。因为无法使用 GUID 在应用程序和驱动程序间作为沟通的凭据,此处的办法是创建命名设备。

RtlUnicodeStringPrintf(&DeviceName, L"%s%d", L"\\Device\\xxxxxx", i);  
RtlUnicodeStringPrintf(&DeviceLinkUnicodeString, L"%s%d", L"\\DosDevices\\xxxxxx", i++);

DeviceObjectAttributes.DeviceName = &DeviceName;
DeviceObjectAttributes.SymbolicName = &DeviceLinkUnicodeString;

细心的同学可能会发现了,我们把我们想要的设备名和数字i拼接在一起构成了设备名,这样做的原因 usbnwifi 给出的解释是:

Repeatedly try to create a named device object until we run out of buffer space or we succeed.

个人认为主要是为了提高可靠性。

第三个参数是设备对象,PDEVICE_OBJECT类型。

第四个参数用于保存 NDIS 句柄,用于销毁。

销毁时,调用NdisDeregisterDeviceEx函数进行释放。

至于创建和销毁的时机,应分别在MPInitializeMPHalt中。

2. IRP 分发函数

传入分发函数的是一个 IRP 对象 Irp。

熟悉 WDM 或 KMDF 下 IOCTL 编程的同学肯定知道,IOCTL 关键的参数无非是缓冲区、长度、状态和返回信息。在WDM/NDIS 下的实现还需要另加一个类型(注:原为 MajorFunction,不知怎么翻译合适)。那我们依次来获取这些参数。

第一步,直接通过 IRP 对象可以获取缓冲区、状态和返回信息。

pBuffer = (PULONG) Irp->AssociatedIrp.SystemBuffer; // 缓冲区
Irp->IoStatus.Information             // 返回信息,即应用程序中的BytesReturn
Irp->IoStatus.Status                // 返回状态

此处特别留意这里的缓冲区。熟悉 KMDF 的同学可能不太理解,不应该是输入缓冲区和输出缓冲区吗,这里为什么只有一个缓冲区。我阅读到一些文档,似乎可以这样理解:

在 WDM/NDIS 的 IOCTL 实现中,驱动视角的输入和输出共用了同一缓冲区

但在 OS 内部的 IO 子系统中,这一缓冲区分别对应于应用程序的输入缓冲区和输出缓冲区。发出 IOCTL 时,OS 将输入缓冲区的数据拷贝到驱动缓冲区;IOCTL 返回时,OS 再将驱动缓冲区的数据拷贝到输出缓冲区。由此达到公用缓冲区的效果。

第二步,获取一个 IRP 栈(注:原为 PIO_STACK_LOCATION):

PIO_STACK_LOCATION  irpStack;
irpStack = IoGetCurrentIrpStackLocation(Irp);

通过 irpStack 我们可以获得很多属性,如:

irpStack->MajorFunction  // 上文所说的IRP类型,有IRP_MJ_CREATE、IRP_MJ_CLEANUP、IRP_MJ_CLOSE、IRP_MJ_DEVICE_CONTROL等。
irpStack->Parameters.DeviceIoControl.InputBufferLength  // 输入缓冲区长度
irpStack->Parameters.DeviceIoControl.OutputBufferLength // 输出缓冲区长度
irpStack->Parameters.DeviceIoControl.IoControlCode      // IOCTL_Code

有了这两步获得的参数,我们便可根据驱动的需要进行 IOCTL 的逻辑实现。最后,返回 Irp。

IoCompleteRequest(Irp, IO_NO_INCREMENT);

3. 应用程序通过 CreateFile 打开 IOCTL

之前在编写 PCIe 接口卡的驱动程序时,应用程序通过 CreateFile 创建与驱动程序之间的 IOCTL 通道,利用的是 GUID。NDIS 中没有利用 GUID 创建这一通道,那该如何进行呢?

上面已经提到了,我们创建了一个命名设备,设备名为\\Device\\xxxxxx。特别留意,参考 usbnwifi 的实现,驱动在创建设备对象时,将命名设置为“设备名+数字”。所以当我们利用 CreateFile 打开设备时,实际传入的设备名应该是\\Device\\xxxxxx0

CreateFile(L"\\\\.\\xxxxxx0",                 // L是为了保证为Unicode编码
    GENERIC_READ | GENERIC_WRITE,
    FILE_SHARE_READ | FILE_SHARE_WRITE,
    NULL,
    OPEN_EXISTING,
    0,
    NULL);

之后的DeviceIoControl函数与普通的 KMDF 实现无异。


Reference:

  1. OSR Online - Problem with NdisRegisterDeviceEx…
  2. MSDN - NDIS 6.20_CreateFile fails to find the device

欢迎来我的 Git Pages 博客交流,本文最早发布于 http://hoxz.me/2018/02/02/use-ioctl-in-ndis

你可能感兴趣的:(Windows驱动程序)