Como selecionar uma configuração para um dispositivo USB

Para selecionar uma configuração para um dispositivo USB, o driver de cliente do dispositivo deve escolher pelo menos uma das configurações compatíveis e especificar as configurações alternativas de cada interface a ser usada. O driver de cliente empacota essas opções em uma solicitação de configuração de seleção e envia a solicitação para a pilha de drivers USB fornecida pela Microsoft, especificamente o driver de barramento USB (PDO do hub USB). O driver de barramento USB seleciona cada interface na configuração especificada e configura um canal de comunicação, ou pipe, para cada ponto de extremidade dentro da interface. Após a conclusão da solicitação, o driver de cliente recebe um identificador para a configuração selecionada e identificadores de pipe para os pontos de extremidade definidos na configuração alternativa ativa de cada interface. O driver de cliente pode usar os identificadores recebidos para alterar as definições de configuração e enviar solicitações de leitura e gravação de E/S para um ponto de extremidade específico.

Um driver de cliente envia uma solicitação de configuração de seleção em um URB (Bloco de Solicitação USB) do tipo URB_FUNCTION_SELECT_CONFIGURATION. O procedimento neste tópico descreve como usar a rotina USBD_SelectConfigUrbAllocateAndBuild para criar esse URB. A rotina aloca memória para um URB, formata o URB para uma solicitação de configuração de seleção e retorna o endereço do URB para o driver de cliente.

Como alternativa, você pode alocar uma estrutura URB e, em seguida, formatar o URB manualmente ou chamando a macro UsbBuildSelectConfigurationRequest.

Pré-requisitos

Etapa 1: crie uma matriz de estruturas USBD_INTERFACE_LIST_ENTRY

  1. Obtenha o número de interfaces na configuração. Essas informações estão contidas no membro bNumInterfaces da estrutura USB_CONFIGURATION_DESCRIPTOR.

  2. Crie uma matriz de estruturas USBD_INTERFACE_LIST_ENTRY. O número de elementos na matriz deve ser um a mais do que o número de interfaces. Inicialize a matriz chamando RtlZeroMemory.

    O driver de cliente especifica configurações alternativas em cada interface a ser habilitada, na matriz de estruturas USBD_INTERFACE_LIST_ENTRY.

    • O membro membro InterfaceDescriptor de cada estrutura aponta para o descritor de interface que contém a configuração alternativa.
    • O membro Interface de cada estrutura aponta para uma estrutura USBD_INTERFACE_INFORMATION que contém informações de pipe em seu membro Pipes. O Pipes armazena informações sobre cada ponto de extremidade definido na configuração alternativa.
  3. Obtenha um descritor para cada interface (ou sua configuração alternativa) na configuração. Você pode obter esses descritores de interface chamando USBD_ParseConfigurationDescriptorEx.

    Sobre drivers de função para um dispositivo composto USB:

    Se o dispositivo USB for um dispositivo composto, a configuração será selecionada pelo Driver pai genérico de USB (Usbccgp.sys) fornecido pela Microsoft. Um driver de cliente, que é um dos drivers de função do dispositivo composto, não pode alterar a configuração, mas o driver ainda pode enviar uma solicitação de configuração de seleção por meio de Usbccgp.sys.

    Antes de enviar essa solicitação, o driver de cliente deve enviar uma solicitação URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE. Em resposta, Usbccgp.sys recupera um descritor de configuração parcial que contém apenas descritores de interface e outros descritores que pertencem à função específica para a qual o driver de cliente é carregado. O número de interfaces relatado no campo bNumInterfaces de um descritor de configuração parcial é menor do que o número total de interfaces definidas para todo o dispositivo composto USB. Além disso, em um descritor de configuração parcial, bInterfaceNumber de um descritor de interface indica o número de interface real relativo a todo o dispositivo. Por exemplo, Usbccgp.sys pode relatar um descritor de configuração parcial com valor de bNumInterfaces de 2 e valor de bInterfaceNumber de 4 para a primeira interface. O número da interface é maior do que o número de interfaces relatadas.

    Ao enumerar interfaces em uma configuração parcial, evite procurar interfaces calculando números de interface com base no número de interfaces. No exemplo anterior, se USBD_ParseConfigurationDescriptorEx for chamado em um loop que começa em zero, termina em (bNumInterfaces - 1) e incrementa o índice de interface (especificado no parâmetro InterfaceNumber) em cada iteração, a rotina não consegue obter a interface correta. Em vez disso, procure todas as interfaces no descritor de configuração passando -1 em InterfaceNumber. Para obter detalhes da implementação, consulte o exemplo de código nesta seção.

    Para obter informações sobre como Usbccgp.sys manipula uma solicitação de configuração de seleção enviada por um driver de cliente, consulte Configurar Usbccgp.sys para selecionar uma configuração USB não padrão.

  4. Para cada elemento (exceto o último elemento) na matriz, defina o membro InterfaceDescriptor como o endereço de um descritor de interface. Para o primeiro elemento na matriz, defina o membro InterfaceDescriptor como o endereço do descritor de interface que representa a primeira interface na configuração. De maneira semelhante para o n elemento na matriz, defina o membro InterfaceDescriptor como o endereço do descritor de interface que representa a n interface na configuração.

  5. O membro InterfaceDescriptor do último elemento deve ser definido como NULL.

Etapa 2: obtenha um ponteiro para um URB alocado pela pilha de drivers USB

Em seguida, chame USBD_SelectConfigUrbAllocateAndBuild especificando a configuração a ser selecionada e a matriz preenchida de estruturas USBD_INTERFACE_LIST_ENTRY. A rotina realiza as seguintes tarefas:

  • Cria um URB e o preenche com informações sobre a configuração especificada, suas interfaces e pontos de extremidade e define o tipo de solicitação como URB_FUNCTION_SELECT_CONFIGURATION.

  • Dentro desse URB, aloca uma estrutura USBD_INTERFACE_INFORMATION para cada descritor de interface especificado pelo driver de cliente.

  • Define o membro Interface do n elemento da matriz USBD_INTERFACE_LIST_ENTRY fornecida pelo autor da chamada para o endereço da estrutura USBD_INTERFACE_INFORMATION correspondente no URB.

  • Inicializa o membro InterfaceNumber, AlternateSetting, NumberOfPipes, Pipes[i].MaximumTransferSize e Pipes[i].PipeFlags.

    Observação

    No Windows 7 e versões anteriores, o driver de cliente criou um URB para uma solicitação de configuração de seleção chamando USBD_CreateConfigurationRequestEx. No Windows 2000, USBD_CreateConfigurationRequestEx inicializa Pipes[i]. MaximumTransferSize para o tamanho máximo de transferência padrão para uma única solicitação de leitura/gravação de URB. O driver de cliente pode especificar um tamanho máximo de transferência diferente nos Pipes[i]. MaximumTransferSize. A pilha USB ignora esse valor no Windows XP, Windows Server 2003 e versões posteriores do sistema operacional. Para obter mais informações sobre MaximumTransferSize, consulte "Definir transferência USB e tamanhos de pacotes" em Alocação de largura de banda USB.

Etapa 3: envie o URB para a pilha de drivers USB

Para enviar o URB para a pilha de driver USB, o driver de cliente deve enviar uma solicitação de controle de E/S IOCTL_INTERNAL_USB_SUBMIT_URB. Para obter informações sobre como enviar um URB, consulte Como enviar um URB.

Depois de receber o URB, a pilha de drivers USB preenche o restante dos membros de cada estrutura USBD_INTERFACE_INFORMATION. Em particular, o membro da matriz Pipes é preenchido com informações sobre os pipes associados aos pontos de extremidade da interface.

Passo 4: após a conclusão da solicitação, inspecione as estruturas USBD_INTERFACE_INFORMATION e o URB

Depois que a pilha de drivers USB conclui o IRP da solicitação, a pilha retorna a lista de configurações alternativas e as interfaces relacionadas na matriz USBD_INTERFACE_LIST_ENTRY.

  1. O membro Pipes de cada estrutura USBD_INTERFACE_INFORMATION aponta para uma matriz de estruturas USBD_PIPE_INFORMATION que contém informações sobre os pipes associados a cada ponto de extremidade dessa interface específica. O driver de cliente pode obter identificadores de pipe de Pipes[i]. PipeHandle e usá-los para enviar solicitações de E/S para pipes específicos. O membro Pipes[i].PipeType especifica o tipo de ponto de extremidade e transferência compatível com esse pipe.

  2. Dentro do membro UrbSelectConfiguration do URB, a pilha de drivers USB retorna um identificador que você pode usar para selecionar uma configuração de interface alternativa enviando outro URB do tipo URB_FUNCTION_SELECT_INTERFACE (solicitação de interface de seleção). Para alocar e criar a estrutura URB para essa solicitação, chame USBD_SelectInterfaceUrbAllocateAndBuild.

    A solicitação de configuração de seleção e a solicitação de interface de seleção podem falhar se não houver largura de banda suficiente para dar suporte aos pontos de extremidade isócronos, de controle e de interrupção nas interfaces habilitadas. Nesse caso, o driver de barramento USB define o membro Status do cabeçalho URB como USBD_STATUS_NO_BANDWIDTH.

O código de exemplo a seguir mostra como criar uma matriz de estruturas USBD_INTERFACE_LIST_ENTRY e chamar USBD_SelectConfigUrbAllocateAndBuild. O exemplo envia a solicitação de forma síncrona chamando SubmitUrbSync. Para ver o exemplo de código de SubmitUrbSync, consulte Como enviar um URB.

/*++

Routine Description:
This helper routine selects the specified configuration.

Arguments:
USBDHandle - USBD handle that is retrieved by the 
client driver in a previous call to the USBD_CreateHandle routine.

ConfigurationDescriptor - Pointer to the configuration
descriptor for the device. The caller receives this pointer
from the URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE request.

Return Value: NT status value
--*/

NTSTATUS SelectConfiguration (PDEVICE_OBJECT DeviceObject,
                              PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor)
{
    PDEVICE_EXTENSION deviceExtension;
    PIO_STACK_LOCATION nextStack;
    PIRP irp;
    PURB urb = NULL;

    KEVENT    kEvent;
    NTSTATUS ntStatus;    

    PUSBD_INTERFACE_LIST_ENTRY   interfaceList = NULL; 
    PUSB_INTERFACE_DESCRIPTOR    interfaceDescriptor = NULL;
    PUSBD_INTERFACE_INFORMATION  Interface = NULL;
    USBD_PIPE_HANDLE             pipeHandle;

    ULONG                        interfaceIndex;

    PUCHAR StartPosition = (PUCHAR)ConfigurationDescriptor;

    deviceExtension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

    // Allocate an array for the list of interfaces
    // The number of elements must be one more than number of interfaces.
    interfaceList = (PUSBD_INTERFACE_LIST_ENTRY)ExAllocatePool (
        NonPagedPool, 
        sizeof(USBD_INTERFACE_LIST_ENTRY) *
        (deviceExtension->NumInterfaces + 1));

    if(!interfaceList)
    {
        //Failed to allocate memory
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto Exit;
    }

    // Initialize the array by setting all members to NULL.
    RtlZeroMemory (interfaceList, sizeof (
        USBD_INTERFACE_LIST_ENTRY) *
        (deviceExtension->NumInterfaces + 1));

    // Enumerate interfaces in the configuration.
    for ( interfaceIndex = 0; 
        interfaceIndex < deviceExtension->NumInterfaces; 
        interfaceIndex++) 
    {
        interfaceDescriptor = USBD_ParseConfigurationDescriptorEx(
            ConfigurationDescriptor, 
            StartPosition, // StartPosition 
            -1,            // InterfaceNumber
            0,             // AlternateSetting
            -1,            // InterfaceClass
            -1,            // InterfaceSubClass
            -1);           // InterfaceProtocol

        if (!interfaceDescriptor) 
        {
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
            goto Exit;
        }

        // Set the interface entry
        interfaceList[interfaceIndex].InterfaceDescriptor = interfaceDescriptor;
        interfaceList[interfaceIndex].Interface = NULL;

        // Move the position to the next interface descriptor
        StartPosition = (PUCHAR)interfaceDescriptor + interfaceDescriptor->bLength;

    }

    // Make sure that the InterfaceDescriptor member of the last element to NULL.
    interfaceList[deviceExtension->NumInterfaces].InterfaceDescriptor = NULL;

    // Allocate and build an URB for the select-configuration request.
    ntStatus = USBD_SelectConfigUrbAllocateAndBuild(
        deviceExtension->UsbdHandle, 
        ConfigurationDescriptor, 
        interfaceList,
        &urb);

    if(!NT_SUCCESS(ntStatus)) 
    {
        goto Exit;
    }

    // Allocate the IRP to send the buffer down the USB stack.
    // The IRP will be freed by IO manager.
    irp = IoAllocateIrp((deviceExtension->NextDeviceObject->StackSize)+1, TRUE);  

    if (!irp)
    {
        //Irp could not be allocated.
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto Exit;
    }

    ntStatus = SubmitUrbSync( 
        deviceExtension->NextDeviceObject, 
        irp, 
        urb, 
        CompletionRoutine);

    // Enumerate the pipes in the interface information array, which is now filled with pipe
    // information.

    for ( interfaceIndex = 0; 
        interfaceIndex < deviceExtension->NumInterfaces; 
        interfaceIndex++) 
    {
        ULONG i;

        Interface = interfaceList[interfaceIndex].Interface;

        for(i=0; i < Interface->NumberOfPipes; i++) 
        {
            pipeHandle = Interface->Pipes[i].PipeHandle;

            if (Interface->Pipes[i].PipeType == UsbdPipeTypeInterrupt)
            {
                deviceExtension->InterruptPipe = pipeHandle;
            }
            if (Interface->Pipes[i].PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_IN (Interface->Pipes[i].EndpointAddress))
            {
                deviceExtension->BulkInPipe = pipeHandle;
            }
            if (Interface->Pipes[i].PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_OUT (Interface->Pipes[i].EndpointAddress))
            {
                deviceExtension->BulkOutPipe = pipeHandle;
            }
        }
    }

Exit:

    if(interfaceList) 
    {
        ExFreePool(interfaceList);
        interfaceList = NULL;
    }

    if (urb)
    {
        USBD_UrbFree( deviceExtension->UsbdHandle, urb); 
    }

    return ntStatus;
}

NTSTATUS CompletionRoutine ( PDEVICE_OBJECT DeviceObject,
                            PIRP           Irp,
                            PVOID          Context)
{
    PKEVENT kevent;

    kevent = (PKEVENT) Context;

    if (Irp->PendingReturned == TRUE)
    {
        KeSetEvent(kevent, IO_NO_INCREMENT, FALSE);
    }

    KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Select-configuration request completed. \n" ));

    return STATUS_MORE_PROCESSING_REQUIRED;
}

Desabilitar uma configuração para um dispositivo USB

Para desabilitar um dispositivo USB, crie e envie uma solicitação de configuração de seleção com um descritor de configuração NULL. Para esse tipo de solicitação, você pode reutilizar o URB criado para a solicitação que selecionou uma configuração no dispositivo. Como alternativa, você pode alocar um novo URB chamando USBD_UrbAllocate. Antes de enviar a solicitação, você deve formatar o URB usando a macro UsbBuildSelectConfigurationRequest, conforme mostrado no código de exemplo a seguir.

URB Urb;
UsbBuildSelectConfigurationRequest(
  &Urb,
  sizeof(_URB_SELECT_CONFIGURATION),
  NULL
);