USB 配置描述符

USB 设备以一系列称为 USB 配置的接口的形式公开其功能。 每个接口由一个或多个备用设置组成,每个备用设置由一组终结点组成。 本主题介绍与 USB 配置关联的各种描述符。

配置描述符中描述了 USB 配置, (请参阅 USB_CONFIGURATION_DESCRIPTOR 结构) 。 配置描述符包含有关配置及其接口、备用设置及其终结点的信息。 每个接口描述符或备用设置均在 USB_INTERFACE_DESCRIPTOR 结构中描述。 在配置中,每个接口描述符在内存中后跟接口和备用设置的所有终结点描述符。 每个终结点描述符都存储在 USB_ENDPOINT_DESCRIPTOR 结构中。

例如,请考虑 USB 设备布局中描述的 USB 网络摄像头设备。 设备支持具有两个接口的配置,第一个接口 (索引 0) 支持两个备用设置。

以下示例显示了 USB 网络摄像头设备的配置描述符:

Configuration Descriptor:
wTotalLength:         0x02CA
bNumInterfaces:       0x02
bConfigurationValue:  0x01
iConfiguration:       0x00
bmAttributes:         0x80 (Bus Powered )
MaxPower:             0xFA (500 mA)

bConfigurationValue 字段指示设备固件中定义的配置编号。 客户端驱动程序使用该数字值来选择活动配置。 有关 USB 设备配置的详细信息,请参阅 如何为 USB 设备选择配置。 USB 配置还指示某些电源特征。 bmAttributes 包含一个位掩码,指示配置是否支持远程唤醒功能,以及设备是总线供电还是自供电。 MaxPower 字段指定当设备采用总线供电时,设备可从主机提取的最大功率) ((以毫安为单位)。 配置描述符还指示设备支持的 (bNumInterfaces) 接口总数。

以下示例显示了网络摄像头设备的接口 0 的备用设置 0 的接口描述符:

Interface Descriptor:
bInterfaceNumber:     0x00
bAlternateSetting:    0x00
bNumEndpoints:        0x01
bInterfaceClass:      0x0E
bInterfaceSubClass:   0x02
bInterfaceProtocol:   0x00
iInterface:           0x02
0x0409: "Microsoft LifeCam VX-5000"
0x0409: "Microsoft LifeCam VX-5000"

在前面的示例中,请注意 bInterfaceNumberbAlternateSetting 字段值。 这些字段包含客户端驱动程序用于激活接口及其备用设置之一的索引值。 对于激活,驱动程序会向 USB 驱动程序堆栈发送选择接口请求。 然后,驱动程序堆栈 (SET INTERFACE) 生成标准控制请求并将其发送到设备。 请注意 bInterfaceClass 字段。 接口描述符或其任何备用设置的描述符指定类代码、子类和协议。 0x0E 的值指示接口适用于视频设备类。 另请注意 iInterface 字段。 该值指示在接口描述符后面追加了两个字符串描述符。 字符串描述符包含 Unicode 说明,这些说明在设备枚举期间用于标识功能。 有关字符串描述符的详细信息,请参阅 USB 字符串描述符

接口中的每个终结点描述设备的单个输入或输出流。 支持不同类型函数流的设备具有多个接口。 支持与一个函数相关的多个流的设备可以支持单个接口上的多个终结点。

除默认终结点之外,所有类型的终结点 () 必须提供终结点描述符,以便主机可以获取有关终结点的信息。 终结点描述符包括其地址、类型、方向和终结点可以处理的数据量等信息。 到终结点的数据传输基于该信息。

以下示例显示了网络摄像头设备的终结点描述符:

Endpoint Descriptor:
bEndpointAddress:   0x82  IN
bmAttributes:       0x01
wMaxPacketSize:     0x0080 (128)
bInterval:          0x01

bEndpointAddress 字段指定包含终结点编号 (位 3.0) 的唯一终结点地址,以及终结点方向 (位 7) 。 通过读取上述示例中的这些值,我们可以确定描述符描述了终结点编号为 2 的 IN 终结点。 bmAttributes 属性指示终结点类型为常时等量。 wMaxPacketSizefield 指示终结点在单个事务中可以发送或接收的最大字节数。 位 12..11 指示每个微帧可以发送的事务总数。 bInterval 指示终结点发送或接收数据的频率。

如何获取配置描述符

配置描述符是通过标准设备请求 (GET_DESCRIPTOR) 从设备获取的,该请求由 USB 驱动程序堆栈作为控制传输发送。 USB 客户端驱动程序可以通过以下方式之一启动请求:

  • 如果设备仅支持一种配置,最简单的方法是调用框架提供的 WdfUsbTargetDeviceRetrieveConfigDescriptor 方法。

  • 对于支持多个配置的设备,如果客户端驱动程序想要获取第一个配置以外的配置描述符,则驱动程序必须提交 URB。 若要提交 URB,驱动程序必须分配、格式化 URB,然后将 URB 提交到 USB 驱动程序堆栈。

    若要分配 URB,客户端驱动程序必须调用 WdfUsbTargetDeviceCreateUrb 方法。 方法接收指向 USB 驱动程序堆栈分配的 URB 的指针。

    若要设置 URB 的格式,客户端驱动程序可以使用 UsbBuildGetDescriptorRequest 宏。 宏在 URB 中设置所有必要的信息,例如要检索其描述符的设备定义的配置编号。 URB 函数设置为 URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE (see _URB_CONTROL_DESCRIPTOR_REQUEST) ,并将描述符的类型设置为 USB_CONFIGURATION_DESCRIPTOR_TYPE。 通过使用 URB 中包含的信息,USB 驱动程序堆栈生成标准控制请求并将其发送到设备。

    若要提交 URB,客户端驱动程序必须使用 WDF 请求对象。 若要以异步方式将请求对象发送到 USB 驱动程序堆栈,驱动程序必须调用 **WdfRequestSend** 方法。 若要以同步方式发送,请调用 WdfUsbTargetDeviceSendUrbSynchronously 方法。

    WDM 驱动程序: Windows 驱动程序模型 (WDM) 客户端驱动程序只能通过提交 URB 来获取配置描述符。 若要分配 URB,驱动程序必须调用 USBD_UrbAllocate 例程。 若要设置 URB 的格式,驱动程序必须调用 UsbBuildGetDescriptorRequest 宏。 若要提交 URB,驱动程序必须将 URB 与 IRP 相关联,并将 IRP 提交到 USB 驱动程序堆栈。 有关详细信息,请参阅 如何提交 URB

在 USB 配置中,接口数及其备用设置是可变的。 因此,很难预测保存配置描述符所需的缓冲区大小。 客户端驱动程序必须通过两个步骤收集所有这些信息。 首先,确定保存所有配置描述符所需的大小缓冲区,然后发出检索整个描述符的请求。 客户端驱动程序可以通过以下方式之一获取大小:

若要通过调用 WdfUsbTargetDeviceRetrieveConfigDescriptor 获取配置描述符,请执行以下步骤:

  1. 通过调用 WdfUsbTargetDeviceRetrieveConfigDescriptor 获取保存所有配置信息所需的缓冲区大小。 驱动程序必须在缓冲区中传递 NULL,并传递一个变量来保存缓冲区的大小。
  2. 根据通过上一个 WdfUsbTargetDeviceRetrieveConfigDescriptor 调用接收的大小分配更大的缓冲区。
  3. 再次调用 WdfUsbTargetDeviceRetrieveConfigDescriptor ,并指定指向步骤 2 中分配的新缓冲区的指针。
 NTSTATUS RetrieveDefaultConfigurationDescriptor (
    _In_  WDFUSBDEVICE  UsbDevice,
    _Out_ PUSB_CONFIGURATION_DESCRIPTOR *ConfigDescriptor 
    )
{
    NTSTATUS ntStatus = -1;

    USHORT sizeConfigDesc;

    PUSB_CONFIGURATION_DESCRIPTOR fullConfigDesc = NULL;

    PAGED_CODE();

    *ConfigDescriptor  = NULL;

    ntStatus = WdfUsbTargetDeviceRetrieveConfigDescriptor (
        UsbDevice, 
        NULL,
        &sizeConfigDesc);

    if (sizeConfigDesc == 0)
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not retrieve the configuration descriptor size.");

        goto Exit;
    }
    else
    {
        fullConfigDesc = (PUSB_CONFIGURATION_DESCRIPTOR) ExAllocatePoolWithTag (
            NonPagedPool, 
            sizeConfigDesc,
            USBCLIENT_TAG);

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

    RtlZeroMemory (fullConfigDesc, sizeConfigDesc);

    ntStatus = WdfUsbTargetDeviceRetrieveConfigDescriptor (
        UsbDevice, 
        fullConfigDesc,
        &sizeConfigDesc);

    if (!NT_SUCCESS(ntStatus))
    {           
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not retrieve the configuration descriptor.");

        goto Exit;
    }

    *ConfigDescriptor = fullConfigDesc;

Exit:

    return ntStatus;   
}

若要通过提交 URB 获取配置描述符,请执行以下步骤:

  1. 通过调用 WdfUsbTargetDeviceCreateUrb 方法分配 URB
  2. 通过调用 UsbBuildGetDescriptorRequest 宏设置 URB 的格式。 URB 的传输缓冲区必须指向足以容纳 USB_CONFIGURATION_DESCRIPTOR 结构的缓冲区。
  3. 通过调用 WdfRequestSendWdfUsbTargetDeviceSendUrbSynchronously 将 URB 作为 WDF 请求对象提交。
  4. 请求完成后,检查 USB_CONFIGURATION_DESCRIPTORwTotalLength 成员。 该值指示包含完整配置描述符所需的缓冲区大小。
  5. 根据 wTotalLength 中检索到的大小分配更大的缓冲区。
  6. 对较大的缓冲区发出相同的请求。

以下示例代码演示请求的 UsbBuildGetDescriptorRequest 调用,以获取第 i 个配置的配置信息:

NTSTATUS FX3_RetrieveConfigurationDescriptor (
    _In_ WDFUSBDEVICE  UsbDevice,
    _In_ PUCHAR ConfigurationIndex,
    _Out_ PUSB_CONFIGURATION_DESCRIPTOR *ConfigDescriptor 
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;

    USB_CONFIGURATION_DESCRIPTOR configDesc;
    PUSB_CONFIGURATION_DESCRIPTOR fullConfigDesc = NULL;

    PURB urb = NULL;

    WDFMEMORY urbMemory = NULL;

    PAGED_CODE();

    RtlZeroMemory (&configDesc, sizeof(USB_CONFIGURATION_DESCRIPTOR));
    *ConfigDescriptor = NULL;

    // Allocate an URB for the get-descriptor request. 
    // WdfUsbTargetDeviceCreateUrb returns the address of the 
    // newly allocated URB and the WDFMemory object that 
    // contains the URB.

    ntStatus = WdfUsbTargetDeviceCreateUrb (
        UsbDevice,
        NULL,
        &urbMemory,
        &urb);

    if (!NT_SUCCESS (ntStatus))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not allocate URB for an open-streams request.");

        goto Exit;
    }

       // Format the URB.
    UsbBuildGetDescriptorRequest (
        urb,                                                        // Points to the URB to be formatted
        (USHORT) sizeof( struct _URB_CONTROL_DESCRIPTOR_REQUEST ),  // Size of the URB.
        USB_CONFIGURATION_DESCRIPTOR_TYPE,                          // Type of descriptor
        *ConfigurationIndex,                                        // Index of the configuration
        0,                                                          // Not used for configuration descriptors
        &configDesc,                                                // Points to a USB_CONFIGURATION_DESCRIPTOR structure
        NULL,                                                       // Not required because we are providing a buffer not MDL
        sizeof(USB_CONFIGURATION_DESCRIPTOR),                       // Size of the USB_CONFIGURATION_DESCRIPTOR structure.
        NULL                                                        // Reserved.
        );

       // Send the request synchronously.
    ntStatus = WdfUsbTargetDeviceSendUrbSynchronously (
        UsbDevice,
        NULL,
        NULL,
        urb);

    if (configDesc.wTotalLength == 0)
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not retrieve the configuration descriptor size.");

        ntStatus = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;

        goto Exit;
    }

    // Allocate memory based on the retrieved size. 
       // The allocated memory is released by the caller.
    fullConfigDesc = (PUSB_CONFIGURATION_DESCRIPTOR) ExAllocatePoolWithTag (
        NonPagedPool, 
        configDesc.wTotalLength,
        USBCLIENT_TAG);

    RtlZeroMemory (fullConfigDesc, configDesc.wTotalLength);

    if (!fullConfigDesc)
    {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;

        goto Exit;
    }

       // Format the URB.
    UsbBuildGetDescriptorRequest (
        urb,                                                        
        (USHORT) sizeof( struct _URB_CONTROL_DESCRIPTOR_REQUEST ),  
        USB_CONFIGURATION_DESCRIPTOR_TYPE,                          
        *ConfigurationIndex,                                         
        0,                                                          
        fullConfigDesc,                                                 
        NULL,                                                       
        configDesc.wTotalLength,                       
        NULL                                                        
        );

       // Send the request again.
    ntStatus = WdfUsbTargetDeviceSendUrbSynchronously (
        UsbDevice,
        NULL,
        NULL,
        urb);

    if ((fullConfigDesc->wTotalLength == 0) || !NT_SUCCESS (ntStatus))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not retrieve the configuration descriptor.");

        ntStatus = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;

        goto Exit;
    }

       // Return to the caller.
    *ConfigDescriptor = fullConfigDesc;

Exit:

    if (urbMemory)
    {
        WdfObjectDelete (urbMemory);
    }

    return ntStatus;
}

当设备返回配置描述符时,请求缓冲区中将填充所有备用设置的接口描述符,以及特定备用设置中所有终结点的终结点描述符。 对于 USB 设备布局中所述的设备,下图演示了配置信息在内存中的布局方式。

配置描述符布局示意图。

USB_INTERFACE_DESCRIPTOR的从零开始的 bInterfaceNumber 成员区分配置中的接口。 对于给定接口,从零开始的 bAlternateSetting 成员区分接口的备用设置。 设备按 bInterfaceNumber 值的顺序返回接口描述符,然后按 bAlternateSetting 值的顺序返回接口描述符。

若要在配置中搜索给定的接口描述符,客户端驱动程序可以调用 USBD_ParseConfigurationDescriptorEx。 在 调用中,客户端驱动程序在配置中提供起始位置。 (可选)驱动程序可以指定接口号、备用设置、类、子类或协议。 例程返回指向下一个匹配接口描述符的指针。

若要检查终结点或字符串描述符的配置描述符,请使用 USBD_ParseDescriptors 例程。 调用方在配置中提供起始位置和描述符类型,例如USB_STRING_DESCRIPTOR_TYPE或USB_ENDPOINT_DESCRIPTOR_TYPE。 例程返回指向下一个匹配描述符的指针。