Поделиться через


Дескрипторы конфигурации 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 или WdfUsbTargetDeviceSendUrbSynchronously.
  4. После завершения запроса проверка член wTotalLengthUSB_CONFIGURATION_DESCRIPTOR. Это значение указывает размер буфера, необходимого для хранения полного дескриптора конфигурации.
  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-устройства, на следующей схеме показано, как сведения о конфигурации размещены в памяти.

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

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

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

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