如何选择 USB 设备的配置

要为 USB 设备选择配置,设备的客户端驱动程序必须至少选择一种支持的配置,并指定要使用的每个接口的备用设置。 客户端驱动程序会将这些选择打包到选择配置请求中,并将请求发送到 Microsoft 提供的 USB 驱动程序堆栈,特别是 USB 总线驱动程序(USB 集线器 PDO)。 USB 总线驱动程序会选择指定配置中的每个接口,并为接口内的每个终结点建立通信通道或管道。 请求完成后,客户端驱动程序会收到所选配置的句柄,以及每个接口的活动备用设置中定义的终结点的管道句柄。 然后,客户端驱动程序可以使用接收到的句柄来更改配置设置,并向特定终结点发送 I/O 读写请求。

客户端驱动程序在 URB_FUNCTION_SELECT_CONFIGURATION 类型的 USB 请求块 (URB) 中发送选择配置请求。 本主题中的过程将介绍如何使用 USBD_SelectConfigUrbAllocateAndBuild 例程来生成 URB。 例程会为 URB 分配内存,为选择配置请求格式化 URB,并将 URB 的地址返回给客户端驱动程序。

或者,可以分配一个 URB 结构,然后手动或通过调用 UsbBuildSelectConfigurationRequest 宏来格式化 URB。

先决条件

步骤 1:创建 USBD_INTERFACE_LIST_ENTRY 结构数组

  1. 获取配置中的接口数量。 这些信息包含在 USB_CONFIGURATION_DESCRIPTOR 结构的 bNumInterfaces 成员中。

  2. 步骤 1:创建 USBD_INTERFACE_LIST_ENTRY 结构数组。 数组中的元素数必须比接口数多一个。 通过调用 RtlZeroMemory 来初始化数组。

    客户端驱动程序会在 USBD_INTERFACE_LIST_ENTRY 结构数组中指定要启用的每个接口的备用设置。

    • 每个结构的 InterfaceDescriptor 成员指向包含备用设置的接口描述符。
    • 每个结构的 Interface 成员指向 USBD_INTERFACE_INFORMATION 结构,该结构的 Pipes 成员包含管道信息。 Pipes 存储备用设置中定义的每个终结点的信息。
  3. 为配置中的每个接口(或其备用设置)获取接口描述符。 可以通过调用 USBD_ParseConfigurationDescriptorEx来获取这些接口描述符。

    关于 USB 复合设备的函数驱动程序:

    如果 USB 设备是复合设备,则配置由 Microsoft 提供的 USB 通用父驱动程序 (Usbccgp.sys) 选择。 作为复合设备功能驱动程序之一的客户端驱动程序不能更改配置,但该驱动程序仍可通过 Usbccgp.sys 发送选择配置请求。

    在发送该请求之前,客户端驱动程序必须提交一个 URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE 请求。 作为回应,Usbccgp.sys 会检索部分配置描述符,其中仅包含与加载客户端驱动程序的特定功能有关的接口描述符和其他描述符。 部分配置描述符的 bNumInterfaces 字段中报告的接口数量少于为整个 USB 复合设备定义的接口总数。 此外,在部分配置描述符中,接口描述符的 bInterfaceNumber 表示相对于整个设备的实际接口编号。 例如,Usbccgp.sys 可能会报告部分配置描述符,其中第一个接口的 bNumInterfaces 值为 2,bInterfaceNumber 值为 4。 请注意,接口数量大于报告的接口数量。

    在部分配置中枚举接口时,应避免根据接口数量计算接口编号来搜索接口。 在上例中,如果在一个循环中调用 USBD_ParseConfigurationDescriptorEx 从 0 开始,在 (bNumInterfaces - 1) 结束,并在每次迭代中递增接口索引(在 InterfaceNumber 参数中指定),则例程无法获得正确的接口。 相反,通过在 InterfaceNumber 中传递 -1 来确保在配置描述符中搜索所有接口。 有关实现详细信息,请参阅本部分中的代码示例。

    有关 Usbccgp.sys 如何处理客户端驱动程序发送的选择配置请求的信息,请参阅配置 Usbccgp.sys 以选择非默认 USB 配置

  4. 对于数组中的每个元素(最后一个元素除外),请将 InterfaceDescriptor 成员设置为接口描述符的地址。 对于数组中的第一个元素,请将 InterfaceDescriptor 成员设置为代表配置中第一个接口的接口描述符的地址。 同样,对于数组中的第 n 个元素,将 InterfaceDescriptor 成员设置为代表配置中第 n 个接口的接口描述符的地址。

  5. 最后一个元素的 InterfaceDescriptor 成员必须设置为 NULL。

步骤 2:获取指向 USB 驱动程序堆栈分配的 URB 的指针

接下来,通过指定要选择的配置和填充的 USBD_INTERFACE_LIST_ENTRY 结构数组来调用 USBD_SelectConfigUrbAllocateAndBuild。 此例程执行以下任务:

  • 创建 URB,并在其中填入有关指定配置、其接口和终结点的信息,并将请求类型设为 URB_FUNCTION_SELECT_CONFIGURATION。

  • 在 URB 中,为客户端驱动程序指定的每个接口描述符分配一个 USBD_INTERFACE_INFORMATION 结构。

  • 将调用方提供的 USBD_INTERFACE_LIST_ENTRY 数组中第 n 个元素的 Interface 成员设置为 URB 中相应 USBD_INTERFACE_INFORMATION 结构的地址。

  • 初始化 InterfaceNumberAlternateSettingNumberOfPipesPipes[i].MaximumTransferSizePipes[i].PipeFlags 成员。

    注意

    在 Windows 7 及更早版本中,客户端驱动程序通过调用 USBD_CreateConfigurationRequestEx 为选择配置请求创建 URB。 在 Windows 2000 中 USBD_CreateConfigurationRequestExPipes[i].MaximumTransferSize 初始化为单个 URB 读/写请求的默认最大传输大小。 客户端驱动程序可以在 Pipes[i].MaximumTransferSize 中指定不同的最大传输大小。 在 Windows XP、Windows Server 2003 和更高版本的操作系统中,USB 堆栈会忽略此值。 有关 MaximumTransferSize 的更多信息,请参阅 USB 带宽分配中的“设置 USB 传输和数据包大小”。

第 3 步:将 URB 提交给 USB 驱动程序堆栈

要向 USB 驱动程序堆栈提交 URB,客户端驱动程序必须发送 IOCTL_INTERNAL_USB_SUBMIT_URB I/O 控制请求。 有关提交 URB 的信息,请参阅如何提交 URB

接收 URB 后,USB 驱动程序堆栈会填充每个 USBD_INTERFACE_INFORMATION 结构的其余成员。 其中,Pipes 数组成员包含与接口终结点相关联的管道相关信息。

步骤 4:请求完成后,检查 USBD_INTERFACE_INFORMATION 结构和 URB

USB 驱动程序堆栈完成请求的 IRP 后,会在 USBD_INTERFACE_LIST_ENTRY 数组中返回备用设置列表和相关接口的列表。

  1. 每个 USBD_INTERFACE_INFORMATION 结构的 Pipes 成员指向一个 USBD_PIPE_INFORMATION 结构数组,其中包含与该特定接口的每个终结点关联的管道信息。 客户端驱动程序可以从 Pipes[i].PipeHandle 中获取管道句柄,并使用它们向特定管道发送 I/O 请求。 Pipes[i].PipeType 成员指定了该管道支持的终结点和传输类型。

  2. 在 URB 的 UrbSelectConfiguration 成员中,USB 驱动程序堆栈会返回一个句柄,可以使用该句柄通过提交另一个 URB_FUNCTION_SELECT_INTERFACE 类型的 URB(选择接口请求)来选择备用接口设置。 要为该请求分配和生成 URB 结构,请调用 USBD_SelectInterfaceUrbAllocateAndBuild

    如果带宽不足以支持启用接口内的时序、控制和中断终结点,则选择配置请求和选择接口请求可能会失败。 在这种情况下,USB 总线驱动程序会将 URB 标头的 Status 成员设置为 USBD_STATUS_NO_BANDWIDTH。

以下示例代码显示了如何创建 USBD_INTERFACE_LIST_ENTRY 结构数组并调用 USBD_SelectConfigUrbAllocateAndBuild。 该示例通过调用 SubmitUrbSync 同步发送请求。 要查看 SubmitUrbSync 的代码示例,请参阅如何提交 URB

/*++

Routine Description:
This helper routine selects the specified configuration.

Arguments:
USBDHandle - USBD handle that is retrieved by the 
client driver in a previous call to the USBD_CreateHandle routine.

ConfigurationDescriptor - Pointer to the configuration
descriptor for the device. The caller receives this pointer
from the URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE request.

Return Value: NT status value
--*/

NTSTATUS SelectConfiguration (PDEVICE_OBJECT DeviceObject,
                              PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor)
{
    PDEVICE_EXTENSION deviceExtension;
    PIO_STACK_LOCATION nextStack;
    PIRP irp;
    PURB urb = NULL;

    KEVENT    kEvent;
    NTSTATUS ntStatus;    

    PUSBD_INTERFACE_LIST_ENTRY   interfaceList = NULL; 
    PUSB_INTERFACE_DESCRIPTOR    interfaceDescriptor = NULL;
    PUSBD_INTERFACE_INFORMATION  Interface = NULL;
    USBD_PIPE_HANDLE             pipeHandle;

    ULONG                        interfaceIndex;

    PUCHAR StartPosition = (PUCHAR)ConfigurationDescriptor;

    deviceExtension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

    // Allocate an array for the list of interfaces
    // The number of elements must be one more than number of interfaces.
    interfaceList = (PUSBD_INTERFACE_LIST_ENTRY)ExAllocatePool (
        NonPagedPool, 
        sizeof(USBD_INTERFACE_LIST_ENTRY) *
        (deviceExtension->NumInterfaces + 1));

    if(!interfaceList)
    {
        //Failed to allocate memory
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto Exit;
    }

    // Initialize the array by setting all members to NULL.
    RtlZeroMemory (interfaceList, sizeof (
        USBD_INTERFACE_LIST_ENTRY) *
        (deviceExtension->NumInterfaces + 1));

    // Enumerate interfaces in the configuration.
    for ( interfaceIndex = 0; 
        interfaceIndex < deviceExtension->NumInterfaces; 
        interfaceIndex++) 
    {
        interfaceDescriptor = USBD_ParseConfigurationDescriptorEx(
            ConfigurationDescriptor, 
            StartPosition, // StartPosition 
            -1,            // InterfaceNumber
            0,             // AlternateSetting
            -1,            // InterfaceClass
            -1,            // InterfaceSubClass
            -1);           // InterfaceProtocol

        if (!interfaceDescriptor) 
        {
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
            goto Exit;
        }

        // Set the interface entry
        interfaceList[interfaceIndex].InterfaceDescriptor = interfaceDescriptor;
        interfaceList[interfaceIndex].Interface = NULL;

        // Move the position to the next interface descriptor
        StartPosition = (PUCHAR)interfaceDescriptor + interfaceDescriptor->bLength;

    }

    // Make sure that the InterfaceDescriptor member of the last element to NULL.
    interfaceList[deviceExtension->NumInterfaces].InterfaceDescriptor = NULL;

    // Allocate and build an URB for the select-configuration request.
    ntStatus = USBD_SelectConfigUrbAllocateAndBuild(
        deviceExtension->UsbdHandle, 
        ConfigurationDescriptor, 
        interfaceList,
        &urb);

    if(!NT_SUCCESS(ntStatus)) 
    {
        goto Exit;
    }

    // Allocate the IRP to send the buffer down the USB stack.
    // The IRP will be freed by IO manager.
    irp = IoAllocateIrp((deviceExtension->NextDeviceObject->StackSize)+1, TRUE);  

    if (!irp)
    {
        //Irp could not be allocated.
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto Exit;
    }

    ntStatus = SubmitUrbSync( 
        deviceExtension->NextDeviceObject, 
        irp, 
        urb, 
        CompletionRoutine);

    // Enumerate the pipes in the interface information array, which is now filled with pipe
    // information.

    for ( interfaceIndex = 0; 
        interfaceIndex < deviceExtension->NumInterfaces; 
        interfaceIndex++) 
    {
        ULONG i;

        Interface = interfaceList[interfaceIndex].Interface;

        for(i=0; i < Interface->NumberOfPipes; i++) 
        {
            pipeHandle = Interface->Pipes[i].PipeHandle;

            if (Interface->Pipes[i].PipeType == UsbdPipeTypeInterrupt)
            {
                deviceExtension->InterruptPipe = pipeHandle;
            }
            if (Interface->Pipes[i].PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_IN (Interface->Pipes[i].EndpointAddress))
            {
                deviceExtension->BulkInPipe = pipeHandle;
            }
            if (Interface->Pipes[i].PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_OUT (Interface->Pipes[i].EndpointAddress))
            {
                deviceExtension->BulkOutPipe = pipeHandle;
            }
        }
    }

Exit:

    if(interfaceList) 
    {
        ExFreePool(interfaceList);
        interfaceList = NULL;
    }

    if (urb)
    {
        USBD_UrbFree( deviceExtension->UsbdHandle, urb); 
    }

    return ntStatus;
}

NTSTATUS CompletionRoutine ( PDEVICE_OBJECT DeviceObject,
                            PIRP           Irp,
                            PVOID          Context)
{
    PKEVENT kevent;

    kevent = (PKEVENT) Context;

    if (Irp->PendingReturned == TRUE)
    {
        KeSetEvent(kevent, IO_NO_INCREMENT, FALSE);
    }

    KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Select-configuration request completed. \n" ));

    return STATUS_MORE_PROCESSING_REQUIRED;
}

禁用 USB 设备的配置

要禁用 USB 设备,请创建并提交一个配置描述符为空的选择配置请求。 对于这种类型的请求,可以重复使用为选择设备配置的请求创建的 URB。 或者,可以通过调用 USBD_UrbAllocate 来分配一个新 URB。 在提交请求之前,必须使用 UsbBuildSelectConfigurationRequest 宏来格式化 URB,如以下示例代码所示。

URB Urb;
UsbBuildSelectConfigurationRequest(
  &Urb,
  sizeof(_URB_SELECT_CONFIGURATION),
  NULL
);