Condividi tramite


Come inviare richieste di trasferimento bulk USB

Questo argomento fornisce una breve panoramica dei trasferimenti in blocco USB. Fornisce inoltre istruzioni dettagliate su come un driver client può inviare e ricevere dati in blocco dal dispositivo.

Informazioni sugli endpoint in blocco

Un endpoint bulk USB può trasferire grandi quantità di dati. I trasferimenti in blocco sono affidabili che consentono il rilevamento degli errori hardware e comportano un numero limitato di tentativi nell'hardware. Per i trasferimenti agli endpoint in blocco, la larghezza di banda non è riservata sul bus. Quando sono presenti più richieste di trasferimento destinate a diversi tipi di endpoint, il controller pianifica prima i trasferimenti per i dati critici per il tempo, ad esempio i pacchetti isocroni e di interrupt. Solo se nel bus è disponibile una larghezza di banda inutilizzata, il controller pianifica i trasferimenti in blocco. Se non c'è altro traffico significativo sull'autobus, il trasferimento in blocco può essere veloce. Tuttavia, quando l'autobus è occupato con altri trasferimenti, i dati in blocco possono attendere per un periodo illimitato.

Ecco le funzionalità principali di un endpoint in blocco:

  • Gli endpoint in blocco sono facoltativi. Sono supportati da un dispositivo USB che vuole trasferire grandi quantità di dati. Ad esempio, il trasferimento di file in un'unità flash, dati da o verso una stampante o uno scanner.
  • I dispositivi USB a velocità elevata e SuperSpeed supportano gli endpoint in blocco. I dispositivi a bassa velocità non supportano gli endpoint in blocco.
  • L'endpoint è unidirezionale e i dati possono essere trasferiti in una direzione IN o OUT. L'endpoint IN bulk viene usato per leggere i dati dal dispositivo all'host e l'endpoint OUT bulk viene usato per inviare dati dall'host al dispositivo.
  • L'endpoint ha bit CRC per verificare la presenza di errori e quindi fornisce l'integrità dei dati. Per gli errori CRC, i dati vengono ritrasmessi automaticamente.
  • Un endpoint in blocco SuperSpeed può supportare i flussi. I flussi consentono all'host di inviare trasferimenti a singole pipe di flusso.
  • La dimensione massima dei pacchetti di un endpoint bulk dipende dalla velocità del bus del dispositivo. Per la massima velocità, alta velocità e SuperSpeed; le dimensioni massime dei pacchetti sono rispettivamente 64, 512 e 1024 byte.

Transazioni in blocco

Come tutti gli altri trasferimenti USB, l'host avvia sempre un trasferimento in blocco. La comunicazione avviene tra l'host e l'endpoint di destinazione. Il protocollo USB non applica alcun formato ai dati inviati in una transazione bulk.

Il modo in cui l'host e il dispositivo comunicano sul bus dipendono dalla velocità con cui il dispositivo è connesso. Questa sezione descrive alcuni esempi di trasferimenti in blocco ad alta velocità e SuperSpeed che mostrano la comunicazione tra l'host e il dispositivo.

È possibile visualizzare la struttura delle transazioni e dei pacchetti usando qualsiasi analizzatore USB, ad esempio Beagle, Ellisys, analizzatori del protocollo USB LeCroy. Un dispositivo analizzatore mostra come i dati vengono inviati o ricevuti da un dispositivo USB in rete. In questo esempio verranno esaminate alcune tracce acquisite da un analizzatore USB LeCroy. Questo esempio è solo per informazioni. Non si tratta di un'approvazione da parte di Microsoft.

Esempio di transazione OUT bulk

Questa traccia dell'analizzatore mostra un esempio di transazione BULK OUT ad alta velocità.

Screenshot che mostra una traccia di una transazione dell'analizzatore OUT bulk di esempio.

Nella traccia precedente, l'host avvia un trasferimento out bulk a un endpoint bulk ad alta velocità inviando un pacchetto di token con PID impostato su OUT (token OUT). Il pacchetto contiene l'indirizzo del dispositivo e dell'endpoint di destinazione. Dopo il pacchetto OUT, l'host invia un pacchetto di dati contenente il payload bulk. Se l'endpoint accetta i dati in ingresso, invia un pacchetto ACK. In questo esempio è possibile notare che l'host ha inviato 31 byte all'indirizzo del dispositivo:1; indirizzo endpoint: 2.

Se l'endpoint è occupato al momento dell'arrivo del pacchetto di dati e non è in grado di ricevere dati, il dispositivo può inviare un pacchetto NAK. In tal caso, l'host inizia a inviare pacchetti PING al dispositivo. Il dispositivo risponde con pacchetti NAK purché il dispositivo non sia pronto per ricevere i dati. Quando il dispositivo è pronto, risponde con un pacchetto ACK. L'host può quindi riprendere il trasferimento OUT.

Questa traccia dell'analizzatore mostra un esempio di transazione OUT bulk SuperSpeed.

Screenshot che mostra una traccia di una transazione dati OUT bulk superSpeed di esempio.

Nella traccia precedente, l'host avvia una transazione OUT a un endpoint bulk SuperSpeed inviando un pacchetto di dati. Il pacchetto di dati contiene gli indirizzi di payload, dispositivo ed endpoint in blocco. In questo esempio è possibile notare che l'host ha inviato 31 byte all'indirizzo del dispositivo:4; indirizzo endpoint: 2.

Il dispositivo riceve e riconosce il pacchetto di dati e invia un pacchetto ACK all'host. Se l'endpoint è occupato al momento dell'arrivo del pacchetto di dati e non è in grado di ricevere dati, il dispositivo può inviare un pacchetto NRDY. A differenza dell'alta velocità, dopo aver ricevuto il pacchetto NRDY, l'host non esegue ripetutamente il polling del dispositivo. L'host attende invece un ERDY dal dispositivo. Quando il dispositivo è pronto, invia un pacchetto ERDY e l'host può quindi inviare dati all'endpoint.

Esempio di transazione BULK IN

Questa traccia dell'analizzatore mostra un esempio di transazione BULK IN ad alta velocità.

Screenshot che mostra una traccia di una transazione dati BULK IN di esempio.

Nella traccia precedente, l'host avvia la transazione inviando un pacchetto di token con PID impostato su IN (token IN). Il dispositivo invia quindi un pacchetto di dati con payload bulk. Se l'endpoint non ha dati da inviare o non è ancora pronto per l'invio dei dati, il dispositivo può inviare un pacchetto handshake NAK. L'host ritenta il trasferimento IN finché non riceve un pacchetto ACK dal dispositivo. Il pacchetto ACK implica che il dispositivo ha accettato i dati.

Questa traccia dell'analizzatore mostra un esempio di transazione Bulk IN SuperSpeed.

traccia di una transazione di dati di esempio.

Per avviare un trasferimento in blocco da un endpoint SuperSpeed, l'host avvia una transazione in blocco inviando un pacchetto ACK. La specifica USB versione 3.0 ottimizza questa parte iniziale del trasferimento unendo pacchetti ACK e IN in un unico pacchetto ACK. Invece di un token IN, per SuperSpeed, l'host invia un token ACK per avviare un trasferimento in blocco. Il dispositivo risponde con un pacchetto di dati. L'host riconosce quindi il pacchetto di dati inviando un pacchetto ACK. Se l'endpoint è occupato e non è stato in grado di inviare dati, il dispositivo può inviare lo stato NRDY. In tal caso, l'host attende finché non ottiene un pacchetto ERDY dal dispositivo.

Attività del driver client USB per un trasferimento bulk

Un'applicazione o un driver nell'host avvia sempre un trasferimento in blocco per inviare o ricevere dati. Il driver client invia la richiesta allo stack di driver USB. Lo stack di driver USB programma la richiesta nel controller host e quindi invia i pacchetti di protocollo (come descritto nella sezione precedente) attraverso il collegamento al dispositivo.

Vediamo come il driver client invia la richiesta di trasferimento in blocco a seguito della richiesta di un'applicazione o di un altro driver. In alternativa, il driver può avviare il trasferimento autonomamente. Indipendentemente dall'approccio, un driver deve avere il buffer di trasferimento e la richiesta per avviare il trasferimento in blocco.

Per un driver KMDF, la richiesta viene descritta in un oggetto richiesta framework (vedere Riferimento all'oggetto richiesta WDF). Il driver client chiama i metodi dell'oggetto richiesta specificando l'handle WDFREQUEST per inviare la richiesta allo stack di driver USB. Se il driver client invia un trasferimento in blocco in risposta a una richiesta da un'applicazione o da un altro driver, il framework crea un oggetto richiesta e recapita la richiesta al driver client usando un oggetto coda framework. In tal caso, il driver client può usare tale richiesta per l'invio del trasferimento in blocco. Se il driver client ha avviato la richiesta, il driver può scegliere di allocare il proprio oggetto richiesta.

Se l'applicazione o un altro driver ha inviato o richiesto dati, il buffer di trasferimento viene passato al driver dal framework. In alternativa, il driver client può allocare il buffer di trasferimento e creare l'oggetto richiesta se il driver avvia il trasferimento autonomamente.

Ecco le attività principali per il driver client:

  1. Ottenere il buffer di trasferimento.
  2. Ottenere, formattare e inviare un oggetto richiesta framework allo stack di driver USB.
  3. Implementare una routine di completamento per ricevere una notifica quando lo stack di driver USB completa la richiesta.

In questo argomento vengono descritte le attività usando un esempio in cui il driver avvia un trasferimento in blocco in seguito alla richiesta di un'applicazione di inviare o ricevere dati.

Per leggere i dati dal dispositivo, il driver client può usare il framework fornito dall'oggetto lettore continuo. Per altre informazioni, vedere Come usare il lettore continuo per la lettura dei dati da una pipe USB.

Esempio di richiesta di trasferimento bulk

Si consideri uno scenario di esempio, in cui un'applicazione vuole leggere o scrivere dati nel dispositivo. L'applicazione chiama le API di Windows per inviare tali richieste. In questo esempio, l'applicazione apre un handle al dispositivo usando il GUID dell'interfaccia del dispositivo pubblicato dal driver in modalità kernel. L'applicazione chiama quindi ReadFile o WriteFile per avviare una richiesta di lettura o scrittura. In tale chiamata, l'applicazione specifica anche un buffer che contiene i dati da leggere o scrivere e la lunghezza del buffer.

Gestione I/O riceve la richiesta, crea un pacchetto di richiesta I/O (IRP) e lo inoltra al driver client.

Il framework intercetta la richiesta, crea un oggetto richiesta framework e lo aggiunge all'oggetto coda del framework. Il framework invia quindi una notifica al driver client che una nuova richiesta è in attesa di essere elaborata. Tale notifica viene eseguita richiamando le routine di callback della coda del driver per EvtIoRead o EvtIoWrite.

Quando il framework invia la richiesta al driver client, riceve questi parametri:

  • Handle WDFQUEUE per l'oggetto coda del framework che contiene la richiesta.
  • Handle WDFREQUEST per l'oggetto richiesta framework che contiene dettagli su questa richiesta.
  • Lunghezza del trasferimento, ovvero il numero di byte da leggere o scrivere.

Nell'implementazione del driver client di EvtIoRead o EvtIoWrite, il driver controlla i parametri della richiesta e può facoltativamente eseguire controlli di convalida.

Se si usano flussi di un endpoint in blocco SuperSpeed, si invierà la richiesta in un'istanza DI ISTRUZIONE PERCHÉ KMDF non supporta i flussi intrinsecamente. Per informazioni sull'invio di una richiesta di trasferimento ai flussi di un endpoint bulk, vedere Come aprire e chiudere flussi statici in un endpoint bulk USB.

Se non si usano flussi, è possibile usare i metodi definiti da KMDF per inviare la richiesta come descritto nella procedura seguente:

Prerequisiti

Prima di iniziare, assicurarsi di avere queste informazioni:

  • Il driver client deve aver creato l'oggetto dispositivo di destinazione USB del framework e ottenuto l'handle WDFUSBDEVICE chiamando il metodo WdfUsbTargetDeviceCreateWithParameters .

    Se si usano i modelli USB forniti con Microsoft Visual Studio Professional 2012, il codice del modello esegue tali attività. Il codice del modello ottiene l'handle per l'oggetto dispositivo di destinazione e archivia nel contesto del dispositivo. Per altre informazioni, vedere "Codice sorgente del dispositivo" in Informazioni sulla struttura del codice del driver client USB (KMDF).

  • Handle WDFREQUEST per l'oggetto richiesta framework che contiene dettagli su questa richiesta.

  • Numero di byte da leggere o scrivere.

  • Handle WDFUSBPIPE per l'oggetto pipe del framework associato all'endpoint di destinazione. È necessario aver ottenuto handle pipe durante la configurazione del dispositivo enumerando le pipe. Per altre informazioni, vedere Come enumerare pipe USB.

    Se l'endpoint bulk supporta i flussi, è necessario disporre dell'handle della pipe al flusso. Per altre informazioni, vedere Come aprire e chiudere flussi statici in un endpoint bulk USB.

Passaggio 1: Ottenere il buffer di trasferimento

Il buffer di trasferimento o il MDL del buffer di trasferimento contiene i dati da inviare o ricevere. In questo argomento si presuppone che si inviino o ricevano dati in un buffer di trasferimento. Il buffer di trasferimento è descritto in un oggetto memoria WDF (vedere WDF Memory Object Reference). Per ottenere l'oggetto memoria associato al buffer di trasferimento, chiamare uno dei metodi seguenti:

Il driver client non deve rilasciare questa memoria. La memoria è associata all'oggetto richiesta padre e viene rilasciata quando viene rilasciato l'elemento padre.

Passaggio 2: Formattare e inviare un oggetto richiesta framework allo stack di driver USB

È possibile inviare la richiesta di trasferimento in modo asincrono o sincrono.

Questi sono i metodi asincroni:

I metodi in questo elenco formattano la richiesta. Se si invia la richiesta in modo asincrono, impostare un puntatore alla routine di completamento implementata dal driver chiamando il metodo WdfRequestSetCompletionRoutine (descritto nel passaggio successivo). Per inviare la richiesta, chiamare il metodo WdfRequestSend .

Se si invia la richiesta in modo sincrono, chiamare questi metodi:

Per esempi di codice, vedere la sezione Esempi degli argomenti di riferimento per tali metodi.

Passaggio 3: Implementare una routine di completamento per la richiesta

Se la richiesta viene inviata in modo asincrono, è necessario implementare una routine di completamento per ricevere una notifica quando lo stack di driver USB completa la richiesta. Al termine, il framework richiama la routine di completamento del driver. Il framework passa questi parametri:

  • Handle WDFREQUEST per l'oggetto richiesta.
  • Handle WDFIOTARGET per l'oggetto di destinazione I/O per la richiesta.
  • Puntatore a una struttura WDF_REQUEST_COMPLETION_PARAMS contenente informazioni di completamento. Le informazioni specifiche di USB sono contenute nel membro CompletionParams-Parameters.Usb>.
  • WDFCONTEXT gestisce il contesto specificato dal driver nella chiamata a WdfRequestSetCompletionRoutine.

Nella routine di completamento eseguire queste attività:

  • Controllare lo stato della richiesta ottenendo il valore CompletionParams-IoStatus.Status>.

  • Controllare lo stato USBD impostato dallo stack di driver USB.

  • In caso di errori di pipe, eseguire operazioni di ripristino degli errori. Per altre informazioni, vedere Come eseguire il ripristino da errori di pipe USB.

  • Controllare il numero di byte trasferiti.

    Un trasferimento in blocco viene completato quando il numero di byte richiesto è stato trasferito da o verso il dispositivo. Se si invia il buffer della richiesta chiamando il metodo KMDF, controllare il valore ricevuto nei membri CompletionParams-Parameters.Usb.Completion-Parameters.PipeWrite.Length>> o CompletionParams-Parameters.Usb.Completion-Parameters.PipeRead.Length>>.

    In un trasferimento semplice in cui lo stack di driver USB invia tutti i byte richiesti in un pacchetto di dati, è possibile controllare confrontare il valore Length con il numero di byte richiesti. Se lo stack di driver USB trasferisce la richiesta in più pacchetti di dati, è necessario tenere traccia del numero di byte trasferiti e del numero di byte rimanenti.

  • Se il numero totale di byte è stato trasferito, completare la richiesta. Se si è verificata una condizione di errore, completare la richiesta con il codice di errore restituito. Completare la richiesta chiamando il metodo WdfRequestComplete . Se vuoi impostare informazioni, ad esempio il numero di byte trasferiti, chiama WdfRequestCompleteWithInformation.

  • Assicurarsi che quando si completa la richiesta con informazioni, il numero di byte deve essere uguale o minore del numero di byte richiesti. Il framework convalida tali valori. Se la lunghezza impostata nella richiesta completata è maggiore della lunghezza della richiesta originale, può verificarsi un controllo bug.

Questo codice di esempio mostra come il driver client può inviare una richiesta di trasferimento in blocco. Il driver imposta una routine di completamento. Tale routine viene visualizzata nel blocco di codice successivo.

/*++

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;
}

Questo codice di esempio mostra l'implementazione della routine di completamento per un trasferimento bulk. Il driver client completa la richiesta nella routine di completamento e imposta queste informazioni sulla richiesta: stato e numero di byte trasferiti.

/*++

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;
}