Partager via


Comment sélectionner une configuration pour un périphérique USB

Pour sélectionner une configuration pour un périphérique USB, le pilote client de ce dernier doit choisir au moins l’une des configurations prises en charge et spécifier les autres paramètres de chaque interface à utiliser. Le pilote client regroupe ces choix dans une demande de configuration de sélection et l’envoie à la pile de pilotes USB fournie par Microsoft, en particulier le pilote de bus USB (PDO de hub USB). Le pilote de bus USB sélectionne chaque interface dans la configuration spécifiée et configure un canal de communication, ou pipe, vers chaque point de terminaison au sein de l’interface. Une fois la demande terminée, le pilote client reçoit un handle pour la configuration sélectionnée et des handles de canal pour les points de terminaison définis dans le paramètre alternatif actif pour chaque interface. Le pilote client peut ensuite utiliser les handles reçus pour modifier les paramètres de configuration et envoyer des demandes d’E/S en lecture et en écriture à un point de terminaison particulier.

Un pilote client envoie une demande de configuration de sélection dans un bloc de demande USB (URB) du type URB_FUNCTION_SELECT_CONFIGURATION. La procédure décrite dans cette rubrique explique comment utiliser la routine USBD_SelectConfigUrbAllocateAndBuild pour générer cet URB. La routine alloue de la mémoire pour un URB, met en forme l’URB pour une demande de configuration de sélection et retourne l’adresse de l’URB au pilote client.

Vous pouvez également allouer une structure URB, puis mettre en forme manuellement l’URB ou en appelant la macro UsbBuildSelectConfigurationRequest.

Prérequis

Étape 1 : créez un tableau de structures USBD_INTERFACE_LIST_ENTRY

  1. Obtenez le nombre d’interfaces dans la configuration. Ces informations sont contenues dans le membre bNumInterfaces de la structure USB_CONFIGURATION_DESCRIPTOR.

  2. Créez un tableau de structures USBD_INTERFACE_LIST_ENTRY. Le nombre d’éléments du tableau doit être supérieur d’une unité au nombre d’interfaces. Initialisez le tableau en appelant RtlZeroMemory.

    Le pilote client spécifie d’autres paramètres dans chaque interface à activer, dans le tableau des structures USBD_INTERFACE_LIST_ENTRY.

    • Le membre InterfaceDescriptor de chaque structure pointe vers le descripteur d’interface qui contient le paramètre de remplacement.
    • Le membre Interface de chaque structure pointe vers une structure USBD_INTERFACE_INFORMATION qui contient des informations de canal dans son membre Pipes. Pipes stocke des informations sur chaque point de terminaison défini dans le paramètre de remplacement.
  3. Obtenez un descripteur d’interface pour chaque interface (ou son paramètre de remplacement) dans la configuration. Vous pouvez obtenir ces descripteurs d’interface en appelant USBD_ParseConfigurationDescriptorEx.

    À propos des pilotes de fonction pour un périphérique composite USB :

    Si le périphérique USB est un périphérique composite, la configuration est sélectionnée par le pilote parent générique USB (Usbccgp.sys) fourni par Microsoft. Un pilote client, qui est l’un des pilotes de fonction du périphérique composite, ne peut pas modifier la configuration, mais il peut tout de même envoyer une demande de configuration de sélection via Usbccgp.sys.

    Avant d’envoyer cette demande, le pilote client doit envoyer une demande URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE. En réponse, Usbccgp.sys récupère un descripteur de configuration partielle qui contient uniquement des descripteurs d’interface et d’autres descripteurs relatifs à la fonction spécifique pour laquelle le pilote client est chargé. Le numéro d’interface signalé dans le champ bNumInterfaces d’un descripteur de configuration partielle est inférieur au nombre total d’interfaces définies pour l’ensemble du périphérique USB composite. En outre, dans un descripteur de configuration partielle, bInterfaceNumber d’un descripteur d’interface indique le numéro d’interface réel par rapport à l’ensemble de l’appareil. Par exemple, Usbccgp.sys peut signaler un descripteur de configuration partielle avec bNumInterfaces ayant une valeur de 2 et bInterfaceNumber ayant une valeur de 4 pour la première interface. Il convient de noter que le numéro de l’interface est supérieur au nombre d’interfaces signalées.

    Lors de l’énumération des interfaces dans une configuration partielle, évitez de les rechercher en calculant les numéros d’interface en fonction du nombre d’interfaces. Dans l’exemple précédent, si USBD_ParseConfigurationDescriptorEx est appelé dans une boucle qui commence à zéro, se termine à (bNumInterfaces - 1) et incrémente l’index d’interface (spécifié dans le paramètre InterfaceNumber) dans chaque itération, la routine ne parvient pas à obtenir l’interface correcte. Au lieu de cela, assurez-vous de rechercher toutes les interfaces dans le descripteur de configuration en mettant -1 dans InterfaceNumber. Pour en savoir plus sur l’implémentation, consultez l’exemple de code dans cette section.

    Pour en savoir plus sur la façon dont Usbccgp.sys gère une demande de configuration de sélection envoyée par un pilote client, consultez Configuration d’Usbccgp.sys pour sélectionner une configuration USB autre que celle par défaut.

  4. Pour chaque élément (sauf le dernier) du tableau, définissez le membre InterfaceDescriptor sur l’adresse d’un descripteur d’interface. Pour le premier élément du tableau, définissez le membre InterfaceDescriptor sur l’adresse du descripteur d’interface qui représente la première interface de la configuration. De même, pour le nième élément du tableau, le membre InterfaceDescriptor est défini à l’adresse du descripteur d’interface qui représente la nième interface de la configuration.

  5. Le membre InterfaceDescriptor du dernier élément doit avoir la valeur NULL.

Étape 2 : obtenez un pointeur vers un URB alloué par la pile de pilotes USB

Ensuite, appelez USBD_SelectConfigUrbAllocateAndBuild en spécifiant la configuration à sélectionner et le tableau rempli de structures USBD_INTERFACE_LIST_ENTRY. La routine exécute les tâches suivantes :

  • Elle crée un URB et le remplit avec des informations sur la configuration spécifiée, ses interfaces et points de terminaison, et définit le type de requête sur URB_FUNCTION_SELECT_CONFIGURATION.

  • Dans cet URB, elle alloue une structure USBD_INTERFACE_INFORMATION pour chaque descripteur d’interface spécifié par le pilote client.

  • Elle définit le membre Interface du nième élément du tableau USBD_INTERFACE_LIST_ENTRY fourni par l’appelant à l’adresse de la structure USBD_INTERFACE_INFORMATION correspondante dans l’URB.

  • Elle initialise les membres InterfaceNumber, AlternateSetting, NumberOfPipes, Pipes[i].MaximumTransferSize, et Pipes[i].PipeFlags.

    Remarque

    Dans Windows 7 et les versions antérieures, le pilote client a créé un URB pour une demande de configuration de sélection en appelant USBD_CreateConfigurationRequestEx. Dans Windows 2000 USBD_CreateConfigurationRequestEx initialise Pipes[i].MaximumTransferSize à la taille de transfert maximale par défaut pour une demande de lecture/écriture URB unique. Le pilote client peut spécifier une taille de transfert maximale différente dans Pipes[i].MaximumTransferSize. La pile USB ignore cette valeur dans Windows XP, Windows Server 2003 et les versions ultérieures du système d’exploitation. Pour en savoir plus sur MaximumTransferSize, consultez « Définition des tailles de transfert et de paquets USB » dans Allocation de bande passante USB.

Étape 3 : envoyez l’URB à la pile de pilotes USB

Pour envoyer l’URB à la pile de pilotes USB, le pilote client doit envoyer une demande de contrôle d’E/S IOCTL_INTERNAL_USB_SUBMIT_URB. Pour en savoir plus sur l’envoi d’un URB, consultez Comment envoyer un URB.

Après avoir reçu l’URB, la pile de pilotes USB remplit le reste des membres de chaque structure USBD_INTERFACE_INFORMATION. En particulier, le membre du tableau Pipes est rempli d’informations relatives aux canaux associés aux points de terminaison de l’interface.

Étape 4 : à la fin de la demande, inspectez les structures USBD_INTERFACE_INFORMATION et l’URB

Une fois que la pile de pilotes USB a complété l’IRP pour la demande, elle renvoie la liste des paramètres alternatifs et les interfaces correspondantes dans le tableau USBD_INTERFACE_LIST_ENTRY.

  1. Le membre Pipes de chaque structure USBD_INTERFACE_INFORMATION pointe vers un tableau de structures USBD_PIPE_INFORMATION qui contient des informations sur les canaux associés à chaque point de terminaison de cette interface particulière. Le pilote client peut obtenir des handles de canal à partir de Pipes[i]. PipeHandle et les utiliser pour envoyer des demandes d’E/S à des canaux spécifiques. Le membre Pipes[i].PipeType spécifie le type de point de terminaison et de transfert pris en charge par ce canal.

  2. Dans le membre UrbSelectConfiguration de l’URB, la pile de pilotes USB renvoie un handle que vous pouvez utiliser pour sélectionner un autre paramètre d’interface en envoyant un autre URB du type URB_FUNCTION_SELECT_INTERFACE (demande d’interface de sélection). Pour allouer et générer la structure URB pour cette demande, appelez USBD_SelectInterfaceUrbAllocateAndBuild.

    La demande de configuration de sélection et la demande d’interface de sélection peuvent échouer si la bande passante est insuffisante pour prendre en charge les points de terminaison isochrones, de contrôle et d’interruption au sein des interfaces activées. Dans ce cas, le pilote de bus USB définit le membre Status de l’en-tête URB sur USBD_STATUS_NO_BANDWIDTH.

L’exemple de code suivant montre comment créer un tableau de structures USBD_INTERFACE_LIST_ENTRY et appeler USBD_SelectConfigUrbAllocateAndBuild. L’exemple envoie la requête de façon synchrone en appelant SubmitUrbSync. Pour voir l’exemple de code de SubmitUrbSync, consultez Comment envoyer 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;
}

Désactivation d’une configuration pour un périphérique USB

Pour désactiver un périphérique USB, créez et envoyez une demande de configuration de sélection avec un descripteur de configuration NULL. Pour ce type de demande, vous pouvez réutiliser l’URB créé pour la demande qui a sélectionné une configuration dans l’appareil. Vous pouvez également allouer un nouveau URB en appelant USBD_UrbAllocate. Avant d’envoyer la demande, vous devez mettre en forme l’URB à l’aide de la macro UsbBuildSelectConfigurationRequest, comme illustré dans l’exemple de code suivant.

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