Partager via


Comment ouvrir et fermer des flux statiques dans un point de terminaison en bloc USB

Cet article décrit la fonctionnalité de flux statiques et explique comment un pilote client USB peut ouvrir et fermer des flux dans un point de terminaison en bloc d’un appareil USB 3.0.

Dans les appareils USB 2.0 et versions antérieures, un point de terminaison en bloc peut envoyer ou recevoir un seul flux de données via le point de terminaison. Dans les appareils USB 3.0, les points de terminaison en bloc peuvent envoyer et recevoir plusieurs flux de données via le point de terminaison.

La pile de pilotes USB fournie par Microsoft dans Windows prend en charge plusieurs flux. Cela permet à un pilote client d’envoyer des demandes d’E/S indépendantes à chaque flux associé à un point de terminaison en bloc dans un appareil USB 3.0. Les requêtes adressées à différents flux ne sont pas sérialisées.

Pour un pilote client, les flux représentent plusieurs points de terminaison logiques qui ont le même ensemble de caractéristiques. Pour envoyer une requête à un flux particulier, le pilote client a besoin d’un handle pour ce flux (similaire à un handle de canal pour un point de terminaison). L’URB pour une demande d’E/S à un flux est similaire à une demande d’E/S adressée à un point de terminaison en bloc. La seule différence est la poignée de canal. Pour envoyer une demande d’E/S à un flux, le pilote spécifie le handle de canal vers le flux.

Pendant la configuration de l’appareil, le pilote client envoie une demande de configuration de sélection et éventuellement une demande de sélection d’interface. Ces demandes récupèrent un ensemble de handles de canal vers les points de terminaison définis dans le paramètre actif d’une interface. Pour un point de terminaison qui prend en charge les flux, le handle de canal de point de terminaison peut être utilisé pour envoyer des demandes d’E/S au flux par défaut (le premier flux) jusqu’à ce que le pilote ait ouvert des flux (discussion suivante).

Si le pilote client souhaite envoyer des requêtes à des flux autres que le flux par défaut, il doit ouvrir et obtenir des handles pour tous les flux. Pour ce faire, le pilote client envoie une demande open-streams en spécifiant le nombre de flux à ouvrir. Une fois que le pilote client a terminé d’utiliser des flux, le pilote peut éventuellement les fermer en envoyant une demande close-streams.

L’infrastructure kmDF (Kernel Mode Driver Framework) ne prend pas en charge intrinsèquement les flux statiques. Le pilote client doit envoyer des URB de style modèle de pilote Windows (WDM) qui ouvrent et ferment des flux. Cet article explique comment mettre en forme et envoyer ces URB. Un pilote client UMDF (User Mode Driver Framework) ne peut pas utiliser la fonctionnalité de flux statiques.

L’article contient des notes étiquetées en tant que pilotes WDM. Ces notes décrivent les routines d’un pilote client USB WDM qui souhaite envoyer des demandes de flux.

Prérequis

Pour qu’un pilote client puisse ouvrir ou fermer des flux, celui-ci doit disposer des éléments suivants :

  • Appelé la méthode WdfUsbTargetDeviceCreateWithParameters .

    La méthode nécessite que la version du contrat client soit USBD_CLIENT_CONTRACT_VERSION_602. En spécifiant cette version, le pilote client doit adhérer à un ensemble de règles. Pour plus d’informations, consultez Meilleures pratiques : utilisation d’URBs.

    L’appel récupère un handle WDFUSBDEVICE sur l’objet périphérique cible USB de l’infrastructure. Ce handle est requis pour effectuer des appels ultérieurs aux flux ouverts. En règle générale, le pilote client s’inscrit lui-même dans la routine de rappel des événements EVT_WDF_DEVICE_PREPARE_HARDWARE du pilote.

    Pilotes WDM : Appelez la routine USBD_CreateHandle et obtenez une poignée USBD pour l’inscription du pilote avec la pile de pilotes USB.

  • Configurez l’appareil et obtenez un handle de canal WDFUSBPIPE vers le point de terminaison en bloc qui prend en charge les flux. Pour obtenir le handle de canal, appelez la méthode WdfUsbInterfaceGetConfiguredPipe sur l’autre paramètre actuel d’une interface dans la configuration sélectionnée.

    Pilotes WDM : Obtenez un handle de canal USBD en envoyant une requête select-configuration ou select-interface. Pour plus d’informations, consultez Comment sélectionner une configuration pour un périphérique USB.

Comment ouvrir des flux statiques

  1. Déterminez si la pile de pilotes USB sous-jacente et le contrôleur hôte prennent en charge la fonctionnalité de flux statiques en appelant la méthode WdfUsbTargetDeviceQueryUsbCapability . En règle générale, le pilote client appelle la routine dans la routine de rappel des événements EVT_WDF_DEVICE_PREPARE_HARDWARE du pilote.

    Pilotes WDM : Appelez la routine USBD_QueryUsbCapability . En règle générale, le pilote interroge les fonctionnalités qu’il souhaite utiliser dans la routine du périphérique de démarrage du pilote (IRP_MN_START_DEVICE). Pour obtenir un exemple de code, consultez USBD_QueryUsbCapability.

    Fournissez les informations suivantes :

    • Handle de l’objet de périphérique USB qui a été récupéré, lors d’un appel précédent à WdfUsbTargetDeviceCreateWithParameters, pour l’inscription du pilote client.

      Pilotes WDM : Transmettez la poignée USBD récupérée lors de l’appel précédent à USBD_CreateHandle.

      Si le pilote client souhaite utiliser une fonctionnalité particulière, il doit d’abord interroger la pile de pilotes USB sous-jacente pour déterminer si la pile de pilotes et le contrôleur hôte prennent en charge la fonctionnalité. Si la fonctionnalité est prise en charge, le pilote doit envoyer une demande d’utilisation de la fonctionnalité. Certaines demandes nécessitent des URB, comme la fonctionnalité de flux (décrite à l’étape 5). Pour ces demandes, assurez-vous d’utiliser le même handle pour interroger les fonctionnalités et allouer des URB. En effet, la pile de pilotes utilise des handles pour suivre les fonctionnalités prises en charge qu’un pilote peut utiliser.

      Par instance, si vous avez obtenu un USBD_HANDLE (en appelant USBD_CreateHandle), interrogez la pile de pilotes en appelant USBD_QueryUsbCapability et allouez l’URB en appelant USBD_UrbAllocate. Passez le même USBD_HANDLE dans ces deux appels.

      Si vous appelez des méthodes KMDF, WdfUsbTargetDeviceQueryUsbCapability et WdfUsbTargetDeviceCreateUrb, spécifiez le même handle WDFUSBDEVICE pour l’objet cible du framework dans ces appels de méthode.

    • GUID affecté à GUID_USB_CAPABILITY_STATIC_STREAMS.

    • Mémoire tampon de sortie (pointeur vers USHORT). Une fois l’opération terminée, la mémoire tampon est remplie avec le nombre maximal de flux (par point de terminaison) pris en charge par le contrôleur hôte.

    • Longueur en octets de la mémoire tampon de sortie. Pour les flux, la longueur est sizeof (USHORT).

  2. Évaluez la valeur NTSTATUS retournée. Si la routine se termine correctement, retourne STATUS_SUCCESS, la fonctionnalité de flux statiques est prise en charge. Sinon, la méthode retourne un code d’erreur approprié.

  3. Déterminez le nombre de flux à ouvrir. Le nombre maximal de flux pouvant être ouverts est limité par :

    • Nombre maximal de flux pris en charge par le contrôleur hôte. Ce numéro est reçu par WdfUsbTargetDeviceQueryUsbCapability (pour les pilotes WDM, USBD_QueryUsbCapability), dans la mémoire tampon de sortie fournie par l’appelant. La pile de pilotes USB fournie par Microsoft prend en charge jusqu’à 255 flux. WdfUsbTargetDeviceQueryUsbCapability prend cette limitation en considération lors du calcul du nombre de flux. La méthode ne retourne jamais une valeur supérieure à 255.
    • Nombre maximal de flux pris en charge par le point de terminaison dans l’appareil. Pour obtenir ce nombre, inspectez le descripteur complémentaire du point de terminaison (consultez USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR dans Usbspec.h). Pour obtenir le descripteur complémentaire du point de terminaison, vous devez analyser le descripteur de configuration. Pour obtenir le descripteur de configuration, le pilote client doit appeler la méthode WdfUsbTargetDeviceRetrieveConfigDescriptor . Vous devez utiliser les routines d’assistance, les USBD_ParseConfigurationDescriptorEx et les USBD_ParseDescriptor. Pour obtenir un exemple de code, consultez l’exemple de fonction nommée RetrieveStreamInfoFromEndpointDesc dans Guide pratique pour énumérer des canaux USB.

    Pour déterminer le nombre maximal de flux, choisissez le moins élevé des deux valeurs prises en charge par le contrôleur hôte et le point de terminaison.

  4. Allouez un tableau de structures USBD_STREAM_INFORMATION avec n éléments, où n est le nombre de flux à ouvrir. Le pilote client est chargé de libérer ce tableau une fois que le pilote a terminé d’utiliser des flux.

  5. Allouez un URB pour la demande open-streams en appelant la méthode WdfUsbTargetDeviceCreateUrb . Si l’appel se termine correctement, la méthode récupère un objet mémoire WDF et l’adresse de la structure URB allouée par la pile de pilotes USB.

    Pilotes WDM : Appelez la routine USBD_UrbAllocate .

  6. Mettez en forme l’URB pour la demande open-stream. L’URB utilise la structure _URB_OPEN_STATIC_STREAMS pour définir la demande. Pour mettre en forme l’URB, vous avez besoin des éléments suivants :

    • La poignée de canal USBD vers le point de terminaison. Si vous avez un objet de canal WDF, vous pouvez obtenir le handle de canal USBD en appelant la méthode WdfUsbTargetPipeWdmGetPipeHandle .
    • Tableau de flux (créé à l’étape 4)
    • Pointeur vers la structure URB (créé à l’étape 5).

    Pour mettre en forme l’URB, appelez UsbBuildOpenStaticStreamsRequest et transmettez les informations requises en tant que valeurs de paramètre. Assurez-vous que le nombre de flux spécifié dans UsbBuildOpenStaticStreamsRequest ne dépasse pas le nombre maximal de flux pris en charge.

  7. Envoyez l’URB en tant qu’objet de requête WDF en appelant la méthode WdfRequestSend . Pour envoyer la requête de manière synchrone, appelez plutôt la méthode WdfUsbTargetDeviceSendUrbSynchronously .

    Pilotes WDM : Associez l’URB à un IRP et envoyez l’IRP à la pile de pilotes USB. Pour plus d’informations, consultez Comment envoyer un URB.

  8. Une fois la demande terminée, case activée la status de la demande.

    Si la pile de pilotes USB échoue à la demande, le status URB contient le code d’erreur approprié. Certaines conditions d’échec courantes sont décrites dans la section Remarques.

Si le status de la demande (IRP ou objet de requête WDF) indique USBD_STATUS_SUCCESS, la demande a été effectuée avec succès. Inspectez le tableau des structures USBD_STREAM_INFORMATION reçues à l’achèvement. Le tableau est rempli d’informations sur les flux demandés. La pile de pilotes USB remplit chaque structure du tableau avec des informations de flux, telles que les handles (reçus en tant que USBD_PIPE_HANDLE), les identificateurs de flux et la taille de transfert maximale du nombre. Les flux sont désormais ouverts pour transférer des données.

Pour une demande de flux ouverts, vous devez allouer un URB et un tableau. Le pilote client doit libérer l’URB en appelant la méthode WdfObjectDelete sur l’objet de mémoire WDF associé, une fois la demande de flux ouverts terminée. Si le pilote a envoyé la requête de manière synchrone en appelant WdfUsbTargetDeviceSendUrbSynchronously, il doit libérer l’objet mémoire WDF, une fois la méthode retournée. Si le pilote client a envoyé la requête de manière asynchrone en appelant WdfRequestSend, le pilote doit libérer l’objet mémoire WDF dans la routine d’achèvement implémentée par le pilote associée à la demande.

Le tableau de flux peut être libéré une fois que le pilote client a terminé d’utiliser des flux ou les a stockés pour les demandes d’E/S. Dans l’exemple de code inclus dans cet article, le pilote stocke le tableau de flux dans le contexte de l’appareil. Le pilote libère le contexte de l’appareil juste avant la suppression de l’objet d’appareil.

Comment transférer des données vers un flux particulier

Pour envoyer une demande de transfert de données à un flux particulier, vous avez besoin d’un objet de requête WDF. En règle générale, le pilote client n’est pas nécessaire pour allouer un objet de requête WDF. Lorsque le Gestionnaire d’E/S reçoit une demande d’une application, celui-ci crée un IRP pour la demande. Cette IRP est interceptée par le framework. L’infrastructure alloue ensuite un objet de requête WDF pour représenter l’IRP. Après cela, l’infrastructure transmet l’objet de requête WDF au pilote client. Le pilote client peut ensuite associer l’objet de requête à l’URB de transfert de données et l’envoyer à la pile de pilotes USB.

Si le pilote client ne reçoit pas d’objet de requête WDF de l’infrastructure et souhaite envoyer la requête de manière asynchrone, le pilote doit allouer un objet de requête WDF en appelant la méthode WdfRequestCreate . Mettez en forme le nouvel objet en appelant WdfUsbTargetPipeFormatRequestForUrb, puis envoyez la requête en appelant WdfRequestSend.

Dans les cas synchrones, la transmission d’un objet de requête WDF est facultative.

Pour transférer des données vers des flux, vous devez utiliser des URIB. L’URB doit être mis en forme en appelant WdfUsbTargetPipeFormatRequestForUrb.

Les méthodes WDF suivantes ne sont pas prises en charge pour les flux :

La procédure suivante suppose que le pilote client reçoit l’objet de requête de l’infrastructure.

  1. Allouez un URB en appelant WdfUsbTargetDeviceCreateUrb. Cette méthode alloue un objet de mémoire WDF qui contient l’URB nouvellement alloué. Le pilote client peut choisir d’allouer une URB pour chaque demande d’E/S ou d’allouer une URB et de la réutiliser pour le même type de requête.

  2. Mettez en forme l’URB pour un transfert en bloc en appelant UsbBuildInterruptOrBulkTransferRequest. Dans le paramètre PipeHandle , spécifiez le handle du flux. Les handles de flux ont été obtenus dans une demande précédente, décrite dans la section Comment ouvrir des flux statiques .

  3. Mettez en forme l’objet de requête WDF en appelant la méthode WdfUsbTargetPipeFormatRequestForUrb . Dans l’appel, spécifiez l’objet de mémoire WDF qui contient l’URB de transfert de données. L’objet mémoire a été alloué à l’étape 1.

  4. Envoyez l’URB en tant que requête WDF en appelant WdfRequestSend ou WdfUsbTargetPipeSendUrbSynchronously. Si vous appelez WdfRequestSend, vous devez spécifier une routine d’achèvement en appelant WdfRequestSetCompletionRoutine afin que le pilote client puisse être averti lorsque l’opération asynchrone est terminée. Vous devez libérer l’URB de transfert de données dans la routine d’achèvement.

Pilotes WDM : Allouez un URB en appelant USBD_UrbAllocate et mettez-le en forme pour le transfert en bloc (voir _URB_BULK_OR_INTERRUPT_TRANSFER). Pour mettre en forme l’URB, vous pouvez appeler UsbBuildInterruptOrBulkTransferRequest ou mettre en forme la structure URB manuellement. Spécifiez le handle du flux dans le membre UrbBulkOrInterruptTransfer.PipeHandle de l’URB .

Comment fermer des flux statiques

Le pilote client peut fermer les flux une fois que le pilote a terminé de les utiliser. Toutefois, la demande de flux de fermeture est facultative. La pile de pilotes USB ferme tous les flux lorsque le point de terminaison associé aux flux est déconfiguré. Un point de terminaison est déconfiguré lorsqu’une autre configuration ou interface est sélectionnée, que l’appareil est supprimé, et ainsi de suite. Un pilote client doit fermer les flux si le pilote souhaite ouvrir un nombre différent de flux. Pour envoyer une demande de flux de fermeture :

  1. Allouez une structure URB en appelant WdfUsbTargetDeviceCreateUrb.

  2. Mettez en forme l’URB pour la demande close-streams. Le membre UrbPipeRequest de la structure URB est une structure _URB_PIPE_REQUEST . Renseignez ses membres comme suit :

    • Le membre Hdr de _URB_PIPE_REQUEST doit être URB_FUNCTION_CLOSE_STATIC_STREAMS
    • Le membre PipeHandle doit être le handle du point de terminaison qui contient les flux ouverts en cours d’utilisation.
  3. Envoyez l’URB en tant que requête WDF en appelant WdfRequestSend ou WdfUsbTargetDeviceSendUrbSynchronously.

La demande close-handle ferme tous les flux qui ont été précédemment ouverts par le pilote client. Le pilote client ne peut pas utiliser la demande pour fermer des flux spécifiques dans le point de terminaison.

Bonnes pratiques pour l’envoi d’une demande de flux statiques

La pile de pilotes USB effectue des validations sur l’URB reçu. Pour éviter les erreurs de validation :

  • N’envoyez pas de demande d’ouverture ou de flux de fermeture à un point de terminaison qui ne prend pas en charge les flux. Appelez WdfUsbTargetDeviceQueryUsbCapability (pour les pilotes WDM, USBD_QueryUsbCapability) pour déterminer la prise en charge des flux statiques et envoyer uniquement des demandes de flux si le point de terminaison les prend en charge.
  • Ne demandez pas un nombre (de flux à ouvrir) qui dépasse le nombre maximal de flux pris en charge ou envoyez une requête sans spécifier le nombre de flux. Déterminez le nombre de flux en fonction du nombre de flux pris en charge par la pile de pilotes USB et le point de terminaison de l’appareil.
  • N’envoyez pas de demande d’open stream à un point de terminaison qui a déjà des flux ouverts.
  • N’envoyez pas de demande de flux de fermeture à un point de terminaison qui n’a pas de flux ouverts.
  • Une fois que les flux statiques sont ouverts pour un point de terminaison, n’envoyez pas de demandes d’E/S à l’aide du handle de canal de point de terminaison obtenu par le biais d’une sélection de demandes de configuration ou de sélection d’interface. Cela est vrai même si les flux statiques ont été fermés.

Réinitialiser et abandonner les opérations de canal

Parfois, les transferts vers ou depuis un point de terminaison peuvent échouer. Ces échecs peuvent résulter d’une condition d’erreur sur le point de terminaison ou le contrôleur hôte, comme une condition de blocage ou d’arrêt. Pour effacer la condition d’erreur, le pilote client annule d’abord les transferts en attente, puis réinitialise le canal auquel le point de terminaison est associé. Pour annuler les transferts en attente, le pilote client peut envoyer une demande d’abandon du canal. Pour réinitialiser un canal, le pilote client doit envoyer une demande de réinitialisation de canal.

Pour les transferts de flux, les demandes d’abandon de canal et de réinitialisation de canal ne sont pas prises en charge pour les flux individuels associés au point de terminaison en bloc. Si un transfert échoue sur un canal de flux particulier, le contrôleur hôte arrête les transferts sur tous les autres canaux (pour les autres flux). Pour récupérer à partir de la condition d’erreur, le pilote client doit annuler manuellement les transferts vers chaque flux. Ensuite, le pilote client doit envoyer une requête de canal de réinitialisation à l’aide du handle de canal vers le point de terminaison en bloc. Pour cette demande, le pilote client doit spécifier le handle de canal vers le point de terminaison dans une structure de _URB_PIPE_REQUEST et définir la fonction URB (Hdr.Function) sur URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL.

Exemple complet

L’exemple de code suivant montre comment ouvrir des flux.

NTSTATUS
    OpenStreams (
    _In_ WDFDEVICE Device,
    _In_ WDFUSBPIPE Pipe)
{
    NTSTATUS status;
    PDEVICE_CONTEXT deviceContext;
    PPIPE_CONTEXT pipeContext;
    USHORT cStreams = 0;
    USBD_PIPE_HANDLE usbdPipeHandle;
    WDFMEMORY urbMemory = NULL;
    PURB      urb = NULL;

    PAGED_CODE();

    deviceContext =GetDeviceContext(Device);
    pipeContext = GetPipeContext (Pipe);

    if (deviceContext->MaxStreamsController == 0)
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Static streams are not supported.");

        status = STATUS_NOT_SUPPORTED;
        goto Exit;
    }

    // If static streams are not supported, number of streams supported is zero.

    if (pipeContext->MaxStreamsSupported == 0)
    {
        status = STATUS_DEVICE_CONFIGURATION_ERROR;

        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Static streams are not supported by the endpoint.");

        goto Exit;
    }

    // Determine the number of streams to open.
    // Compare the number of streams supported by the endpoint with the
    // number of streams supported by the host controller, and choose the
    // lesser of the two values. The deviceContext->MaxStreams value was
    // obtained in a previous call to WdfUsbTargetDeviceQueryUsbCapability
    // that determined whether or not static streams is supported and
    // retrieved the maximum number of streams supported by the
    // host controller. The device context stores the values for IN and OUT
    // endpoints.

    // Allocate an array of USBD_STREAM_INFORMATION structures to store handles to streams.
    // The number of elements in the array is the number of streams to open.
    // The code snippet stores the array in its device context.

    cStreams = min(deviceContext->MaxStreamsController, pipeContext->MaxStreamsSupported);

    // Allocate an array of streams associated with the IN bulk endpoint
    // This array is released in CloseStreams.

    pipeContext->StreamInfo = (PUSBD_STREAM_INFORMATION) ExAllocatePoolWithTag (
        NonPagedPool,
        sizeof (USBD_STREAM_INFORMATION) * cStreams,
        USBCLIENT_TAG);

    if (pipeContext->StreamInfo == NULL)
    {
        status = STATUS_INSUFFICIENT_RESOURCES;

        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not allocate stream information array.");

        goto Exit;
    }

    RtlZeroMemory (pipeContext->StreamInfo,
        sizeof (USBD_STREAM_INFORMATION) * cStreams);

    // Get USBD pipe handle from the WDF target pipe object. The client driver received the
    // endpoint pipe handles during device configuration.

    usbdPipeHandle = WdfUsbTargetPipeWdmGetPipeHandle (Pipe);

    // Allocate an URB for the open streams request.
    // WdfUsbTargetDeviceCreateUrb returns the address of the
    // newly allocated URB and the WDFMemory object that
    // contains the URB.

    status = WdfUsbTargetDeviceCreateUrb (
        deviceContext->UsbDevice,
        NULL,
        &urbMemory,
        &urb);

    if (status != STATUS_SUCCESS)
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not allocate URB for an open-streams request.");

        goto Exit;
    }

    // Format the URB for the open-streams request.
    // The UsbBuildOpenStaticStreamsRequest inline function formats the URB by specifying the
    // pipe handle to the entire bulk endpoint, number of streams to open, and the array of stream structures.

    UsbBuildOpenStaticStreamsRequest (
        urb,
        usbdPipeHandle,
        (USHORT)cStreams,
        pipeContext->StreamInfo);

    // Send the request synchronously.
    // Upon completion, the USB driver stack populates the array of with handles to streams.

    status = WdfUsbTargetPipeSendUrbSynchronously (
        Pipe,
        NULL,
        NULL,
        urb);

    if (status != STATUS_SUCCESS)
    {
        goto Exit;
    }

Exit:
    if (urbMemory)
    {
        WdfObjectDelete (urbMemory);
    }

    return status;
}