Partilhar via


Descritores de configuração USB

Um dispositivo USB expõe suas capacidades na forma de uma série de interfaces chamadas 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 correspondentes à interface e à definiçã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 suporta uma configuração com duas interfaces, e a primeira interface (índice 0) suporta 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 para a configuração definida no firmware do dispositivo. O driver do cliente usa esse valor numérico para selecionar uma configuração ativa. Para obter mais informações sobre a configuração de dispositivos 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 suporta a funcionalidade de ativação remota e se o dispositivo é alimentado por barramento ou autoalimentado. O campo MaxPower especifica a potência máxima (em unidades de miliamperes) 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) que o dispositivo suporta.

O exemplo a seguir mostra o descritor de interface para configuração alternativa 0 da interface 0 para o dispositivo de 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 dos campos bInterfaceNumber e bAlternateSetting . Esses campos contêm valores de índice que um driver de cliente usa para ativar a interface e uma de suas configurações alternativas. Para ativação, o driver envia uma solicitação de escolha de interface para a pilha de drivers USB. A pilha de drivers cria um pedido de controlo padrão (SET INTERFACE) e envia-o 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, subclasse e protocolo. O valor de 0x0E indica que a interface é para a classe de dispositivo de vídeo. Observe também o campo iInterface . Esse valor indica que existem dois descritores de cadeia de caracteres anexados ao descritor de interface. Os descritores de cadeia de caracteres contêm descrições Unicode que são usadas durante a enumeração de dispositivos 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 suporta fluxos para diferentes tipos de funções tem várias interfaces. Um dispositivo que suporta vários fluxos que pertencem a uma função pode suportar vários pontos finais 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 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 de 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). 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 final é 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 a frequência com que 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 do dispositivo (GET_DESCRIPTOR), enviada como uma transferência de controlo pela stack de drivers USB. Um driver de cliente USB pode iniciar a solicitação de uma das seguintes maneiras:

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

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

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

    Para formatar o URB, o driver do 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 (ver _URB_CONTROL_DESCRIPTOR_REQUEST) e o tipo de descritor é definido como USB_CONFIGURATION_DESCRIPTOR_TYPE. Usando as informações contidas no URB, a pilha de drivers USB cria uma solicitação de controle padrão e a envia para o dispositivo.

    Para enviar o URB, o driver do cliente deve usar um objeto de solicitação WDF. Para enviar o objeto de solicitação ao stack 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 de cliente WDM (Windows Driver Model) só pode obter o descritor de configuração enviando um URB. Para alocar o 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 condutor deve associar o URB a um IRP e enviar o IRP para a stack de drivers 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 armazenar todo o descritor de configuração e, em seguida, emita uma solicitação para recuperar o descritor inteiro. Um driver cliente pode obter o tamanho de uma das formas a seguir:

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

  1. Obtenha o tamanho do buffer necessário para armazenar 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 WdfUsbTargetDeviceRetrieveConfigDescriptor anterior.
  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 suficientemente grande 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 i-ésima configuração:

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 dentro de uma configuração alternativa específica. Para o dispositivo descrito em USB Device Layout, o diagrama a seguir ilustra como as informações de configuração são dispostas na memória.

Diagrama de um layout descritor de configuração.

O membro bInterfaceNumber baseado em zero do 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 na ordem dos valores bInterfaceNumber e, em seguida, na ordem dos valores bAlternateSetting .

Para procurar um determinado descritor de interface dentro da configuração, o driver do 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 descritor de endpoint ou uma 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.