Compartilhar via


Descritores de configuração de USB

Um dispositivo USB expõe seus recursos na forma de uma série de interfaces chamada configuração USB. Cada interface consiste em uma ou mais configurações alternativas, e cada configuração alternativa é composta de um conjunto de endpoints. Este tópico descreve os vários descritores associados a uma configuração USB.

Uma configuração USB é descrita em um descritor de configuração (consulte USB_CONFIGURATION_DESCRIPTOR estrutura). Um descritor de configuração contém informações sobre a configuração e suas interfaces, configurações alternativas e seus pontos de extremidade. Cada descritor de interface ou configuração alternativa é descrito em uma estrutura USB_INTERFACE_DESCRIPTOR . Em uma configuração, cada descritor de interface é seguido na memória por todos os descritores de endpoints para a interface e suas configurações alternativas. Cada descritor de ponto de extremidade é armazenado em uma USB_ENDPOINT_DESCRIPTOR estrutura.

Por exemplo, considere um dispositivo de webcam USB descrito no Layout do Dispositivo USB. O dispositivo dá suporte a uma configuração com duas interfaces e a primeira interface (índice 0) dá suporte a duas configurações alternativas.

O exemplo a seguir mostra o descritor de configuração para o dispositivo de webcam USB:

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

O campo bConfigurationValue indica o número da configuração definida no firmware do dispositivo. O driver cliente usa esse valor numérico para selecionar uma configuração ativa. Para obter mais informações sobre a configuração do dispositivo USB, consulte Como selecionar uma configuração para um dispositivo USB. Uma configuração USB também indica determinadas características de energia. O campo bmAttributes contém uma máscara de bits que indica se a configuração dá suporte ao recurso de ativação remota, e se o dispositivo é alimentado pelo barramento ou auto-alimentado. O campo MaxPower especifica a potência máxima (em unidades de miliampère) que o dispositivo pode extrair do host, quando alimentado pelo barramento. O descritor de configuração também indica o número total de interfaces (bNumInterfaces) que o dispositivo dá suporte.

O exemplo a seguir mostra o descritor de interface para a Configuração Alternativa 0 da Interface 0 para o dispositivo webcam:

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"

No exemplo anterior, observe os valores de campo bInterfaceNumber e bAlternateSetting . Esses campos contêm valores de índice que um driver cliente usa para ativar a interface e uma de suas configurações alternativas. Para ativação, o driver envia uma solicitação de seleção de interface para a pilha de drivers USB. Em seguida, a pilha de drivers cria uma solicitação de controle padrão (SET INTERFACE) e a envia para o dispositivo. Observe o campo bInterfaceClass . O descritor de interface ou o descritor para qualquer uma de suas configurações alternativas especifica um código de classe, uma subclasse e um protocolo. O valor de 0x0E indica que a interface é para a classe de dispositivo de vídeo. Além disso, observe o campo iInterface . Esse valor indica que há dois descritores de cadeia de caracteres acrescentados ao descritor de interface. Os descritores de cadeia de caracteres contêm descrições Unicode usadas durante a enumeração do dispositivo para identificar a funcionalidade. Para obter mais informações sobre descritores de cadeia de caracteres, consulte Descritores de cadeia de caracteres USB.

Cada ponto de extremidade, em uma interface, descreve um único fluxo de entrada ou saída para o dispositivo. Um dispositivo que dá suporte a fluxos para diferentes tipos de funções tem várias interfaces. Um dispositivo que dá suporte a vários fluxos de dados que pertencem a uma função pode dar suporte a vários endpoints em uma única interface.

Todos os tipos de pontos de extremidade (exceto o ponto de extremidade padrão) devem fornecer descritores de ponto de extremidade para que o host possa obter informações sobre o ponto de extremidade. Um descritor de ponto de extremidade inclui informações, como seu endereço, tipo, direção e a quantidade de dados que o ponto de extremidade pode manipular. As transferências de dados para o endpoint são fundamentadas nessas informações.

O exemplo a seguir mostra um descritor de ponto de extremidade para o dispositivo webcam:

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

O campo bEndpointAddress especifica o endereço de ponto de extremidade exclusivo que contém o número do ponto de extremidade (Bits 3..0) e a direção do ponto de extremidade (Bit 7). Lendo esses valores no exemplo anterior, podemos determinar que o descritor descreve um ponto de extremidade IN cujo número de ponto de extremidade é 2. O atributo bmAttributes indica que o tipo de ponto de extremidade é isocrono. O wMaxPacketSizefield indica o número máximo de bytes que o ponto de extremidade pode enviar ou receber em uma única transação. Os bits 12..11 indicam o número total de transações que podem ser enviadas por microframe. O bInterval indica com que frequência o ponto de extremidade pode enviar ou receber dados.

Como obter o descritor de configuração

O descritor de configuração é obtido do dispositivo por meio de uma solicitação padrão (GET_DESCRIPTOR), enviada como uma transferência de controle pela pilha de drivers USB. Um driver cliente USB pode iniciar a solicitação de uma das seguintes maneiras:

  • Se o dispositivo der suporte a apenas uma configuração, a maneira mais fácil é chamar o método WdfUsbTargetDeviceRetrieveConfigDescriptor fornecido pela estrutura.

  • Para um dispositivo que dá suporte a várias configurações, se o driver cliente quiser obter o descritor da configuração diferente do primeiro, o driver deverá enviar um URB. Para enviar um URB, o driver deve primeiro alocar, depois formatar e então submeter o URB para a pilha de driver USB.

    Para alocar o URB, o driver cliente deve chamar o método WdfUsbTargetDeviceCreateUrb . O método recebe um ponteiro para um URB alocado pela pilha de driver USB.

    Para formatar o URB, o driver cliente pode usar a macro UsbBuildGetDescriptorRequest . A macro define todas as informações necessárias na URB, como o número de configuração definido pelo dispositivo para o qual recuperar o descritor. A função URB é definida como URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE (consulte _URB_CONTROL_DESCRIPTOR_REQUEST) e o tipo de descritor é definido como USB_CONFIGURATION_DESCRIPTOR_TYPE. Usando as informações contidas na URB, a pilha de driver USB cria uma solicitação de controle padrão e a envia para o dispositivo.

    Para enviar o URB, o driver cliente deve usar um objeto de solicitação WDF. Para enviar o objeto de solicitação para a pilha do driver USB de forma assíncrona, o driver deve chamar o método **WdfRequestSend**. Para enviá-lo de forma síncrona, chame o método WdfUsbTargetDeviceSendUrbSynchronously .

    Drivers WDM: Um driver cliente WDM (Windows Driver Model) só pode obter o descritor de configuração enviando um URB. Para alocar a URB, o driver deve chamar a rotina USBD_UrbAllocate. Para formatar o URB, o driver deve chamar a macro UsbBuildGetDescriptorRequest . Para enviar o URB, o driver deve associar o URB a um IRP e enviar o IRP para a pilha de driver USB. Para obter mais informações, consulte Como enviar um URB.

Dentro de uma configuração USB, o número de interfaces e suas configurações alternativas são variáveis. Portanto, é difícil prever o tamanho do buffer necessário para manter o descritor de configuração. O driver cliente deve coletar todas essas informações em duas etapas. Primeiro, determine o tamanho do buffer necessário para manter todo o descritor de configuração e emita uma solicitação para recuperar todo o descritor. Um driver cliente pode obter o tamanho de uma das seguintes maneiras:

Para obter o descritor de configuração chamando WdfUsbTargetDeviceRetrieveConfigDescriptor, execute estas etapas:

  1. Obtenha o tamanho do buffer necessário para manter todas as informações de configuração chamando WdfUsbTargetDeviceRetrieveConfigDescriptor. O driver deve passar NULL no buffer e uma variável para manter o tamanho do buffer.
  2. Aloque um buffer maior com base no tamanho recebido por meio da chamada anterior WdfUsbTargetDeviceRetrieveConfigDescriptor.
  3. Chame WdfUsbTargetDeviceRetrieveConfigDescriptor novamente e especifique um ponteiro para o novo buffer alocado na etapa 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;   
}

Para obter o descritor de configuração enviando um URB, execute estas etapas:

  1. Aloque um URB chamando o método WdfUsbTargetDeviceCreateUrb .
  2. Formate o URB chamando a macro UsbBuildGetDescriptorRequest . O buffer de transferência da URB deve apontar para um buffer grande o suficiente para conter uma estrutura USB_CONFIGURATION_DESCRIPTOR.
  3. Envie o URB como um objeto de solicitação WDF chamando WdfRequestSend ou WdfUsbTargetDeviceSendUrbSynchronously.
  4. Após a conclusão da solicitação, verifique o membro wTotalLength do USB_CONFIGURATION_DESCRIPTOR. Esse valor indica o tamanho do buffer necessário para conter um descritor de configuração completo.
  5. Aloque um buffer maior com base no tamanho recuperado em wTotalLength.
  6. Emita a mesma solicitação com o buffer maior.

O código de exemplo a seguir mostra a chamada UsbBuildGetDescriptorRequest para uma solicitação para obter informações de configuração para a configuração 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;
}

Quando o dispositivo retorna o descritor de configuração, o buffer de solicitação é preenchido com descritores de interface para todas as configurações alternativas e descritores de ponto de extremidade para todos os pontos de extremidade em uma configuração alternativa específica. Para o dispositivo descrito no Layout do Dispositivo USB, o diagrama a seguir ilustra como as informações de configuração são apresentadas na memória.

Diagrama de um layout de descritor de configuração.

O membro bInterfaceNumber baseado em zero do USB_INTERFACE_DESCRIPTOR distingue as interfaces dentro de uma configuração. Para uma determinada interface, o membro bAlternateSetting baseado em zero distingue entre as configurações alternativas da interface. O dispositivo retorna descritores de interface em ordem de valores bInterfaceNumber e, em seguida, em ordem de valores bAlternateSetting .

Para pesquisar um determinado descritor de interface dentro da configuração, o driver cliente pode chamar USBD_ParseConfigurationDescriptorEx. Na chamada, o driver cliente fornece uma posição inicial dentro da configuração. Opcionalmente, o driver pode especificar um número de interface, uma configuração alternativa, uma classe, uma subclasse ou um protocolo. A rotina retorna um ponteiro para o próximo descritor de interface correspondente.

Para examinar um descritor de configuração para um ponto de extremidade ou descritor de string, use a rotina USBD_ParseDescriptors. O chamador fornece uma posição inicial dentro da configuração e um tipo de descritor, como USB_STRING_DESCRIPTOR_TYPE ou USB_ENDPOINT_DESCRIPTOR_TYPE. A rotina retorna um ponteiro para o próximo descritor correspondente.