版权信息:
版权归smilestone322所有,保留所有权利,仅用来学习,请勿用于商业用途,欢迎转载,转载请注明出处。谢谢!
3.2 ddk驱动开发
上面讲解了driver studio+ddk开发驱动的方法,但是开发driver studio的公司已经停止对ds3.2进行维护了,ds版本最终定格在3.2,换句话说ds 3.2 已经成为了过气的明星,不过初学者学习 driver studio进行入门还是挺不错的,等入门了,学习DDK也变得相对容易了,因为ds3.2就是对ddk进行封装,ddk采用C语言,而ds3.2使用c++开发驱动,其实原理都是一样的,呵呵linux的创始人linus就说过,不建议使用c++,采用c语言可以搞定一切,所以在linux系统中都是采用c语言编写的,包括它的内核,在linux的驱动开发基础中,我们也讲过linux内核里面无处不包含c++的思想,可见dw3.2和ddk是通的,就像学习武林秘籍,一通百通,题外话说多了。下面转入正题,讲解ddk驱动开发,在这一部分中,边讲解边和dw3.2驱动开发进行对比,这样才能提高。
分析驱动程序和c语言一样,从main函数开始,但是驱动里面的main函数就是DriverEntry函数,下面我以ddk 2600下的bulkusb的例子开始讲解本节的内容。
DriverEntry例程是驱动程序的入口点,由I/O管理器在驱动程序加载时调用,它负责执行一些初始化操作,主要工作是设置驱动程序对象中指向各种例程的指针。在驱动程序中必须包含这些例程的具体函数实现,以便主机系统软件的调用。ddk中DriverEntry的函数如下:NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING UniRegistryPath
)
{
NTSTATUS ntStatus;
PUNICODE_STRING registryPath;
//
// initialization of variables
//
registryPath = &Globals.BulkUsb_RegistryPath;
//
// Allocate pool to hold a null-terminated copy of the path.
// Safe in paged pool since all registry routines execute at
// PASSIVE_LEVEL.
//
registryPath->MaximumLength = UniRegistryPath->Length + sizeof(UNICODE_NULL);
registryPath->Length = UniRegistryPath->Length;
registryPath->Buffer = ExAllocatePool(PagedPool,
registryPath->MaximumLength);
if (!registryPath->Buffer) {
BulkUsb_DbgPrint(1, ("Failed to allocate memory for registryPath\n"));
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
goto DriverEntry_Exit;
}
RtlZeroMemory (registryPath->Buffer,
registryPath->MaximumLength);
RtlMoveMemory (registryPath->Buffer,
UniRegistryPath->Buffer,
UniRegistryPath->Length);
ntStatus = STATUS_SUCCESS;
//
// Initialize the driver object with this driver's entry points.
//
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = BulkUsb_DispatchDevCtrl;
/*BulkUsb_DispatchDevCtrl设备控制的函数,相当于driver studio 3.2中的DeviceIoControl函数对应的驱动里面的DeviceControl函数*/
DriverObject->MajorFunction[IRP_MJ_POWER] = BulkUsb_DispatchPower;
/* BulkUsb_DispatchPower电源管理派遣例程 */
DriverObject->MajorFunction[IRP_MJ_PNP] = BulkUsb_DispatchPnP;
/* BulkUsb_DispatchPnP即插即用派遣例程 */
DriverObject->MajorFunction[IRP_MJ_CREATE] = BulkUsb_DispatchCreate;
/*CreateFile对应的打开文件对象派遣例程*/
DriverObject->MajorFunction[IRP_MJ_CLOSE] = BulkUsb_DispatchClose;
/*CreateFile对应的派遣例程*/
DriverObject->MajorFunction[IRP_MJ_CLEANUP] = BulkUsb_DispatchClean;
/*CloseFile对应的派遣例程*/
DriverObject->MajorFunction[IRP_MJ_READ] =
DriverObject->MajorFunction[IRP_MJ_WRITE] = BulkUsb_DispatchReadWrite;
/*ReadFile/WriteFile对应的读写函数派遣例程*/
DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = BulkUsb_DispatchSysCtrl;
/*系统控制派遣例程*/
DriverObject->DriverUnload = BulkUsb_DriverUnload;
/*卸载设备对应的派遣例程*/
DriverObject->DriverExtension->AddDevice = (PDRIVER_ADD_DEVICE)
BulkUsb_AddDevice;
/*AddDevice对象的派遣例程*/
DriverEntry_Exit:
return ntStatus;
}
一看这个DriverEntry就比driver studio 3.2中usbbulk的那个复杂的多,在该例程中,指出了驱动程序其他例程的名字,如即插即用例程为DispatchPnP,分发例程中为DispatchReadWrite(数据读写)、DispatchCreate(创建句柄)等。ds3.2中的DriverEntry就是空壳子,什么都没有,因为driver studio将它们都封装起来了,比如说在ds3.2中应用程序采用ReadFile进行读数据的时候,我们知道驱动程序是通过IRP和应用程序对应起来的,但是这种对应关系是怎样建立起来的呢,ds3.2中就看不到了,都封装起来了。但是在ddk中就看的很明显了,在DriverEntry中,将Irp和派遣函数联系起来了,比如:
DriverObject->MajorFunction[IRP_MJ_WRITE] = BulkUsb_DispatchReadWrite;
当应用程序调用WriteFile时,会产生一个IRP_MJ_WRITE,它对应着BulkUsb_DispatchReadWrite函数,在ds3.2就没有派遣函数的概念。
在WDM的ddk中,AddDevice函数也比ds3.2中清楚多了,同样也是作为一个派遣例程调用的,同时pnp也是作为派遣例程调用,这样我们就可以清楚的看到它们的实现,及应用程序与驱动通信的过程。
在ddk的bulkusb中,驱动程序向系统注册了一个AddDevice例程,名字叫BulkUsb_AddDevice,该例程由pnp管理器负责调用,其主要职责是创建设备对象。BulkUsb_AddDevice源码如下,它有2个参数,DriverObject是由pnp管理器传递进来的驱动对象,PhysicalDeviceObject是pnp管理器传递的底层驱动对象。
NTSTATUS
BulkUsb_AddDevice(
IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject
)
{
NTSTATUS ntStatus;
PDEVICE_OBJECT deviceObject;
PDEVICE_EXTENSION deviceExtension;
POWER_STATE state;
KIRQL oldIrql;
BulkUsb_DbgPrint(3, ("BulkUsb_AddDevice - begins\n"));
deviceObject = NULL;
ntStatus = IoCreateDevice(
DriverObject, // our driver object
sizeof(DEVICE_EXTENSION), // extension size for us
NULL, // name for this device
FILE_DEVICE_UNKNOWN,
FILE_AUTOGENERATED_DEVICE_NAME, // device characteristics
FALSE, // Not exclusive
&deviceObject); // Our device object
/*
IoCreateDevice 用于创建设备对象,它的原型如下:
NTSTATUS IoCreateDevice
(
IN PDRIVER_OBJECTDriverObject,
IN ULONGDeviceExtensionSize,
IN PUNICODE_STRINGDeviceName OPTIONAL,
IN DEVICE_TYPEDeviceType,
IN ULONGDeviceCharacteristics,
IN BOOLEANExclusive,
OUT PDEVICE_OBJECT *DeviceObject
);
参数说明:
DriverObject:一个指向调用该函数的驱动程序对象,每个驱动程序在它的DriverEntry过程里接收一个指向它的驱动程序对象。
DeviceExtensionSize:指定驱动程序为设备扩展对象而定义的结构体的大小。
(可选的参数)指向一个以零结尾的包含Unicode字符串的缓冲区,那是这个设备的名称,该字符串必须是一个完整的设备路径名。
DeviceType:指定一个由一个系统定义的FILE_DEVICE_XXX常量,表明了这个设备的类型。如FILE_DEVICE_KEYBOARD等。
DeviceCharacteristics:指定一个或多个系统定义的常量,连接在一起,提供有关驱动程序的设备其他信息.对于可能的设备特征信息, 见DEVICE_OBJECT结构体.
Exclusive:如果指定设备是独占的,大部分驱动程序设置这个值为FALSE,如果是独占的话设置为TRUE,非独占设置为FALSE。
DeviceObject:一个指向DEVICE_OBJECT结构体指针的指针,这是一个指针的指针,指向的指针用来接收DEVICE_OBJECT结构体的指针。
返回值:IoCreateDevice函数成功时返回STATUS_SUCCESS。
*/
if(!NT_SUCCESS(ntStatus)) {
BulkUsb_DbgPrint(1, ("Failed to create device object\n"));
return ntStatus;
}
// 初始化设备扩展
deviceExtension = (PDEVICE_EXTENSION) deviceObject->DeviceExtension;
deviceExtension->FunctionalDeviceObject =deviceObject;
/*设备扩展中保存IoCreateDevice建立的deviceObject*/
deviceExtension->PhysicalDeviceObject = PhysicalDeviceObject;
deviceObject->Flags |= DO_DIRECT_IO; //直接方式读写
//
// initialize the device state lock and set the device state
//
KeInitializeSpinLock(&deviceExtension->DevStateLock); //初始化自旋锁
INITIALIZE_PNP_STATE(deviceExtension);
//
//initialize OpenHandleCount
//
deviceExtension->OpenHandleCount = 0;
//
// Initialize the selective suspend variables
//
KeInitializeSpinLock(&deviceExtension->IdleReqStateLock);
deviceExtension->IdleReqPend = 0;
deviceExtension->PendingIdleIrp = NULL;
//
// Hold requests until the device is started
//
deviceExtension->QueueState = HoldRequests;
//
// Initialize the queue and the queue spin lock
//
InitializeListHead(&deviceExtension->NewRequestsQueue);
KeInitializeSpinLock(&deviceExtension->QueueLock);
//
// Initialize the remove event to not-signaled.
//
KeInitializeEvent(&deviceExtension->RemoveEvent,
SynchronizationEvent,
FALSE);
//
// Initialize the stop event to signaled.
// This event is signaled when the OutstandingIO becomes 1
//
KeInitializeEvent(&deviceExtension->StopEvent,
SynchronizationEvent,
TRUE);
//
// OutstandingIo count biased to 1.
// Transition to 0 during remove device means IO is finished.
// Transition to 1 means the device can be stopped
//
deviceExtension->OutStandingIO = 1;
KeInitializeSpinLock(&deviceExtension->IOCountLock);
//
// Delegating to WMILIB
//
ntStatus = BulkUsb_WmiRegistration(deviceExtension);
if(!NT_SUCCESS(ntStatus)) {
BulkUsb_DbgPrint(1, ("BulkUsb_WmiRegistration failed with %X\n", ntStatus));
IoDeleteDevice(deviceObject);
return ntStatus;
}
//
// set the flags as underlying PDO
//
if(PhysicalDeviceObject->Flags & DO_POWER_PAGABLE) {
deviceObject->Flags |= DO_POWER_PAGABLE; //电源管理
}
//初始化电源管理的状态 D0
deviceExtension->DevPower = PowerDeviceD0;
deviceExtension->SysPower = PowerSystemWorking;
state.DeviceState = PowerDeviceD0;
PoSetPowerState(deviceObject, DevicePowerState, state);
//
// attach our driver to device stack
// The return value of IoAttachDeviceToDeviceStack is the top of the
// attachment chain. This is where all the IRPs should be routed.
//
deviceExtension->TopOfStackDeviceObject =
IoAttachDeviceToDeviceStack(deviceObject,
PhysicalDeviceObject);
/* IoAttachDeviceToDeviceStack函数的作用是将IoCreateDevice创建的设备对象挂载到pdo,并且返回deviceObjcet下层的堆栈指针,如果没有过滤驱动的话就是返回pdo*/
if(NULL == deviceExtension->TopOfStackDeviceObject) {
BulkUsb_WmiDeRegistration(deviceExtension);
IoDeleteDevice(deviceObject);
return STATUS_NO_SUCH_DEVICE;
}
//
// Register device interfaces
//
ntStatus = IoRegisterDeviceInterface(deviceExtension->PhysicalDeviceObject,
&GUID_CLASS_CCD_BULK,
NULL,
&deviceExtension->InterfaceName);
if(!NT_SUCCESS(ntStatus)) {
BulkUsb_WmiDeRegistration(deviceExtension);
IoDetachDevice(deviceExtension->TopOfStackDeviceObject);
IoDeleteDevice(deviceObject);
return ntStatus;
}
if(IoIsWdmVersionAvailable(1, 0x20)) {
deviceExtension->WdmVersion = WinXpOrBetter;
}
else if(IoIsWdmVersionAvailable(1, 0x10)) {
deviceExtension->WdmVersion = Win2kOrBetter;
}
else if(IoIsWdmVersionAvailable(1, 0x5)) {
deviceExtension->WdmVersion = WinMeOrBetter;
}
else if(IoIsWdmVersionAvailable(1, 0x0)) {
deviceExtension->WdmVersion = Win98OrBetter;
}
deviceExtension->SSRegistryEnable = 0;
deviceExtension->SSEnable = 0;
//
// WinXP only
// check the registry flag -
// whether the device should selectively
// suspend when idle
//
if(WinXpOrBetter == deviceExtension->WdmVersion) {
BulkUsb_GetRegistryDword(BULKUSB_REGISTRY_PARAMETERS_PATH,
L"BulkUsbEnable",
&deviceExtension->SSRegistryEnable);
if(deviceExtension->SSRegistryEnable) {
//
// initialize DPC
//
KeInitializeDpc(&deviceExtension->DeferredProcCall,
DpcRoutine,
deviceObject);
//
// initialize the timer.
// the DPC and the timer in conjunction,
// monitor the state of the device to
// selectively suspend the device.
//
KeInitializeTimerEx(&deviceExtension->Timer,
NotificationTimer); //初始化定时器
//
// Initialize the NoDpcWorkItemPendingEvent to signaled state.
// This event is cleared when a Dpc is fired and signaled
// on completion of the work-item.
//
KeInitializeEvent(&deviceExtension->NoDpcWorkItemPendingEvent,
NotificationEvent,
TRUE);
//
// Initialize the NoIdleReqPendEvent to ensure that the idle request
// is indeed complete before we unload the drivers.
//
KeInitializeEvent(&deviceExtension->NoIdleReqPendEvent,
NotificationEvent,
TRUE);
}
}
//
// Clear the DO_DEVICE_INITIALIZING flag.
// Note: Do not clear this flag until the driver has set the
// device power state and the power DO flags.
//
deviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
BulkUsb_DbgPrint(3, ("BulkUsb_AddDevice - ends\n"));
return ntStatus;
}
这个函数和ds 3.2比的话,复杂多了,主要的工作是创建一个设备对象,并将它挂载到设备堆栈,在这个函数还初始化了设备扩展对象,电源管理等等。
Windows驱动程序一个重要的数据结构就是IRP,应用程序采用CreateFile,ReadFile,WriteFile,CloseFile等i/o操作,在驱动程序中都会转化成IRP,然后在驱动中会处理这个IRP,然后完成IRP请求,完成IRP的函数为IoCompleteRequest。它的原型如下:
VOID IoCompleteRequest(
IN PIRP Irp,
IN CCHAR PriorityBoost
);
其中参数:Irp表示需要结束的IRP。PriorityBoost:表示线程恢复时的优先级别,一般为IO_NO_INCREMENT。
讲解完DriverEntry和AddDevice函数就接着讲解DriverEntry中重要的派遣函数了,对于我们开发驱动的人员来说,ddk bulkusb这个例子很有借鉴意义。因为它已经为我们实现了Pnp及电源管理,同时很好的支持了bulk读写,这些几乎在开发驱动中都不要修改,我们需要修改的是给硬件发送控制命令,即发送厂商请求的部分代码。我们可以在BulkUsb_DispatchDevCtrl里面添加一个case分支用来发送厂商请求,就可以将bulkusb改造成我们的usb驱动程序了。NTSTATUS
BulkUsb_DispatchDevCtrl(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
ULONG code;
PVOID ioBuffer;
ULONG inputBufferLength;
ULONG outputBufferLength;
ULONG info;
NTSTATUS ntStatus;
PDEVICE_EXTENSION deviceExtension;
PIO_STACK_LOCATION irpStack;
info = 0;
irpStack = IoGetCurrentIrpStackLocation(Irp);
code = irpStack->Parameters.DeviceIoControl.IoControlCode;
deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
ioBuffer = Irp->AssociatedIrp.SystemBuffer;
inputBufferLength = irpStack->Parameters.DeviceIoControl.InputBufferLength; //输入缓冲区的大小
outputBufferLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength; //得到输出缓冲区的大小
if(deviceExtension->DeviceState != Working) {
BulkUsb_DbgPrint(1, ("Invalid device state\n"));
Irp->IoStatus.Status = ntStatus = STATUS_INVALID_DEVICE_STATE;
Irp->IoStatus.Information = info;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return ntStatus;
}
BulkUsb_DbgPrint(3, ("BulkUsb_DispatchDevCtrl::"));
BulkUsb_IoIncrement(deviceExtension);
BulkUsb_DbgPrint(3, ("Waiting on the IdleReqPendEvent\n"));
if(deviceExtension->SSEnable) {
KeWaitForSingleObject(&deviceExtension->NoIdleReqPendEvent,
Executive,
KernelMode,
FALSE,
NULL);
}
switch(code) {
case IOCTL_BULKUSB_RESET_PIPE://设置端点
{
PFILE_OBJECT fileObject;
PUSBD_PIPE_INFORMATION pipe;
pipe = NULL;
fileObject = NULL;
fileObject = irpStack->FileObject;
if(fileObject == NULL) {
ntStatus = STATUS_INVALID_PARAMETER;
break;
}
pipe = (PUSBD_PIPE_INFORMATION) fileObject->FsContext;
if(pipe == NULL) {
ntStatus = STATUS_INVALID_PARAMETER;
}
else {
ntStatus = BulkUsb_ResetPipe(DeviceObject, pipe);
}
break;
}
case IOCTL_BULKUSB_GET_CONFIG_DESCRIPTOR://获取设备描述符
{
ULONG length;
if(deviceExtension->UsbConfigurationDescriptor) {
length = deviceExtension->UsbConfigurationDescriptor->wTotalLength;
if(outputBufferLength >= length) {
RtlCopyMemory(ioBuffer,
deviceExtension->UsbConfigurationDescriptor,
length);
info = length;
ntStatus = STATUS_SUCCESS;
}
else {
ntStatus = STATUS_BUFFER_TOO_SMALL;
}
}
else {
ntStatus = STATUS_UNSUCCESSFUL;
}
break;
}
case IOCTL_BULKUSB_RESET_DEVICE: //设置设备
ntStatus = BulkUsb_ResetDevice(DeviceObject);
break;
case IOCTL_BULKUSB_XXXXXX://我们自己的命令
{
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}
break;
default :
ntStatus = STATUS_INVALID_DEVICE_REQUEST;
break;
}
Irp->IoStatus.Status = ntStatus;
Irp->IoStatus.Information = info;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
BulkUsb_DbgPrint(3, ("BulkUsb_DispatchDevCtrl::"));
BulkUsb_IoDecrement(deviceExtension);
return ntStatus;
}
在IOCTL_BULKUSB_XXXXXX中我们可以这样写,首先通过UsbBuildVendorRequest构建一个厂商请求,然后调用CallUSBD将厂商请求的URB发送给底层usb总线,然后在转发给硬件。UsbBuildVendorRequest的函数原型如下:
void UsbBuildVendorRequest(
[in] PURB Urb, //
[in] USHORT Function,
[in] USHORT Length,
[in] ULONG TransferFlags,
[in] UCHAR ReservedBits,
[in] UCHAR Request,//对应usb设备请求中的bRequest
[in] USHORT Value, //对应usb设备请求中的wValue
[in] USHORT Index,//对应usb设备请求中wIndex
[in, optional] PVOID TransferBuffer, //缓冲区
[in, optional] PMDL TransferBufferMDL,
[in] ULONG TransferBufferLength,//传输的长度,对应usb设备请求中WLength
[in] PURB Link
);
调用实例:
UsbBuildVendorRequest(
urb,
URB_FUNCTION_VENDOR_DEVICE,
(USHORT)sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST),
0,
0x0,
XXX, //请求类型
0,
0,
(PVOID)((unsigned char*)buffer),
NULL,
8,
NULL
);
CallUSBD的源码如下:
NTSTATUS
CallUSBD(
IN PDEVICE_OBJECT DeviceObject,
IN PURB Urb
)
{
PIRP irp;
KEVENT event;
NTSTATUS ntStatus;
IO_STATUS_BLOCK ioStatus;
PIO_STACK_LOCATION nextStack;
PDEVICE_EXTENSION deviceExtension;
irp = NULL;
deviceExtension = DeviceObject->DeviceExtension;
KeInitializeEvent(&event, NotificationEvent, FALSE);
//创建我们的IO控制码的相关的IRP
irp = IoBuildDeviceIoControlRequest(IOCTL_INTERNAL_USB_SUBMIT_URB,
deviceExtension->TopOfStackDeviceObject,
NULL,
0,
NULL,
0,
TRUE,
&event,
&ioStatus);
if(!irp) {
BulkUsb_DbgPrint(1, ("IoBuildDeviceIoControlRequest failed\n"));
return STATUS_INSUFFICIENT_RESOURCES;
}
//得到下一层的设备堆栈
nextStack = IoGetNextIrpStackLocation(irp);
ASSERT(nextStack != NULL);
nextStack->Parameters.Others.Argument1 = Urb;
BulkUsb_DbgPrint(3, ("CallUSBD::"));
BulkUsb_IoIncrement(deviceExtension);
//将irp请求发给底层usb总线驱动,然后转发给硬件
ntStatus = IoCallDriver(deviceExtension->TopOfStackDeviceObject, irp);
//如果irp为异步完成,必须等待它结束,KeWaitForSingleObject函数和应用程序的WaitForSingleObject类似,等待event有信号。
if(ntStatus == STATUS_PENDING) {
KeWaitForSingleObject(&event,
Executive,
KernelMode,
FALSE,
NULL);
ntStatus = ioStatus.Status;
}
BulkUsb_DbgPrint(3, ("CallUSBD::"));
BulkUsb_IoDecrement(deviceExtension);
return ntStatus;
}
其中IoBuildDeviceIoControlRequest函数的作用在我们采用UsbBuildVendorRequest构造完URB后,URB要和一个IRP关联起来,这就是这个函数的作用,在driverstudio 3.2中,构造完厂商请求后,直接调用SubmitUrb直接将urb发送到底层usb总线了,但是在ddk中必须采用IoBuildDeviceIoControlRequest创建一个io请求的控制码,然后将URB作为Irp的参数,然后调用IoCallDriver将Urb发送到底层usb总线。
函数功能:allocates and sets up an IRP for a synchronously processed device control request.函数主要用来构造一个用于设备i/o控制请求的irp包,该irp包将被同步处理。
PIRPIoBuildDeviceIoControlRequest(
__in ULONG IoControlCode,
__in PDEVICE_OBJECT DeviceObject,
__in_opt PVOID InputBuffer,
__in ULONG InputBufferLength,
__out_opt PVOID OutputBuffer,
__in ULONG OutputBufferLength,
__in BOOLEAN InternalDeviceIoControl,
__in PKEVENT Event,
__out PIO_STATUS_BLOCK IoStatusBlock
);
参数:
IoControlCode:
提供i/o控制请求所需的i/o控制码。这个i/o控制码可以在msdn中查询到。
DeviceObject:
指向下层驱动的设备对象的指针。这个就是构造的irp要被发向的目标对象。
InputBuffer:
指向输入缓冲区的指针,这个缓冲区中的内容是给下层驱动使用的。此指针可为NULL。
InputBufferLength [in]
输入缓冲区的长度,按字节计算。如果InputBuffer为NULL,则此参数必须为0。
OutputBuffer:
指向输出缓冲区的指针,这个缓冲区是用于给下层驱动返回数据用的。此指针可为NULL。
OutputBufferLength
输出缓冲区的长度,按字节计算。如果OutputBuffer为NULL,则此参数必须为0。
InternalDeviceIoControl:
如果此参数为TRUE,这个函数设置所构造的irp的主函数码(major function code)为IRP_MJ_INTERNAL_DEVICE_CONTROL,否则这个函数设置所构造的irp的主函数码(major function code)为IRP_MJ _DEVICE_CONTROL。
Event:
提供一个指向事件对象的指针,该事件对象由调用者分配并初始化。当下层驱动程序完成这个irp请求时i/o管理器将此事件对象设置为通知状态(signaled)。当调用IoCallDriver后,调用者可以等待这个事件对象成为通知状态。
IoStatusBlock:
调用者指定一个i/o状态块,当这个irp完成时,下层驱动会把相应信息填入这个i/o状态块。
返回值:
当这个函数调用成功时,将返回一个指向所构造的irp的指针并且下一层驱动的i/o堆栈会根据调用此函数提供的参数设置好,若调用失败,将返回NULL。
注意事项:
1、 此函数构造的irp包将被同步处理。当构造好irp包后,调用者调用IoCallDriver将这个irp发送给目标对象,如果IoCallDriver返回STATUS_PENDING,调用者必须调用KeWaitForSingleObject等待调用IoBuildDeviceIoControlRequest时所提供的那个Event。对于大多数的驱动程序我们不用给该irp设置完成函数。
2、 由IoBuildDeviceIoControlRequest构造的irp必须由某个驱动调用IoCompleteRequest来完成,并且注意调用IoBuildDeviceIoControlRequest的驱动程序不能调用IoFreeIrp来释放这些构造的irp,因为i/o管理器会在IoCompleteRequest被调用后自动释放这些irp。
3、 IoBuildDeviceIoControlRequest将把它构造的irp放在当前线程特有的一个irp队列上,如果当前线程退出,则i/o管理器将取消这些irp。
4、 InputBuffer和OutputBuffer这两个参数如何存放在所构造的irp中将取决于IoControlCode的TransferType,具体可查相关资料。
5、 IoBuildDeviceIoControlRequest的调用者必须运行在IRQL <= APC_LEVEL。
6、 这个函数并不初始化所构造irp中的FileObject指针,因此如果你在写和文件系统相关的驱动,你必须自己初始化这个指针。
7、 使用IoBuildDeviceIoControlRequest构造的irp其主函数代码只能是IRP_MJ_DEVICE_CONTROL或IRP_MJ_INTERNAL_DEVICE_CONTROL。
IoCallDriver的函数原型如下:
1. NTSTATUS
2. IoCallDriver(
3. IN PDEVICE_OBJECT DeviceObject,
4. IN OUT PIRP Irp
5. );
The IoCallDriver routine sends an IRP to the driver associated with a specified device object.( IoCallDriver这个函数向DeviceObject设备对象的驱动对象发送一个IRP请求)。
发送控制命令的部分讲完后,就轮到讲解usb设备的读写了,在bulkusb中,读写派遣例程放在同一个函数中,这个函数可以不用修改,直接搬到我们的驱动程序中,在DriverEntry我们知道这个派遣例程就是BulkUsb_DispatchReadWrite,源码如下:
NTSTATUS
BulkUsb_DispatchReadWrite(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
PMDL mdl;
PURB urb;
ULONG totalLength;
ULONG stageLength;
ULONG urbFlags;
BOOLEAN read;
NTSTATUS ntStatus;
ULONG_PTR virtualAddress;
PFILE_OBJECT fileObject;
PDEVICE_EXTENSION deviceExtension;
PIO_STACK_LOCATION irpStack;
PIO_STACK_LOCATION nextStack;
PBULKUSB_RW_CONTEXT rwContext;
PUSBD_PIPE_INFORMATION pipeInformation;
urb = NULL;
mdl = NULL;
rwContext = NULL;
totalLength = 0;
irpStack = IoGetCurrentIrpStackLocation(Irp);
fileObject = irpStack->FileObject;
read = (irpStack->MajorFunction == IRP_MJ_READ) ? TRUE : FALSE;//判断是读还是写
deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
BulkUsb_DbgPrint(3, ("BulkUsb_DispatchReadWrite - begins\n"));
if(deviceExtension->DeviceState != Working) {
BulkUsb_DbgPrint(1, ("Invalid device state\n"));
ntStatus = STATUS_INVALID_DEVICE_STATE;
goto BulkUsb_DispatchReadWrite_Exit;
}
BulkUsb_DbgPrint(3, ("Waiting on the IdleReqPendEvent\n"));
if(deviceExtension->SSEnable) {
KeWaitForSingleObject(&deviceExtension->NoIdleReqPendEvent, //
Executive,
KernelMode,
FALSE,
NULL);
}
if(fileObject && fileObject->FsContext) {
pipeInformation = fileObject->FsContext;
if(UsbdPipeTypeBulk != pipeInformation->PipeType) {
BulkUsb_DbgPrint(1, ("Usbd pipe type is not bulk\n"));
ntStatus = STATUS_INVALID_HANDLE;
goto BulkUsb_DispatchReadWrite_Exit;
}
}
else {
BulkUsb_DbgPrint(1, ("Invalid handle\n"));
ntStatus = STATUS_INVALID_HANDLE;
goto BulkUsb_DispatchReadWrite_Exit;
}
rwContext = (PBULKUSB_RW_CONTEXT)
ExAllocatePool(NonPagedPool,
sizeof(BULKUSB_RW_CONTEXT));
if(rwContext == NULL) {
BulkUsb_DbgPrint(1, ("Failed to alloc mem for rwContext\n"));
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
goto BulkUsb_DispatchReadWrite_Exit;
}
if(Irp->MdlAddress) {
totalLength = MmGetMdlByteCount(Irp->MdlAddress);
}
//如果总的长度比BULKUSB_TEST_BOARD_TRANSFER_BUFFER_SIZE还是要大,那么就退出
if(totalLength > BULKUSB_TEST_BOARD_TRANSFER_BUFFER_SIZE) {
BulkUsb_DbgPrint(1, ("Transfer length > circular buffer\n"));
ntStatus = STATUS_INVALID_PARAMETER;
ExFreePool(rwContext);
goto BulkUsb_DispatchReadWrite_Exit;
}
if(totalLength == 0) {
BulkUsb_DbgPrint(1, ("Transfer data length = 0\n"));
ntStatus = STATUS_SUCCESS;
ExFreePool(rwContext);
goto BulkUsb_DispatchReadWrite_Exit;
}
urbFlags = USBD_SHORT_TRANSFER_OK;
virtualAddress = (ULONG_PTR) MmGetMdlVirtualAddress(Irp->MdlAddress);
if(read) {
urbFlags |= USBD_TRANSFER_DIRECTION_IN; //传输方向为In,为读端点
BulkUsb_DbgPrint(3, ("Read operation\n"));
}
else {
urbFlags |= USBD_TRANSFER_DIRECTION_OUT;
BulkUsb_DbgPrint(3, ("Write operation\n")); //传输方向为Out,为写端点
}
//如果总的传输大小大于BULKUSB_MAX_TRANSFER_SIZE,那么
if(totalLength > BULKUSB_MAX_TRANSFER_SIZE) {
stageLength = BULKUSB_MAX_TRANSFER_SIZE;
}
else {
stageLength = totalLength;
}
//建立MDL
mdl = IoAllocateMdl((PVOID) virtualAddress,
totalLength,
FALSE,
FALSE,
NULL);
if(mdl == NULL) {
BulkUsb_DbgPrint(1, ("Failed to alloc mem for mdl\n"));
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
ExFreePool(rwContext);
goto BulkUsb_DispatchReadWrite_Exit;//转退出
}
//
// map the portion of user-buffer described by an mdl to another mdl
//将新MDL进行映射
IoBuildPartialMdl(Irp->MdlAddress,
mdl,
(PVOID) virtualAddress,
stageLength);
//申请URB数据结构
urb = ExAllocatePool(NonPagedPool,
sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER));
if(urb == NULL) {
BulkUsb_DbgPrint(1, ("Failed to alloc mem for urb\n"));
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
ExFreePool(rwContext);
IoFreeMdl(mdl);
goto BulkUsb_DispatchReadWrite_Exit;
}
//建立中断或批传输请求,这个也是构建urb请求
UsbBuildInterruptOrBulkTransferRequest(
urb,
sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER),
pipeInformation->PipeHandle,
NULL,
mdl,
stageLength,
urbFlags,
NULL);
//
// set BULKUSB_RW_CONTEXT parameters.
//设置完成例程参数
rwContext->Urb = urb;
rwContext->Mdl = mdl;
rwContext->Length = totalLength - stageLength;
rwContext->Numxfer = 0;
rwContext->VirtualAddress = virtualAddress + stageLength;
rwContext->DeviceExtension = deviceExtension;
//
// use the original read/write irp as an internal device control irp
//设置设备堆栈
nextStack = IoGetNextIrpStackLocation(Irp);
nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
nextStack->Parameters.Others.Argument1 = (PVOID) urb;
nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;
//设置完成例程
IoSetCompletionRoutine(Irp,
(PIO_COMPLETION_ROUTINE)BulkUsb_ReadWriteCompletion,
rwContext,
TRUE,
TRUE,
TRUE);
//将当前IRP阻塞
IoMarkIrpPending(Irp);
BulkUsb_DbgPrint(3, ("BulkUsb_DispatchReadWrite::"));
BulkUsb_IoIncrement(deviceExtension);
//将IRP转发到底层USB总线驱动
ntStatus = IoCallDriver(deviceExtension->TopOfStackDeviceObject,Irp);
if(!NT_SUCCESS(ntStatus)) {
BulkUsb_DbgPrint(1, ("IoCallDriver fails with status %X\n", ntStatus));
if((ntStatus != STATUS_CANCELLED) &&
(ntStatus != STATUS_DEVICE_NOT_CONNECTED)) {
ntStatus = BulkUsb_ResetPipe(DeviceObject,
pipeInformation);
if(!NT_SUCCESS(ntStatus)) {
BulkUsb_DbgPrint(1, ("BulkUsb_ResetPipe failed\n"));
ntStatus = BulkUsb_ResetDevice(DeviceObject);
}
}
else {
BulkUsb_DbgPrint(3, ("ntStatus is STATUS_CANCELLED or "
"STATUS_DEVICE_NOT_CONNECTED\n"));
}
}
return STATUS_PENDING;
BulkUsb_DispatchReadWrite_Exit:
Irp->IoStatus.Status = ntStatus;
Irp->IoStatus.Information= rwContext->Urb->UrbBulkOrInterruptTransfer.TransferBufferLength;
//完成Irp请求
IoCompleteRequest(Irp, IO_NO_INCREMENT);
BulkUsb_DbgPrint(3, ("BulkUsb_DispatchReadWrite - ends\n"));
return ntStatus;
}
在应用程序调用了ReadFile或WriteFile函数后,会产生2个IRP请求,IRP_MJ_READ和IRP_MJ_WRITE,也就是对应上面的派遣例程了,在BulkUsb_DispatchReadWrite中设置了完成例程,在完成例程中将读写的size分成BULKUSB_MAX_TRANSFER_SIZE的若干块,一次将它们发送到usb总线驱动,然后调用完成例程读完BULKUSB_MAX_TRANSFER_SIZE的大小数据后,在BulkUsb_ReadWriteCompletion函数中有调用了IoCallDriver将请求发送给usb总线驱动,。。。。。。,直到数据都读写完成。完成例程的源码如下:
NTSTATUS
BulkUsb_ReadWriteCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
ULONG stageLength;
NTSTATUS ntStatus;
PIO_STACK_LOCATION nextStack;
PBULKUSB_RW_CONTEXT rwContext;
rwContext = (PBULKUSB_RW_CONTEXT) Context;
ntStatus = Irp->IoStatus.Status;
UNREFERENCED_PARAMETER(DeviceObject);
BulkUsb_DbgPrint(3, ("BulkUsb_ReadWriteCompletion - begins\n"));
if(NT_SUCCESS(ntStatus)) {
if(rwContext) {
rwContext->Numxfer +=
rwContext->Urb->UrbBulkOrInterruptTransfer.TransferBufferLength;//得到一次传输的长度
if(rwContext->Length) { //length=0表示已经传输完
BulkUsb_DbgPrint(3, ("Another stage transfer...\n"));
if(rwContext->Length > BULKUSB_MAX_TRANSFER_SIZE) {
stageLength = BULKUSB_MAX_TRANSFER_SIZE;
}
else {
stageLength = rwContext->Length;
}
IoBuildPartialMdl(Irp->MdlAddress,
rwContext->Mdl,
(PVOID) rwContext->VirtualAddress,
stageLength);
rwContext->Urb->UrbBulkOrInterruptTransfer.TransferBufferLength
= stageLength;
rwContext->VirtualAddress += stageLength;
rwContext->Length -= stageLength;
nextStack = IoGetNextIrpStackLocation(Irp);
nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
nextStack->Parameters.Others.Argument1 = rwContext->Urb;
nextStack->Parameters.DeviceIoControl.IoControlCode =
IOCTL_INTERNAL_USB_SUBMIT_URB;
IoSetCompletionRoutine(Irp,
BulkUsb_ReadWriteCompletion,
rwContext,
TRUE,
TRUE,
TRUE);
IoCallDriver(rwContext->DeviceExtension->TopOfStackDeviceObject,
Irp);
return STATUS_MORE_PROCESSING_REQUIRED;
}
else {
Irp->IoStatus.Information = rwContext->Numxfer;
}
}
}
else {
BulkUsb_DbgPrint(1, ("ReadWriteCompletion - failed with status = %X\n", ntStatus));
}
if(rwContext) {
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}
BulkUsb_DbgPrint(3, ("BulkUsb_ReadWriteCompletion - ends\n"));
return ntStatus;
}
其它派遣例程就不在进行讲解了,几乎可以不用修改的应用,但是电源管理派遣例程中的一个函数,在这里在讲解下,PoCallDriver,初一看这个函数和IoCallDriver函数一样,连参数都一样,其函数原型:
1. NTSTATUS
2. PoCallDriver(
3. IN PDEVICE_OBJECT DeviceObject,
4. IN OUT PIRP Irp
5. );
但是它的功能和不同的,而PoCallDriver函数向设备栈中的下层设备传递一个主功能
号为IRP_MJ_POWER的请求,且限于特定的OS。(ThePoCallDriver routine passes a power IRP to the next-lower driver in the device stack. (Windows Server 2003, Windows XP, and Windows 2000 only.)