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;
}
在后续会讲述复合设备的电源管理问题。