Senden von USB-Massenübertragungsanforderungen

Dieses Thema bietet eine kurze Übersicht über USB-Massenübertragungen. Es enthält auch schrittweise Anweisungen dazu, wie ein Clienttreiber Massendaten vom Gerät senden und empfangen kann.

Informationen zu Massenendpunkten

Ein USB-Massenendpunkt kann große Datenmengen übertragen. Massenübertragungen sind zuverlässig und ermöglichen die Erkennung von Hardwarefehlern und beinhalten eine begrenzte Anzahl von Wiederholungen in der Hardware. Bei Übertragungen an Massenendpunkte ist die Bandbreite nicht auf dem Bus reserviert. Wenn mehrere Übertragungsanforderungen für verschiedene Arten von Endpunkten vorhanden sind, plant der Controller zunächst übertragungen für zeitkritische Daten, z. B. isochrone und Interruptpakete. Nur wenn auf dem Bus nicht verwendete Bandbreite verfügbar ist, plant der Controller Massenübertragungen. Wenn kein anderer wichtiger Datenverkehr im Bus vorhanden ist, kann der Massentransfer schnell sein. Wenn der Bus jedoch mit anderen Übertragungen beschäftigt ist, können Massendaten unbegrenzt warten.

Hier sind die wichtigsten Features eines Massenendpunkts:

  • Massenendpunkte sind optional. Sie werden von einem USB-Gerät unterstützt, das große Datenmengen übertragen möchte. Beispiel: Übertragen von Dateien auf ein Flash-Laufwerk, Daten auf oder von einem Drucker oder Scanner.
  • USB-Geräte mit voller Geschwindigkeit, Hoher Geschwindigkeit und SuperSpeed unterstützen Massenendpunkte. Geräte mit niedriger Geschwindigkeit unterstützen keine Massenendpunkte.
  • Der Endpunkt ist ein unidirektionaler Endpunkt, und Daten können entweder in IN- oder OUT-Richtung übertragen werden. Der Bulk IN-Endpunkt wird verwendet, um Daten vom Gerät an den Host zu lesen, und der Bulk OUT-Endpunkt wird verwendet, um Daten vom Host an das Gerät zu senden.
  • Der Endpunkt verfügt über CRC-Bits, die auf Fehler überprüft werden sollen, und stellt somit Datenintegrität bereit. Bei CRC-Fehlern werden Daten automatisch erneut übertragen.
  • Ein SuperSpeed-Massenendpunkt kann Streams unterstützen. Streams ermöglichen es dem Host, Übertragungen an einzelne Streampipes zu senden.
  • Die maximale Paketgröße eines Massenendpunkts hängt von der Busgeschwindigkeit des Geräts ab. Für volle Geschwindigkeit, hohe Geschwindigkeit und SuperSpeed; die maximalen Paketgrößen sind 64, 512 bzw. 1024 Bytes.

Massentransaktionen

Wie alle anderen USB-Übertragungen initiiert der Host immer eine Massenübertragung. Die Kommunikation findet zwischen dem Host und dem Zielendpunkt statt. Das USB-Protokoll erzwingt kein Format für die daten, die in einer Massentransaktion gesendet werden.

Wie Host und Gerät auf dem Bus kommunizieren, hängt von der Geschwindigkeit ab, mit der das Gerät verbunden ist. In diesem Abschnitt werden einige Beispiele für Hochgeschwindigkeits- und SuperSpeed-Massenübertragungen beschrieben, die die Kommunikation zwischen Host und Gerät darstellen.

Sie können die Struktur von Transaktionen und Paketen mithilfe eines beliebigen USB-Analysetools wie Beagle, Ellisys und LeCroy USB-Protokollanalysatoren anzeigen. Ein Analysegerät zeigt an, wie Daten über das Kabel an ein USB-Gerät gesendet oder von diesem empfangen werden. In diesem Beispiel untersuchen wir einige Ablaufverfolgungen, die von einem LeCroy-USB-Analysegerät erfasst wurden. Dieses Beispiel dient nur zur Information. Dies ist keine Billigung durch Microsoft.

Beispiel für eine Bulk OUT-Transaktion

Diese Analyseablaufverfolgung zeigt eine Beispiel-Bulk OUT-Transaktion mit hoher Geschwindigkeit.

Screenshot: Ablaufverfolgung einer Beispieltransaktion des Bulk OUT-Analysetools

In der vorherigen Ablaufverfolgung initiiert der Host eine Massenübertragung von OUT an einen Hochgeschwindigkeits-Massenendpunkt, indem er ein Tokenpaket sendet, bei dem PID auf OUT (OUT-Token) festgelegt ist. Das Paket enthält die Adresse des Geräts und des Zielendpunkts. Nach dem OUT-Paket sendet der Host ein Datenpaket, das die Massennutzlast enthält. Wenn der Endpunkt die eingehenden Daten akzeptiert, sendet er ein ACK-Paket. In diesem Beispiel sehen wir, dass der Host 31 Bytes an die Geräteadresse gesendet hat:1; Endpunktadresse: 2.

Wenn der Endpunkt zum Zeitpunkt des Eintreffens des Datenpakets ausgelastet ist und keine Daten empfangen kann, kann das Gerät ein NAK-Paket senden. In diesem Fall beginnt der Host mit dem Senden von PING-Paketen an das Gerät. Das Gerät reagiert mit NAK-Paketen, solange das Gerät nicht bereit ist, Daten zu empfangen. Wenn das Gerät bereit ist, antwortet es mit einem ACK-Paket. Der Host kann dann die OUT-Übertragung fortsetzen.

Diese Analyseablaufverfolgung zeigt eine Beispieltransaktion für SuperSpeed Bulk OUT.

Screenshot: Ablaufverfolgung einer SuperSpeed-Massendatentransaktion

In der vorherigen Ablaufverfolgung initiiert der Host eine OUT-Transaktion an einen SuperSpeed-Massenendpunkt, indem er ein Datenpaket sendet. Das Datenpaket enthält die Massennutznutz-, Geräte- und Endpunktadressen. In diesem Beispiel sehen wir, dass der Host 31 Bytes an die Geräteadresse gesendet hat: 4; Endpunktadresse: 2.

Das Gerät empfängt und bestätigt das Datenpaket und sendet ein ACK-Paket zurück an den Host. Wenn der Endpunkt zum Zeitpunkt des Eintreffens des Datenpakets ausgelastet ist und keine Daten empfangen kann, kann das Gerät ein NRDY-Paket senden. Im Gegensatz zu hoher Geschwindigkeit ruft der Host nach dem Empfang des NRDY-Pakets das Gerät nicht wiederholt ab. Stattdessen wartet der Host auf ein ERDY vom Gerät. Wenn das Gerät bereit ist, sendet es ein ERDY-Paket, und der Host kann dann Daten an den Endpunkt senden.

Beispiel für Massen-IN-Transaktionen

Diese Analyseablaufverfolgung zeigt eine Beispiel-Massen-IN-Transaktion mit hoher Geschwindigkeit.

Screenshot: Ablaufverfolgung einer Beispieltransaktion für Massendaten

In der vorherigen Ablaufverfolgung initiiert der Host die Transaktion, indem er ein Tokenpaket sendet, bei dem PID auf IN (IN-Token) festgelegt ist. Das Gerät sendet dann ein Datenpaket mit Massennutzlast. Wenn der Endpunkt über keine zu sendenden Daten verfügt oder noch nicht zum Senden von Daten bereit ist, kann das Gerät ein NAK-Handshakepaket senden. Der Host wiederholt die IN-Übertragung, bis er ein ACK-Paket vom Gerät empfängt. Dieses ACK-Paket impliziert, dass das Gerät die Daten akzeptiert hat.

Diese Analyseablaufverfolgung zeigt eine SuperSpeed-Massen-IN-Beispieltransaktion.

Ablaufverfolgung einer Beispieldatentransaktion.

Um eine Massen-IN-Übertragung von einem SuperSpeed-Endpunkt zu initiieren, startet der Host eine Massentransaktion, indem er ein ACK-Paket sendet. Die USB-Spezifikation Version 3.0 optimiert diesen anfänglichen Teil der Übertragung, indem ACK- und IN-Pakete zu einem ACK-Paket zusammengeführt werden. Anstelle eines IN-Tokens sendet der Host für SuperSpeed ein ACK-Token, um eine Massenübertragung zu initiieren. Das Gerät antwortet mit einem Datenpaket. Der Host bestätigt dann das Datenpaket, indem er ein ACK-Paket sendet. Wenn der Endpunkt ausgelastet ist und keine Daten senden konnte, kann das Gerät status von NRDY senden. In diesem Fall wartet der Host, bis er ein ERDY-Paket vom Gerät erhält.

USB-Clienttreiberaufgaben für eine Massenübertragung

Eine Anwendung oder ein Treiber auf dem Host initiiert immer eine Massenübertragung zum Senden oder Empfangen von Daten. Der Clienttreiber sendet die Anforderung an den USB-Treiberstapel. Der USB-Treiberstapel programmiert die Anforderung in den Hostcontroller und sendet dann die Protokollpakete (wie im vorherigen Abschnitt beschrieben) über das Kabel an das Gerät.

Sehen Wir uns an, wie der Clienttreiber die Anforderung für eine Massenübertragung als Ergebnis der Anforderung einer Anwendung oder einer anderen Treiberanforderung übermittelt. Alternativ kann der Treiber die Übertragung selbst initiieren. Unabhängig vom Ansatz muss ein Treiber über den Übertragungspuffer und die Anforderung verfügen, um die Massenübertragung zu initiieren.

Für einen KMDF-Treiber wird die Anforderung in einem Frameworkanforderungsobjekt beschrieben (siehe WDF-Anforderungsobjektreferenz). Der Clienttreiber ruft Methoden des Anforderungsobjekts auf, indem er das WDFREQUEST-Handle angibt, um die Anforderung an den USB-Treiberstapel zu senden. Wenn der Clienttreiber als Antwort auf eine Anforderung von einer Anwendung oder einem anderen Treiber eine Massenübertragung sendet, erstellt das Framework ein Anforderungsobjekt und übermittelt die Anforderung mithilfe eines Framework-Warteschlangenobjekts an den Clienttreiber. In diesem Fall kann der Clienttreiber diese Anforderung zum Senden der Massenübertragung verwenden. Wenn der Clienttreiber die Anforderung initiiert hat, kann der Treiber sein eigenes Anforderungsobjekt zuordnen.

Wenn die Anwendung oder ein anderer Treiber Daten gesendet oder angefordert hat, wird der Übertragungspuffer vom Framework an den Treiber übergeben. Alternativ kann der Clienttreiber den Übertragungspuffer zuordnen und das Anforderungsobjekt erstellen, wenn der Treiber die Übertragung selbst initiiert.

Hier sind die Standard Aufgaben für den Clienttreiber:

  1. Rufen Sie den Übertragungspuffer ab.
  2. Abrufen, Formatieren und Senden eines Frameworkanforderungsobjekts an den USB-Treiberstapel.
  3. Implementieren Sie eine Vervollständigungsroutine, um benachrichtigt zu werden, wenn der USB-Treiberstapel die Anforderung abgeschlossen hat.

In diesem Thema werden diese Aufgaben anhand eines Beispiels beschrieben, in dem der Treiber eine Massenübertragung als Ergebnis der Anforderung einer Anwendung zum Senden oder Empfangen von Daten initiiert.

Zum Lesen von Daten vom Gerät kann der Clienttreiber das vom Framework bereitgestellte Continuous Reader-Objekt verwenden. Weitere Informationen finden Sie unter Verwenden des kontinuierlichen Lesegeräts zum Lesen von Daten aus einer USB-Pipe.

Beispiel für eine Massenübertragungsanforderung

Stellen Sie sich ein Beispielszenario vor, in dem eine Anwendung Daten auf Ihr Gerät lesen oder schreiben möchte. Die Anwendung ruft Windows-APIs auf, um solche Anforderungen zu senden. In diesem Beispiel öffnet die Anwendung mithilfe der vom Treiber im Kernelmodus veröffentlichten Geräteschnittstellen-GUID ein Handle für das Gerät. Die Anwendung ruft dann ReadFile oder WriteFile auf, um eine Lese- oder Schreibanforderung zu initiieren. In diesem Aufruf gibt die Anwendung auch einen Puffer an, der die zu lesenden oder zu schreibenden Daten und die Länge dieses Puffers enthält.

Der E/A-Manager empfängt die Anforderung, erstellt ein E/A-Anforderungspaket (IRP) und leitet es an den Clienttreiber weiter.

Das Framework fängt die Anforderung ab, erstellt ein Frameworkanforderungsobjekt und fügt es dem Framework-Warteschlangenobjekt hinzu. Das Framework benachrichtigt dann den Clienttreiber, dass eine neue Anforderung auf die Verarbeitung wartet. Diese Benachrichtigung erfolgt durch Aufrufen der Warteschlangenrückrufroutinen des Treibers für EvtIoRead oder EvtIoWrite.

Wenn das Framework die Anforderung an den Clienttreiber übermittelt, empfängt es die folgenden Parameter:

  • WDFQUEUE-Handle für das Framework-Warteschlangenobjekt, das die Anforderung enthält.
  • WDFREQUEST-Handle für das Frameworkanforderungsobjekt, das Details zu dieser Anforderung enthält.
  • Die Übertragungslänge, d. h. die Anzahl der zu lesenden oder zu schreibenden Bytes.

In der Implementierung von EvtIoRead oder EvtIoWrite durch den Clienttreiber überprüft der Treiber die Anforderungsparameter und kann optional Überprüfungen durchführen.

Wenn Sie Streams eines SuperSpeed-Massenendpunkts verwenden, senden Sie die Anforderung in einer URB, da KMDF datenintern keine Datenströme unterstützt. Informationen zum Übermitteln einer Anforderung für die Übertragung an Streams eines Massenendpunkts finden Sie unter Öffnen und Schließen statischer Datenströme in einem USB-Massenendpunkt.

Wenn Sie keine Streams verwenden, können Sie kmdf-definierte Methoden verwenden, um die Anforderung zu senden, wie im folgenden Verfahren beschrieben:

Voraussetzungen

Bevor Sie beginnen, stellen Sie sicher, dass Sie über diese Informationen verfügen:

  • Der Clienttreiber muss das Framework-USB-Zielgerätobjekt erstellt und das WDFUSBDEVICE-Handle durch Aufrufen der WdfUsbTargetDeviceCreateWithParameters-Methode abgerufen haben.

    Wenn Sie die USB-Vorlagen verwenden, die mit Microsoft Visual Studio Professional 2012 bereitgestellt werden, führt der Vorlagencode diese Aufgaben aus. Der Vorlagencode ruft das Handle für das Zielgerätobjekt ab und speichert im Gerätekontext. Weitere Informationen finden Sie unter "Gerätequellcode" unter Grundlegendes zur USB-Clienttreibercodestruktur (KMDF).

  • WDFREQUEST-Handle für das Frameworkanforderungsobjekt, das Details zu dieser Anforderung enthält.

  • Die Anzahl der zu lesenden oder zu schreibenden Bytes.

  • Das WDFUSBPIPE-Handle für das Framework-Pipeobjekt, das dem Zielendpunkt zugeordnet ist. Sie müssen pipe handles während der Gerätekonfiguration durch Aufzählen von Rohren abgerufen haben. Weitere Informationen finden Sie unter Auflisten von USB-Rohren.

    Wenn der Massenendpunkt Streams unterstützt, müssen Sie über das Pipehandle für den Stream verfügen. Weitere Informationen finden Sie unter Öffnen und Schließen statischer Datenströme in einem USB-Massenendpunkt.

Schritt 1: Abrufen des Übertragungspuffers

Der Übertragungspuffer oder die Übertragungspuffer-MDL enthält die daten, die gesendet oder empfangen werden sollen. In diesem Thema wird davon ausgegangen, dass Sie Daten in einem Übertragungspuffer senden oder empfangen. Der Übertragungspuffer wird in einem WDF-Speicherobjekt beschrieben (siehe WDF-Speicherobjektreferenz). Um das Speicherobjekt abzurufen, das dem Übertragungspuffer zugeordnet ist, rufen Sie eine der folgenden Methoden auf:

Der Clienttreiber muss diesen Arbeitsspeicher nicht freigeben. Der Arbeitsspeicher ist dem übergeordneten Anforderungsobjekt zugeordnet und wird freigegeben, wenn das übergeordnete Objekt freigegeben wird.

Schritt 2: Formatieren und Senden eines Frameworkanforderungsobjekts an den USB-Treiberstapel

Sie können die Übertragungsanforderung asynchron oder synchron senden.

Dies sind die asynchronen Methoden:

Die Methoden in dieser Liste formatieren die Anforderung. Wenn Sie die Anforderung asynchron senden, legen Sie einen Zeiger auf die vom Treiber implementierte Vervollständigungsroutine fest, indem Sie die WdfRequestSetCompletionRoutine-Methode aufrufen (im nächsten Schritt beschrieben). Rufen Sie zum Senden der Anforderung die WdfRequestSend-Methode auf.

Wenn Sie die Anforderung synchron senden, rufen Sie die folgenden Methoden auf:

Codebeispiele finden Sie im Abschnitt Beispiele der Referenzthemen für diese Methoden.

Schritt 3: Implementieren einer Vervollständigungsroutine für die Anforderung

Wenn die Anforderung asynchron gesendet wird, müssen Sie eine Vervollständigungsroutine implementieren, um benachrichtigt zu werden, wenn der USB-Treiberstapel die Anforderung abgeschlossen hat. Nach Abschluss ruft das Framework die Vervollständigungsroutine des Treibers auf. Das Framework übergibt die folgenden Parameter:

  • WDFREQUEST-Handle für das Anforderungsobjekt.
  • WDFIOTARGET-Handle für das E/A-Zielobjekt für die Anforderung.
  • Ein Zeiger auf eine WDF_REQUEST_COMPLETION_PARAMS-Struktur , die Abschlussinformationen enthält. USB-spezifische Informationen sind im CompletionParams-Parameters.Usb-Member> enthalten.
  • WDFCONTEXT-Handle für den Kontext, den der Treiber in seinem Aufruf von WdfRequestSetCompletionRoutine angegeben hat.

Führen Sie in der Abschlussroutine die folgenden Aufgaben aus:

  • Überprüfen Sie die status der Anforderung, indem Sie den Wert CompletionParams-IoStatus.Status> abrufen.

  • Überprüfen Sie die vom USB-Treiberstapel festgelegten USBD-status.

  • Führen Sie bei Pipefehlern Fehlerwiederherstellungsvorgänge aus. Weitere Informationen finden Sie unter Wiederherstellen nach USB-Pipefehlern.

  • Überprüfen Sie die Anzahl der übertragenen Bytes.

    Eine Massenübertragung ist abgeschlossen, wenn die angeforderte Anzahl von Bytes an oder vom Gerät übertragen wurde. Wenn Sie den Anforderungspuffer durch Aufrufen der KMDF-Methode senden, überprüfen Sie den Wert, der in completionParams-Parameters.Usb.Completion-Parameters.PipeWrite.Length>> oder CompletionParams-Parameters.Usb.Completion-Parameters.PipeRead.Length-Membern>> empfangen wird.

    In einer einfachen Übertragung, bei der der USB-Treiberstapel alle angeforderten Bytes in einem Datenpaket sendet, können Sie überprüfen, ob der Length-Wert mit der Anzahl der angeforderten Bytes verglichen wird. Wenn der USB-Treiberstapel die Anforderung in mehreren Datenpaketen überträgt, müssen Sie die Anzahl der übertragenen Bytes und die verbleibende Anzahl von Bytes nachverfolgen.

  • Wenn die Gesamtzahl der Bytes übertragen wurde, schließen Sie die Anforderung ab. Wenn eine Fehlerbedingung aufgetreten ist, schließen Sie die Anforderung mit dem zurückgegebenen Fehlercode ab. Schließen Sie die Anforderung ab, indem Sie die WdfRequestComplete-Methode aufrufen. Wenn Sie Informationen festlegen möchten, z. B. die Anzahl der übertragenen Bytes, rufen Sie WdfRequestCompleteWithInformation auf.

  • Stellen Sie sicher, dass die Anzahl der Bytes gleich oder kleiner sein muss, wenn Sie die Anforderung mit Informationen abschließen. Das Framework überprüft diese Werte. Wenn die in der abgeschlossenen Anforderung festgelegte Länge größer als die ursprüngliche Anforderungslänge ist, kann eine Fehlerüberprüfung auftreten.

Dieser Beispielcode zeigt, wie der Clienttreiber eine Massenübertragungsanforderung übermitteln kann. Der Treiber legt eine Vervollständigungsroutine fest. Diese Routine wird im nächsten Codeblock angezeigt.

/*++

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

Dieser Beispielcode zeigt die Implementierung der Vervollständigungsroutine für eine Massenübertragung. Der Clienttreiber schließt die Anforderung in der Vervollständigungsroutine ab und legt diese Anforderungsinformationen fest: status und die Anzahl der übertragenen Bytes.

/*++

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