如何注册复合设备

本文介绍了 USB 多功能设备(称为复合驱动程序)的驱动程序如何在基础 USB 驱动程序堆栈中注册和取消注册复合设备。 Windows 会加载 Microsoft 提供的驱动程序 Usbccgp.sys 作为默认的复合驱动程序。 本文中的步骤适用于基于 Windows 驱动程序模型 (WDM) 的自定义复合驱动程序,该驱动程序将取代 Usbccgp.sys。

通用串行总线 (USB) 设备可同时提供多种激活的功能。 此类多功能设备也被称为复合设备。 例如,一个复合设备可以为键盘功能定义一个功能,为鼠标定义另一个功能。 复合驱动程序会枚举设备的功能。 复合驱动程序可在单体模型中自行管理这些功能,或为每个功能创建物理设备对象 (PDO)。 USB 功能驱动程序(如键盘驱动程序和鼠标驱动程序)管理各自的 PDO。

USB 3.0 规范定义了功能挂起和远程唤醒功能,使单个功能能够进入和退出低功耗状态,而不会影响其他功能或整个设备的电源状态。 有关该功能的详细信息,请参阅如何在复合驱动程序中实现功能挂起

要使用该功能,复合驱动程序需要在基础 USB 驱动程序堆栈中注册设备。 由于该功能适用于 USB 3.0 设备,因此复合驱动程序必须确保基础堆栈支持 USBD_INTERFACE_VERSION_602 版本。 通过注册请求,复合驱动程序:

  • 通知基础 USB 驱动程序堆栈,该驱动程序将负责发送请求,以启用远程唤醒的功能。 USB 驱动程序堆栈会处理远程唤醒请求,并向设备发送必要的协议请求。
  • 获取 USB 驱动程序堆栈分配的功能句柄列表(每个功能一个)。 然后,复合驱动程序可以在驱动程序中使用功能句柄,请求远程唤醒与句柄相关的功能。

通常,复合驱动程序会在驱动程序的 AddDevice 或启动设备例程中发送注册请求,以处理 IRP_MN_START_DEVICE。 因此,复合驱动程序会释放在驱动程序卸载例程中为注册分配的资源,如停止设备 (IRP_MN_STOP_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 来分配一个 I/O 请求数据包 (IRP),并通过调用 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,
        &registerInfo);

    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 = &registerInfo;

    //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;
}

取消注册复合设备

  1. 通过调用 IoAllocateIrp 来分配一个 IRP,并通过调用 IoGetNextIrpStackLocation 来获取指向 IRP 第一个堆栈位置 (IO_STACK_LOCATION) 的指针。
  2. 通过将 IO_STACK_LOCATION 中的 Parameters.DeviceIoControl.IoControlCode 成员设置为 IOCTL_INTERNAL_USB_UNREGISTER_COMPOSITE_DEVICE 来生成请求。
  3. 调用 IoCallDriver,通过将 IRP 传递到下一个堆栈位置来发送请求。

IOCTL_INTERNAL_USB_UNREGISTER_COMPOSITE_DEVICE 请求由复合驱动程序在 remove-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;
}