Auflisten von USB-Rohren

Dieser Artikel bietet eine Übersicht über USB-Pipes und beschreibt die Schritte, die von einem USB-Clienttreiber zum Abrufen von Pipehandles aus dem USB-Treiberstapel erforderlich sind.

Ein USB-Endpunkt ist ein Puffer auf dem Gerät, an das der Clienttreiber Daten sendet oder von dem Daten empfangen werden. Zum Senden oder Empfangen von Daten sendet der Clienttreiber eine E/A-Übertragungsanforderung an den USB-Treiberstapel, der die Daten an den Hostcontroller übermittelt. Der Hostcontroller folgt dann bestimmten Protokollen (abhängig vom Typ des Endpunkts: massenhaft, interrupt oder isochron), um Anforderungen zu erstellen, die Daten an oder vom Gerät übertragen. Alle Details der Datenübertragung werden vom Clienttreiber abstrahiert. Solange der Clienttreiber eine wohlgeformte Anforderung übermittelt, verarbeitet der USB-Treiberstapel die Anforderung und überträgt Daten an das Gerät.

Während der Gerätekonfiguration erstellt der USB-Treiberstapel eine USB-Pipe (auf der Hostseite) für jeden in der USB-Schnittstelle definierten Endpunkt des Geräts und seiner aktiven alternativen Einstellung. Eine USB-Pipe ist ein Kommunikationskanal zwischen dem Hostcontroller und dem Endpunkt. Für den Clienttreiber ist eine Pipe eine logische Abstraktion des Endpunkts. Um Datenübertragungen zu senden, muss der Treiber das Pipehandle abrufen, das dem Endpunkt zugeordnet ist, der das Ziel für die Übertragung ist. Pipe-Handles sind auch erforderlich, wenn der Treiber die Übertragungen abbrechen oder die Pipe zurücksetzen möchte, wenn Fehlerbedingungen auftreten.

Alle Attribute einer Pipe werden vom zugeordneten Endpunktdeskriptor abgeleitet. Für instance weist der USB-Treiberstapel abhängig vom Typ des Endpunkts einen Typ für die Pipe zu. Für einen Massenendpunkt erstellt der USB-Treiberstapel eine Massenpipe; für einen isochronen Endpunkt wird eine isochrone Pipe erstellt usw. Ein weiteres wichtiges Attribut ist die Datenmenge, die der Hostcontroller an den Endpunktpunkt in einer Anforderung senden kann. Abhängig von diesem Wert muss der Clienttreiber das Layout des Übertragungspuffers bestimmen.

Windows Driver Foundation (WDF) stellt spezielle E/ A-Zielobjekte im Kernelmodustreiberframework (KMDF) und im Benutzermodustreiberframework (UMDF) bereit, die viele Konfigurationsaufgaben für den Clienttreiber vereinfachen. Mithilfe dieser Objekte kann der Clienttreiber Informationen zur aktuellen Konfiguration abrufen, z. B. die Anzahl der Schnittstellen, die alternative Einstellung innerhalb jeder Schnittstelle und deren Endpunkte. Eines dieser Objekte, das als Zielpipeobjekt bezeichnet wird, führt endpunktbezogene Aufgaben aus. In diesem Artikel wird beschrieben, wie Pipeinformationen mithilfe des Zielpipeobjekts abgerufen werden.

Für WDM-Clienttreiber (Windows Driver Model) gibt der USB-Treiberstapel ein Array von USBD_PIPE_INFORMATION Strukturen zurück. Die Anzahl der Elemente im Array hängt von der Anzahl der Endpunkte ab, die für die aktive alternative Einstellung einer Schnittstelle in der ausgewählten Konfiguration definiert sind. Jedes Element enthält Informationen zu der Pipe, die für einen bestimmten Endpunkt erstellt wurde. Informationen zum Auswählen einer Konfiguration und zum Abrufen des Arrays von Pipeinformationen finden Sie unter Auswählen einer Konfiguration für ein USB-Gerät.

Wichtige Informationen

Bevor der Clienttreiber Pipes aufzählen kann, stellen Sie sicher, dass diese Anforderungen erfüllt sind:

Abrufen von USB-Pipehandles in einem KMDF-Clienttreiber

Das Framework stellt jede Pipe, die vom USB-Treiberstapel geöffnet wird, als USB-Zielpipeobjekt dar. Ein KMDF-Clienttreiber kann auf die Methoden des Zielpipeobjekts zugreifen, um Informationen zur Pipe abzurufen. Zum Durchführen von Datenübertragungen muss der Clienttreiber über WDFUSBPIPE-Pipehandles verfügen. Um die Pipehandles abzurufen, muss der Treiber die Schnittstellen und alternativen Einstellungen der aktiven Konfiguration aufzählen und dann die in den einzelnen Einstellungen definierten Endpunkte aufzählen. Das Ausführen von Enumerationsvorgängen für jede Datenübertragung kann teuer sein. Daher besteht ein Ansatz darin, Pipehandles abzurufen, nachdem das Gerät konfiguriert wurde, und sie im treiberdefinierten Gerätekontext zu speichern. Wenn der Treiber Datenübertragungsanforderungen empfängt, kann der Treiber die erforderlichen Pipehandles aus dem Gerätekontext abrufen und zum Senden der Anforderung verwenden. Wenn der Clienttreiber die Konfiguration des Geräts ändert, z. B. eine alternative Einstellung auswählt, muss der Treiber auch den Gerätekontext mit den neuen Pipehandles aktualisieren. Andernfalls kann der Treiber fälschlicherweise Übertragungsanforderungen an veraltete Pipehandles senden.

Pipehandles sind für Steuerungsübertragungen nicht erforderlich. Um Steuerungsübertragungsanforderungen zu senden, ruft ein WDF-Clienttreiber WdfUsbDevicexxxx-Methoden auf, die vom Framework-Geräteobjekt verfügbar gemacht werden. Diese Methoden erfordern ein WDFUSBDEVICE-Handle, um Steuerungsübertragungen zu initiieren, die auf den Standardendpunkt ausgerichtet sind. Bei solchen Übertragungen ist das E/A-Ziel für die Anforderung der Standardendpunkt und wird durch das WDFIOTARGET-Handle dargestellt, das vom WDFUSBPIPE-Handle abstrahiert wird. Auf Geräteebene ist das WDFUSBDEVICE-Handle eine Abstraktion des WDFUSBPIPE-Handles an den Standardendpunkt.

Informationen zum Senden von Steuerungsübertragungen und den KMDF-Methoden finden Sie unter Senden einer USB-Steuerübertragung.

  1. Erweitern Sie die Gerätekontextstruktur, um Pipehandles zu speichern.

    Wenn Sie die Endpunkte in Ihrem Gerät kennen, erweitern Sie die Gerätekontextstruktur, indem Sie WDFUSBPIPE-Member hinzufügen, um die zugehörigen USB-Pipehandles zu speichern. Beispielsweise können Sie die Gerätekontextstruktur wie hier gezeigt erweitern:

    typedef struct _DEVICE_CONTEXT {
        WDFUSBDEVICE    UsbDevice;
        WDFUSBINTERFACE UsbInterface;
        WDFUSBPIPE      BulkReadPipe;   // Pipe opened for the bulk IN endpoint.
        WDFUSBPIPE      BulkWritePipe;  // Pipe opened for the bulk IN endpoint.
        WDFUSBPIPE      InterruptPipe;  // Pipe opened for the interrupt IN endpoint.
        WDFUSBPIPE      StreamInPipe;   // Pipe opened for stream IN endpoint.
        WDFUSBPIPE      StreamOutPipe;  // Pipe opened for stream OUT endpoint.
        UCHAR           NumberConfiguredPipes;  // Number of pipes opened.
        ...
        ...                                     // Other members. Not shown.
    
    } DEVICE_CONTEXT, *PDEVICE_CONTEXT;
    
  2. Deklarieren Sie eine Pipekontextstruktur.

    Jede Pipe kann endpunktbezogene Merkmale in einer anderen Struktur speichern, die als Pipekontext bezeichnet wird. Ähnlich wie bei einem Gerätekontext ist ein Pipekontext eine (vom Clienttreiber definierte) Datenstruktur zum Speichern von Informationen zu Pipes, die Endpunkten zugeordnet sind. Während der Gerätekonfiguration übergibt der Clienttreiber einen Zeiger auf den zugehörigen Pipekontext an das Framework. Das Framework ordnet einen Speicherblock basierend auf der Größe der Struktur zu und speichert einen Zeiger auf diesen Speicherort mit dem Framework-USB-Zielpipeobjekt. Der Clienttreiber kann den Zeiger verwenden, um auf Pipeinformationen zuzugreifen und diese in Membern des Pipekontexts zu speichern.

    typedef struct _PIPE_CONTEXT {
    
        ULONG MaxPacketSize;
        ULONG MaxStreamsSupported;
        PUSBD_STREAM_INFORMATION StreamInfo;
    } PIPE_CONTEXT, *PPIPE_CONTEXT;
    
    WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(PIPE_CONTEXT, GetPipeContext)
    
    

    In diesem Beispiel speichert der Pipekontext die maximale Anzahl von Bytes, die in einer Übertragung gesendet werden können. Der Clienttreiber kann diesen Wert verwenden, um die Größe des Übertragungspuffers zu bestimmen. Die Deklaration enthält auch das makro WDF_DECLARE_CONTEXT_TYPE_WITH_NAME , das die Inlinefunktion GetPipeContext generiert. Der Clienttreiber kann diese Funktion aufrufen, um einen Zeiger auf den Speicherblock abzurufen, der den Pipekontext speichert.

    Weitere Informationen zu Kontexten finden Sie unter Kontextbereich des Frameworkobjekts.

    Um einen Zeiger an das Framework zu übergeben, initialisiert der Clienttreiber zunächst seinen Pipekontext, indem er WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE aufruft. Übergibt dann einen Zeiger auf den Pipekontext, während WdfUsbTargetDeviceSelectConfig (zum Auswählen einer Konfiguration) oder WdfUsbInterfaceSelectSetting (zum Auswählen einer alternativen Einstellung) aufgerufen wird.

  3. Nachdem die Gerätekonfigurationsanforderung abgeschlossen ist, können Sie die Schnittstelle auflisten und die Pipehandles für die konfigurierten Pipes abrufen. Sie benötigen diese Informationen:

    • WDFUSBINTERFACE-Handle für die Schnittstelle, die die aktuelle Einstellung enthält. Sie können dieses Handle abrufen, indem Sie die Schnittstellen in der aktiven Konfiguration auflisten. Wenn Sie alternativ einen Zeiger auf eine WDF_USB_DEVICE_SELECT_CONFIG_PARAMS-Struktur in WdfUsbTargetDeviceSelectConfig angegeben haben, können Sie das Handle vom Type.SingleInterface.ConfigureUsbInterface-Member (für Geräte mit einer einzelnen Schnittstelle) oder vom Type.MultiInterface.Pairs.UsbInterface-Member (für Geräte mit mehreren Schnittstellen) abrufen.
    • Anzahl der Pipes, die für die Endpunkte in der aktuellen Einstellung geöffnet wurden. Sie können diese Nummer auf einer bestimmten Schnittstelle abrufen, indem Sie die WdfUsbInterfaceGetNumConfiguredPipes-Methode aufrufen.
    • WDFUSBPIPE-Handles für alle konfigurierten Pipes. Sie können das Handle abrufen, indem Sie die WdfUsbInterfaceGetConfiguredPipe-Methode aufrufen.

    Nach dem Abrufen des Pipehandles kann der Clienttreiber Methoden aufrufen, um den Typ und die Richtung der Pipe zu bestimmen. Der Treiber kann Informationen zum Endpunkt in einer WDF_USB_PIPE_INFORMATION-Struktur abrufen. Der Treiber kann die aufgefüllte Struktur abrufen, indem er die WdfUsbTargetPipeGetInformation-Methode aufruft. Alternativ kann der Treiber einen Zeiger auf die Struktur im WdfUsbInterfaceGetConfiguredPipe-Aufruf bereitstellen.

Im folgenden Codebeispiel werden die Pipes in der aktuellen Einstellung aufgelistet. Es ruft Pipehandles für die Massen- und Interruptendpunkte des Geräts ab und speichert sie in der Gerätekontextstruktur des Treibers. Es speichert die maximale Paketgröße jedes Endpunkts im zugeordneten Pipekontext. Wenn der Endpunkt Datenströme unterstützt, öffnet er statische Datenströme, indem er die OpenStreams-Routine aufruft. Die Implementierung von OpenStreams wird unter Öffnen und Schließen statischer Datenströme in einem USB-Massenendpunkt veranschaulicht.

Um zu bestimmen, ob ein bestimmter Massenendpunkt statische Datenströme unterstützt, untersucht der Clienttreiber den Endpunktdeskriptor. Dieser Code wird in einer Hilfsroutine namens RetrieveStreamInfoFromEndpointDesc implementiert, die im nächsten Codeblock angezeigt wird.

NTSTATUS
    FX3EnumeratePipes(
    _In_ WDFDEVICE Device)

{
    NTSTATUS                    status;
    PDEVICE_CONTEXT             pDeviceContext;
    UCHAR                       i;
    PPIPE_CONTEXT               pipeContext;
    WDFUSBPIPE                  pipe;
    WDF_USB_PIPE_INFORMATION    pipeInfo;

    PAGED_CODE();

    pDeviceContext = GetDeviceContext(Device);

    // Get the number of pipes in the current altenrate setting.
    pDeviceContext->NumberConfiguredPipes = WdfUsbInterfaceGetNumConfiguredPipes(
        pDeviceContext->UsbInterface);

    if (pDeviceContext->NumberConfiguredPipes == 0)
    {
        status = USBD_STATUS_BAD_NUMBER_OF_ENDPOINTS;
        goto Exit;
    }
    else
    {
        status = STATUS_SUCCESS;
    }

    // Enumerate the pipes and get pipe information for each pipe.
    for (i = 0; i < pDeviceContext->NumberConfiguredPipes; i++)
    {
        WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);

        pipe =  WdfUsbInterfaceGetConfiguredPipe(
            pDeviceContext->UsbInterface,
            i,
            &pipeInfo);

        if (pipe == NULL)
        {
            continue;
        }

        pipeContext = GetPipeContext (pipe);

        // If the pipe is a bulk endpoint that supports streams,
        // If the host controller supports streams, open streams.
        // Use the endpoint as an IN bulk endpoint.
        // Store the maximum packet size.

        if ((WdfUsbPipeTypeBulk == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsInEndpoint (pipe))
        {

            // Check if this is a streams IN endpoint. If it is,
            // Get the maximum number of streams and store
            // the value in the pipe context.
            RetrieveStreamInfoFromEndpointDesc (
                Device,
                pipe);

            if ((pipeContext->IsStreamsCapable) &&
                (pipeContext->MaxStreamsSupported > 0))
            {
                status = OpenStreams (
                    Device,
                    pipe);

                if (status != STATUS_SUCCESS)
                {
                    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "%!FUNC! Could not open streams.");

                    pDeviceContext->StreamInPipe = NULL;
                }
                else
                {
                    pDeviceContext->StreamInPipe = pipe;
                    pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
                }
            }
            else
            {
                pDeviceContext->BulkReadPipe = pipe;
                pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            }

            continue;
        }

        if ((WdfUsbPipeTypeBulk == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsOutEndpoint (pipe))
        {
            // Check if this is a streams IN endpoint. If it is,
            // Get the maximum number of streams and store
            // the value in the pipe context.
            RetrieveStreamInfoFromEndpointDesc (
                Device,
                pipe);

            if ((pipeContext->IsStreamsCapable) &&
                (pipeContext->MaxStreamsSupported > 0))
            {
                status = OpenStreams (
                    Device,
                    pipe);

                if (status != STATUS_SUCCESS)
                {
                    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "%!FUNC! Could not open streams.");

                    pDeviceContext->StreamOutPipe = NULL;
                }
                else
                {
                    pDeviceContext->StreamOutPipe = pipe;
                    pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
                }
            }
            else
            {
                pDeviceContext->BulkWritePipe = pipe;
                pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            }

            continue;
        }

        if ((WdfUsbPipeTypeInterrupt == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsInEndpoint (pipe))
        {
            pDeviceContext->InterruptPipe = pipe;
            pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            continue;
        }

    }

Exit:
    return status;
}

Das folgende Codebeispiel zeigt eine Hilfsroutine namens RetrieveStreamInfoFromEndpointDesc, die der Clienttreiber beim Aufzählen von Pipes aufruft.

Im folgenden Codebeispiel ruft der Clienttreiber beim Aufzählen von Pipes die vorangehende Hilfsroutine RetrieveStreamInfoFromEndpointDesc auf. Die Routine untersucht zuerst den Konfigurationsdeskriptor und analysiert ihn, um Endpunktdeskriptoren abzurufen. Wenn der Endpunktdeskriptor für die Pipe einen USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR_TYPE Deskriptor enthält, ruft der Treiber die maximale Anzahl von Streams ab, die vom Endpunkt unterstützt werden.

/*++
Routine Description:

This routine parses the configuration descriptor and finds the endpoint
with which the specified pipe is associated.
It then retrieves the maximum number of streams supported by the endpoint.
It stores maximum number of streams in the pipe context.

Arguments:

Device - WDFUSBDEVICE handle to the target device object.
The driver obtained that handle in a previous call to
WdfUsbTargetDeviceCreateWithParameters.

Pipe - WDFUSBPIPE handle to the target pipe object.

Return Value:

NTSTATUS
++*/

VOID RetrieveStreamInfoFromEndpointDesc (
    WDFDEVICE Device,
    WDFUSBPIPE Pipe)
{
    PDEVICE_CONTEXT                                 deviceContext                = NULL;
    PUSB_CONFIGURATION_DESCRIPTOR                   configDescriptor             = NULL;
    WDF_USB_PIPE_INFORMATION                        pipeInfo;
    PUSB_COMMON_DESCRIPTOR                          pCommonDescriptorHeader      = NULL;
    PUSB_INTERFACE_DESCRIPTOR                       pInterfaceDescriptor         = NULL;
    PUSB_ENDPOINT_DESCRIPTOR                        pEndpointDescriptor          = NULL;
    PUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR   pEndpointCompanionDescriptor = NULL;
    ULONG                                           maxStreams;
    ULONG                                           index;
    BOOLEAN                                         found                        = FALSE;
    UCHAR                                           interfaceNumber = 0;
    UCHAR                                           alternateSetting = 1;
    PPIPE_CONTEXT                                   pipeContext = NULL;
    NTSTATUS                                        status;

    PAGED_CODE();

    deviceContext = GetDeviceContext (Device);
    pipeContext = GetPipeContext (Pipe);

    // Get the configuration descriptor of the currently selected configuration
    status = FX3RetrieveConfigurationDescriptor (
        deviceContext->UsbDevice,
        &deviceContext->ConfigurationNumber,
        &configDescriptor);

    if (!NT_SUCCESS (status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not retrieve the configuration descriptor.");

        status = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;

        goto Exit;
    }

    if (deviceContext->ConfigurationNumber == 1)
    {
        alternateSetting = 1;
    }
    else
    {
        alternateSetting = 0;
    }

    // Get the Endpoint Address of the pipe
    WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);
    WdfUsbTargetPipeGetInformation (Pipe, &pipeInfo);

    // Parse the ConfigurationDescriptor (including all Interface and
    // Endpoint Descriptors) and locate a Interface Descriptor which
    // matches the InterfaceNumber, AlternateSetting, InterfaceClass,
    // InterfaceSubClass, and InterfaceProtocol parameters.
    pInterfaceDescriptor = USBD_ParseConfigurationDescriptorEx(
        configDescriptor,
        configDescriptor,
        interfaceNumber,  //Interface number is 0.
        alternateSetting,  // Alternate Setting is 1
        -1, // InterfaceClass, ignore
        -1, // InterfaceSubClass, ignore
        -1  // InterfaceProtocol, ignore
        );

    if (pInterfaceDescriptor == NULL )
    {
        // USBD_ParseConfigurationDescriptorEx failed to retrieve Interface Descriptor.
        goto Exit;
    }

    pCommonDescriptorHeader = (PUSB_COMMON_DESCRIPTOR) pInterfaceDescriptor;

    for(index = 0; index < pInterfaceDescriptor->bNumEndpoints; index++)
    {

        pCommonDescriptorHeader = USBD_ParseDescriptors(
            configDescriptor,
            configDescriptor->wTotalLength,
            pCommonDescriptorHeader,
            USB_ENDPOINT_DESCRIPTOR_TYPE);

        if (pCommonDescriptorHeader == NULL)
        {
            // USBD_ParseDescriptors failed to retrieve Endpoint Descriptor unexpectedly.
            goto Exit;
        }

        pEndpointDescriptor = (PUSB_ENDPOINT_DESCRIPTOR) pCommonDescriptorHeader;

        // Search an Endpoint Descriptor that matches the EndpointAddress
        if (pEndpointDescriptor->bEndpointAddress == pipeInfo.EndpointAddress)
        {

            found = TRUE;
            break;
        }

        // Skip the current Endpoint Descriptor and search for the next.
        pCommonDescriptorHeader = (PUSB_COMMON_DESCRIPTOR)(((PUCHAR)pCommonDescriptorHeader)
            + pCommonDescriptorHeader->bLength);
    }

    if (found)
    {
        // Locate the SuperSpeed Endpoint Companion Descriptor
        // associated with the endpoint descriptor
        pCommonDescriptorHeader = USBD_ParseDescriptors (
            configDescriptor,
            configDescriptor->wTotalLength,
            pEndpointDescriptor,
            USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR_TYPE);

        if (pCommonDescriptorHeader != NULL)
        {
            pEndpointCompanionDescriptor =
                (PUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR) pCommonDescriptorHeader;

            maxStreams = pEndpointCompanionDescriptor->bmAttributes.Bulk.MaxStreams;

            if (maxStreams == 0)
            {
                pipeContext->MaxStreamsSupported = 0;
                pipeContext->IsStreamsCapable = FALSE;
            }
            else
            {
                pipeContext->IsStreamsCapable = TRUE;
                pipeContext->MaxStreamsSupported = 1 << maxStreams;
            }
        }
        else
        {
            KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
                "USBD_ParseDescriptors failed to retrieve SuperSpeed Endpoint Companion Descriptor unexpectedly.\n" ));
        }
    }
    else
    {
        pipeContext->MaxStreamsSupported = 0;
        pipeContext->IsStreamsCapable = FALSE;
    }

Exit:
    if (configDescriptor)
    {
        ExFreePoolWithTag (configDescriptor, USBCLIENT_TAG);
    }

    return;
}

Abrufen von Pipehandles in einem UMDF-Clienttreiber

Ein UMDF-Clienttreiber verwendet DIE COM-Infrastruktur und implementiert COM-Rückrufklassen, die mit Frameworkgeräteobjekten koppeln. Ähnlich wie ein KMDF-Treiber kann ein UMDF-Clienttreiber Pipeinformationen erst abrufen, nachdem das Gerät konfiguriert wurde. Um Pipeinformationen abzurufen, muss der Clienttreiber einen Zeiger auf die IWDFUsbTargetPipe-Schnittstelle des Framework-Schnittstellenobjekts abrufen, das die aktive Einstellung enthält. Mithilfe des Schnittstellenzeigers kann der Treiber die Pipes in dieser Einstellung auflisten, um IWDFUsbTargetPipe-Schnittstellenzeiger abzurufen, die von den Frameworkzielpipeobjekten verfügbar gemacht werden.

Bevor der Treiber mit dem Aufzählen der Pipes beginnt, muss der Treiber über die Gerätekonfiguration und die unterstützten Endpunkte Bescheid wissen. Basierend auf diesen Informationen kann der Treiber Pipeobjekte als Klassenmembervariablen speichern.

Im folgenden Codebeispiel wird die USB-UMDF-Vorlage erweitert, die mit Visual Studio Professional 2012 bereitgestellt wird. Eine Erklärung des Startcodes finden Sie unter "IPnpCallbackHardware-Implementierung und USB-spezifische Aufgaben" unter Grundlegendes zur Codestruktur des USB-Clienttreibers (UMDF).

Erweitern Sie die Deklaration der CDevice-Klasse wie hier gezeigt. In diesem Beispielcode wird davon ausgegangen, dass es sich bei dem Gerät um das OSR FX2-Board handelt. Informationen zum Deskriptorlayout finden Sie unter USB-Gerätelayout.

class CMyDevice :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IPnpCallbackHardware
{

public:
    DECLARE_NOT_AGGREGATABLE(CMyDevice)

    BEGIN_COM_MAP(CMyDevice)
        COM_INTERFACE_ENTRY(IPnpCallbackHardware)
    END_COM_MAP()

    CMyDevice() :
        m_FxDevice(NULL),
        m_IoQueue(NULL),
        m_FxUsbDevice(NULL)
    {
    }

    ~CMyDevice()
    {
    }

private:
    IWDFDevice *            m_FxDevice;
    CMyIoQueue *            m_IoQueue;
    IWDFUsbTargetDevice *   m_FxUsbDevice;
    IWDFUsbInterface *      m_pIUsbInterface;  //Pointer to the target interface object.
    IWDFUsbTargetPipe *     m_pIUsbInputPipe;  // Pointer to the target pipe object for the bulk IN endpoint.
    IWDFUsbTargetPipe *     m_pIUsbOutputPipe; // Pointer to the target pipe object for the bulk OUT endpoint.
    IWDFUsbTargetPipe *     m_pIUsbInterruptPipe; // Pointer to the target pipe object for the interrupt endpoint.

private:
    HRESULT
    Initialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit
        );

public:
    static
    HRESULT
    CreateInstanceAndInitialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit,
        __out CMyDevice **Device
        );

    HRESULT
    Configure(
        VOID
        );

    HRESULT                     // Declare a helper function to enumerate pipes.
    ConfigureUsbPipes(
        );

public:
    // IPnpCallbackHardware methods
    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnPrepareHardware(
            __in IWDFDevice *FxDevice
            );

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnReleaseHardware(
        __in IWDFDevice *FxDevice
        );

};

Implementieren Sie in der Definition der CDevice-Klasse eine Hilfsmethode namens CreateUsbIoTargets. Diese Methode wird von der IPnpCallbackHardware::OnPrepareHardware-Implementierung aufgerufen, nachdem der Treiber einen Zeiger auf das Zielgerätobjekt abgerufen hat.

HRESULT  CMyDevice::CreateUsbIoTargets()
{
    HRESULT                 hr;
    UCHAR                   NumEndPoints = 0;
    IWDFUsbInterface *      pIUsbInterface = NULL;
    IWDFUsbTargetPipe *     pIUsbPipe = NULL;

    if (SUCCEEDED(hr))
    {
        UCHAR NumInterfaces = pIUsbTargetDevice->GetNumInterfaces();

        WUDF_TEST_DRIVER_ASSERT(1 == NumInterfaces);

        hr = pIUsbTargetDevice->RetrieveUsbInterface(0, &pIUsbInterface);
        if (FAILED(hr))
        {
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Unable to retrieve USB interface from USB Device I/O Target %!HRESULT!",
                        hr
                        );
        }
        else
        {
            m_pIUsbInterface = pIUsbInterface;

            DriverSafeRelease (pIUsbInterface); //release creation reference
        }
     }

    if (SUCCEEDED(hr))
    {
        NumEndPoints = pIUsbInterface->GetNumEndPoints();

        if (NumEndPoints != NUM_OSRUSB_ENDPOINTS)
        {
            hr = E_UNEXPECTED;
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Has %d endpoints, expected %d, returning %!HRESULT! ",
                        NumEndPoints,
                        NUM_OSRUSB_ENDPOINTS,
                        hr
                        );
        }
    }

    if (SUCCEEDED(hr))
    {
        for (UCHAR PipeIndex = 0; PipeIndex < NumEndPoints; PipeIndex++)
        {
            hr = pIUsbInterface->RetrieveUsbPipeObject(PipeIndex,
                                                  &pIUsbPipe);

            if (FAILED(hr))
            {
                TraceEvents(TRACE_LEVEL_ERROR,
                            TEST_TRACE_DEVICE,
                            "%!FUNC! Unable to retrieve USB Pipe for PipeIndex %d, %!HRESULT!",
                            PipeIndex,
                            hr
                            );
            }
            else
            {
                if ( pIUsbPipe->IsInEndPoint() )
                {
                    if ( UsbdPipeTypeInterrupt == pIUsbPipe->GetType() )
                    {
                        m_pIUsbInterruptPipe = pIUsbPipe;
                    }
                    else if ( UsbdPipeTypeBulk == pIUsbPipe->GetType() )
                    {
                        m_pIUsbInputPipe = pIUsbPipe;
                    }
                    else
                    {
                        pIUsbPipe->DeleteWdfObject();
                    }
                }
                else if ( pIUsbPipe->IsOutEndPoint() && (UsbdPipeTypeBulk == pIUsbPipe->GetType()) )
                {
                    m_pIUsbOutputPipe = pIUsbPipe;
                }
                else
                {
                    pIUsbPipe->DeleteWdfObject();
                }

                DriverSafeRelease(pIUsbPipe);  //release creation reference
            }
        }

        if (NULL == m_pIUsbInputPipe || NULL == m_pIUsbOutputPipe)
        {
            hr = E_UNEXPECTED;
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Input or output pipe not found, returning %!HRESULT!",
                        hr
                        );
        }
    }

    return hr;
}

In UMDF verwendet der Clienttreiber einen Pipeindex zum Senden von Datenübertragungsanforderungen. Ein Pipeindex ist eine Zahl, die vom USB-Treiberstapel beim Öffnen von Pipes für die Endpunkte in einer Einstellung zugewiesen wird. Um den Pipeindex abzurufen, rufen Sie die Methode**IWDFUsbTargetPipe::GetInformation** auf. Die -Methode füllt eine WINUSB_PIPE_INFORMATION-Struktur auf. Der PipeId-Wert gibt den Pipeindex an.

Eine Möglichkeit zum Ausführen von Lese- und Schreibvorgängen für die Zielpipeline besteht darin , IWDFUsbInterface::GetWinUsbHandle aufzurufen, um ein WinUSB-Handle abzurufen und dann WinUSB-Funktionen aufzurufen. Der Treiber kann beispielsweise die WinUsb_ReadPipe - oder WinUsb_WritePipe-Funktion aufrufen. In diesen Funktionsaufrufen muss der Treiber den Pipeindex angeben. Weitere Informationen finden Sie unter Zugreifen auf ein USB-Gerät mithilfe von WinUSB-Funktionen.

Pipehandles für WDM-basierte Clienttreiber

Nachdem eine Konfiguration ausgewählt wurde, richtet der USB-Treiberstapel eine Pipe an jeden Endpunkt des Geräts ein. Der USB-Treiberstapel gibt ein Array von USBD_PIPE_INFORMATION Strukturen zurück. Die Anzahl der Elemente im Array hängt von der Anzahl der Endpunkte ab, die für die aktive alternative Einstellung einer Schnittstelle in der ausgewählten Konfiguration definiert sind. Jedes Element enthält Informationen zu der Pipe, die für einen bestimmten Endpunkt erstellt wurde. Weitere Informationen zum Abrufen von Pipehandles finden Sie unter Auswählen einer Konfiguration für ein USB-Gerät.

Zum Erstellen einer E/A-Übertragungsanforderung muss der Clienttreiber über ein Handle für die Pipe verfügen, die diesem Endpunkt zugeordnet ist. Der Clienttreiber kann das Pipehandle-Element von USBD_PIPE_INFORMATION im Array abrufen.

Zusätzlich zum Pipehandle benötigt der Clienttreiber auch den Pipetyp. Der Clienttreiber kann den Pipetyp ermitteln, indem er das PipeType-Element untersucht.

Basierend auf dem Endpunkttyp unterstützt der USB-Treiberstapel verschiedene Arten von Pipes. Der Clienttreiber kann den Pipetyp ermitteln, indem er das PipeType-Elementvon USBD_PIPE_INFORMATION untersucht. Die verschiedenen Pipetypen erfordern unterschiedliche Typen von USB-Anforderungsblöcken (URBs), um E/A-Transaktionen durchzuführen.

Der Clienttreiber sendet dann die URB an den USB-Treiberstapel. Der USB-Treiberstapel verarbeitet die Anforderung und sendet die angegebenen Daten an die angeforderte Zielpipe.

Die URB enthält Informationen zur Anforderung, z. B. das Zielpipehandle, den Übertragungspuffer und ihre Länge. Jede Struktur innerhalb der URB-Union teilt bestimmte Member: TransferFlags, TransferBuffer, TransferBufferLength und TransferBufferMDL. Im TransferFlags-Member gibt es typspezifische Flags, die jedem URB-Typ entsprechen. Für alle Datenübertragungs-URBs gibt das USBD_TRANSFER_DIRECTION_IN-Flag in TransferFlags die Richtung der Übertragung an. Clienttreiber legen das USBD_TRANSFER_DIRECTION_IN-Flag fest, um Daten vom Gerät zu lesen. Treiber löschen dieses Flag, um Daten an das Gerät zu senden. Daten können aus einem Puffer gelesen oder in einen Puffer geschrieben werden, der sich im Arbeitsspeicher oder in eine MDL befindet. In beiden Fällen gibt der Treiber die Größe des Puffers im Element TransferBufferLength an. Der Treiber stellt einen residenten Puffer im TransferBuffer-Member und eine MDL im TransferBufferMDL-Member bereit. Unabhängig davon, was der Treiber bereitstellt, muss der andere NULL sein.