Compartilhar via


Descritores de configuração USB

Um dispositivo USB expõe seus recursos na forma de uma série de interfaces chamada de configuração USB. Cada interface consiste em uma ou mais configurações alternativas e cada configuração alternativa é composta por um conjunto de pontos de extremidade. 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 ponto de extremidade para a interface e a configuração alternativa. Cada descritor de ponto de extremidade é armazenado em uma estrutura USB_ENDPOINT_DESCRIPTOR .

Por exemplo, considere um dispositivo de webcam USB descrito em Layout de 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 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 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 é movido a barramento ou auto-alimentado. O campo MaxPower especifica a potência máxima (em unidades miliamp) que o dispositivo pode extrair do host, quando o dispositivo é alimentado por barramento. O descritor de configuração também indica o número total de interfaces (bNumInterfaces) compatíveis com o dispositivo.

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 select-interface para a pilha de driver USB. Em seguida, a pilha de driver 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 de 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 que são 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 pertencentes a uma função pode dar suporte a vários pontos de extremidade 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 ponto de extremidade são baseadas 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 exclusivo do ponto de extremidade que contém o número do ponto de extremidade (Bits 3..0) e a direção do ponto de extremidade (Bit 7). Ao ler 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 é isócrono. 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 de dispositivo padrão (GET_DESCRIPTOR), que é enviada como uma transferência de controle pela pilha de driver USB. Um driver de 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 alocar, formatar e, em seguida, enviar 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 no 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 de 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 de cliente WDM (Modelo de Driver do Windows) só pode obter o descritor de configuração enviando um URB. Para alocar o URB, o driver deve chamar a rotina de 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 do cliente deve coletar todas essas informações em duas etapas. Primeiro, determine qual buffer de tamanho necessário para manter todo o descritor de configuração e, em seguida, 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 do 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 do URB deve apontar para um buffer grande o suficiente para manter uma estrutura de 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, marcar 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 em Layout de Dispositivo USB, o diagrama a seguir ilustra como as informações de configuração são dispostas na memória.

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

O membro bInterfaceNumber baseado em zero de USB_INTERFACE_DESCRIPTOR distingue interfaces dentro de uma configuração. Para uma determinada interface, o membro bAlternateSetting baseado em zero distingue entre 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 do 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 cadeia de caracteres, 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.