Дескрипторы конфигурации 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"

В предыдущем примере запишите значения полей bInterfaceNumber и bAlternateSetting . Эти поля содержат значения индекса, которые драйвер клиента использует для активации интерфейса и одного из его альтернативных параметров. Для активации драйвер отправляет запрос на выборку интерфейса в стек USB-драйверов. Затем стек драйверов создает стандартный запрос управления (SET INTERFACE) и отправляет его на устройство. Обратите внимание на поле bInterfaceClass . Дескриптор интерфейса или дескриптор для любого из его альтернативных параметров задает код класса, подкласс и протокол. Значение 0x0E указывает, что интерфейс предназначен для класса видеоустройства. Кроме того, обратите внимание на поле iInterface . Это значение указывает, что к дескриптору интерфейса добавляется два дескриптора строк. Дескрипторы строк содержат описания Юникода, используемые во время перечисления устройств для идентификации функциональных возможностей. Дополнительные сведения о дескрипторах строк см. в разделе "Дескрипторы строк USB".

Каждая конечная точка в интерфейсе описывает один поток входных или выходных данных для устройства. Устройство, поддерживающее потоки для различных типов функций, имеет несколько интерфейсов. Устройство, поддерживающее несколько потоков, относящихся к функции, может поддерживать несколько конечных точек в одном интерфейсе.

Все типы конечных точек (кроме конечной точки по умолчанию) должны предоставлять дескрипторы конечных точек, чтобы хост смог получить сведения о конечной точке. Дескриптор конечной точки содержит сведения, такие как адрес, тип, направление и объем данных, которые может обрабатывать конечная точка. Передача данных в конечную точку основана на этой информации.

В следующем примере показан дескриптор конечной точки для устройства веб-камеры:

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

Поле bEndpointAddress указывает уникальный адрес конечной точки, содержащий номер конечной точки (Bits 3.0) и направление конечной точки (бит 7). Считывая эти значения в предыдущем примере, мы можем определить, что дескриптор описывает конечную точку IN, номер конечной точки которой равен 2. Атрибут bmAttributes указывает, что тип конечной точки является изохронным. WMaxPacketSizefield указывает максимальное количество байтов, которое конечная точка может отправлять или получать в одной транзакции. Биты 12..11 указывают общее количество транзакций, которые можно отправлять на микрофрейм. BInterval указывает, как часто конечная точка может отправлять или получать данные.

Как получить дескриптор конфигурации

Дескриптор конфигурации получается с устройства через стандартный запрос устройства (GET_DESCRIPTOR), который отправляется в виде управляющей передачи стеком драйверов USB. USB-клиент может инициировать запрос одним из следующих способов:

  • Если устройство поддерживает только одну конфигурацию, проще всего вызвать предоставленный платформой метод WdfUsbTargetDeviceRetrieveConfigDescriptor .

  • Для устройства, поддерживающего несколько конфигураций, если драйвер клиента хочет получить дескриптор конфигурации, отличной от первой, драйвер должен отправить URB. Чтобы отправить URB, драйвер должен выделить, отформатировать и отправить URB в стек USB-драйверов.

    Чтобы выделить URB, драйвер клиента должен вызвать метод WdfUsbTargetDeviceCreateUrb . Метод получает указатель на URB, выделенный стеком USB-драйверов.

    Чтобы отформатировать URB, драйвер клиента может использовать макрос UsbBuildGetDescriptorRequest . Макрос задает всю необходимую информацию в URB, такую как номер конфигурации, заданный устройством, для которого требуется извлечь дескриптор. Функция URB имеет значение URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE (см. _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. Выделите URB путем вызова метода WdfUsbTargetDeviceCreateUrb .
  2. Отформатируйте URB, вызвав макрос UsbBuildGetDescriptorRequest . Буфер передачи URB должен указывать на буфер достаточно большой для хранения структуры USB_CONFIGURATION_DESCRIPTOR .
  3. Отправьте URB в качестве объекта запроса WDF путем вызова WdfRequestSend или WdfUsbTargetDeviceSendUrbSynchronous.
  4. После завершения запроса проверьте значение члена wTotalLength в USB_CONFIGURATION_DESCRIPTOR. Это значение указывает размер буфера, необходимого для хранения полного дескриптора конфигурации.
  5. Выделите больший буфер на основе размера, полученного в wTotalLength.
  6. Выполните тот же запрос с большим буфером.

В следующем примере кода показано вызов UsbBuildGetDescriptorRequest для получения сведений о конфигурации i-th:

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-устройства, на следующей схеме показано, как сведения о конфигурации размещаются в памяти.

Схема макета дескриптора конфигурации.

Отсчитываемый от нуля член bInterfaceNumberUSB_INTERFACE_DESCRIPTOR различает интерфейсы в конфигурации. Для данного интерфейса элемент bAlternateSetting от нуля различает альтернативные параметры интерфейса. Устройство возвращает дескрипторы интерфейса в порядке значений bInterfaceNumber , а затем в порядке значений bAlternateSetting .

Чтобы найти дескриптор интерфейса в конфигурации, драйвер клиента может вызвать USBD_ParseConfigurationDescriptorEx. В вызове драйвер клиента предоставляет начальную позицию в конфигурации. При необходимости драйвер может указать номер интерфейса, альтернативный параметр, класс, подкласс или протокол. Подпрограмма возвращает указатель на следующий соответствующий дескриптор интерфейса.

Чтобы проверить дескриптор конфигурации для конечной точки или дескриптора строки, используйте подпрограмму USBD_ParseDescriptors . Вызывающий объект предоставляет начальную позицию в конфигурации и тип дескриптора, например USB_STRING_DESCRIPTOR_TYPE или USB_ENDPOINT_DESCRIPTOR_TYPE. Подпрограмма возвращает указатель на следующий соответствующий дескриптор.