USB 多功能设备的驱动程序(称为复合驱动程序)可以向基础 USB 驱动程序堆栈注册和注销复合设备。 Microsoft 提供的驱动程序(Usbccgp.sys)是由 Windows 加载的默认复合驱动程序。 本文中的过程适用于替换Usbccgp.sys的基于 WDM的自定义 Windows 驱动程序模型 。
通用串行总线 (USB) 设备可以提供多个同时处于活动状态的功能。 此类多功能设备也称为 复合设备。 例如,复合设备可能为键盘功能定义一个函数,为鼠标定义另一个函数。 复合驱动程序枚举设备的功能。 复合驱动程序可以在整体模型中管理这些函数本身,或为每个函数创建物理设备对象 (PDO) 。 这些单独的 PDO 由各自的 USB 功能驱动程序、键盘驱动程序和鼠标驱动程序管理。
USB 3.0 规范定义了 函数暂停和远程唤醒功能 ,使各个函数能够进入和退出低功耗状态,而不会影响其他功能或整个设备的电源状态。
若要使用该功能,复合驱动程序需要将设备注册到基础 USB 驱动程序堆栈。 由于该功能适用于 USB 3.0 设备,因此复合驱动程序必须确保基础堆栈支持版本USBD_INTERFACE_VERSION_602。 通过注册请求,复合驱动程序:
- 通知基础 USB 驱动程序堆栈,驱动程序负责发送请求,以支持功能驱动进行远程唤醒。 远程唤醒请求由 USB 驱动程序堆栈处理,该堆栈将必要的协议请求发送到设备;
- 获取 (USB 驱动程序堆栈分配的每个函数) 一个函数句柄的列表。 然后,复合驱动程序可以在驱动程序的请求中使用函数句柄,以便远程唤醒与句柄关联的函数;
通常,复合驱动程序在驱动程序的 AddDevice 或启动设备例程中发送注册请求来处理 IRP_MN_START_DEVICE。 因此,复合驱动程序会释放在驱动程序的卸载例程,例如 stop-device (IRP_MN_STOP_DEVICE ) 或 remove-device 例程 (IRP_MN_REMOVE_DEVICE) 中为释放在注册时分配的资源。
先决条件
发送注册请求之前,请确保:
- 你拥有设备中的函数数。 该数字可以派生 get-configuration 请求检索到的描述符;
- 在对 USBD_CreateHandle 的上一次调用中,你已获得 USBD 句柄;
- 基础 USB 驱动程序堆栈支持 USB 3.0 设备。 为此,请调用 USBD_IsInterfaceVersionSupported 并将 USBD_INTERFACE_VERSION_602 作为版本传递给 检查;
注册复合设备
以下过程介绍如何生成和发送注册请求,以将复合驱动程序与 USB 驱动程序堆栈相关联。
1.分配 COMPOSITE_DEVICE_CAPABILITIES 结构并通过调用 COMPOSITE_DEVICE_CAPABILITIES_INIT 宏对其进行初始化;
2.将 COMPOSITE_DEVICE_CAPABILITIES 的 CapabilityFunctionSuspend 成员设置为 1;
3.分配 REGISTER_COMPOSITE_DEVICE 结构,并通过调用 USBD_BuildRegisterCompositeDevice 例程初始化 结构 。 在调用中,指定 USBD 句柄、初始化 COMPOSITE_DEVICE_CAPABILITIES 结构和函数数;
4.通过调用 IoAllocateIrp (IRP) 分配 I/O 请求数据包,并通过调用 IoGetNextIrpStackLocation 获取指向 IRP 的第一个堆栈位置的指针 (IO_STACK_LOCATION) ;
5.为足以容纳函数句柄数组的缓冲区分配内存 (USBD_FUNCTION_HANDLE) 。 数组中的元素数必须是 PDO 的数量;
6.通过设置 IO_STACK_LOCATION的以下成员来生成请求:
- 通过将 Parameters.DeviceIoControl.IoControlCode 设置为 IOCTL_INTERNAL_USB_REGISTER_COMPOSITE_DEVICE,指定请求的类型。
- 通过将 Parameters.Others.Argument1 设置为初始化的 REGISTER_COMPOSITE_DEVICE 结构的地址来指定输入参数。
- 通过将 AssociatedIrp.SystemBuffer 设置为在步骤 5 中分配的缓冲区来指定输出参数。
7.调用 IoCallDriver ,通过将 IRP 传递到下一个堆栈位置来发送请求;
完成后,检查 USB 驱动程序堆栈返回的函数句柄数组。 可以将数组存储在驱动程序的设备上下文中,以供将来使用;
下面的代码示例演示如何生成和发送注册请求。 该示例假定复合驱动程序将以前获取的函数数和 USBD 句柄存储在驱动程序的设备上下文中。
VOID  RegisterCompositeDriver(PPARENT_FDO_EXT parentFdoExt)  
{  PIRP                            irp;  REGISTER_COMPOSITE_DRIVER       registerInfo;  COMPOSITE_DRIVER_CAPABILITIES   capabilities;  NTSTATUS                        status;  PVOID                           buffer;  ULONG                           bufSize;  PIO_STACK_LOCATION              nextSp;  buffer = NULL;  COMPOSITE_DRIVER_CAPABILITIES_INIT(&capabilities);  capabilities.CapabilityFunctionSuspend = 1;  USBD_BuildRegisterCompositeDriver(parentFdoExt->usbdHandle,  capabilities,  parentFdoExt->numFunctions,  ®isterInfo);  irp = IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);  if (irp == NULL) {  //IoAllocateIrp failed.status = STATUS_INSUFFICIENT_RESOURCES;  goto ExitRegisterCompositeDriver;    }  nextSp = IoGetNextIrpStackLocation(irp);  bufSize = parentFdoExt->numFunctions * sizeof(USBD_FUNCTION_HANDLE);  buffer = ExAllocatePoolWithTag (NonPagedPool, bufSize, POOL_TAG);  if (buffer == NULL) {  // Memory alloc for function-handles failed.status = STATUS_INSUFFICIENT_RESOURCES;  goto ExitRegisterCompositeDriver;    }  nextSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;     nextSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_REGISTER_COMPOSITE_DRIVER;  //Set the input buffer in Argument1      nextSp->Parameters.Others.Argument1 = ®isterInfo;  //Set the output buffer in SystemBuffer field for USBD_FUNCTION_HANDLE.      irp->AssociatedIrp.SystemBuffer = buffer;  // Pass the IRP down to the next device object in the stack. Not shown.status = CallNextDriverSync(parentFdoExt, irp, FALSE);  if (!NT_SUCCESS(status)){  //Failed to register the composite driver.goto ExitRegisterCompositeDriver;    }  parentFdoExt->compositeDriverRegistered = TRUE;  parentFdoExt->functionHandleArray = (PUSBD_FUNCTION_HANDLE) buffer;  End:  if (!NT_SUCCESS(status)) {  if (buffer != NULL) {  ExFreePoolWithTag (buffer, POOL_TAG);  buffer = NULL;  }  }  if (irp != NULL) {  IoFreeIrp(irp);  irp = NULL;  }  return;  
}取消注册复合设备
- 通过调用 IoAllocateIrp 分配 IRP,并通过调用 IoGetNextIrpStackLocation 获取指向 IRP 的第一个堆栈位置 (IO_STACK_LOCATION) 的指针;
- 通过将 IO_STACK_LOCATION 的 Parameters.DeviceIoControl.IoControlCode 成员设置为IOCTL_INTERNAL_USB_UNREGISTER_COMPOSITE_DEVICE来生成请求;
- 调用 IoCallDriver ,通过将 IRP 传递到下一个堆栈位置来发送请求;
复合驱动程序在 remove-device 例程的上下文中发送一次 IOCTL_INTERNAL_USB_UNREGISTER_COMPOSITE_DEVICE 请求。 请求的目的是删除 USB 驱动程序堆栈与复合驱动程序及其枚举函数之间的关联。 该请求还会清理为维护该关联而创建的所有资源,以及上一个注册请求中返回的所有函数句柄。
下面的代码示例演示如何生成并发送取消注册复合设备的请求。 该示例假定复合驱动程序以前是通过注册请求注册的,如本主题前面所述。
VOID  UnregisterCompositeDriver(  PPARENT_FDO_EXT parentFdoExt )  
{  PIRP                irp;  PIO_STACK_LOCATION  nextSp;  NTSTATUS            status;  PAGED_CODE();  irp = IoAllocateIrp(parentFdoExt->topDevObj->StackSize, FALSE);  if (irp == NULL) {  //IoAllocateIrp failed.status = STATUS_INSUFFICIENT_RESOURCES;  return;  }  nextSp = IoGetNextIrpStackLocation(irp);  nextSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;     nextSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_UNREGISTER_COMPOSITE_DRIVER;  // Pass the IRP down to the next device object in the stack. Not shown.status = CallNextDriverSync(parentFdoExt, irp, FALSE);  if (NT_SUCCESS(status)) {  parentFdoExt->compositeDriverRegistered = FALSE;      }  IoFreeIrp(irp);  return;  
}在后续会讲述复合设备的电源管理问题。