WinXP下USB驱动开发(六)

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中可以发现采用的函数是IoCallDriverIoCallDriver函数的第一个参数指定的为挂载的设备对象,第二参数为当前的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保存了构造后的URBnextStack->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);

提示:IoSkipCurrentIrpStackLocationIoCopyCurrentIrpStackLocationToNext区别

在操作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回调函数。

Ø       ExecuteWmiMethodWmiFunctionControl一般设置为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_REGINFOWMILIB已经填充完IRP中的IoStatus块,所以我们仅需要调用IoCompleteRequest

Ø       defaultIrpNotWmi情况不应该发生在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

你可能感兴趣的:(WinXP下USB驱动开发(六))