您的位置:首页 > 其它

USB驱动开发(六)操作USBD.SYS

2016-04-20 11:12 330 查看
转自:http://blog.csdn.net/xxxluozhen/article/details/5023666

3.3.2.3.    操作USBD.SYS

正如前面所述,对USBD.SYS驱动操作的中介只URB结构体,因此对USBD.SYS的操作主要可以分构造URB和调用请求两部分,如下以USB读写操作过程为例进行分析。
第一步构造URB:由于EasyArm2400下位机固件程序USB采用的是中断方式进行数据传输,因此我们就需要构造一个中断传输的URB,实现如下代码。
 UsbBuildInterruptOrBulkTransferRequest(urb,
       sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER),
       pipeInformation->PipeHandle,
       NULL,
       mdl,
       stageLength,
       urbFlags,
       NULL);
其中_URB_BULK_OR_INTERRUPT_TRANSFER类型指明了构造的RB用于中断方式传输的请求。
第二步调用请求:在对挂载的设备进行操作的时候,从DDK DOC中可以发现采用的函数是IoCallDriver。IoCallDriver函数的第一个参数指定的为挂载的设备对象,第二参数为当前的IRP。
其实对USBD.SYS的操作有以上两步基本能够完成,但是你会发现其实并没有真正意义上操作了USBD.SYS。分析一下可以发现,其实的构造的URB并没有传输到挂载的设备上。
在DDK中提供IoGetNextIrpStackLocation函数获取挂载的设备当前的IRP,如下代码帮你实现URB的传输。
   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;
其中nextStack->Parameters.Others.Argument1保存了构造后的URB,nextStack->Parameters.DeviceIoControl.IoControlCode保存了需要执行的I/O请求。
有了进一步的补充,操作USBD.SYS已经可以实现了,但是挂载的设备的何时操作完成,返回的状态是什么?这些对于上层的驱动程序来说根本无法知道。因此DDK引入了完成用例的概念,完成用例将帮助实现以上的这些需求。
完成用例的概念就是当下层设备完成操作时,IRP会产生回滚,当回滚到上层设备IRP的时候,如果该层IRP存在完成用例子时,系统将进入用例函数进行处理,DDK提供的设置完成用例函数如下。
VOID
  IoSetCompletionRoutine(
    IN PIRP  Irp,
    IN PIO_COMPLETION_ROUTINE  CompletionRoutine,
    IN PVOID   Context,
    IN BOOLEAN    InvokeOnSuccess,
    IN BOOLEAN  InvokeOnError,
    IN BOOLEAN  InvokeOnCancel);
其中CompletionRoutine指向的是用例服务函数,Context指定为需要传递的参数。一般传递需要触发的事件。
到此为止,操作USBD.SYS的整个过程已经完成,如下展示读写操作请求的核心代码。
;申请一段内存,用于保存URB结构体。
 urb = (PURB)ExAllocatePool(NonPagedPool,
sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER));
;构建一个用于中断传输的URB。
UsbBuildInterruptOrBulkTransferRequest(urb,
sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER),
     pipeInformation->PipeHandle,
     NULL,
     mdl,
     stageLength,
     urbFlags,
     NULL);
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)LPC2478_USB_ReadWriteCompletion,
     rwContext,
     TRUE,
     TRUE,
     TRUE);
;设置Pending位
IoMarkIrpPending(Irp);
;向挂载设备发送URB,请求传输数据。
ntStatus = IoCallDriver(deviceExtension->NextDeviceObject,Irp);
提示:IoSkipCurrentIrpStackLocation与IoCopyCurrentIrpStackLocationToNext区别
在操作USDB.SYS的时候,上述的两个函数也是比较常见,同时也是比较容易混淆的。其实这两个函数的实际功能基本上一样,都是将当前的IRP
I/O堆栈拷贝给下层的驱动的I/O堆栈。但是这两个函数也存在一些特殊用途的区别,当操作USBD.SYS,设置了完成用例时,DDK规定就不能使用IoSkipCurrentIrpStackLocation函数了,只能使用IoCopyCurrentIrpStackLocationToNext。

3.3.2.4.     选择配置USB

DriverEntry函数执行完成后,开始执行AddDevice函数。这个函数创建设备对象把设备对象连接到设备堆栈上,清除DO_DEVICE_INITIALIZING标志。然后配置管理器向驱动程序发送一个即插即用请求IRP_MN_START_DEVICE,该IRP例程将配置设备。
配置USB的步骤一般是首先为设备选择一个配置(大多数设备仅有一种配置)。选定了某种配置后,接着应该选择配置中的一个或多个接口。然后向总线驱动程序发送配置选择URB,总线驱动程序接收到该URB后向设备发出命令使用选定的配置和接口。
1.        设备选择配置的过程其实就是获取设备的配置描述符的过程,首先获取固定大小的配置描述符,这时,此描述符不包含接口描述符和端点描述符。然后获取全部的配置描述符,包括接口描述符和端点描述符,代码如下。
//首先获取固定大小的配置描述符,这时,此描述符不包含接口描述符和端点描述符。
siz = sizeof(USB_CONFIGURATION_DESCRIPTOR);
configurationDescriptor = ExAllocatePool(NonPagedPool, siz);
if(configurationDescriptor) {
    UsbBuildGetDescriptorRequest(urb,
         (USHORT) sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST),
         USB_CONFIGURATION_DESCRIPTOR_TYPE,
         0,
         0,
         configurationDescriptor,
         NULL,
         sizeof(USB_CONFIGURATION_DESCRIPTOR),
         NULL);
         /* 向USBD.SYS发送URB
*/
          ntStatus = CallUSBD(DeviceObject, urb);
        ……
}
//然后获取全部的配置描述符,包括接口描述符和端点描述符
siz = configurationDescriptor->wTotalLength;
ExFreePool(configurationDescriptor);
configurationDescriptor = ExAllocatePool(NonPagedPool, siz);
if(configurationDescriptor) {
     UsbBuildGetDescriptorRequest(urb,
          (USHORT)sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST),
          USB_CONFIGURATION_DESCRIPTOR_TYPE,
          0,
          0,
          configurationDescriptor,
          NULL,
          siz,
          NULL);
     ntStatus = CallUSBD(DeviceObject, urb);
        ……
}
2.        从配置描述符中提取感兴趣的接口描述符,总线驱动程序提供了函数USBD_ParseConfigurationDescriptorEx以简化这个过程。
interfaceDescriptor =USBD_ParseConfigurationDescriptorEx(
                      ConfigurationDescriptor,ConfigurationDescriptor,
                      interfaceindex,0, -1, -1, -1);
该函数各个参数的含义是:第一个参数是上一步获取的完整的配置描述符;第二个参数是描述符内部开始搜索的地址,如果从头开始搜索,需要设置和第一个参数相同;剩下的五个参数是和感兴趣的接口相关搜索关键字,分别是InterfaceNumber,
AlternateSetting, InterfaceClass, InterfaceSubClass, InterfaceProtoco。但相关的关键字不需要的时候,可以设置成-1。
由于配置描述符中可能包含多个接口,所以驱动程序需要将上述函数返回的接口描述符保存在USBD_INTERFACE_LIST_ENTRY类型的数组中。iso_usb程序首先使用ExAllocatePool函数为接口描述符分配足够的内存。
interfaceList =ExAllocatePool(NonPagedPool,
   sizeof(USBD_INTERFACE_LIST_ENTRY) * (numberOfInterfaces + 1));
然后通过循环使用USBD_ParseConfigurationDescriptorEx函数获取的接口描述符对数组进行初始化。初始化时,应该把接口描述符地址赋给USBD_INTERFACE_LIST_ENTRY结构的InterfaceDescriptor成员,并把Interface成员置NULL。最后需要将数组的最后一个元素的两个成员全部置为NULL。
3.        初始化接口。首先调用USBD_CreateConfigurationRequestEx函数创建一个urb。然后需要对接口中的管道进行相应的初始化,最后将这个urb传递给底层驱动程序,由底层总线驱动程序完成接口的初始化。
urb = USBD_CreateConfigurationRequestEx(ConfigurationDescriptor, tmp);
Interface = &urb->UrbSelectConfiguration.Interface;
/* 需要初始化管道的MaximumTransferSize成员。它代表单一URB能携带的最大数据量 */
for(i=0; i<Interface->NumberOfPipes; i++) {
Interface->Pipes[i].MaximumTransferSize = <constant>
}
ntStatus = CallUSBD(DeviceObject, urb);
     配置USB的过程有这几步基本上就可以完成了,配置后的信息可以通过上位机枚举显示出来。

3.3.2.5.    支持WMI(委托WMILIB处理IRP)

3.3.2.5.  1. WMI概念
Windows 2000支持一种称为Windows管理诊断(WMI)的控件,用于管理计算机系统。WBEM(基于Web的企业管理)是一个广泛的工业标准,而WMI是这个工业标准的Microsoft实现。WMI的目标是为系统管理和企业网络中管理数据的描述提供了一个模型,并尽可能独立于专用API或数据对象模型。这种独立性促进了能创建、传输,和显示控制数据的独立系统部件的发展。
WDM驱动程序以三种方式适应WMI,见图3-3-2-5-0。第一,WMI通常能响应提取性能数据的请求。第二,各种控制器应用程序可以使用WMI方式控制设备的通用特征。第三,WMI提供了一个事件通知机制,允许驱动程序通知应用程序有重要的事件发生。
在WMI模型中,数据和事件被分成了消费者和生产者两类。数据块就是抽象类的实例,其概念与C++中的类概念一致。如同C++中的类,WMI类也有数据成员和实现对象行为的方法。数据块中的内容并不是由WMI指定,而是由数据生产者和数据的使用目的决定的。送往驱动程序的数据最有可能来自管理者本身的操作。而驱动程序发出的数据通常是某种性能的统计数据,这些数据的消费者可能是某个性能监视程序,图3-3-2-5-1展示了WMI的整体结构。
WDM驱动程序可以作为WMI类实例的生产者。一个描述了驱动程序支持的各种类(驱动程序可以为这些类提供数据)的脚本称为驱动程序规划(schema)。我们可以使用MOF(Managed
Object Format)语言定义规划。系统则维护一个称为repository的数据字典,它包含了所有已知的规划定义。如果驱动程序做得正确,系统将在初始化驱动程序时自动把规划放到repository中。
3.3.2.5.  2. 支持和取消WMI服务
在WDM驱动当中,对WMI的支持需要实例化IRP_MJ_SYSTEM_CONTROL例程。在该IRP例程服务函数当中再调用IoWMIRegistrationControl函数为该驱动注册对WMI服务支持。
IoWMIRegistrationControl(DeviceExtension->FunctionalDeviceObject,
                          WMIREG_ACTION_REGISTER);
相反如果要取消对WMI服务支持,需要调用同样需要调用IoWMIRegistrationControl,但是地二个参数需要设置为WMI_ACTION_DEREGISTER。
IoWMIRegistrationControl(DeviceExtension->FunctionalDeviceObject,
                          WMIREG_ACTION_DEREGISTER);
3.3.2.5.  3. 委托WMILIB处理IRP
在系统控制IRP的派遣例程中,你可以委托WMILIB来完成大部分工作。WMILIB_CONTEXT结构体是WMILIB提供的一个用于处理WMI的服务器,该结构体描述如下:
typedef struct _WMILIB_CONTEXT {
  ULONG  GuidCount;
  PWMIGUIDREGINFO  GuidList;
  PWMI_QUERY_REGINFO  QueryWmiRegInfo;
  PWMI_QUERY_DATABLOCK  QueryWmiDataBlock;
  PWMI_SET_DATABLOCK  SetWmiDataBlock;
  PWMI_SET_DATAITEM  SetWmiDataItem;
  PWMI_EXECUTE_METHOD  ExecuteWmiMethod;
  PWMI_FUNCTION_CONTROL  WmiFunctionControl;
} WMILIB_CONTEXT, *PWMILIB_CONTEXT;
Ø       GuidCount域指定注册WMI的驱动WMIGUIDREGINFO结构体数量,另外GuidList则指定WMIGUIDREGINFO数组。
Ø       QueryWmiRegInfo指向QueryRegInfo回调函数;QueryWmiDataBlock指向QueryDataBlock回调函数;SetWmiDataBlock指向SetDataBlock回调函数;SetWmiDataItem指向SetDataItem回调函数。
Ø       ExecuteWmiMethod和WmiFunctionControl一般设置为NULL。
有了这个结构体,你可以委托WMILIB来完成大部分工作,代码如下;
WMIGUIDREGINFO guidlist[] = {                                                                                                                      <--1

  {&GUID_WMI42_SCHEMA, 1, WMIREG_FLAG_INSTANCE_PDO},

};

 

WMILIB_CONTEXT libinfo = {

  arraysize(guidlist),

  guidlist,

  QueryRegInfo,

  QueryDataBlock,

  SetDataBlock,

  SetDataItem,

  ExecuteMethod,

  FunctionControl,

};

 

NTSTATUS DispatchWmi(IN PDEVICE_OBJECT fdo, IN PIRP Irp)

{

  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;

  NTSTATUS status = IoAcquireRemoveLock(&pdx->RemoveLock, Irp);                                                        <--2

  if (!NT_SUCCESS(status))

    return CompleteRequest(Irp, status, 0);

 

  SYSCTL_IRP_DISPOSITION disposition;

  status = WmiSystemControl(&libinfo, fdo, Irp, &disposition);                                                         <--3

 

  switch (disposition)                                                                                                                                       <--4

  {

 

  case IrpProcessed:

    break;

  case IrpNotCompleted:                                                                                                                                 <--5

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    break;

 

  default:                                                                                                                                                         <--6

  case IrpNotWmi:

  case IrpForward:                                                                                                                           <--7

    IoSkipCurrentIrpStackLocation(Irp);

    status = IoCallDriver(pdx->LowerDeviceObject, Irp);

    break;

  }

 

  IoReleaseRemoveLock(&pdx->RemoveLock, Irp);

  return status;

}

Ø       guidlist声明的有效范围是整个文件,它描述了驱动程序支持的类GUID,并列出几个WMILIB用于处理WMI请求的回调函数。
Ø       与其它派遣例程相同,我们在处理这种IRP时也获取和释放删除锁。我们要防止PnP事件使下层设备对象消失。我们自己的设备对象不会消失,因为IoWMIRegistrationControl调用获得了对它的一次引用。
Ø      status
= WmiSystemControl(&libinfo, fdo, Irp, &disposition)语句调用WMILIB来处理该IRP。我们传递了WMILIB_CONTEXT结构的地址。通常我们应使用一个静态上下文结构,因为从一个IRP到另一个IRP,其中的信息不可能被改变。WmiSystemControl返回两个信息:一个NTSTATUS代码和一个SYSCTL_IRP_DISPOSITION值。
Ø       执行这个IRP时我们可能需要做一些额外工作,这取决于它的特征代码,如果这个代码为IrpProcessed,则该IRP已经完成,我们不需要再做任何事情。对于除IRP_MN_REGINFO之外的其它副功能码,这种情况就是通常情况。
Ø       如果代码是IrpNotCompleted,则我们有责任完成该IRP。这也是通常情况,除了IRP_MN_REGINFO。WMILIB已经填充完IRP中的IoStatus块,所以我们仅需要调用IoCompleteRequest。
Ø       default和IrpNotWmi情况不应该发生在Windows
2000中。如果不能处理所有可能的特征代码,我们将到达default。如果我们向WMILIB发送一个IRP,但其副功能码在WMI中未定义,则我们到达IrpNotWmi处。
Ø       IrpForward情况发生于该系统控制IRP是发往其它驱动程序的。回想一下ProviderId参数,它指出处理该IRP的驱动程序。WmiSystemControl用设备对象指针指向的值与第二个参数比较。如果不相同,它就返回到IrpForward,然后我们把该IRP下传到下一个驱动程序。
WMI消费者通过查看WMILIB_CONTEXT结构中的GUID来判断驱动程序是否是一个WMI生产者。当一个消费者想提取数据时,它通过(间接地)访问WMI数据字典(
repository),把一个符号对象名翻译成一个GUID,这个GUID就是以前提到的MOF语句的一部分,它应该与WMILIB_CONTEXT结构中的GUID一致,WMILIB会关心这个匹配。
WMILIB将回调驱动程序中的例程来执行设备相关或驱动程序相关的处理。回调函数大部分时间以同步方式执行IRP的操作。除了IRP_MN_REGINFO之外,我们可以推迟IRP处理并返回STATUS_PENDING。如果一个回调例程挂起了该IRP,那么它应该额外再调用一次IoAcquireRemoveLock。任何完成该IRP的例程都应该调用相反的IoReleaseRemoveLock函数。
3.3.2.5.  4. 完成WMI修改服务
在SetDataBlock回调函数和SetDataItem回调函数完成之后,需要将修改后的信息保存在资源文件当中,由于委托WMILIB处理WMI服务,因此WMILIB为我们提供了一个WmiCompleteRequest函数,该函数将实现我们需要的功能,函数描述如下;
NTSTATUS
  WmiCompleteRequest(
    IN PDEVICE_OBJECT  DeviceObject,
    IN PIRP  Irp,
    IN NTSTATUS  Status,
    IN ULONG  BufferUsed,
    IN CCHAR  PriorityBoost
    );
BufferUsed指定实际需要完成的数据量,如果该值设置过小,系统会返回STATUS_BUFFER_TOO_SMALL,如果该值设置过大,系统会自动根据实际需要设置完成的数据量。
在WmiCompleteRequest函数实现过程当中,其实操作系统仍然调用IoCompleteRequest函数完成相关的IRP例程,PriorityBoost域指定I/O处理的优先级别。PriorityBoost值一般设置为IO_NO_INCREMENT。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: