Dela via


Så här skickar du begäranden om USB-massöverföring

Det här avsnittet innehåller en kort översikt över USB-massöverföringar. Den innehåller också stegvisa instruktioner om hur en klientdrivrutin kan skicka och ta emot massdata från enheten.

Om bulk-endpunkter

En USB-massslutpunkt kan överföra stora mängder data. Massöverföringar är tillförlitliga som tillåter identifiering av maskinvarufel och omfattar ett begränsat antal återförsök i maskinvaran. För överföringar till massslutpunkter är bandbredden inte reserverad på bussen. När det finns flera överföringsbegäranden som riktar sig mot olika typer av slutpunkter schemalägger kontrollanten först överföringar för tidskritiska data, till exempel isochroniska paket och avbrottspaket. Endast om det finns oanvänd bandbredd tillgänglig på bussen schemalägger kontrollanten massöverföringar. Om det inte finns någon annan betydande trafik på bussen kan massöverföring vara snabb. Men när bussen är upptagen med andra överföringar kan massdata vänta på obestämd tid.

Här är de viktigaste funktionerna i en massslutpunkt:

  • Bulkändpunkter är valfria. De stöds av en USB-enhet som vill överföra stora mängder data. Till exempel överföra filer till ett flashminne, data till eller från en skrivare eller en skanner.
  • USB-enheter med full hastighet, hög hastighet och SuperSpeed stöder massslutpunkter. Låghastighetsenheter stöder inte massslutpunkter.
  • Slutpunkten är en enkelriktad och data kan överföras antingen i IN- eller OUT-riktning. Bulk-IN-endpunkten används för att läsa data från enheten till värddatorn och bulk-OUT-endpunkten används för att skicka data från värddatorn till enheten.
  • Slutpunkten har CRC-bitar för att söka efter fel och ger därmed dataintegritet. För CRC-fel överförs data automatiskt.
  • En SuperSpeed-massslutpunkt kan stödja strömmar. Strömmar gör det möjligt för värden att skicka överföringar till enskilda strömkanaler.
  • Den maximala paketstorleken för en massslutpunkt beror på enhetens busshastighet. För full hastighet, hög hastighet och SuperSpeed; de maximala paketstorlekarna är 64, 512 respektive 1 024 byte.

Masstransaktioner

Precis som alla andra USB-överföringar initierar hosten alltid en bulköverföring. Kommunikationen sker mellan värddatorn och målslutpunkt. USB-protokollet tillämpar inte något format på de data som skickas i en masstransaktion.

Hur värden och enheten kommunicerar på bussen beror på hur snabbt enheten är ansluten. I det här avsnittet beskrivs några exempel på massöverföringar i höghastighetsläge och SuperSpeed-läge som visar kommunikationen mellan värddatorn och enheten.

Du kan se strukturen för transaktioner och paket med hjälp av usb-analysverktyg, till exempel Beagle, Ellisys och LeCroy USB-protokollanalyserare. En analysenhet visar hur data skickas till eller tas emot från en USB-enhet via kabeln. I det här exemplet ska vi undersöka några spårningar som fångas av en LeCroy USB-analysator. Det här exemplet är endast för information. Detta är inte ett godkännande från Microsoft.

Bulk OUT-transaktionsexempel

Den här analysspårningen visar ett exempel på en bulk OUT-transaktion med hög fart.

Skärmbild som visar ett spår av en exempeltransaktion i en massanalys för utdata.

I den föregående spårningen initierar värddatorn en massöverföring till en hög-hastighets bulk-endpoint genom att skicka ett tokenpaket med PID inställt på OUT (OUT-token). Paketet innehåller adressen till enheten och målslutpunkten. Efter OUT-paketet skickar värddatorn ett datapaket som innehåller den stora datalasten. Om slutpunkten accepterar inkommande data skickar den ett ACK-paket. I det här exemplet kan vi se att hosten skickade 31 byte till enhetsadress: 1, slutpunktsadress: 2.

Om slutpunkten är upptagen när datapaketet tas emot och inte kan ta emot data kan enheten skicka ett NAK-paket. I så fall börjar värden skicka ping-paket till enheten. Enheten svarar med NAK-paket så länge enheten inte är redo att ta emot data. När enheten är klar svarar den med ett ACK-paket. Värden kan sedan återuppta OUT-överföringen.

Den här analysspårningen visar ett exempel på en SuperSpeed bulk OUT-transaktion.

Skärmbild som visar en spårning av ett exempel på en SuperSpeed-massdatatransaktion.

I föregående spårning initierar värden en OUT-transaktion till en SuperSpeed-massslutpunkt genom att skicka ett datapaket. Datapaketet innehåller masslasten, enhetsadresserna och slutpunktsadresserna. I det här exemplet kan vi se att värden skickade 31 byte till enhetsadress: 4; slutpunktsadress: 2.

Enheten tar emot och bekräftar datapaketet och skickar tillbaka ett ACK-paket till värden. Om slutpunkten är upptagen när datapaketet tas emot och inte kan ta emot data kan enheten skicka ett NRDY-paket. Till skillnad från i hög hastighet, när värden har tagit emot NRDY-paketet, avsöker den inte enheten upprepade gånger. I stället väntar värden på en ERDY från enheten. När enheten är klar skickar den ett ERDY-paket och värden kan sedan skicka data till mottagarpunkten.

Exempel på bulk-IN-transaktion

Den här analysspårningen visar ett exempel på en bulk IN-transaktion med hög hastighet.

Skärmbild som visar en spårning av ett exempel på massindatatransaktion.

I föregående spårning initierar värden transaktionen genom att skicka ett tokenpaket med PID inställt på IN (IN-token). Enheten skickar sedan ett datapaket med stornyttolast. Om slutpunkten inte har några data att skicka eller ännu inte är redo att skicka data kan enheten skicka ett NAK-handskakningspaket. Värden försöker överföra IN igen tills den tar emot ett ACK-paket från enheten. ACK-paketet innebär att enheten har godkänt data.

Den här analysspårningen visar ett exempel på en SuperSpeed-massinloggningstransaktion.

spårning av en exempeldatatransaktion.

För att initiera en bulk IN-överföring från en SuperSpeed-slutpunkt startar värden en masstransaktion genom att skicka ett ACK-paket. USB Specification version 3.0 optimerar den här första delen av överföringen genom att sammanfoga ACK- och IN-paket till ett ACK-paket. I stället för en IN-token skickar värden för SuperSpeed en ACK-token för att initiera en massöverföring. Enheten svarar med ett datapaket. Värden bekräftar sedan datapaketet genom att skicka ett ACK-paket. Om slutpunkten är upptagen och inte kunde skicka data kan enheten skicka status för NRDY. I så fall väntar värden tills det mottar ett ERDY-paket från enheten.

USB-klientdrivrutinsaktiviteter för massöverföring

Ett program eller en drivrutin på värddatorn initierar alltid en massöverföring för att skicka eller ta emot data. Klientdrivrutinen skickar begäran till USB-drivrutinsstacken. USB-drivrutinsstacken programmerar begäran till värdstyrenheten och skickar sedan protokollpaketen (enligt beskrivningen i föregående avsnitt) över kabeln till enheten.

Nu ska vi se hur klientdrivrutinen skickar begäran om bulköverföring som svar på en applikations eller en annan drivrutins begäran. Alternativt kan drivrutinen initiera överföringen på egen hand. Oavsett metod måste en förare ha överföringsbufferten och begäran för att initiera stora överföringar.

För en KMDF-drivrutin beskrivs begäran i ett ramverksbegärandeobjekt (se referens för WDF-begärandeobjektet). Klientdrivrutinen anropar metoder för begärandeobjektet genom att ange WDFREQUEST-handtaget för att skicka begäran till USB-drivrutinsstacken. Om klientdrivrutinen skickar en massöverföring som svar på en begäran från ett program eller en annan drivrutin skapar ramverket ett begärandeobjekt och levererar begäran till klientdrivrutinen med hjälp av ett ramverksköobjekt. I så fall kan klientdrivrutinen använda den begäran för att skicka massöverföringen. Om klientdrivrutinen initierade begäran kan drivrutinen välja att allokera sitt eget begärandeobjekt.

Om programmet eller en annan drivrutin skickade eller begärde data skickas överföringsbufferten till drivrutinen av ramverket. Alternativt kan klientdrivrutinen allokera överföringsbufferten och skapa begärandeobjektet om drivrutinen initierar överföringen på egen hand.

Här är de viktigaste uppgifterna för klientdrivrutinen:

  1. Hämta överföringsbufferten.
  2. Hämta, formatera och skicka ett ramverksbegärandeobjekt till USB-drivrutinsstacken.
  3. Implementera en slutföranderutin för att få ett meddelande när USB-drivrutinsstacken slutför begäran.

Det här avsnittet beskriver dessa uppgifter med hjälp av ett exempel där drivrutinen initierar en massöverföring som ett resultat av ett programs begäran om att skicka eller ta emot data.

För att läsa data från enheten kan klientdrivrutinen använda det ramverk som tillhandahåller en kontinuerlig läsare. Mer information finns i Så här använder du den kontinuerliga läsaren för att läsa data från ett USB-rör.

Exempel på massöverföringsbegäran

Tänk dig ett exempelscenario där ett program vill läsa eller skriva data till din enhet. Programmet anropar Windows-API:er för att skicka sådana begäranden. I det här exemplet öppnar programmet ett handtag till enheten med hjälp av GUID för enhetsgränssnittet, som publicerats av din drivrutin i kernelläge. Programmet anropar sedan ReadFile eller WriteFile för att initiera en läs- eller skrivbegäran. I det anropet anger programmet också en buffert som innehåller de data som ska läsas eller skrivas och längden på bufferten.

I/O-chefen tar emot begäran, skapar ett I/O-begärandepaket (IRP) och vidarebefordrar det till klientdrivrutinen.

Ramverket fångar upp begäran, skapar ett ramverksbegärandeobjekt och lägger till det i ramverksköobjektet. Ramverket meddelar sedan klientdrivrutinen att en ny begäran väntar på att bearbetas. Det meddelandet görs genom att anropa förarens köåteranropsrutiner för EvtIoRead eller EvtIoWrite.

När ramverket levererar begäran till klientdrivrutinen tar det emot följande parametrar:

  • WDFQUEUE hanterar det ramverksköobjekt som innehåller begäran.
  • WDFREQUEST hanterar det ramverksbegärandeobjekt som innehåller information om den här begäran.
  • Överföringslängden, d.v.s. antalet byte som ska läsas eller skrivas.

I klientdrivrutinens implementering av EvtIoRead eller EvtIoWritekontrollerar drivrutinen parametrarna för begäran och kan eventuellt utföra verifieringskontroller.

Om du använder strömmar av en SuperSpeed-massslutpunkt skickar du begäran i en URB eftersom KMDF inte stöder strömmar i sig. Information om hur du skickar en begäran om överföring till strömmar av en massslutpunkt finns i Så här öppnar och stänger du statiska strömmar i en USB-massslutpunkt.

Om du inte använder strömmar kan du använda KMDF-definierade metoder för att skicka begäran enligt beskrivningen i följande procedur:

Förutsättningar

Kontrollera att du har den här informationen innan du börjar:

  • Klientdrivrutinen måste ha skapat ramverkets USB-målenhetsobjekt och hämtat WDFUSBDEVICE-handtaget genom att anropa metoden WdfUsbTargetDeviceCreateWithParameters.

    Om du använder DE USB-mallar som tillhandahålls med Microsoft Visual Studio Professional 2012 utför mallkoden dessa uppgifter. Mallkoden hämtar handtaget till målenhetsobjektet och lagrar i enhetskontexten. För mer information, se "Enhetens källkod" i Förstå strukturen för USB-klientdrivrutins koden (KMDF).

  • WDFREQUEST hanterar det ramverksbegärandeobjekt som innehåller information om den här begäran.

  • Antalet byte som ska läsas eller skrivas.

  • WDFUSBPIPE-handtaget till ramverkspipeobjektet som är associerat med målslutpunkten. Du måste ha fått rörhandtag under enhetskonfigurationen genom att räkna upp rör. Mer information finns i Så här räknar du upp USB-rör.

    Om massslutpunkten stöder strömmar måste du ha rörhandtaget till strömmen. Mer information finns i Så här öppnar och stänger du statiska strömmar i en USB-slutpunkt.

Steg 1: Hämta överföringsbufferten

Överföringsbufferten eller MDL-överföringsbufferten innehåller de data som ska skickas eller ta emot. Det här avsnittet förutsätter att du skickar eller tar emot data i en överföringsbuffert. Överföringsbufferten beskrivs i ett WDF-minnesobjekt (se WDF-minnesobjektreferens). Om du vill hämta minnesobjektet som är associerat med överföringsbufferten anropar du någon av följande metoder:

Klientdrivrutinen behöver inte frigöra det här minnet. Minnet är associerat med det överordnade begäransobjektet och frigörs när det överordnade objektet släpps.

Steg 2: Formatera och skicka ett ramverksbegärandeobjekt till USB-drivrutinsstacken

Du kan skicka överföringsbegäran asynkront eller synkront.

Det här är de asynkrona metoderna:

Metoderna i den här listan formaterar begäran. Om du skickar begäran asynkront anger du en pekare till den drivrutins-implementerade slutföranderutinen genom att anropa metoden WdfRequestSetCompletionRoutine (beskrivs i nästa steg). Om du vill skicka begäran anropar du metoden WdfRequestSend.

Om du skickar begäran synkront anropar du följande metoder:

Kodexempel finns i avsnittet Exempel i referensavsnitten för dessa metoder.

Steg 3: Implementera en slutföranderutin för begäran

Om begäran skickas asynkront måste du implementera en slutföranderutin för att få ett meddelande när USB-drivrutinsstacken slutför begäran. När uppgiften är slutförd anropar ramverket drivrutinens slutföranderutin. Ramverket skickar följande parametrar:

  • WDFREQUEST-handtag till begäransobjektet.
  • WDFIOTARGET-handtag till I/O-målobjektet för begäran.
  • En pekare till en WDF_REQUEST_COMPLETION_PARAMS struktur som innehåller kompletteringsinformation. USB-specifik information finns i medlemmen CompletionParams->Parameters.Usb.
  • WDFCONTEXT hanterar kontexten som drivrutinen angav i sitt anrop till WdfRequestSetCompletionRoutine.

Utför följande uppgifter i slutföranderutinen:

  • Kontrollera statusen för begäran genom att hämta värdet CompletionParams–>IoStatus.Status.

  • Kontrollera USBD-statusen som angetts av USB-drivrutinsstacken.

  • Om det uppstår rörfel utför du felåterställningsåtgärder. Mer information finns i Så här återställer du från USB-rörfel.

  • Kontrollera antalet överförda byte.

    En massöverföring slutförs när det begärda antalet byte har överförts till eller från enheten. Om du skickar begärandebufferten genom att anropa KMDF-metoden kontrollerar du värdet som tas emot i CompletionParams–>Parameters.Usb.Completion–>Parameters.PipeWrite.Length eller CompletionParams–>Parameters.Usb.Completion–>Parameters.PipeRead.Length medlemmar.

    I en enkel överföring där USB-drivrutinsstacken skickar alla begärda byte i ett datapaket kan du jämföra värdet Längd med antalet begärda byte. Om USB-drivrutinsstacken överför begäran i flera datapaket måste du hålla reda på antalet byte som överförs och det återstående antalet byte.

  • Om det totala antalet byte har överförts slutför du begäran. Om ett feltillstånd uppstod slutför du begäran med den returnerade felkoden. Slutför begäran genom att anropa metoden WdfRequestComplete. Om du vill ange information, till exempel antalet byte som överförs, anropar du WdfRequestCompleteWithInformation.

  • Kontrollera att när du slutför begäran med information måste antalet byte vara lika med eller mindre än antalet begärda byte. Ramverket validerar dessa värden. Om längden i den slutförda begäran är större än den ursprungliga begärandelängden kan en felkontroll utföras.

Den här exempelkoden visar hur klientdrivrutinen kan skicka en massöverföringsbegäran. Drivrutinen anger en slutföranderutin. Den rutinen visas i nästa kodblock.

/*++

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

Den här exempelkoden visar implementeringen av slutföranderutinen för en massöverföring. Klientdrivrutinen slutför begäran i slutföranderutinen och anger den här begärandeinformationen: status och antalet byte som överförs.

/*++

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