Share via


Schreiben eines UDE-Clienttreibers

In diesem Artikel wird das Verhalten der USB-Geräteemulationsklasse (UDE) und der Aufgaben beschrieben, die ein Clienttreiber für einen emulierten Hostcontroller und an ihn angeschlossene Geräte ausführen muss. Sie enthält Informationen dazu, wie der Klassentreiber und die Klassenerweiterung über eine Reihe von Routinen und Rückruffunktionen mit beiden kommunizieren. Außerdem werden die Features beschrieben, die vom Clienttreiber implementiert werden sollen.

Zusammenfassung

  • UDE-Objekte und -Handles, die von der Klassenerweiterung und dem Clienttreiber verwendet werden.
  • Erstellen eines emulierten Hostcontrollers mit Funktionen zum Abfragen von Controllerfunktionen und Zurücksetzen des Controllers.
  • Erstellen eines virtuellen USB-Geräts, Einrichten für die Energieverwaltung und Datenübertragungen über Endpunkte.

Wichtige APIs

Voraussetzungen

  • Installieren Sie das neueste Windows Driver Kit (WDK) Ihres Entwicklungscomputers. Das Kit verfügt über die erforderlichen Headerdateien und Bibliotheken zum Schreiben eines UDE-Clienttreibers, insbesondere benötigen Sie Folgendes:
    • Die Stubbibliothek (Udecxstub.lib). Die Bibliothek übersetzt Aufrufe des Clienttreibers und übergibt sie an UdeCx.
    • Die Headerdatei Udecx.h.
  • Installieren Sie Windows 10 auf Ihrem Zielcomputer.
  • Machen Sie sich mit der UDE vertraut. Siehe Architektur: USB-Geräteemulation(UDE).
  • Machen Sie sich mit Windows Driver Foundation (WDF) vertraut. Empfohlene Lektüre: Entwickeln von Treibern mit Windows Driver Foundation, geschrieben von Penny Orwick und Guy Smith.

UDE-Objekte und -Handles

Die UDE-Klassenerweiterung und der Clienttreiber verwenden bestimmte WDF-Objekte, die den emulierten Hostcontroller und das virtuelle Gerät darstellen, einschließlich seiner Endpunkte und URBs, die zum Übertragen von Daten zwischen dem Gerät und dem Host verwendet werden. Der Clienttreiber fordert die Erstellung der Objekte an, und die Lebensdauer des Objekts wird von der Klassenerweiterung verwaltet.

  • Emuliertes Hostcontrollerobjekt (WDFDEVICE)

    Stellt den emulierten Hostcontroller dar und ist das Standard Handle zwischen der UDE-Klassenerweiterung und dem Clienttreiber.

  • UDE-Geräteobjekt (UDECXUSBDEVICE)

    Stellt ein virtuelles USB-Gerät dar, das mit einem Port des emulierten Hostcontrollers verbunden ist.

  • UDE-Endpunktobjekt (UDECXUSBENDPOINT)

    Stellen Sie sequenzielle Datenpipes von USB-Geräten dar. Dient zum Empfangen von Softwareanforderungen zum Senden oder Empfangen von Daten auf einem Endpunkt.

Initialisieren des emulierten Hostcontrollers

Hier ist die Zusammenfassung der Sequenz, in der der Clienttreiber ein WDFDEVICE-Handle für den emulierten Hostcontroller abruft. Es wird empfohlen, dass der Treiber diese Aufgaben in seiner Rückruffunktion EvtDriverDeviceAdd ausführt.

  1. Rufen Sie UdecxInitializeWdfDeviceInit auf, indem Sie den Verweis auf WDFDEVICE_INIT übergeben, der vom Framework übergeben wurde.

  2. Initialisieren Sie die WDFDEVICE_INIT-Struktur mit Setupinformationen, sodass dieses Gerät ähnlich wie andere USB-Hostcontroller aussieht. Weisen Sie beispielsweise einen FDO-Namen und einen symbolischen Link zu, registrieren Sie eine Geräteschnittstelle mit der von Microsoft bereitgestellten GUID_DEVINTERFACE_USB_HOST_CONTROLLER GUID als GUID der Geräteschnittstelle, damit Anwendungen ein Handle für das Gerät öffnen können.

  3. Rufen Sie WdfDeviceCreate auf , um das Framework-Geräteobjekt zu erstellen.

  4. Rufen Sie UdecxWdfDeviceAddUsbDeviceEmulation auf, und registrieren Sie die Rückruffunktionen des Clienttreibers.

    Hier sind die Rückruffunktionen, die dem Hostcontrollerobjekt zugeordnet sind, die von der UDE-Klassenerweiterung aufgerufen werden. Diese Funktionen müssen vom Clienttreiber implementiert werden.

    
    EVT_WDF_DRIVER_DEVICE_ADD                 Controller_WdfEvtDeviceAdd;
    
    #define BASE_DEVICE_NAME                  L"\\Device\\USBFDO-"
    #define BASE_SYMBOLIC_LINK_NAME           L"\\DosDevices\\HCD"
    
    #define DeviceNameSize                    sizeof(BASE_DEVICE_NAME)+MAX_SUFFIX_SIZE
    #define SymLinkNameSize                   sizeof(BASE_SYMBOLIC_LINK_NAME)+MAX_SUFFIX_SIZE
    
    NTSTATUS
    Controller_WdfEvtDeviceAdd(
        _In_
            WDFDRIVER Driver,
        _Inout_
            PWDFDEVICE_INIT WdfDeviceInit
        )
    {
        NTSTATUS                            status;
        WDFDEVICE                           wdfDevice;
        WDF_PNPPOWER_EVENT_CALLBACKS        wdfPnpPowerCallbacks;
        WDF_OBJECT_ATTRIBUTES               wdfDeviceAttributes;
        WDF_OBJECT_ATTRIBUTES               wdfRequestAttributes;
        UDECX_WDF_DEVICE_CONFIG             controllerConfig;
        WDF_FILEOBJECT_CONFIG               fileConfig;
        PWDFDEVICE_CONTEXT                  pControllerContext;
        WDF_IO_QUEUE_CONFIG                 defaultQueueConfig;
        WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS
                                            idleSettings;
        UNICODE_STRING                      refString;
        ULONG instanceNumber;
        BOOLEAN isCreated;
    
        DECLARE_UNICODE_STRING_SIZE(uniDeviceName, DeviceNameSize);
        DECLARE_UNICODE_STRING_SIZE(uniSymLinkName, SymLinkNameSize);
    
        UNREFERENCED_PARAMETER(Driver);
    
        ...
    
        WdfDeviceInitSetPnpPowerEventCallbacks(WdfDeviceInit, &wdfPnpPowerCallbacks);
    
        WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wdfRequestAttributes, REQUEST_CONTEXT);
        WdfDeviceInitSetRequestAttributes(WdfDeviceInit, &wdfRequestAttributes);
    
    // To distinguish I/O sent to GUID_DEVINTERFACE_USB_HOST_CONTROLLER, we will enable
    // enable interface reference strings by calling WdfDeviceInitSetFileObjectConfig
    // with FileObjectClass WdfFileObjectWdfXxx.
    
    WDF_FILEOBJECT_CONFIG_INIT(&fileConfig,
                                WDF_NO_EVENT_CALLBACK,
                                WDF_NO_EVENT_CALLBACK,
                                WDF_NO_EVENT_CALLBACK // No cleanup callback function
                                );
    
    ...
    
    WdfDeviceInitSetFileObjectConfig(WdfDeviceInit,
                                        &fileConfig,
                                        WDF_NO_OBJECT_ATTRIBUTES);
    
    ...
    
    // Do additional setup required for USB controllers.
    
    status = UdecxInitializeWdfDeviceInit(WdfDeviceInit);
    
    ...
    
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wdfDeviceAttributes, WDFDEVICE_CONTEXT);
    wdfDeviceAttributes.EvtCleanupCallback = _ControllerWdfEvtCleanupCallback;
    
    // Call WdfDeviceCreate with a few extra compatibility steps to ensure this device looks
    // exactly like other USB host controllers.
    
    isCreated = FALSE;
    
    for (instanceNumber = 0; instanceNumber < ULONG_MAX; instanceNumber++) {
    
        status = RtlUnicodeStringPrintf(&uniDeviceName,
                                        L"%ws%d",
                                        BASE_DEVICE_NAME,
                                        instanceNumber);
    
        ...
    
        status = WdfDeviceInitAssignName(*WdfDeviceInit, &uniDeviceName);
    
        ...
    
        status = WdfDeviceCreate(WdfDeviceInit, WdfDeviceAttributes, WdfDevice);
    
        if (status == STATUS_OBJECT_NAME_COLLISION) {
    
            // This is expected to happen at least once when another USB host controller
            // already exists on the system.
    
        ...
    
        } else if (!NT_SUCCESS(status)) {
    
        ...
    
        } else {
    
            isCreated = TRUE;
            break;
        }
    }
    
    if (!isCreated) {
    
        ...
    }
    
    // Create the symbolic link (also for compatibility).
    status = RtlUnicodeStringPrintf(&uniSymLinkName,
                                    L"%ws%d",
                                    BASE_SYMBOLIC_LINK_NAME,
                                    instanceNumber);
    ...
    
    status = WdfDeviceCreateSymbolicLink(*WdfDevice, &uniSymLinkName);
    
    ...
    
    // Create the device interface.
    
    RtlInitUnicodeString(&refString,
                         USB_HOST_DEVINTERFACE_REF_STRING);
    
    status = WdfDeviceCreateDeviceInterface(wdfDevice,
                                            (LPGUID)&GUID_DEVINTERFACE_USB_HOST_CONTROLLER,
                                            &refString);
    
    ...
    
    UDECX_WDF_DEVICE_CONFIG_INIT(&controllerConfig, Controller_EvtUdecxWdfDeviceQueryUsbCapability);
    
    status = UdecxWdfDeviceAddUsbDeviceEmulation(wdfDevice,
                                               &controllerConfig);
    
    // Create default queue. It only supports USB controller IOCTLs. (USB I/O will come through
    // in separate USB device queues.)
    // Shown later in this topic.
    
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&defaultQueueConfig, WdfIoQueueDispatchSequential);
    defaultQueueConfig.EvtIoDeviceControl = ControllerEvtIoDeviceControl;
    defaultQueueConfig.PowerManaged = WdfFalse;
    
    status = WdfIoQueueCreate(wdfDevice,
                              &defaultQueueConfig,
                              WDF_NO_OBJECT_ATTRIBUTES,
                              &pControllerContext->DefaultQueue);
    
    ...
    
    // Initialize virtual USB device software objects.
    // Shown later in this topic.
    
    status = Usb_Initialize(wdfDevice);
    
    ...
    
    exit:
    
        return status;
    }
    ```1.
    
    

Verarbeiten von IOCTL-Anforderungen im Benutzermodus, die an den Hostcontroller gesendet werden

Während der Initialisierung macht der UDE-Clienttreiber die GUID_DEVINTERFACE_USB_HOST_CONTROLLER Geräteschnittstellen-GUID verfügbar. Dadurch kann der Treiber IOCTL-Anforderungen von einer Anwendung empfangen, die mithilfe dieser GUID ein Gerätehandle öffnet. Eine Liste der IOCTL-Steuercodes finden Sie unter USB-IOCTLs mit Geräteschnittstellen-GUID: GUID_DEVINTERFACE_USB_HOST_CONTROLLER.

Um diese Anforderungen zu verarbeiten, registriert der Clienttreiber den EvtIoDeviceControl-Ereignisrückruf . In der Implementierung kann der Treiber anstelle der Anforderung die Anforderung zur Verarbeitung an die UDE-Klassenerweiterung weiterleiten. Um die Anforderung weiterzuleiten, muss der Treiber UdecxWdfDeviceTryHandleUserIoctl aufrufen. Wenn der empfangene IOCTL-Steuerungscode einer Standardanforderung entspricht, z. B. dem Abrufen von Gerätedeskriptoren, verarbeitet und schließt die Klassenerweiterung die Anforderung erfolgreich ab. In diesem Fall wird UdecxWdfDeviceTryHandleUserIoctl mit TRUE als Rückgabewert abgeschlossen. Andernfalls gibt der Aufruf FALSE zurück, und der Treiber muss festlegen, wie die Anforderung abgeschlossen werden soll. In einer einfachsten Implementierung kann der Treiber die Anforderung mit einem entsprechenden Fehlercode abschließen, indem er WdfRequestComplete aufruft.


EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL        Controller_EvtIoDeviceControl;

VOID
Controller_EvtIoDeviceControl(
    _In_
        WDFQUEUE Queue,
    _In_
        WDFREQUEST Request,
    _In_
        size_t OutputBufferLength,
    _In_
        size_t InputBufferLength,
    _In_
        ULONG IoControlCode
)
{
    BOOLEAN handled;
    NTSTATUS status;
    UNREFERENCED_PARAMETER(OutputBufferLength);
    UNREFERENCED_PARAMETER(InputBufferLength);

    handled = UdecxWdfDeviceTryHandleUserIoctl(WdfIoQueueGetDevice(Queue),
                                                Request);

    if (handled) {

        goto exit;
    }

    // Unexpected control code.
    // Fail the request.

    status = STATUS_INVALID_DEVICE_REQUEST;

    WdfRequestComplete(Request, status);

exit:

    return;
}

Melden der Funktionen des Hostcontrollers

Bevor Treiber der oberen Ebene die Funktionen eines USB-Hostcontrollers verwenden können, müssen die Treiber bestimmen, ob diese Funktionen vom Controller unterstützt werden. Treiber stellen solche Abfragen durch Aufrufen von WdfUsbTargetDeviceQueryUsbCapability und USBD_QueryUsbCapability. Diese Aufrufe werden an die UDE-Klassenerweiterung (USB Device Emulation) weitergeleitet. Beim Abrufen der Anforderung ruft die Klassenerweiterung die EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY Implementierung des Clienttreibers auf. Dieser Aufruf erfolgt nur nach Abschluss von EvtDriverDeviceAdd , in der Regel in EvtDevicePrepareHardware und nicht nach EvtDeviceReleaseHardware. Dies ist eine Rückruffunktion erforderlich.

In der Implementierung muss der Clienttreiber melden, ob er die angeforderte Funktion unterstützt. Bestimmte Funktionen werden von der UDE nicht unterstützt, z. B. statische Datenströme.

NTSTATUS
Controller_EvtControllerQueryUsbCapability(
    WDFDEVICE     UdeWdfDevice,
    PGUID         CapabilityType,
    ULONG         OutputBufferLength,
    PVOID         OutputBuffer,
    PULONG        ResultLength
)

{
    NTSTATUS status;

    UNREFERENCED_PARAMETER(UdeWdfDevice);
    UNREFERENCED_PARAMETER(OutputBufferLength);
    UNREFERENCED_PARAMETER(OutputBuffer);

    *ResultLength = 0;

    if (RtlCompareMemory(CapabilityType,
                         &GUID_USB_CAPABILITY_CHAINED_MDLS,
                         sizeof(GUID)) == sizeof(GUID)) {

        //
        // TODO: Is GUID_USB_CAPABILITY_CHAINED_MDLS supported?
        // If supported, status = STATUS_SUCCESS
        // Otherwise, status = STATUS_NOT_SUPPORTED
    }

    else {

        status = STATUS_NOT_IMPLEMENTED;
    }

    return status;
}

Erstellen eines virtuellen USB-Geräts

Ein virtuelles USB-Gerät verhält sich ähnlich wie ein USB-Gerät. Es unterstützt eine Konfiguration mit mehreren Schnittstellen, und jede Schnittstelle unterstützt alternative Einstellungen. Jede Einstellung kann über einen weiteren Endpunkt verfügen, der für Datenübertragungen verwendet wird. Alle Deskriptoren (Gerät, Konfiguration, Schnittstelle, Endpunkt) werden vom UDE-Clienttreiber festgelegt, sodass das Gerät Informationen ähnlich wie ein echtes USB-Gerät melden kann.

Hinweis

Der UDE-Clienttreiber unterstützt keine externen Hubs.

Hier ist die Zusammenfassung der Sequenz, in der der Clienttreiber ein UDECXUSBDEVICE-Handle für ein UDE-Geräteobjekt erstellt. Der Treiber muss diese Schritte ausführen, nachdem er das WDFDEVICE-Handle für den emulierten Hostcontroller abgerufen hat. Es wird empfohlen, dass der Treiber diese Aufgaben in seiner Rückruffunktion EvtDriverDeviceAdd ausführt.

  1. Rufen Sie UdecxUsbDeviceInitAllocate auf, um einen Zeiger auf die Zum Erstellen des Geräts erforderlichen Initialisierungsparameter zu erhalten. Diese Struktur wird von der UDE-Klassenerweiterung zugeordnet.

  2. Registrieren Sie Ereignisrückruffunktionen, indem Sie Member von UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS festlegen und dann UdecxUsbDeviceInitSetStateChangeCallbacks aufrufen. Hier sind die Rückruffunktionen aufgeführt, die dem UDE-Geräteobjekt zugeordnet sind, die von der UDE-Klassenerweiterung aufgerufen werden.

    Diese Funktionen werden vom Clienttreiber implementiert, um Endpunkte zu erstellen oder zu konfigurieren.

  3. Rufen Sie UdecxUsbDeviceInitSetSpeed an, um die USB-Gerätegeschwindigkeit und auch den Gerätetyp, USB 2.0 oder ein SuperSpeed-Gerät festzulegen.

  4. Rufen Sie UdecxUsbDeviceInitSetEndpointsType auf, um den Typ der vom Gerät unterstützten Endpunkte anzugeben: einfach oder dynamisch. Wenn der Clienttreiber sich für die Erstellung einfacher Endpunkte entscheidet, muss der Treiber alle Endpunktobjekte erstellen, bevor er das Gerät einsteckt. Das Gerät darf nur eine Konfiguration und nur eine Schnittstelleneinstellung pro Schnittstelle aufweisen. Bei dynamischen Endpunkten kann der Treiber endpunkte jederzeit nach dem Anschließen des Geräts erstellen, wenn er einen EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE Ereignisrückruf empfängt. Weitere Informationen finden Sie unter Erstellen dynamischer Endpunkte.

  5. Rufen Sie eine dieser Methoden auf, um dem Gerät die erforderlichen Deskriptoren hinzuzufügen.

    • UdecxUsbDeviceInitAddDescriptor

    • UdecxUsbDeviceInitAddDescriptorWithIndex

    • UdecxUsbDeviceInitAddStringDescriptor

    • UdecxUsbDeviceInitAddStringDescriptorRaw

      Wenn die UDE-Klassenerweiterung eine Anforderung für einen Standarddeskriptor empfängt, den der Clienttreiber während der Initialisierung mithilfe einer der vorherigen Methoden bereitgestellt hat, schließt die Klassenerweiterung die Anforderung automatisch ab. Die Klassenerweiterung leitet diese Anforderung nicht an den Clienttreiber weiter. Dieser Entwurf reduziert die Anzahl der Anforderungen, die der Treiber für Steuerungsanforderungen verarbeiten muss. Darüber hinaus entfällt die Notwendigkeit, dass der Treiber Deskriptorlogik implementiert, die eine umfangreiche Analyse des Setuppakets und die ordnungsgemäße Behandlung von wLength und TransferBufferLength erfordert. Diese Liste enthält die Standardanforderungen. Der Clienttreiber muss nicht nach diesen Anforderungen suchen (nur, wenn die vorherigen Methoden aufgerufen wurden, um Deskriptor hinzuzufügen):

    • USB_REQUEST_GET_DESCRIPTOR

    • USB_REQUEST_SET_CONFIGURATION

    • USB_REQUEST_SET_INTERFACE

    • USB_REQUEST_SET_ADDRESS

    • USB_REQUEST_SET_FEATURE

    • USB_FEATURE_FUNCTION_SUSPEND

    • USB_FEATURE_REMOTE_WAKEUP

    • USB_REQUEST_CLEAR_FEATURE

    • USB_FEATURE_ENDPOINT_STALL

    • USB_REQUEST_SET_SEL

    • USB_REQUEST_ISOCH_DELAY

      Anforderungen für die Schnittstelle, klassenspezifische oder herstellerdefinierte Deskriptor, die UDE-Klassenerweiterung leitet sie jedoch an den Clienttreiber weiter. Der Treiber muss diese GET_DESCRIPTOR Anforderungen verarbeiten.

  6. Rufen Sie UdecxUsbDeviceCreate auf , um das UDE-Geräteobjekt zu erstellen und das UDECXUSBDEVICE-Handle abzurufen.

  7. Erstellen Sie statische Endpunkte, indem Sie UdecxUsbEndpointCreate aufrufen. Weitere Informationen finden Sie unter Erstellen einfacher Endpunkte.

  8. Rufen Sie UdecxUsbDevicePlugIn auf, um der UDE-Klassenerweiterung anzugeben, dass das Gerät angefügt ist und E/A-Anforderungen an Endpunkte empfangen kann. Nach diesem Aufruf kann die Klassenerweiterung auch Rückruffunktionen auf Endpunkten und dem USB-Gerät aufrufen. Hinweis Wenn das USB-Gerät zur Laufzeit entfernt werden muss, kann der Clienttreiber UdecxUsbDevicePlugOutAndDelete aufrufen. Wenn der Treiber das Gerät verwenden möchte, muss es durch Aufrufen von UdecxUsbDeviceCreate erstellt werden.

In diesem Beispiel werden die Deskriptordeklarationen als globale Variablen angenommen, die wie hier für ein HID-Gerät nur als Beispiel deklariert werden:

const UCHAR g_UsbDeviceDescriptor[] = {
    // Device Descriptor
    0x12, // Descriptor Size
    0x01, // Device Descriptor Type
    0x00, 0x03, // USB 3.0
    0x00, // Device class
    0x00, // Device sub-class
    0x00, // Device protocol
    0x09, // Maxpacket size for EP0 : 2^9
    0x5E, 0x04, // Vendor ID
    0x39, 0x00, // Product ID
    0x00, // LSB of firmware version
    0x03, // MSB of firmware version
    0x01, // Manufacture string index
    0x03, // Product string index
    0x00, // Serial number string index
    0x01 // Number of configurations
};

Hier ist ein Beispiel, in dem der Clienttreiber Initialisierungsparameter angibt, indem er Rückruffunktionen registriert, die Gerätegeschwindigkeit festlegt, den Typ der Endpunkte angibt und schließlich einige Gerätedeskriptoren festlegt.


NTSTATUS
Usb_Initialize(
    _In_
        WDFDEVICE WdfDevice
    )
{
    NTSTATUS                                status;
    PUSB_CONTEXT                            usbContext;    //Client driver declared context for the host controller object
    PUDECX_USBDEVICE_CONTEXT                deviceContext; //Client driver declared context for the UDE device object
    UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS callbacks;
    WDF_OBJECT_ATTRIBUTES                   attributes;

    UDECX_USB_DEVICE_PLUG_IN_OPTIONS        pluginOptions;

    usbContext = WdfDeviceGetUsbContext(WdfDevice);

    usbContext->UdecxUsbDeviceInit = UdecxUsbDeviceInitAllocate(WdfDevice);

    if (usbContext->UdecxUsbDeviceInit == NULL) {

        ...
        goto exit;
    }

    // State changed callbacks

    UDECX_USB_DEVICE_CALLBACKS_INIT(&callbacks);
#ifndef SIMPLEENDPOINTS
    callbacks.EvtUsbDeviceDefaultEndpointAdd = UsbDevice_EvtUsbDeviceDefaultEndpointAdd;
    callbacks.EvtUsbDeviceEndpointAdd = UsbDevice_EvtUsbDeviceEndpointAdd;
    callbacks.EvtUsbDeviceEndpointsConfigure = UsbDevice_EvtUsbDeviceEndpointsConfigure;
#endif
    callbacks.EvtUsbDeviceLinkPowerEntry = UsbDevice_EvtUsbDeviceLinkPowerEntry;
    callbacks.EvtUsbDeviceLinkPowerExit = UsbDevice_EvtUsbDeviceLinkPowerExit;
    callbacks.EvtUsbDeviceSetFunctionSuspendAndWake = UsbDevice_EvtUsbDeviceSetFunctionSuspendAndWake;

    UdecxUsbDeviceInitSetStateChangeCallbacks(usbContext->UdecxUsbDeviceInit, &callbacks);

    // Set required attributes.

    UdecxUsbDeviceInitSetSpeed(usbContext->UdecxUsbDeviceInit, UdecxUsbLowSpeed);

#ifdef SIMPLEENDPOINTS
    UdecxUsbDeviceInitSetEndpointsType(usbContext->UdecxUsbDeviceInit, UdecxEndpointTypeSimple);
#else
    UdecxUsbDeviceInitSetEndpointsType(usbContext->UdecxUsbDeviceInit, UdecxEndpointTypeDynamic);
#endif

    // Add device descriptor
    //
    status = UdecxUsbDeviceInitAddDescriptor(usbContext->UdecxUsbDeviceInit,
                                           (PUCHAR)g_UsbDeviceDescriptor,
                                           sizeof(g_UsbDeviceDescriptor));

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

#ifdef USB30

    // Add BOS descriptor for a SuperSpeed device

    status = UdecxUsbDeviceInitAddDescriptor(pUsbContext->UdecxUsbDeviceInit,
                                           (PUCHAR)g_UsbBOSDescriptor,
                                           sizeof(g_UsbBOSDescriptor));

    if (!NT_SUCCESS(status)) {

        goto exit;
    }
#endif

    // String descriptors

    status = UdecxUsbDeviceInitAddDescriptorWithIndex(usbContext->UdecxUsbDeviceInit,
                                                    (PUCHAR)g_LanguageDescriptor,
                                                    sizeof(g_LanguageDescriptor),
                                                    0);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

    status = UdecxUsbDeviceInitAddStringDescriptor(usbContext->UdecxUsbDeviceInit,
                                                 &g_ManufacturerStringEnUs,
                                                 g_ManufacturerIndex,
                                                 US_ENGLISH);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, UDECX_USBDEVICE_CONTEXT);

    status = UdecxUsbDeviceCreate(&usbContext->UdecxUsbDeviceInit,
                                &attributes,
                                &usbContext->UdecxUsbDevice);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

#ifdef SIMPLEENDPOINTS
   // Create the default control endpoint
   // Shown later in this topic.

    status = UsbCreateControlEndpoint(WdfDevice);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

#endif

    UDECX_USB_DEVICE_PLUG_IN_OPTIONS_INIT(&pluginOptions);
#ifdef USB30
    pluginOptions.Usb30PortNumber = 2;
#else
    pluginOptions.Usb20PortNumber = 1;
#endif
    status = UdecxUsbDevicePlugIn(usbContext->UdecxUsbDevice, &pluginOptions);

exit:

    if (!NT_SUCCESS(status)) {

        UdecxUsbDeviceInitFree(usbContext->UdecxUsbDeviceInit);
        usbContext->UdecxUsbDeviceInit = NULL;

    }

    return status;
}

Energieverwaltung des USB-Geräts

Die UDE-Klassenerweiterung ruft die Rückruffunktionen des Clienttreibers auf, wenn sie eine Anforderung empfängt, das Gerät in den Energiesparzustand zu senden oder es wieder in den Betriebszustand zu versetzen. Diese Rückruffunktionen sind für USB-Geräte erforderlich, die die Aktivierung unterstützen. Der Clienttreiber registrierte seine Implementierung durch im vorherigen Aufruf von UdecxUsbDeviceInitSetStateChangeCallbacks.

Weitere Informationen finden Sie unter Usb-Gerätestromzustände.

Ein USB 3.0-Gerät ermöglicht es einzelnen Funktionen, in einen niedrigeren Leistungszustand zu gelangen. Jede Funktion kann auch ein Aktivierungssignal senden. Die UDE-Klassenerweiterung benachrichtigt den Clienttreiber, indem EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE aufgerufen wird. Dieses Ereignis gibt eine Funktionsleistungsänderung an und informiert den Clienttreiber darüber, ob die Funktion aus dem neuen Zustand reaktiviert werden kann. In der Funktion übergibt die Klassenerweiterung die Schnittstellennummer der Funktion, die aufwacht.

Der Clienttreiber kann die Aktion eines virtuellen USB-Geräts simulieren, indem er ein eigenes Reaktivieren aus einem Low-Link-Stromzustand, einem Funktionsangehalten oder beidem initiiert. Bei einem USB 2.0-Gerät muss der Treiber UdecxUsbDeviceSignalWake aufrufen, wenn der Treiber die Aktivierung auf dem Gerät im letzten EVT_UDECX_USB_DEVICE_D0_EXIT aktiviert hat. Für ein USB 3.0-Gerät muss der Treiber UdecxUsbDeviceSignalFunctionWake aufrufen, da die USB 3.0-Aktivierungsfunktion pro Funktion verwendet wird. Wenn sich das gesamte Gerät in einem stromarmen Zustand befindet oder in einen solchen Zustand eintritt, wird das Gerät von UdecxUsbDeviceSignalFunctionWake aktiviert.

Erstellen einfacher Endpunkte

Der Clienttreiber erstellt UDE-Endpunktobjekte, um Datenübertragungen an und vom USB-Gerät zu verarbeiten. Der Treiber erstellt einfache Endpunkte nach dem Erstellen des UDE-Geräts und vor der Meldung, dass das Gerät angeschlossen ist.

Hier ist die Zusammenfassung der Sequenz, in der der Clienttreiber ein UDECXUSBENDPOINT-Handle für ein UDE-Endpunktobjekt erstellt. Der Treiber muss diese Schritte ausführen, nachdem er das UDECXUSBDEVICE-Handle für das virtuelle USB-Gerät abgerufen hat. Es wird empfohlen, dass der Treiber diese Aufgaben in seiner Rückruffunktion EvtDriverDeviceAdd ausführt.

  1. Rufen Sie UdecxUsbSimpleEndpointInitAllocate auf, um einen Zeiger auf die Initialisierungsparameter abzurufen, die von der Klassenerweiterung zugewiesen werden.

  2. Rufen Sie UdecxUsbEndpointInitSetEndpointAddress auf, um die Endpunktadresse in den Initialisierungsparametern festzulegen.

  3. Rufen Sie UdecxUsbEndpointInitSetCallbacks auf , um die vom Clienttreiber implementierten Rückruffunktionen zu registrieren.

    Diese Funktionen werden vom Clienttreiber implementiert, um Warteschlangen und Anforderungen an einem Endpunkt zu verarbeiten.

  4. Rufen Sie UdecxUsbEndpointCreate auf , um das Endpunktobjekt zu erstellen und das UDECXUSBENDPOINT-Handle abzurufen.

  5. Rufen Sie UdecxUsbEndpointSetWdfIoQueue auf , um dem Endpunkt ein Framework-Warteschlangenobjekt zuzuordnen. Falls zutreffend, kann das Endpunktobjekt als das übergeordnete WDF-Objekt der Warteschlange festgelegt werden, indem entsprechende Attribute festgelegt werden.

    Jedes Endpunktobjekt verfügt über ein Framework-Warteschlangenobjekt, um Übertragungsanforderungen zu verarbeiten. Für jede Übertragungsanforderung, die die Klassenerweiterung empfängt, wird ein Frameworkanforderungsobjekt in die Warteschlange gestellt. Der Zustand der Warteschlange (gestartet, gelöscht) wird von der UDE-Klassenerweiterung verwaltet, und der Clienttreiber darf diesen Zustand nicht ändern. Jedes Anforderungsobjekt enthält einen USB Request Block (URB), der Details der Übertragung enthält.

In diesem Beispiel erstellt der Clienttreiber den Standard-Steuerelementendpunkt.

EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL IoEvtControlUrb;
EVT_UDECX_USB_ENDPOINT_RESET UsbEndpointReset;
EVT_UDECX_USB_ENDPOINT_PURGE UsEndpointEvtPurge;
EVT_UDECX_USB_ENDPOINT_START UsbEndpointEvtStart;

NTSTATUS
UsbCreateControlEndpoint(
    _In_
        WDFDEVICE WdfDevice
    )
{
    NTSTATUS                      status;
    PUSB_CONTEXT                  pUsbContext;
    WDF_IO_QUEUE_CONFIG           queueConfig;
    WDFQUEUE                      controlQueue;
    UDECX_USB_ENDPOINT_CALLBACKS  callbacks;
    PUDECXUSBENDPOINT_INIT        endpointInit;

    pUsbContext = WdfDeviceGetUsbContext(WdfDevice);
    endpointInit = NULL;

    WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);

    queueConfig.EvtIoInternalDeviceControl = IoEvtControlUrb;

    status = WdfIoQueueCreate (Device,
                               &queueConfig,
                               WDF_NO_OBJECT_ATTRIBUTES,
                               &controlQueue);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

    endpointInit = UdecxUsbSimpleEndpointInitAllocate(pUsbContext->UdecxUsbDevice);

    if (endpointInit == NULL) {

        status = STATUS_INSUFFICIENT_RESOURCES;
        goto exit;
    }

    UdecxUsbEndpointInitSetEndpointAddress(endpointInit, USB_DEFAULT_ENDPOINT_ADDRESS);

    UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, UsbEndpointReset);
    UdecxUsbEndpointInitSetCallbacks(endpointInit, &callbacks);

    callbacks.EvtUsbEndpointStart = UsbEndpointEvtStart;
    callbacks.EvtUsbEndpointPurge = UsEndpointEvtPurge;

    status = UdecxUsbEndpointCreate(&endpointInit,
        WDF_NO_OBJECT_ATTRIBUTES,
        &pUsbContext->UdecxUsbControlEndpoint);

    if (!NT_SUCCESS(status)) {
        goto exit;
    }

    UdecxUsbEndpointSetWdfIoQueue(pUsbContext->UdecxUsbControlEndpoint,
        controlQueue);

exit:

    if (endpointInit != NULL) {

        NT_ASSERT(!NT_SUCCESS(status));
        UdecxUsbEndpointInitFree(endpointInit);
        endpointInit = NULL;
    }

    return status;
}

Erstellen dynamischer Endpunkte

Der Clienttreiber kann dynamische Endpunkte auf Anforderung der UDE-Klassenerweiterung (im Auftrag des Hubtreibers und der Clienttreiber) erstellen. Die Klassenerweiterung stellt die Anforderung durch Aufrufen einer der folgenden Rückruffunktionen:

*EVT_UDECX_USB_DEVICE_DEFAULT_ENDPOINT_ADD Der Clienttreiber erstellt den Standardsteuerungsendpunkt (Endpunkt 0)

*EVT_UDECX_USB_DEVICE_ENDPOINT_ADD Der Clienttreiber erstellt einen dynamischen Endpunkt.

*EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE Der Clienttreiber ändert die Konfiguration, indem er eine alternative Einstellung wählt, aktuelle Endpunkte deaktiviert oder dynamische Endpunkte hinzufügt.

Der Clienttreiber registrierte den vorherigen Rückruf während des Aufrufs von UdecxUsbDeviceInitSetStateChangeCallbacks. Weitere Informationen finden Sie unter Erstellen eines virtuellen USB-Geräts. Dieser Mechanismus ermöglicht es dem Clienttreiber, die USB-Konfigurations- und Schnittstelleneinstellungen auf dem Gerät dynamisch zu ändern. Wenn beispielsweise ein Endpunktobjekt benötigt wird oder ein vorhandenes Endpunktobjekt freigegeben werden muss, ruft die Klassenerweiterung den EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE auf.

Hier ist die Zusammenfassung der Sequenz, in der der Clienttreiber ein UDECXUSBENDPOINT-Handle für ein Endpunktobjekt in seiner Implementierung der Rückruffunktion erstellt.

  1. Rufen Sie UdecxUsbEndpointInitSetEndpointAddress auf, um die Endpunktadresse in den Initialisierungsparametern festzulegen.

  2. Rufen Sie UdecxUsbEndpointInitSetCallbacks auf , um die vom Clienttreiber implementierten Rückruffunktionen zu registrieren. Ähnlich wie bei einfachen Endpunkten kann der Treiber diese Rückruffunktionen registrieren:

  3. Rufen Sie UdecxUsbEndpointCreate auf , um das Endpunktobjekt zu erstellen und das UDECXUSBENDPOINT-Handle abzurufen.

  4. Rufen Sie UdecxUsbEndpointSetWdfIoQueue auf , um dem Endpunkt ein Framework-Warteschlangenobjekt zuzuordnen.

In dieser Beispielimplementierung erstellt der Clienttreiber einen dynamischen Standardsteuerelementendpunkt.

NTSTATUS
UsbDevice_EvtUsbDeviceDefaultEndpointAdd(
    _In_
        UDECXUSBDEVICE            UdecxUsbDevice,
    _In_
        PUDECXUSBENDPOINT_INIT    UdecxUsbEndpointInit
)
{
    NTSTATUS                    status;
    PUDECX_USBDEVICE_CONTEXT    deviceContext;
    WDFQUEUE                    controlQueue;
    WDF_IO_QUEUE_CONFIG         queueConfig;
    UDECX_USB_ENDPOINT_CALLBACKS  callbacks;

    deviceContext = UdecxDeviceGetContext(UdecxUsbDevice);

    WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);

    queueConfig.EvtIoInternalDeviceControl = IoEvtControlUrb;

    status = WdfIoQueueCreate (deviceContext->WdfDevice,
                               &queueConfig,
                               WDF_NO_OBJECT_ATTRIBUTES,
                               &controlQueue);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

    UdecxUsbEndpointInitSetEndpointAddress(UdecxUsbEndpointInit, USB_DEFAULT_DEVICE_ADDRESS);

    UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, UsbEndpointReset);
    UdecxUsbEndpointInitSetCallbacks(UdecxUsbEndpointInit, &callbacks);

    status = UdecxUsbEndpointCreate(UdecxUsbEndpointInit,
        WDF_NO_OBJECT_ATTRIBUTES,
        &deviceContext->UdecxUsbControlEndpoint);

    if (!NT_SUCCESS(status)) {
        goto exit;
    }

    UdecxUsbEndpointSetWdfIoQueue(deviceContext->UdecxUsbControlEndpoint,
        controlQueue);

exit:

    return status;
}

Durchführen der Fehlerwiederherstellung durch Zurücksetzen eines Endpunkts

Manchmal können Datenübertragungen aus verschiedenen Gründen fehlschlagen, z. B. aufgrund eines Stillstands im Endpunkt. Bei fehlgeschlagenen Übertragungen kann der Endpunkt keine Anforderungen verarbeiten, bis die Fehlerbedingung gelöscht ist. Wenn bei der UDE-Klassenerweiterung Datenübertragungsfehler auftreten, ruft sie die EVT_UDECX_USB_ENDPOINT_RESET Rückruffunktion des Clienttreibers auf, die der Treiber im vorherigen Aufruf von UdecxUsbEndpointInitSetCallbacks registriert hat. In der Implementierung kann der Treiber den HALT-Zustand der Pipe löschen und andere erforderliche Schritte ausführen, um die Fehlerbedingung zu löschen.

Dieser Aufruf ist asynchron. Nachdem der Client den Vorgang zum Zurücksetzen abgeschlossen hat, muss der Treiber die Anforderung mit einem entsprechenden Fehlercode abschließen, indem er WdfRequestComplete aufruft. Dieser Aufruf benachrichtigt die UDE-Clienterweiterung über den Abschluss des Zurücksetzungsvorgangs mit status.

Hinweis Wenn für die Fehlerwiederherstellung eine komplexe Lösung erforderlich ist, hat der Clienttreiber die Möglichkeit, den Hostcontroller zurückzusetzen. Diese Logik kann in der EVT_UDECX_WDF_DEVICE_RESET Rückruffunktion implementiert werden, die der Treiber in seinem Aufruf UdecxWdfDeviceAddUsbDeviceEmulation registriert hat . Falls zutreffend, kann der Treiber den Hostcontroller und alle nachgeschalteten Geräte zurücksetzen. Wenn der Clienttreiber den Controller nicht zurücksetzen, sondern alle nachgeschalteten Geräte zurücksetzen muss, muss der Treiber während der Registrierung in den Konfigurationsparametern UdeWdfDeviceResetActionResetEachUsbDevice angeben. In diesem Fall ruft die Klassenerweiterung EVT_UDECX_WDF_DEVICE_RESET für jedes verbundene Gerät auf.

Implementieren der Warteschlangenstatusverwaltung

Der Zustand des Framework-Warteschlangenobjekts, das einem UDE-Endpunktobjekt zugeordnet ist, wird von der UDE-Klassenerweiterung verwaltet. Wenn der Clienttreiber Jedoch Anforderungen von Endpunktwarteschlangen an andere interne Warteschlangen weiterleitet, muss der Client Logik implementieren, um Änderungen im E/A-Flow des Endpunkts zu behandeln. Diese Rückruffunktionen werden bei UdecxUsbEndpointInitSetCallbacks registriert.

Endpunktlöschvorgang

Ein UDE-Clienttreiber mit einer Warteschlange pro Endpunkt kann EVT_UDECX_USB_ENDPOINT_PURGE implementieren, wie in diesem Beispiel gezeigt:

In der EVT_UDECX_USB_ENDPOINT_PURGE Implementierung muss der Clienttreiber sicherstellen, dass alle von der Warteschlange des Endpunkts weitergeleiteten E/A abgeschlossen wurden, und dass die neu weitergeleitete E/A auch fehlschlägt, bis die EVT_UDECX_USB_ENDPOINT_START des Clienttreibers aufgerufen wird. Diese Anforderungen werden durch Aufrufen von UdecxUsbEndpointPurgeComplete erfüllt, wodurch sichergestellt wird, dass alle weitergeleiteten E/A abgeschlossen sind und zukünftige weitergeleitete E/A fehlschlägt.

Endpunktstartvorgang

In der EVT_UDECX_USB_ENDPOINT_START Implementierung muss der Clienttreiber mit der Verarbeitung von E/A in der Warteschlange des Endpunkts und in allen Warteschlangen beginnen, die weitergeleitete E/A für den Endpunkt empfangen. Nachdem ein Endpunkt erstellt wurde, erhält er erst nach der Rückgabe dieser Rückruffunktion E/A. Durch diesen Rückruf wird der Endpunkt nach Abschluss EVT_UDECX_USB_ENDPOINT_PURGE in den Zustand der Verarbeitungs-E/A zurück versetzt.

Verarbeiten von Datenübertragungsanforderungen (URBs)

Um USB-E/A-Anforderungen zu verarbeiten, die an die Endpunkte des Clientgeräts gesendet werden, fangen Sie den EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL Rückruf für das Warteschlangenobjekt ab, das mit UdecxUsbEndpointInitSetCallbacks verwendet wird, wenn die Warteschlange dem Endpunkt zugeordnet wird. Verarbeiten Sie in diesem Rückruf E/A für die IOCTL_INTERNAL_USB_SUBMIT_URB IoControlCode (siehe Beispielcode unter URB-Behandlungsmethoden).

URB-Behandlungsmethoden

Im Rahmen der Verarbeitung von URBs über IOCTL_INTERNAL_USB_SUBMIT_URB einer Warteschlange, die einem Endpunkt auf einem virtuellen Gerät zugeordnet ist, kann ein UDE-Clienttreiber mithilfe der folgenden Methoden einen Zeiger auf den Übertragungspuffer einer E/A-Anforderung abrufen:

Diese Funktionen werden vom Clienttreiber implementiert, um Warteschlangen und Anforderungen an einem Endpunkt zu verarbeiten.

UdecxUrbRetrieveControlSetupPacket Ruft ein Setuppaket für die USB-Steuerung aus einem angegebenen Frameworkanforderungsobjekt ab.

UdecxUrbRetrieveBuffer Ruft den Übertragungspuffer einer URB aus dem angegebenen Frameworkanforderungsobjekt ab, das an die Endpunktwarteschlange gesendet wurde.

UdecxUrbSetBytesCompleted Legt die Anzahl der für die URB übertragenen Bytes fest, die in einem Frameworkanforderungsobjekt enthalten sind.

UdecxUrbComplete Schließt die URB-Anforderung mit einem USB-spezifischen Vervollständigungscode status ab.

UdecxUrbCompleteWithNtStatus Schließt die URB-Anforderung mit einem NTSTATUS-Code ab.

Unten sehen Sie den Ablauf der typischen E/A-Verarbeitung für die URB einer USB OUT-Übertragung.

static VOID
IoEvtSampleOutUrb(
    _In_ WDFQUEUE Queue,
    _In_ WDFREQUEST Request,
    _In_ size_t OutputBufferLength,
    _In_ size_t InputBufferLength,
    _In_ ULONG IoControlCode
)
{
    PENDPOINTQUEUE_CONTEXT pEpQContext;
    NTSTATUS status = STATUS_SUCCESS;
    PUCHAR transferBuffer;
    ULONG transferBufferLength = 0;

    UNREFERENCED_PARAMETER(OutputBufferLength);
    UNREFERENCED_PARAMETER(InputBufferLength);

    // one possible way to get context info
    pEpQContext = GetEndpointQueueContext(Queue);

    if (IoControlCode != IOCTL_INTERNAL_USB_SUBMIT_URB)
    {
        LogError(TRACE_DEVICE, "WdfRequest %p Incorrect IOCTL %x, %!STATUS!",
            Request, IoControlCode, status);
        status = STATUS_INVALID_PARAMETER;
        goto exit;
    }

    status = UdecxUrbRetrieveBuffer(Request, &transferBuffer, &transferBufferLength);
    if (!NT_SUCCESS(status))
    {
        LogError(TRACE_DEVICE, "WdfRequest %p unable to retrieve buffer %!STATUS!",
            Request, status);
        goto exit;
    }

    if (transferBufferLength >= 1)
    {
        //consume one byte of output data
        pEpQContext->global_storage = transferBuffer[0];
    }

exit:
    // writes never pended, always completed
    UdecxUrbSetBytesCompleted(Request, transferBufferLength);
    UdecxUrbCompleteWithNtStatus(Request, status);
    return;
}

Der Clienttreiber kann eine E/A-Anforderung für einen separaten DPC ausführen. Befolgen Sie die folgenden bewährten Methoden:

  • Um die Kompatibilität mit vorhandenen USB-Treibern sicherzustellen, muss der UDE-Client WdfRequestComplete unter DISPATCH_LEVEL aufrufen.
  • Wenn die URB der Warteschlange eines Endpunkts hinzugefügt wurde und der Treiber die Verarbeitung synchron im Thread oder DPC des aufrufenden Treibers beginnt, darf die Anforderung nicht synchron abgeschlossen werden. Zu diesem Zweck ist ein separater DPC erforderlich, den der Treiber durch Aufrufen von WdfDpcEnqueue in die Warteschlange stellt.
  • Wenn die UDE-Klassenerweiterung EvtIoCanceledOnQueue oder EvtRequestCancel aufruft, muss der Clienttreiber die empfangene URB für einen separaten DPC vom Aufruferthread oder DPC abschließen. Dazu muss der Treiber einen EvtIoCanceledOnQueue-Rückruf für seine URB-Warteschlangen bereitstellen.