Comment envoyer des demandes de transfert en bloc USB

Cette rubrique fournit une brève vue d’ensemble des transferts en bloc USB. Il fournit également des instructions détaillées sur la façon dont un pilote client peut envoyer et recevoir des données en bloc à partir de l’appareil.

À propos des points de terminaison en bloc

Un point de terminaison en bloc USB peut transférer de grandes quantités de données. Les transferts en bloc sont fiables qui permettent la détection des erreurs matérielles et impliquent un nombre limité de nouvelles tentatives dans le matériel. Pour les transferts vers des points de terminaison en bloc, la bande passante n’est pas réservée sur le bus. Lorsqu’il existe plusieurs demandes de transfert qui ciblent différents types de points de terminaison, le contrôleur planifie d’abord les transferts pour les données temporelles critiques, telles que les paquets isochroques et les paquets d’interruption. Seulement si la bande passante inutilisée est disponible sur le bus, le contrôleur planifie les transferts en bloc. Lorsqu’il n’y a pas d’autre trafic important dans l’autobus, le transfert en bloc peut être rapide. Toutefois, lorsque le bus est occupé par d’autres transferts, les données en bloc peuvent attendre indéfiniment.

Voici les principales fonctionnalités d’un point de terminaison en bloc :

  • Les points de terminaison en bloc sont facultatifs. Ils sont pris en charge par un périphérique USB qui souhaite transférer de grandes quantités de données. Par exemple, le transfert de fichiers vers un lecteur flash, de données vers ou à partir d’une imprimante ou d’un scanneur.
  • Les appareils USB à pleine vitesse, haute vitesse et SuperSpeed prennent en charge les points de terminaison en bloc. Les appareils à faible vitesse ne prennent pas en charge les points de terminaison en bloc.
  • Le point de terminaison est un point de terminaison unidirectionnel et les données peuvent être transférées dans une direction IN ou OUT. Le point de terminaison IN en bloc est utilisé pour lire les données de l’appareil vers l’hôte et le point de terminaison OUT en bloc est utilisé pour envoyer des données de l’hôte à l’appareil.
  • Le point de terminaison a des bits CRC à case activée pour les erreurs et fournit ainsi l’intégrité des données. Pour les erreurs CRC, les données sont retransmises automatiquement.
  • Un point de terminaison en bloc SuperSpeed peut prendre en charge les flux. Les flux permettent à l’hôte d’envoyer des transferts à des canaux de flux individuels.
  • La taille maximale des paquets d’un point de terminaison en bloc dépend de la vitesse de bus de l’appareil. Pour toute vitesse, vitesse élevée et SuperSpeed ; les tailles maximales de paquets sont respectivement de 64, 512 et 1 024 octets.

Transactions en bloc

Comme tous les autres transferts USB, l’hôte lance toujours un transfert en bloc. La communication a lieu entre l’hôte et le point de terminaison cible. Le protocole USB n’applique aucun format aux données envoyées dans une transaction en bloc.

La façon dont l’hôte et l’appareil communiquent sur le bus dépend de la vitesse à laquelle l’appareil est connecté. Cette section décrit quelques exemples de transferts en bloc haute vitesse et SuperSpeed qui montrent la communication entre l’hôte et l’appareil.

Vous pouvez voir la structure des transactions et des paquets à l’aide de n’importe quel analyseur USB, tel que Beagle, Ellisys et les analyseurs de protocole USB LeCroy. Un appareil analyseur montre comment les données sont envoyées ou reçues à partir d’un périphérique USB via le réseau. Dans cet exemple, examinons certaines traces capturées par un analyseur USB LeCroy. Cet exemple est à titre d’information uniquement. Il ne s’agit pas d’une approbation de Microsoft.

Exemple de transaction OUT en bloc

Cette trace d’analyseur montre un exemple de transaction OUT en bloc à grande vitesse.

Capture d’écran montrant une trace d’un exemple de transaction d’analyseur OUT en bloc.

Dans la trace précédente, l’hôte lance un transfert OUT en bloc vers un point de terminaison en bloc à haut débit, en envoyant un paquet de jeton avec PID défini sur OUT (out token). Le paquet contient l’adresse de l’appareil et du point de terminaison cible. Après le paquet OUT, l’hôte envoie un paquet de données qui contient la charge utile en bloc. Si le point de terminaison accepte les données entrantes, il envoie un paquet ACK. Dans cet exemple, nous pouvons voir que l’hôte a envoyé 31 octets à l’adresse de l’appareil : 1 ; adresse du point de terminaison : 2.

Si le point de terminaison est occupé au moment où le paquet de données arrive et n’est pas en mesure de recevoir des données, l’appareil peut envoyer un paquet NAK. Dans ce cas, l’hôte commence à envoyer des paquets PING à l’appareil. L’appareil répond avec des paquets NAK tant que l’appareil n’est pas prêt à recevoir des données. Lorsque l’appareil est prêt, il répond avec un paquet ACK. L’hôte peut ensuite reprendre le transfert OUT.

Cette trace d’analyseur montre un exemple de transaction OUT en bloc SuperSpeed.

Capture d’écran montrant une trace d’un exemple de transaction de données OUT en bloc SuperSpeed.

Dans la trace précédente, l’hôte lance une transaction OUT vers un point de terminaison en bloc SuperSpeed en envoyant un paquet de données. Le paquet de données contient les adresses de charge utile, d’appareil et de point de terminaison en bloc. Dans cet exemple, nous pouvons voir que l’hôte a envoyé 31 octets à l’adresse de l’appareil : 4 ; adresse du point de terminaison : 2.

L’appareil reçoit et accuse réception du paquet de données, puis renvoie un paquet ACK à l’hôte. Si le point de terminaison est occupé au moment où le paquet de données arrive et n’est pas en mesure de recevoir des données, l’appareil peut envoyer un paquet NRDY. Contrairement à la vitesse élevée, après avoir reçu le paquet NRDY, l’hôte n’interroge pas à plusieurs reprises l’appareil. Au lieu de cela, l’hôte attend un ERDY à partir de l’appareil. Lorsque l’appareil est prêt, il envoie un paquet ERDY et l’hôte peut ensuite envoyer des données au point de terminaison.

Exemple de transaction IN en bloc

Cette trace d’analyseur montre un exemple de transaction IN en bloc à grande vitesse.

Capture d’écran montrant une trace d’un exemple de transaction de données IN en bloc.

Dans la trace précédente, l’hôte lance la transaction en envoyant un paquet de jeton avec PID défini sur IN (jeton IN). L’appareil envoie ensuite un paquet de données avec une charge utile en bloc. Si le point de terminaison n’a pas de données à envoyer ou n’est pas encore prêt à envoyer des données, l’appareil peut envoyer un paquet de négociation NAK. L’hôte retente le transfert IN jusqu’à ce qu’il reçoive un paquet ACK de l’appareil. Ce paquet ACK implique que l’appareil a accepté les données.

Cette trace d’analyseur montre un exemple de transaction IN en bloc SuperSpeed.

trace d’un exemple de transaction de données.

Pour lancer un transfert IN en bloc à partir d’un point de terminaison SuperSpeed, l’hôte démarre une transaction en bloc en envoyant un paquet ACK. La spécification USB version 3.0 optimise cette partie initiale du transfert en fusionnant les paquets ACK et IN en un seul paquet ACK. Au lieu d’un jeton IN, pour SuperSpeed, l’hôte envoie un jeton ACK pour lancer un transfert en bloc. L’appareil répond avec un paquet de données. L’hôte accuse ensuite réception du paquet de données en envoyant un paquet ACK. Si le point de terminaison est occupé et n’a pas pu envoyer de données, l’appareil peut envoyer status de NRDY. Dans ce cas, l’hôte attend qu’il obtienne un paquet ERDY de l’appareil.

Tâches du pilote client USB pour un transfert en bloc

Une application ou un pilote sur l’hôte lance toujours un transfert en bloc pour envoyer ou recevoir des données. Le pilote client envoie la demande à la pile de pilotes USB. La pile de pilotes USB programme la requête dans le contrôleur hôte, puis envoie les paquets de protocole (comme décrit dans la section précédente) sur le réseau à l’appareil.

Voyons comment le pilote client envoie la demande de transfert en bloc à la suite de la demande d’une application ou d’un autre pilote. Le pilote peut également lancer le transfert lui-même. Quelle que soit l’approche, un pilote doit disposer de la mémoire tampon de transfert et de la demande pour lancer le transfert en bloc.

Pour un pilote KMDF, la demande est décrite dans un objet de demande d’infrastructure (voir Référence de l’objet de requête WDF). Le pilote client appelle les méthodes de l’objet de requête en spécifiant le handle WDFREQUEST pour envoyer la demande à la pile de pilotes USB. Si le pilote client envoie un transfert en bloc en réponse à une demande d’une application ou d’un autre pilote, l’infrastructure crée un objet de requête et remet la demande au pilote client à l’aide d’un objet de file d’attente d’infrastructure. Dans ce cas, le pilote client peut utiliser cette demande pour envoyer le transfert en bloc. Si le pilote client a lancé la demande, il peut choisir d’allouer son propre objet de requête.

Si l’application ou un autre pilote a envoyé ou demandé des données, la mémoire tampon de transfert est passée au pilote par le framework. Le pilote client peut également allouer la mémoire tampon de transfert et créer l’objet de requête si le pilote lance le transfert lui-même.

Voici les tâches main pour le pilote client :

  1. Obtenez la mémoire tampon de transfert.
  2. Obtenez, mettez en forme et envoyez un objet de demande d’infrastructure à la pile de pilotes USB.
  3. Implémentez une routine d’achèvement pour recevoir une notification lorsque la pile de pilotes USB termine la demande.

Cette rubrique décrit ces tâches à l’aide d’un exemple dans lequel le pilote lance un transfert en bloc à la suite de la demande d’une application d’envoyer ou de recevoir des données.

Pour lire des données à partir de l’appareil, le pilote client peut utiliser l’objet lecteur continu fourni par l’infrastructure. Pour plus d’informations, consultez Utilisation du lecteur continu pour lire des données à partir d’un canal USB.

Exemple de demande de transfert en bloc

Prenons un exemple de scénario, où une application souhaite lire ou écrire des données sur votre appareil. L’application appelle des API Windows pour envoyer de telles demandes. Dans cet exemple, l’application ouvre un handle à l’appareil à l’aide du GUID d’interface de périphérique publié par votre pilote en mode noyau. L’application appelle ensuite ReadFile ou WriteFile pour lancer une demande de lecture ou d’écriture. Dans cet appel, l’application spécifie également une mémoire tampon qui contient les données à lire ou à écrire et la longueur de cette mémoire tampon.

Le Gestionnaire d’E/S reçoit la demande, crée un paquet de demandes d’E/S (IRP) et le transfère au pilote client.

L’infrastructure intercepte la requête, crée un objet de demande d’infrastructure et l’ajoute à l’objet de file d’attente du framework. L’infrastructure avertit ensuite le pilote client qu’une nouvelle demande attend d’être traitée. Cette notification s’effectue en appelant les routines de rappel de la file d’attente du pilote pour EvtIoRead ou EvtIoWrite.

Lorsque l’infrastructure remet la demande au pilote client, elle reçoit les paramètres suivants :

  • Handle WDFQUEUE pour l’objet de file d’attente de framework qui contient la requête.
  • Handle WDFREQUEST pour l’objet de demande d’infrastructure qui contient des détails sur cette demande.
  • Longueur de transfert, c’est-à-dire le nombre d’octets à lire ou à écrire.

Dans l’implémentation du pilote client d’EvtIoRead ou EvtIoWrite, le pilote inspecte les paramètres de la requête et peut éventuellement effectuer des vérifications de validation.

Si vous utilisez des flux d’un point de terminaison en bloc SuperSpeed, vous allez envoyer la demande dans un URB, car KMDF ne prend pas en charge les flux intrinsèquement. Pour plus d’informations sur l’envoi d’une demande de transfert aux flux d’un point de terminaison en bloc, consultez Comment ouvrir et fermer des flux statiques dans un point de terminaison en bloc USB.

Si vous n’utilisez pas de flux, vous pouvez utiliser des méthodes définies par KMDF pour envoyer la requête comme décrit dans la procédure suivante :

Prérequis

Avant de commencer, vérifiez que vous disposez des informations suivantes :

  • Le pilote client doit avoir créé l’objet de périphérique cible USB du framework et obtenu le handle WDFUSBDEVICE en appelant la méthode WdfUsbTargetDeviceCreateWithParameters .

    Si vous utilisez les modèles USB fournis avec Microsoft Visual Studio Professional 2012, le code du modèle effectue ces tâches. Le code de modèle obtient le handle de l’objet d’appareil cible et les stocke dans le contexte de l’appareil. Pour plus d’informations, consultez « Code source de l’appareil » dans Présentation de la structure de code du pilote client USB (KMDF).

  • Handle WDFREQUEST pour l’objet de demande d’infrastructure qui contient des détails sur cette demande.

  • Nombre d’octets à lire ou à écrire.

  • Handle WDFUSBPIPE pour l’objet de canal d’infrastructure associé au point de terminaison cible. Vous devez avoir obtenu des poignées de canal pendant la configuration de l’appareil en énumérant les canaux. Pour plus d’informations, consultez Guide pratique pour énumérer des canaux USB.

    Si le point de terminaison en bloc prend en charge les flux, vous devez disposer du handle de canal vers le flux. Pour plus d’informations, consultez Comment ouvrir et fermer des flux statiques dans un point de terminaison en bloc USB.

Étape 1 : Obtenir la mémoire tampon de transfert

La mémoire tampon de transfert ou la mémoire tampon de transfert MDL contient les données à envoyer ou à recevoir. Cette rubrique suppose que vous envoyez ou recevez des données dans une mémoire tampon de transfert. La mémoire tampon de transfert est décrite dans un objet mémoire WDF (consultez Référence de l’objet mémoire WDF). Pour obtenir l’objet mémoire associé à la mémoire tampon de transfert, appelez l’une des méthodes suivantes :

Le pilote client n’a pas besoin de libérer cette mémoire. La mémoire est associée à l’objet de requête parent et est libérée lorsque le parent est libéré.

Étape 2 : Mettre en forme et envoyer un objet de demande d’infrastructure à la pile de pilotes USB

Vous pouvez envoyer la demande de transfert de manière asynchrone ou synchrone.

Voici les méthodes asynchrones :

Les méthodes de cette liste mettez en forme la requête. Si vous envoyez la requête de manière asynchrone, définissez un pointeur vers la routine d’achèvement implémentée par le pilote en appelant la méthode WdfRequestSetCompletionRoutine (décrite à l’étape suivante). Pour envoyer la demande, appelez la méthode WdfRequestSend .

Si vous envoyez la requête de manière synchrone, appelez les méthodes suivantes :

Pour obtenir des exemples de code, consultez la section Exemples des rubriques de référence pour ces méthodes.

Étape 3 : Implémenter une routine d’achèvement pour la demande

Si la requête est envoyée de manière asynchrone, vous devez implémenter une routine d’achèvement pour être averti lorsque la pile de pilotes USB termine la demande. Une fois l’exécution terminée, l’infrastructure appelle la routine d’achèvement du pilote. L’infrastructure transmet les paramètres suivants :

  • Handle WDFREQUEST pour l’objet de requête.
  • Handle WDFIOTARGET vers l’objet cible d’E/S pour la requête.
  • Pointeur vers une structure de WDF_REQUEST_COMPLETION_PARAMS qui contient des informations d’achèvement. Les informations spécifiques à USB sont contenues dans le membre CompletionParams-Parameters.Usb>.
  • WDFCONTEXT gère le contexte spécifié par le pilote dans son appel à WdfRequestSetCompletionRoutine.

Dans la routine d’achèvement, effectuez les tâches suivantes :

  • Vérifiez le status de la demande en obtenant la valeur CompletionParams-IoStatus.Status>.

  • Vérifiez le status USBD défini par la pile de pilotes USB.

  • En cas d’erreurs de canal, effectuez des opérations de récupération d’erreurs. Pour plus d’informations, consultez Comment récupérer des erreurs de canal USB.

  • Vérifiez le nombre d’octets transférés.

    Un transfert en bloc est terminé lorsque le nombre d’octets demandé a été transféré vers ou depuis l’appareil. Si vous envoyez la mémoire tampon de requête en appelant la méthode KMDF, case activée la valeur reçue dans les membres CompletionParams-Parameters.Usb.Completion-Parameters.PipeWrite.Length>> ou CompletionParams-Parameters.Usb.Completion-Parameters.PipeRead.Length>>.

    Dans un transfert simple où la pile de pilotes USB envoie tous les octets demandés dans un paquet de données, vous pouvez case activée comparer la valeur Length au nombre d’octets demandés. Si la pile de pilotes USB transfère la requête dans plusieurs paquets de données, vous devez suivre le nombre d’octets transférés et le nombre d’octets restants.

  • Si le nombre total d’octets a été transféré, effectuez la demande. Si une condition d’erreur s’est produite, complétez la demande avec le code d’erreur retourné. Terminez la demande en appelant la méthode WdfRequestComplete . Si vous souhaitez définir des informations, telles que le nombre d’octets transférés, appelez WdfRequestCompleteWithInformation.

  • Assurez-vous que lorsque vous terminez la demande avec des informations, le nombre d’octets doit être égal ou inférieur au nombre d’octets demandés. L’infrastructure valide ces valeurs. Si la longueur définie dans la demande terminée est supérieure à la longueur de la requête d’origine, une vérification de bogue peut se produire.

Cet exemple de code montre comment le pilote client peut envoyer une demande de transfert en bloc. Le pilote définit une routine d’achèvement. Cette routine s’affiche dans le bloc de code suivant.

/*++

Routine Description:

This routine sends a bulk write request to the
USB driver stack. The request is sent asynchronously and
the driver gets notified through a completion routine.

Arguments:

Queue - Handle to a framework queue object.
Request - Handle to the framework request object.
Length - Number of bytes to transfer.


Return Value:

VOID

--*/


VOID Fx3EvtIoWrite(
    IN WDFQUEUE  Queue,
    IN WDFREQUEST  Request,
    IN size_t  Length
    )
{
    NTSTATUS  status;
    WDFUSBPIPE  pipe;
    WDFMEMORY  reqMemory;
    PDEVICE_CONTEXT  pDeviceContext;

    pDeviceContext = GetDeviceContext(WdfIoQueueGetDevice(Queue));

    pipe = pDeviceContext->BulkWritePipe;

    status = WdfRequestRetrieveInputMemory(
                                           Request,
                                           &reqMemory
                                           );
    if (!NT_SUCCESS(status))
    {
        goto Exit;
    }

    status = WdfUsbTargetPipeFormatRequestForWrite(
                                                   pipe,
                                                   Request,
                                                   reqMemory,
                                                   NULL
                                                   );
    if (!NT_SUCCESS(status))
       {
        goto Exit;
    }

    WdfRequestSetCompletionRoutine(
                                   Request,
                                   BulkWriteComplete,
                                   pipe
                                   );

    if (WdfRequestSend( Request,
                        WdfUsbTargetPipeGetIoTarget(pipe),
                        WDF_NO_SEND_OPTIONS) == FALSE)
       {
        status = WdfRequestGetStatus(Request);
        goto Exit;
    }

Exit:
    if (!NT_SUCCESS(status)) {
        WdfRequestCompleteWithInformation(
                                          Request,
                                          status,
                                          0
                                          );
    }
    return;
}

Cet exemple de code montre l’implémentation de la routine d’achèvement pour un transfert en bloc. Le pilote client termine la demande dans la routine de saisie semi-automatique et définit ces informations : status et le nombre d’octets transférés.

/*++

Routine Description:

This completion routine is invoked by the framework when
the USB drive stack completes the previously sent
bulk write request. The client driver completes the
the request if the total number of bytes were transferred
to the device.
In case of failure it queues a work item to start the
error recovery by resetting the target pipe.

Arguments:

Queue - Handle to a framework queue object.
Request - Handle to the framework request object.
Length - Number of bytes to transfer.
Pipe - Handle to the pipe that is the target for this request.

Return Value:

VOID

--*/

VOID BulkWriteComplete(
    _In_ WDFREQUEST                  Request,
    _In_ WDFIOTARGET                 Target,
    PWDF_REQUEST_COMPLETION_PARAMS   CompletionParams,
    _In_ WDFCONTEXT                  Context
    )
{

    PDEVICE_CONTEXT deviceContext;

    size_t          bytesTransferred=0;

    NTSTATUS        status;


    UNREFERENCED_PARAMETER (Target);
    UNREFERENCED_PARAMETER (Context);


    KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
        "In completion routine for Bulk transfer.\n"));

    // Get the device context. This is the context structure that
    // the client driver provided when it sent the request.

    deviceContext = (PDEVICE_CONTEXT)Context;

    // Get the status of the request
    status = CompletionParams->IoStatus.Status;
    if (!NT_SUCCESS (status))
    {
        // Get the USBD status code for more information about the error condition.
        status = CompletionParams->Parameters.Usb.Completion->UsbdStatus;

        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
            "Bulk transfer failed. 0x%x\n",
            status));

        // Queue a work item to start the reset-operation on the pipe
        // Not shown.

        goto Exit;
    }

    // Get the actual number of bytes transferred.
    bytesTransferred =
            CompletionParams->Parameters.Usb.Completion->Parameters.PipeWrite.Length;

    KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
            "Bulk transfer completed. Transferred %d bytes. \n",
            bytesTransferred));

Exit:

    // Complete the request and update the request with
    // information about the status code and number of bytes transferred.

    WdfRequestCompleteWithInformation(Request, status, bytesTransferred);

    return;
}