现在来探讨一下比较重要的xxxAddDevice 例程。
NTSTATUS xxxAddDevice(IN PDRIVER_OBJECT DriverObject,IN PDEVICE_OBJECT PhysicalDeviceObject)
该函数用来创建设和添加新设备对象。其中DriverObject是由I/O管理器传来的驱动对象,也就是是Driver_Entry()函数中的那个驱动程序对象。PhysicalDeviceObject 代表设备堆栈底部的物理设备对象(由总线驱动创建,其实就是操作系统创建,一般被称为PDO)。
xxxAddDevice函数的基本职责是创建一个设备对象并把它连接到以PDO为底的设备堆栈中。可以通过以下步骤完成:
- 调用IoCreateDevice创建设备对象,并建立一个私有的设备扩展对象。
- 调用IoAttachDeviceToDeviceStack函数把新设备对象放到堆栈上。
- 寄存一个或多个设备接口,以便应用程序能知道设备的存在。或者使用创建符号连接的方式。
- 初始化设备扩展和设备对象的Flag成员。
通过IoCreateDevice()函数我们可以这样创建设备对象:
NTSTATUS status;
PDEVICE_OBJECT fdo;
//创建设备名称,注意使用Unicode宽字符
UNICODE_STRING devName;
RtlInitUnicodeString(&devName,L"\\Device\xxxDevice");
status = IoCreateDevice(
DriverObject,
sizeof(DEVICE_EXTENSION),
&(UNICODE_STRING)devName,//指定设备名
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&fdo);
各个参数说明:
第一个参数(DriverObject) 就是AddDevice的第一个参数。该参数用于在驱动程序和新设备对象之间建立连接,这样I/O管理器就可以向设备发送指定的IRP。
第二个参数是设备扩展结构的大小。I/O管理器自动分配这个内存,并把设备对象中的DeviceExtension指针指向这块内存。
第三个参数表示设备名称
第四个参数(FILE_DEVICE_UNKNOWN)这个值可以被设备硬件键或类键中的超越值所替代,如果这两个键都含有该参数的超越值,那么硬件键中的超越值具有更高的优先权。对于属于某个已存在类的设备,必须在这些地方指定正确的值,因为驱动程序与外围系统的交互需要依靠这个值。另外,设备对象的默认安全设置也依靠这个设备类型值。
第五个参数这里设置为0.
第六个参数(FALSE) 指出设备是否是排斥的。通常,对于排斥设备,I/O管理器仅允许打开该设备的一个句柄。这个值同样也能被注册表中硬件键和类键中的值超越,如果两个超越值都存在,硬件键中的超越值具有更高的优先权。
第七个参数(&fdo) 是存放设备对象指针的地址,IoCreateDevice函数使用该变量保存刚创建设备对象的地址。
当IoCreateDevice返回后,我们可以建立一个私有设备扩展对象,私有设备对象DEVICE_EXTENSION是驱动程序开发者自己定义的。
可以这样来完成:先得到设备扩展,然后设置设备扩展的设备对象,如下:
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
pdx->fdo = fdo;
可以定义为这样:
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT fdo; //功能层设备对象
PDEVICE_OBJECT NextStackDevice; //底层设备对象
UNICODE_STRING interfaceName; //设备接口
UNICODE_STRING devName; //设备名
PKINTERRUPT InterruptObject; // address of interrupt object
BOOLEAN mappedport; //如果为真需要做IO端口映射
PVOID MemBar0; //内存基地址0
ULONG nMem0; //基地址BAR0占用字节数
ULONG DmaChannel; //DMA通道
PDMA_ADAPTER DmaAdapter; //DMA Adapter 对象
PALLOCATE_COMMON_BUFFER allocateCommonBuffer; //分配连续的物理内存
//DMA函数
PFREE_COMMON_BUFFER freeCommonBuffer; //释放连续的物理内存DMA函数
PPUT_DMA_ADAPTER putDmaAdapter; //释放DMA Adapter对象
PHYSICAL_ADDRESS RegsPhyBase; //寄存器物理地址首地址
PVOID RegsBase; //寄存器虚拟地址首地址
PHBA_REGS pHBARegs;
ULONG DmaPort; //设备DMA物理端口
…
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
注:该设备扩展的定义针对了PCI驱动
设备扩展主要用来维护设备状态信息、存储驱动程序使用的内核对象或系统资源(如自旋锁)、保存驱动程序需要的数据等。设备扩展是保存设备状态信息和数据的主要空间。
建立私有设备扩展之后,可以将该设备加载到设备堆栈中。设备堆栈的最底层就是PDO。可以使用IoAttachDeviceToDeviceStack()函数。需要这要使用:
IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
第一个参数为我们刚刚创建的fdo的地址(注:fdo为PDEVICE_OBJECT类型),第二个参数是pdo的地址,在这里就是PhysicalDeviceObject,是由xxxAddDevice传进来的。函数返回值为紧接着下面的设备对象的地址,在这里也就是PhysicalDeviceObject的地址。这个地址在后面还多次要用到。
创建设备接口可以通过两种方式,第一种也就是WDM的方式是通过IoRegisterDeviceInterface()来实现;也可以使用Win2000的方式,通过IoCreateSymbolicLink()来实现。
这里我们使用IoRegisterDeviceInterface()。
status = IoRegisterDeviceInterface(PhysicalDeviceObject, &GUID _INTERFACE, NULL, &pdx->interfaceName);
IoRegisterDeviceInterface的第一个参数必须是设备pdo的地址。第二个参数指出与接口关联的GUID,第三个参数指出额外的接口细分类名。只有Microsoft的代码才使用名称细分类方案。第四个参数是一个UNICODE_STRING串的地址,该串用于接收设备对象的符号连接名,我们定义在设备扩展中,后面还会多次用到。GUID是一个128位的Unicode字符串用来唯一标识一个设备。
当IoRegisterDeviceInterface()返回之后还需要调用IoSetDeviceInterfaceState()来使能该接口。
status=IoSetDeviceInterfaceState(&pdx->interfaceName, TRUE);
最后还需要初始化设备扩展flag成员。这里这样设置:
fdo->Flags |= DO_DIRECT_IO | DO_POWER_PAGABLE;
fdo->Flags &= ~DO_DEVICE_INITIALIZING;
其中DO_DIRECT_IO用于设置IO方式,针对于大块高速数据的传输。由于这里代码是针对PCIE的所以使用DO_DIRECT_IO。还有另一种DO_BUFFERED_IO这里就不讨论了。MSDN上要求所有可分页的驱动必须设置DO_POWER_PAGABLE参数。
DO_DEVICE_INITIALIZING 的目的是防止其它组件在驱动程序完成初始化设备对象之前向设备发送 I/O。当设置DO_DEVICE_INITIALIZING标志时,I/O管理器将拒绝所有打开该设备句柄的请求以及向该设备对象上附着其他设备对象。按照MSDN说明我们在这里将该标志清零。
具体的设置方式也可以参见MSDN下面的链接:
http://msdn.microsoft.com/en-us/library/windows/hardware/ff543147(v=vs.85).aspx
在xxxAddDevice() 返回之前还可以初始化一些要使用的资源如定时器、自旋锁之类的,这就要根据自己的需求了。