如何选择 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. 创建 USBD_INTERFACE_LIST_ENTRY 结构的数组。 数组中的元素数必须比接口数多一个。 通过调用 RtlZeroMemory 初始化数组。

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

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

    关于 USB 复合设备的功能驱动程序:

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

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

    枚举部分配置中的接口时,请根据接口数计算接口编号,避免搜索接口。 在前面的示例中,如果在循环中调用 USBD_ParseConfigurationDescriptorEx ,该循环从零开始,在 (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 和 ealier 中,客户端驱动程序通过调用 USBD_CreateConfigurationRequestEx 为选择配置请求创建了 URB。 在 Windows 2000 USBD_CreateConfigurationRequestEx 初始化 Pipes[i]。MaximumTransferSize 为单个 URB 读/写请求的默认最大传输大小。 客户端驱动程序可以在 Pipes[i] 中指定不同的最大传输大小。MaximumTransferSize。 USB 堆栈在 Windows XP、Windows Server 2003 和更高版本的操作系统中忽略此值。 有关 MaximumTransferSize 的详细信息,请参阅 USB 带宽分配中的“设置 USB 传输和数据包大小”。

步骤 3:将 URB 提交到 USB 驱动程序堆栈

若要将 URB 提交到 USB 驱动程序堆栈,客户端驱动程序必须发送 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 URB_FUNCTION_SELECT_INTERFACE (选择接口请求) 来选择备用接口设置。 若要为该请求分配和生成 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 设备,请使用 NULL 配置描述符创建并提交选择配置请求。 对于该类型的请求,可以重复使用为在设备中选择配置的请求创建的 URB。 或者,可以通过调用 USBD_UrbAllocate 来分配新的 URB。 在提交请求之前,必须使用 UsbBuildSelectConfigurationRequest 宏设置 URB 的格式,如以下示例代码所示。

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