Öffnen und Schließen statischer Datenströme in einem USB-Massenendpunkt

In diesem Artikel wird die Funktion statischer Streams erläutert und erläutert, wie ein USB-Clienttreiber Datenströme in einem Massenendpunkt eines USB 3.0-Geräts öffnen und schließen kann.

Bei USB 2.0- und früheren Geräten kann ein Massenendpunkt einen einzelnen Datenstrom über den Endpunkt senden oder empfangen. Auf USB 3.0-Geräten können Massenendpunkte mehrere Datenströme über den Endpunkt senden und empfangen.

Der von Microsoft bereitgestellte USB-Treiberstapel in Windows unterstützt mehrere Streams. Dadurch kann ein Clienttreiber unabhängige E/A-Anforderungen an jeden Stream senden, der einem Massenendpunkt auf einem USB 3.0-Gerät zugeordnet ist. Die Anforderungen an verschiedene Streams werden nicht serialisiert.

Für einen Clienttreiber stellen Streams mehrere logische Endpunkte dar, die denselben Satz von Merkmalen aufweisen. Um eine Anforderung an einen bestimmten Stream zu senden, benötigt der Clienttreiber ein Handle für diesen Stream (ähnlich einem Pipehandle für einen Endpunkt). Die URB für eine E/A-Anforderung an einen Stream ähnelt einer URB für eine E/A-Anforderung an einen Massenendpunkt. Der einzige Unterschied ist der Ziehpunkt. Um eine E/A-Anforderung an einen Stream zu senden, gibt der Treiber das Pipehandle an den Stream an.

Während der Gerätekonfiguration sendet der Clienttreiber eine Select-Configuration-Anforderung und optional eine Select-Interface-Anforderung. Diese Anforderungen rufen eine Reihe von Pipehandles an die Endpunkte ab, die in der aktiven Einstellung einer Schnittstelle definiert sind. Für einen Endpunkt, der Datenströme unterstützt, kann das Endpunktpipehandle verwendet werden, um E/A-Anforderungen an den Standarddatenstrom (den ersten Stream) zu senden, bis der Treiber Streams geöffnet hat (nächstes Thema).

Wenn der Clienttreiber Anforderungen an andere Streams als den Standarddatenstrom senden möchte, muss der Treiber Handles für alle Streams öffnen und abrufen. Dazu sendet der Clienttreiber eine Open-Streams-Anforderung , indem er die Anzahl der zu öffnenden Streams angibt. Nachdem der Clienttreiber die Verwendung von Streams abgeschlossen hat, kann der Treiber diese optional schließen, indem er eine Close-Streams-Anforderung sendet.

Das Kernelmodustreiberframework (KMDF) unterstützt statische Datenströme intrinsisch nicht. Der Clienttreiber muss URBs im WDM-Format (Windows Driver Model) senden, die Datenströme öffnen und schließen. In diesem Artikel wird beschrieben, wie Diese URBs formatiert und gesendet werden. Ein UMDF-Clienttreiber (User Mode Driver Framework) kann die Funktion statische Datenströme nicht verwenden.

Der Artikel enthält einige Notizen, die als WDM-Treiber bezeichnet werden. Diese Hinweise beschreiben Routinen für einen WDM-basierten USB-Clienttreiber, der Streamanforderungen senden möchte.

Voraussetzungen

Bevor ein Clienttreiber Streams öffnen oder schließen kann, muss der Treiber über Folgendes verfügen:

  • Die WdfUsbTargetDeviceCreateWithParameters-Methode wurde aufgerufen.

    Die -Methode erfordert, dass die Clientvertragsversion USBD_CLIENT_CONTRACT_VERSION_602 ist. Durch Angeben dieser Version muss der Clienttreiber eine Reihe von Regeln einhalten. Weitere Informationen finden Sie unter Bewährte Methoden: Verwenden von URBs.

    Der Aufruf ruft ein WDFUSBDEVICE-Handle für das USB-Zielgerätobjekt des Frameworks ab. Dieses Handle ist erforderlich, um nachfolgende Aufrufe von geöffneten Streams auszuführen. In der Regel registriert sich der Clienttreiber in der EVT_WDF_DEVICE_PREPARE_HARDWARE Ereignisrückrufroutine des Treibers.

    WDM-Treiber: Rufen Sie die USBD_CreateHandle Routine auf, und rufen Sie ein USBD-Handle für die Registrierung des Treibers beim USB-Treiberstapel ab.

  • Das Gerät konfiguriert und ein WDFUSBPIPE-Pipehandle für den Massenendpunkt abgerufen, der Streams unterstützt. Um das Pipehandle abzurufen, rufen Sie die WdfUsbInterfaceGetConfiguredPipe-Methode für die aktuelle alternative Einstellung einer Schnittstelle in der ausgewählten Konfiguration auf.

    WDM-Treiber: Rufen Sie ein USBD-Pipehandle ab, indem Sie eine select-configuration- oder select-interface-Anforderung senden. Weitere Informationen finden Sie unter Auswählen einer Konfiguration für ein USB-Gerät.

Öffnen statischer Datenströme

  1. Ermitteln Sie, ob der zugrunde liegende USB-Treiberstapel und der Hostcontroller die Funktion statische Datenströme unterstützen, indem Sie die WdfUsbTargetDeviceQueryUsbCapability-Methode aufrufen. In der Regel ruft der Clienttreiber die Routine in der EVT_WDF_DEVICE_PREPARE_HARDWARE Ereignisrückrufroutine des Treibers auf.

    WDM-Treiber: Rufen Sie die routine USBD_QueryUsbCapability auf. In der Regel fragt der Treiber die Funktionen ab, die er in der Start-/Geräteroutine des Treibers (IRP_MN_START_DEVICE) verwenden möchte. Codebeispiel finden Sie unter USBD_QueryUsbCapability.

    Geben Sie die folgenden Informationen ein:

    • Ein Handle für das USB-Geräteobjekt, das in einem vorherigen Aufruf von WdfUsbTargetDeviceCreateWithParameters für die Clienttreiberregistrierung abgerufen wurde.

      WDM-Treiber: Übergeben Sie das USBD-Handle, das im vorherigen Aufruf abgerufen wurde, an USBD_CreateHandle.

      Wenn der Clienttreiber eine bestimmte Funktion verwenden möchte, muss der Treiber zuerst den zugrunde liegenden USB-Treiberstapel abfragen, um festzustellen, ob der Treiberstapel und der Hostcontroller die Funktion unterstützen. Wenn die Funktion unterstützt wird, sollte der Treiber nur dann eine Anforderung senden, um die Funktion zu verwenden. Einige Anforderungen erfordern URBs, z. B. die Datenstromfunktion (erläutert in Schritt 5). Stellen Sie für diese Anforderungen sicher, dass Sie dasselbe Handle verwenden, um Funktionen abzufragen und URBs zuzuweisen. Dies liegt daran, dass der Treiberstapel Handles verwendet, um die unterstützten Funktionen nachzuverfolgen, die ein Treiber verwenden kann.

      Wenn Sie für instance eine USBD_HANDLE erhalten haben (durch Aufrufen von USBD_CreateHandle), fragen Sie den Treiberstapel ab, indem Sie USBD_QueryUsbCapability aufrufen, und ordnen Sie die URB zu, indem Sie USBD_UrbAllocate aufrufen. Übergeben Sie in beiden Aufrufen dieselbe USBD_HANDLE.

      Wenn Sie KMDF-Methoden aufrufen, WdfUsbTargetDeviceQueryUsbCapability und WdfUsbTargetDeviceCreateUrb, geben Sie in diesen Methodenaufrufen dasselbe WDFUSBDEVICE-Handle für das Frameworkzielobjekt an.

    • Die GUID, die GUID_USB_CAPABILITY_STATIC_STREAMS zugewiesen ist.

    • Ein Ausgabepuffer (Zeiger auf USHORT). Nach Abschluss des Vorgangs wird der Puffer mit der maximalen Anzahl von Streams (pro Endpunkt) gefüllt, die vom Hostcontroller unterstützt werden.

    • Die Länge des Ausgabepuffers in Bytes. Für Streams ist sizeof (USHORT)die Länge .

  2. Werten Sie den zurückgegebenen NTSTATUS-Wert aus. Wenn die Routine erfolgreich abgeschlossen wurde, wird STATUS_SUCCESS zurückgegeben, die Funktion statische Datenströme wird unterstützt. Andernfalls gibt die Methode einen geeigneten Fehlercode zurück.

  3. Bestimmen Sie die Anzahl der zu öffnenden Streams. Die maximale Anzahl von Datenströmen, die geöffnet werden können, ist begrenzt durch:

    • Die maximale Anzahl von Streams, die vom Hostcontroller unterstützt werden. Diese Nummer wird von WdfUsbTargetDeviceQueryUsbCapability (für WDM-Treiber, USBD_QueryUsbCapability) im vom Aufrufer bereitgestellten Ausgabepuffer empfangen. Der von Microsoft bereitgestellte USB-Treiberstapel unterstützt bis zu 255 Streams. WdfUsbTargetDeviceQueryUsbCapability berücksichtigt diese Einschränkung bei der Berechnung der Anzahl der Datenströme. Die -Methode gibt nie einen Wert zurück, der größer als 255 ist.
    • Die maximale Anzahl von Streams, die vom Endpunkt auf dem Gerät unterstützt werden. Um diese Nummer zu erhalten, überprüfen Sie den Endpunkt-Begleitdeskriptor (siehe USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR in Usbspec.h). Um den Endpunkt-Begleitdeskriptor abzurufen, müssen Sie den Konfigurationsdeskriptor analysieren. Um den Konfigurationsdeskriptor abzurufen, muss der Clienttreiber die WdfUsbTargetDeviceRetrieveConfigDescriptor-Methode aufrufen. Sie müssen die Hilfsroutinen, USBD_ParseConfigurationDescriptorEx und USBD_ParseDescriptor verwenden. Codebeispiele finden Sie in der Beispielfunktion RetrieveStreamInfoFromEndpointDesc unter Auflisten von USB-Pipes.

    Um die maximale Anzahl von Streams zu bestimmen, wählen Sie den niedrigeren von zwei Werten aus, die vom Hostcontroller und dem Endpunkt unterstützt werden.

  4. Ordnen Sie ein Array von USBD_STREAM_INFORMATION-Strukturen mit n-Elementen zu, wobei n die Anzahl der zu öffnenden Datenströme ist. Der Clienttreiber ist für die Freigabe dieses Arrays verantwortlich, nachdem der Treiber die Verwendung von Streams abgeschlossen hat.

  5. Ordnen Sie einen URB für die Open-Streams-Anforderung zu, indem Sie die WdfUsbTargetDeviceCreateUrb-Methode aufrufen. Wenn der Aufruf erfolgreich abgeschlossen wird, ruft die -Methode ein WDF-Speicherobjekt und die Adresse der VOM USB-Treiberstapel zugeordneten URB-Struktur ab.

    WDM-Treiber: Rufen Sie die routine USBD_UrbAllocate auf.

  6. Formatieren Sie die URB für die Open-Stream-Anforderung. Die URB verwendet die _URB_OPEN_STATIC_STREAMS-Struktur , um die Anforderung zu definieren. Zum Formatieren der URB benötigen Sie Folgendes:

    • Das USBD-Pipehandle an den Endpunkt. Wenn Sie über ein WDF-Pipeobjekt verfügen, können Sie das USBD-Pipehandle abrufen, indem Sie die WdfUsbTargetPipeWdmGetPipeHandle-Methode aufrufen.
    • Das Streamarray (erstellt in Schritt 4)
    • Ein Zeiger auf die URB-Struktur (erstellt in Schritt 5).

    Um den URB zu formatieren, rufen Sie UsbBuildOpenStaticStreamsRequest auf, und übergeben Sie die erforderlichen Informationen als Parameterwerte. Stellen Sie sicher, dass die für UsbBuildOpenStaticStreamsRequest angegebene Anzahl von Streams die maximale Anzahl unterstützter Streams nicht überschreitet.

  7. Senden Sie den URB als WDF-Anforderungsobjekt, indem Sie die WdfRequestSend-Methode aufrufen. Um die Anforderung synchron zu senden, rufen Sie stattdessen die WdfUsbTargetDeviceSendUrbSynchronously-Methode auf.

    WDM-Treiber: Ordnen Sie die URB einem IRP zu, und übermitteln Sie den IRP an den USB-Treiberstapel. Weitere Informationen finden Sie unter Übermitteln einer URB.

  8. Überprüfen Sie nach Abschluss der Anforderung die status der Anforderung.

    Wenn der USB-Treiberstapel bei der Anforderung fehlschlägt, enthält die URB-status den entsprechenden Fehlercode. Einige häufige Fehlerbedingungen werden im Abschnitt Hinweise beschrieben.

Wenn die status der Anforderung (IRP oder das WDF-Anforderungsobjekt) USBD_STATUS_SUCCESS angibt, wurde die Anforderung erfolgreich abgeschlossen. Überprüfen Sie das Array der USBD_STREAM_INFORMATION Strukturen, die nach Abschluss empfangen wurden. Das Array ist mit Informationen zu den angeforderten Streams gefüllt. Der USB-Treiberstapel füllt jede Struktur im Array mit Streaminformationen auf, z. B. Handles (empfangen als USBD_PIPE_HANDLE), Streambezeichnern und der maximalen Anzahl der Übertragungsgrößen. Datenströme können nun übertragen werden.

Für eine Open-Streams-Anforderung müssen Sie eine URB und ein Array zuordnen. Der Clienttreiber muss die URB freigeben, indem die WdfObjectDelete-Methode für das zugeordnete WDF-Speicherobjekt aufgerufen wird, nachdem die Anforderung für offene Datenströme abgeschlossen wurde. Wenn der Treiber die Anforderung synchron gesendet hat, indem er WdfUsbTargetDeviceSendUrbSynchronously aufruft, muss er das WDF-Speicherobjekt freigeben, nachdem die -Methode zurückgegeben wurde. Wenn der Clienttreiber die Anforderung asynchron gesendet hat, indem er WdfRequestSend aufruft, muss der Treiber das WDF-Speicherobjekt in der mit dem Treiber implementierten Vervollständigungsroutine freigeben, die der Anforderung zugeordnet ist.

Das Streamarray kann freigegeben werden, nachdem der Clienttreiber mit Streams fertig ist oder sie für E/A-Anforderungen gespeichert hat. Im in diesem Artikel enthaltenen Codebeispiel speichert der Treiber das Datenstromarray im Gerätekontext. Der Treiber gibt den Gerätekontext kurz vor dem Freigeben des Geräteobjekts frei.

Übertragen von Daten in einen bestimmten Stream

Zum Senden einer Datenübertragungsanforderung an einen bestimmten Stream benötigen Sie ein WDF-Anforderungsobjekt. In der Regel ist der Clienttreiber nicht erforderlich, um ein WDF-Anforderungsobjekt zuzuweisen. Wenn der E/A-Manager eine Anforderung von einer Anwendung empfängt, erstellt der E/A-Manager eine IRP für die Anforderung. Diese IRP wird vom Framework abgefangen. Das Framework weist dann ein WDF-Anforderungsobjekt zu, das die IRP darstellt. Danach übergibt das Framework das WDF-Anforderungsobjekt an den Clienttreiber. Der Clienttreiber kann dann das Anforderungsobjekt der Datenübertragungs-URB zuordnen und an den USB-Treiberstapel senden.

Wenn der Clienttreiber kein WDF-Anforderungsobjekt vom Framework empfängt und die Anforderung asynchron senden möchte, muss der Treiber ein WDF-Anforderungsobjekt zuordnen, indem er die WdfRequestCreate-Methode aufruft. Formatieren Sie das neue Objekt, indem Sie WdfUsbTargetPipeFormatRequestForUrb aufrufen und die Anforderung senden, indem Sie WdfRequestSend aufrufen.

In den synchronen Fällen ist das Übergeben eines WDF-Anforderungsobjekts optional.

Zum Übertragen von Daten in Streams müssen Sie URBs verwenden. Die URB muss durch Aufrufen von WdfUsbTargetPipeFormatRequestForUrb formatiert werden.

Die folgenden WDF-Methoden werden für Streams nicht unterstützt:

Beim folgenden Verfahren wird davon ausgegangen, dass der Clienttreiber das Anforderungsobjekt vom Framework empfängt.

  1. Ordnen Sie eine URB zu, indem Sie WdfUsbTargetDeviceCreateUrb aufrufen. Diese Methode weist ein WDF-Speicherobjekt zu, das die neu zugeordnete URB enthält. Der Clienttreiber kann eine URB für jede E/A-Anforderung zuweisen oder eine URB zuordnen und sie für denselben Anforderungstyp wiederverwenden.

  2. Formatieren Sie die URB für eine Massenübertragung, indem Sie UsbBuildInterruptOrBulkTransferRequest aufrufen. Geben Sie im PipeHandle-Parameter das Handle für den Stream an. Die Streamhandles wurden in einer vorherigen Anforderung abgerufen, die im Abschnitt Öffnen statischer Datenströme beschrieben wird.

  3. Formatieren Sie das WDF-Anforderungsobjekt, indem Sie die WdfUsbTargetPipeFormatRequestForUrb-Methode aufrufen. Geben Sie im Aufruf das WDF-Speicherobjekt an, das die Datenübertragungs-URB enthält. Das Speicherobjekt wurde in Schritt 1 zugeordnet.

  4. Senden Sie die URB als WDF-Anforderung, indem Sie WdfRequestSend oder WdfUsbTargetPipeSendUrbSynchronously aufrufen. Wenn Sie WdfRequestSend aufrufen, müssen Sie eine Vervollständigungsroutine angeben, indem Sie WdfRequestSetCompletionRoutine aufrufen, damit der Clienttreiber benachrichtigt werden kann, wenn der asynchrone Vorgang abgeschlossen ist. Sie müssen die Datenübertragungs-URB in der Vervollständigungsroutine freigeben.

WDM-Treiber: Ordnen Sie eine URB zu, indem Sie USBD_UrbAllocate aufrufen, und formatieren Sie sie für die Massenübertragung (siehe _URB_BULK_OR_INTERRUPT_TRANSFER). Um die URB zu formatieren, können Sie UsbBuildInterruptOrBulkTransferRequest aufrufen oder die URB-Struktur manuell formatieren. Geben Sie das Handle für den Stream im UrbBulkOrInterruptTransfer.PipeHandle-Element der URB an.

Schließen statischer Datenströme

Der Clienttreiber kann Streams schließen, nachdem der Treiber sie verwendet hat. Die Close-Stream-Anforderung ist jedoch optional. Der USB-Treiberstapel schließt alle Streams, wenn der den Streams zugeordnete Endpunkt dekonfiguriert wird. Ein Endpunkt wird deaktiviert, wenn eine alternative Konfiguration oder Schnittstelle ausgewählt, das Gerät entfernt wird usw. Ein Clienttreiber muss Streams schließen, wenn der Treiber eine andere Anzahl von Streams öffnen möchte. So senden Sie eine Close-Stream-Anforderung:

  1. Ordnen Sie eine URB-Struktur zu, indem Sie WdfUsbTargetDeviceCreateUrb aufrufen.

  2. Formatieren Sie die URB für die Close-Streams-Anforderung. Das UrbPipeRequest-Element der URB-Struktur ist eine _URB_PIPE_REQUEST-Struktur . Füllen Sie die Elemente wie folgt aus:

    • Das Hdr-Element von _URB_PIPE_REQUEST muss URB_FUNCTION_CLOSE_STATIC_STREAMS
    • Das PipeHandle-Element muss das Handle für den Endpunkt sein, der die verwendeten offenen Streams enthält.
  3. Senden Sie die URB als WDF-Anforderung, indem Sie WdfRequestSend oder WdfUsbTargetDeviceSendUrbSynchronously aufrufen.

Die Close-Handle-Anforderung schließt alle Datenströme, die zuvor vom Clienttreiber geöffnet wurden. Der Clienttreiber kann die Anforderung nicht verwenden, um bestimmte Streams im Endpunkt zu schließen.

Bewährte Methoden für das Senden einer statischen Datenstromanforderung

Der USB-Treiberstapel führt Überprüfungen für die empfangene URB durch. So vermeiden Sie Überprüfungsfehler:

  • Senden Sie keine Open-Stream- oder Close-Stream-Anforderung an einen Endpunkt, der keine Streams unterstützt. Rufen Sie WdfUsbTargetDeviceQueryUsbCapability (für WDM-Treiber , USBD_QueryUsbCapability) auf, um die Unterstützung statischer Datenströme zu ermitteln und datenstromanforderungen nur zu senden, wenn sie vom Endpunkt unterstützt werden.
  • Fordern Sie keine Anzahl (zu öffnende Streams) an, die die maximale Anzahl unterstützter Streams überschreitet, oder senden Sie keine Anforderung, ohne die Anzahl der Streams anzugeben. Bestimmen Sie die Anzahl der Streams basierend auf der Anzahl der Streams, die vom USB-Treiberstapel und dem Endpunkt des Geräts unterstützt werden.
  • Senden Sie keine Open-Stream-Anforderung an einen Endpunkt, der bereits über offene Streams verfügt.
  • Senden Sie keine Close-Stream-Anforderung an einen Endpunkt, der keine offenen Streams aufweist.
  • Nachdem statische Datenströme für einen Endpunkt geöffnet sind, senden Sie keine E/A-Anforderungen mithilfe des Endpunktpipehandles, das über eine Select-Configuration- oder Select-Interface-Anforderung abgerufen wurde. Dies gilt auch dann, wenn die statischen Datenströme geschlossen wurden.

Zurücksetzen und Abbrechen von Pipevorgängen

Manchmal können Übertragungen an oder von einem Endpunkt fehlschlagen. Solche Fehler können auf eine Fehlerbedingung für den Endpunkt oder Hostcontroller zurückzuführen sein, z. B. eine Verzögerungs- oder Stoppbedingung. Um die Fehlerbedingung zu löschen, bricht der Clienttreiber zunächst ausstehende Übertragungen ab und setzt dann die Pipe zurück, der der Endpunkt zugeordnet ist. Um ausstehende Übertragungen abzubrechen, kann der Clienttreiber eine Abbruch-Pipe-Anforderung senden. Um eine Pipe zurückzusetzen, muss der Clienttreiber eine Reset-Pipe-Anforderung senden.

Bei Streamübertragungen werden sowohl Abbruch-Pipe- als auch Reset-Pipe-Anforderungen für einzelne Datenströme, die dem Massenendpunkt zugeordnet sind, nicht unterstützt. Wenn eine Übertragung für eine bestimmte Streampipe fehlschlägt, beendet der Hostcontroller die Übertragungen für alle anderen Pipes (für andere Streams). Um die Fehlerbedingung wiederherzustellen, sollte der Clienttreiber Die Übertragungen an jeden Stream manuell abbrechen. Anschließend muss der Clienttreiber eine Anforderung zum Zurücksetzen der Pipe mithilfe des Pipehandles an den Massenendpunkt senden. Für diese Anforderung muss der Clienttreiber das Pipehandle für den Endpunkt in einer _URB_PIPE_REQUEST-Struktur angeben und die URB-Funktion (Hdr.Function) auf URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL festlegen.

Vollständiges Beispiel

Das folgende Codebeispiel zeigt, wie Streams geöffnet werden.

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