Condividi tramite


Come selezionare una configurazione per un dispositivo USB

Per selezionare una configurazione per un dispositivo USB, il driver client per il dispositivo deve scegliere almeno una delle configurazioni supportate e specificare le impostazioni alternative di ogni interfaccia da usare. Il driver client crea un pacchetto di queste opzioni in una richiesta di configurazione selezionata e invia la richiesta allo stack di driver USB fornito da Microsoft, in particolare il driver bus USB (PDO dell'hub USB). Il driver del bus USB seleziona ogni interfaccia nella configurazione specificata e configura un canale di comunicazione, o pipe, a ogni endpoint all'interno dell'interfaccia. Al termine della richiesta, il driver client riceve un handle per la configurazione selezionata e gli handle di pipe per gli endpoint definiti nell'impostazione alternativa attiva per ogni interfaccia. Il driver client può quindi usare gli handle ricevuti per modificare le impostazioni di configurazione e inviare richieste di lettura e scrittura di I/O a un endpoint specifico.

Un driver client invia una richiesta di selezione-configurazione in un USB Request Block (URB) del tipo URB_FUNCTION_SELECT_CONFIGURATION. La procedura descritta in questo argomento descrive come utilizzare la routine USBD_SelectConfigUrbAllocateAndBuild per creare tale URB. La routine alloca la memoria per un URB, formatta l'URB per una richiesta di selezione della configurazione e restituisce l'indirizzo dell'URB al driver client.

In alternativa, è possibile allocare una URB struttura e quindi formattare l'URB manualmente o chiamando la macro UsbBuildSelectConfigurationRequest.

Prerequisiti

Passaggio 1: Creare una matrice di strutture USBD_INTERFACE_LIST_ENTRY

  1. Ottenere il numero di interfacce nella configurazione. Queste informazioni sono contenute nel membro bNumInterfaces della struttura USB_CONFIGURATION_DESCRIPTOR .

  2. Creare una matrice di strutture USBD_INTERFACE_LIST_ENTRY . Il numero di elementi nella matrice deve essere maggiore del numero di interfacce. Inizializzare la matrice chiamando RtlZeroMemory.

    Il driver client specifica impostazioni alternative in ogni interfaccia da abilitare, nella matrice di strutture USBD_INTERFACE_LIST_ENTRY .

    • Il membro InterfaceDescriptor di ogni struttura punta al descrittore di interfaccia che contiene l'impostazione alternativa.
    • Il membro Interface di ogni struttura punta a una struttura USBD_INTERFACE_INFORMATION che contiene informazioni sul canale nel membro Pipes. Le pipe archivia le informazioni su ogni endpoint definito nell'impostazione alternativa.
  3. Ottenere un descrittore di interfaccia per ogni interfaccia (o la relativa impostazione alternativa) nella configurazione. È possibile ottenere tali descrittori di interfaccia chiamando USBD_ParseConfigurationDescriptorEx.

    Informazioni sui driver di funzione per un dispositivo composito USB:

    Se il dispositivo USB è un dispositivo composito, la configurazione viene selezionata dal driver padre generico USB fornito da Microsoft (Usbccgp.sys). Un driver client, uno dei driver di funzione del dispositivo composito, non può modificare la configurazione, ma il driver può comunque inviare una richiesta di configurazione select tramite Usbccgp.sys.

    Prima di inviare tale richiesta, il driver client deve inviare una richiesta di URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE. In risposta, Usbccgp.sys recupera un descrittore di configurazione parziale che contiene solo descrittori di interfaccia e altri descrittori relativi alla funzione specifica per cui viene caricato il driver client. Il numero di interfacce segnalate nel campo bNumInterfaces di un descrittore di configurazione parziale è minore del numero totale di interfacce definite per l'intero dispositivo composito USB. Inoltre, in un descrittore di configurazione parziale, un descrittore di interfaccia bInterfaceNumber indica il numero di interfaccia effettivo relativo all'intero dispositivo. Ad esempio, Usbccgp.sys potrebbe segnalare un descrittore di configurazione parziale con valore bNumInterfaces pari a 2 e bInterfaceNumber pari a 4 per la prima interfaccia. Si noti che il numero di interfaccia è maggiore del numero di interfacce segnalate.

    Durante l'enumerazione delle interfacce in una configurazione parziale, evitare di cercare interfacce calcolando i numeri di interfaccia in base al numero di interfacce. Nell'esempio precedente, se USBD_ParseConfigurationDescriptorEx viene chiamato in un ciclo che inizia da zero, termina con (bNumInterfaces - 1)e incrementa l'indice dell'interfaccia (specificato nel parametro InterfaceNumber ) in ogni iterazione, la routine non riesce a ottenere l'interfaccia corretta. Assicurarsi invece di cercare tutte le interfacce nel descrittore di configurazione passando -1 in InterfaceNumber. Per informazioni dettagliate sull'implementazione, vedere l'esempio di codice in questa sezione.

    Per informazioni su come Usbccgp.sys gestisce una richiesta di configurazione selezionata inviata da un driver client, vedere Configurazione di Usbccgp.sys per selezionare una configurazione USB non predefinita.

  4. Per ogni elemento (ad eccezione dell'ultimo elemento) nella matrice, impostare il membro InterfaceDescriptor sull'indirizzo di un descrittore di interfaccia. Per il primo elemento della matrice, impostare il membro InterfaceDescriptor sull'indirizzo del descrittore di interfaccia che rappresenta la prima interfaccia nella configurazione. Analogamente per l'nelemento nella matrice, impostare il membro InterfaceDescriptor sull'indirizzo del descrittore di interfaccia che rappresenta l'ninterfaccia nella configurazione.

  5. Il membro InterfaceDescriptor dell'ultimo elemento deve essere impostato su NULL.

Passaggio 2: Ottenere un puntatore a un URB allocato dallo stack di driver USB

Prossimamente, chiamare USBD_SelectConfigUrbAllocateAndBuild specificando la configurazione da selezionare e l'array popolato di strutture USBD_INTERFACE_LIST_ENTRY. La routine esegue le attività seguenti:

  • Crea un URB e lo riempie con informazioni sulla configurazione specificata, sulle interfacce e sugli endpoint, e imposta il tipo di richiesta su URB_FUNCTION_SELECT_CONFIGURATION.

  • All'interno di quell'URB, alloca una struttura USBD_INTERFACE_INFORMATION per ogni descrittore di interfaccia specificato dal driver client.

  • Imposta il membro Interface dell'ennesimo elemento dell'array USBD_INTERFACE_LIST_ENTRY fornito dal chiamante sull'indirizzo della struttura USBD_INTERFACE_INFORMATION corrispondente nell'URB.

  • Inizializza InterfaceNumber, AlternateSetting, NumberOfPipes, Pipes[i].MaximumTransferSize e Pipes[i].PipeFlags.

    Annotazioni

    In Windows 7 e versioni precedenti, il driver client ha creato un URB per una richiesta di selezione di configurazione chiamando USBD_CreateConfigurationRequestEx. In Windows 2000 USBD_CreateConfigurationRequestEx inizializza Pipes[i].MaximumTransferSize alle dimensioni massime di trasferimento predefinite per una singola richiesta di lettura/scrittura URB. Il driver client può specificare una dimensione di trasferimento massima diversa in Pipe[i]. MaximumTransferSize. Lo stack USB ignora questo valore in Windows XP, Windows Server 2003 e versioni successive del sistema operativo. Per altre informazioni su MaximumTransferSize, vedere "Impostazione del trasferimento USB e delle dimensioni dei pacchetti" in Allocazione della larghezza di banda USB.

Passaggio 3: Inviare l'URB allo stack di driver USB

Per inviare l'URB allo stack di driver USB, il driver client deve inviare una IOCTL_INTERNAL_USB_SUBMIT_URB richiesta di controllo I/O. Per informazioni sull'invio di un URB, vedere How to Submit an URB.

Dopo aver ricevuto l'URB, lo stack di driver USB completa i restanti membri di ogni struttura USBD_INTERFACE_INFORMATION. In particolare, il membro della matrice Pipes viene riempito con informazioni sulle pipe associate agli endpoint dell'interfaccia.

Passaggio 4: Al completamento della richiesta, controllare le strutture USBD_INTERFACE_INFORMATION e l'URB

Dopo che lo stack di driver USB ha completato l'IRP relativo alla richiesta, lo stack restituisce l'elenco delle impostazioni alternative e le interfacce correlate nell'array USBD_INTERFACE_LIST_ENTRY.

  1. Il membro Pipe di ogni struttura USBD_INTERFACE_INFORMATION punta a una matrice di strutture USBD_PIPE_INFORMATION che contiene informazioni sulle pipe associate a ogni endpoint di tale interfaccia specifica. Il driver client può ottenere gli handle delle pipe da Pipes[i].PipeHandle e usarli per inviare richieste di I/O alle pipe specifiche. Il membro Pipes[i].PipeType specifica il tipo di endpoint e il trasferimento supportati da quel pipe.

  2. All'interno del membro Di TipoSelectConfiguration dell'UTILITÀ, lo stack di driver USB restituisce un handle che è possibile usare per selezionare un'impostazione di interfaccia alternativa inviando un'altra OPZIONE DEL TIPO URB_FUNCTION_SELECT_INTERFACE (richiesta di interfaccia select). Per allocare e compilare la struttura DELL'ISTRUZIONE PER tale richiesta, chiamare USBD_SelectInterfaceUrbAllocateAndBuild.

    La richiesta di selezione delle configurazioni e la richiesta di selezione delle interfacce potrebbero non riuscire se la larghezza di banda non è sufficiente per supportare gli endpoint isocroni, di controllo e di interrupt all'interno delle interfacce abilitate. In tal caso, il driver del bus USB imposta il membro Status dell'intestazione ODBC su USBD_STATUS_NO_BANDWIDTH.

Il codice di esempio seguente illustra come creare una matrice di strutture USBD_INTERFACE_LIST_ENTRY e chiamare USBD_SelectConfigUrbAllocateAndBuild. L'esempio invia la richiesta in modo sincrono chiamando SubmitUrbSync. Per visualizzare l'esempio di codice per SubmitUrbSync, vedere Come inviare un 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;
}

Disabilitazione di una configurazione per un dispositivo USB

Per disabilitare un dispositivo USB, creare e inviare una richiesta select-configuration con un descrittore di configurazione NULL. Per quel tipo di richiesta, è possibile riutilizzare l'URB che hai creato per la richiesta che ha selezionato una configurazione nel dispositivo. In alternativa, è possibile allocare un nuovo URB chiamando USBD_UrbAllocate. Prima di inviare la richiesta, è necessario formattare l'URB usando la macro UsbBuildSelectConfigurationRequest, come illustrato nel codice di esempio seguente.

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