Freigeben über


Schreiben eines UDE-Clienttreibers

In diesem Artikel wird das Verhalten der Erweiterung der USB-Geräteemulation (UDE) und der Aufgaben beschrieben, die ein Clienttreiber für einen emulierten Hostcontroller und Geräte ausführen muss, der an ihn angeschlossen ist. Es enthält Informationen darüber, wie der Klassentreiber und die Klassenerweiterung über eine Reihe von Routinen und Rückruffunktionen miteinander 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 Features zum Abfragen von Controllerfunktionen und Zurücksetzen des Controllers.
  • Erstellen eines virtuellen USB-Geräts, Einrichten des Geräts für die Energieverwaltung und Datenübertragung über Endpunkte.

Wichtige APIs

Voraussetzungen

  • Installiert das neueste Windows Driver Kit (WDK) auf Ihrem Entwicklungscomputer. Das Kit verfügt über die erforderlichen Headerdateien und Bibliotheken zum Schreiben eines UDE-Clienttreibers, insbesondere für 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 dem UDE vertraut. Siehe Architektur: USB-Geräteemulation(UDE).
  • Machen Sie sich mit Windows Driver Foundation (WDF) vertraut. Literatur-EmpfehlungEntwicklung 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 der Endpunkte und URBs, die zum Übertragen von Daten zwischen dem Gerät und dem Host verwendet werden. Der Clienttreiber fordert die Erstellung der Objekte und lebensdauer des Objekts an, wird von der Klassenerweiterung verwaltet.

  • Emuliertes Hostcontrollerobjekt (WDFDEVICE)

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

  • UDE-Geräteobjekt (UDECXUSBDEVICE)

    Stellt ein virtuelles USB-Gerät dar, das an einen Port auf dem emulierten Hostcontroller angeschlossen ist.

  • UDE-Endpunktobjekt (UDECXUSBENDPOINT)

    Stellt sequenzielle Datenrohre von USB-Geräten dar. Wird verwendet, um Softwareanforderungen zum Senden oder Empfangen von Daten an einem Endpunkt zu empfangen.

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 EvtDriverDeviceAdd-Rückruffunktion ausführt.

  1. Rufen Sie UdecxInitializeWdfDeviceInit auf, indem Sie die Referenz an WDFDEVICE_INIT übergeben, die vom Framework übergeben wurde.

  2. Initialisieren Sie die WDFDEVICE_INIT Struktur mit Setupinformationen, sodass dieses Gerät ähnlich wie andere USB-Hostcontroller erscheint. Weisen Sie beispielsweise einen FDO-Namen und eine symbolische Verknüpfung 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.

    Nachfolgend sind die Rückruffunktionen aufgeführt, die dem Hostcontrollerobjekt zugeordnet sind, das von UDE-Klassenerweiterung aufgerufen wird. 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.
    
    

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

Während der Initialisierung macht der UDE-Clienttreiber die GUID der GUID_DEVINTERFACE_USB_HOST_CONTROLLER Geräteschnittstelle verfügbar. Dadurch kann der Treiber IOCTL-Anforderungen von einer Anwendung empfangen, die ein Gerätehandle mithilfe dieser GUID ö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 Client-Treiber den Ereignisrückruf EvtIoDeviceControl. In der Implementierung kann der Treiber die Anforderung zur Verarbeitung an die UDE-Klassenerweiterung weiterleiten, anstatt die Anforderung zu verarbeiten. Um die Anfrage weiterzuleiten, muss der Treiber UdecxWdfDeviceTryHandleUserIoctl aufrufen. Wenn der empfangene IOCTL-Kontrollcode einer Standardanforderung entspricht, z. B. das Abrufen von Gerätedeskriptoren, werden die Klassenerweiterungsprozesse verarbeitet und die Anforderung erfolgreich abgeschlossen. In diesem Fall schließt UdecxWdfDeviceTryHandleUserIoctl mit TRUE als Rückgabewert ab. Andernfalls gibt der Aufruf FALSE zurück, und der Treiber muss bestimmen, 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, indem sie WdfUsbTargetDeviceQueryUsbCapability und USBD_QueryUsbCapability aufrufen. Diese Aufrufe werden an die USB Device Emulation(UDE)-Klassenerweiterung weitergeleitet. Nach Erhalt der Anforderung ruft die Klassenerweiterung die EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY Implementierung des Client-Treibers auf. Dieser Aufruf erfolgt erst nach Abschluss von EvtDriverDeviceAdd, normalerweise in EvtDevicePrepareHardware und nicht nach EvtDeviceReleaseHardware. Dies ist die Rückruffunktion erforderlich.

In der Implementierung muss der Clienttreiber melden, ob er die angeforderte Funktion unterstützt. Bestimmte Funktionen werden von 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 Sie ein virtuelles USB-Gerät

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 EvtDriverDeviceAdd-Rückruffunktion ausführt.

  1. Rufen Sie UdecxUsbDeviceInitAllocate auf, um einen Zeiger auf die Initialisierungsparameter abzurufen, die zum Erstellen des Geräts erforderlich sind. Diese Struktur wird von der UDE-Klassenerweiterung zugewiesen.

  2. egistrieren Sie Ereignisrückruffunktionen, indem Sie Mitglieder von UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS festlegen und dann UdecxUsbDeviceInitSetStateChangeCallbacks aufrufen. Hier sind die Rückruffunktionen, die dem UDE-Geräteobjekt zugeordnet sind, das von der UDE-Klassenerweiterung aufgerufen wird.

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

  3. Rufen Sie UdecxUsbDeviceInitSetSpeed auf, 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 Endpunkte anzugeben, die das Gerät unterstützt: einfach oder dynamisch. Wenn sich der Clienttreiber für die Erstellung einfacher Endpunkte entscheidet, muss der Treiber alle Endpunktobjekte erstellen, bevor er das Gerät einbindet. Das Gerät darf nur eine Konfiguration und nur eine Schnittstelleneinstellung pro Schnittstelle aufweisen. Bei dynamischen Endpunkten kann der Treiber jederzeit Endpunkte erstellen, nachdem das Gerät ein EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE Ereignisrückruf empfangen hat. Siehe Erstellen dynamischer Endpunkte.

  5. Rufen Sie eine dieser Methoden auf, um dem Gerät erforderliche 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 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 vom Anbieter definierte Deskriptor leitet die UDE-Klassenerweiterung 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 den UDECXUSBDEVICE-Handle abzurufen.

  7. Erstellen Sie statische Endpunkte, indem Sie UdecxUsbEndpointCreate aufrufen. Siehe Erstellen einfacher Endpunkte.

  8. Rufen Sie UdecxUsbDevicePlugIn auf, um an die UDE-Klassenerweiterung hinzuweisen, dass das Gerät angeschlossen ist und E/A-Anforderungen auf Endpunkten empfangen kann. Nach diesem Aufruf kann die Klassenerweiterung auch Rückruffunktionen auf Endpunkten und auf dem USB-Gerät aufrufen. HinweisWenn das USB-Gerät zur Laufzeit entfernt werden muss, kann der Client-Treiber 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 deklariert werden, genau wie ein Beispiel:

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

Nachfolgend finden Sie ein Beispiel, in dem der Clienttreiber Initialisierungsparameter durch Registrieren von Rückruffunktionen, Festlegen der Gerätegeschwindigkeit, Angeben des Endpunkttyps und schließlich Festlegen einiger Gerätedeskriptoren angibt.


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 an den Energiesparmodus zu senden oder zurück in den Arbeitszustand zu bringen. Diese Rückruffunktionen sind für USB-Geräte erforderlich, die die Aktivierung unterstützen. Der Client-Treiber hat seine Implementierung im vorherigen Aufruf von UdecxUsbDeviceInitSetStateChangeCallbacks reagiert.

Weitere Informationen finden Sie unter Energiezustände von USB-Geräten.

Ein USB 3.0-Gerät ermöglicht es einzelnen Funktionen, den Energiesparmodus zu senken. Jede Funktion ist auch in der Lage, ein Wake-Signal zu senden. Die UDE-Klassenerweiterung benachrichtigt den Clienttreiber durch Aufrufen von EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE. 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, das eine eigene Aktivierung über einen Energiesparmodus, funktionsangehalten oder beides initiiert. Für ein USB 2.0-Gerät muss der Treiber UdecxUsbDeviceSignalWake aufrufen, wenn der Treiber die Aktivierung des Geräts 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 verfügbar ist. Wenn sich das gesamte Gerät in einem Energiesparzustand befindet oder einen solchen Zustand eingibt, aktiviert UdecxUsbDeviceSignalFunctionWake das Gerät.

Erstellen einfacher Endpunkte

Der Clienttreiber erstellt UDE-Endpunktobjekte zum Verarbeiten von Datenübertragungen an und vom USB-Gerät. Der Treiber erstellt einfache Endpunkte nach dem Erstellen des UDE-Geräts und vor dem Melden des Geräts als angeschlossen.

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 EvtDriverDeviceAdd-Rückruffunktion ausführt.

  1. Rufen Sie UdecxUsbSimpleEndpointInitAllocate auf, um einen Zeiger auf die initialisierungsparameter abzurufen, die der Klassenerweiterung zugeordnet sind.

  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 das übergeordnete WDF-Objekt der Warteschlange sein, indem entsprechende Attribute festgelegt werden.

    Jedes Endpunktobjekt verfügt über ein Framework-Warteschlangenobjekt, um Übertragungsanforderungen zu verarbeiten. Für jede Transferanforderung, die die Klassenerweiterung empfängt, wird ein Framework-Anforderungsobjekt in die Warteschlange gestellt. Der Status 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-Anforderungsblock (URB), der Details der Übertragung enthält.

In diesem Beispiel erstellt der Clienttreiber den Standardsteuerungsendpunkt.

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 Clienttreibers) erstellen. Die Klassenerweiterung führt die Anforderung durch Aufrufen einer dieser Rückruffunktionen aus:

* 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 eine alternative Einstellung ausgewählt, aktuelle Endpunkte deaktiviert oder dynamische Endpunkte hinzugefügt werden.

Der Clienttreiber registriert den vorherigen Rückruf während des Aufrufs von UdecxUsbDeviceInitSetStateChangeCallbacks. Siehe Virtuelles USB-Gerät erstellen. Dieser Mechanismus ermöglicht es dem Clienttreiber, die USB-Konfiguration 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 die 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 Standardsteuerungsendpunkt.

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. eine Stallbedingung im Endpunkt. Bei fehlgeschlagenen Übertragungen kann der Endpunkt Keine Anforderungen verarbeiten, bis die Fehlerbedingung gelöscht wird. Wenn bei der UDE-Klassenerweiterung Datenübertragungen fehlschlagen, ruft sie die Rückruffunktion EVT_UDECX_USB_ENDPOINT_RESET des Client-Treibers auf, die der Treiber im vorherigen Aufruf von UdecxUsbEndpointInitSetCallbacks registriert hat. In der Implementierung kann der Treiber den HALT-Zustand des Rohrs löschen und weitere erforderliche Schritte ausführen, um die Fehlerbedingung zu löschen.

Dies ist ein asynchroner Aufruf. Nachdem der Client mit dem Zurücksetzungsvorgang fertig ist, muss der Treiber die Anforderung mit einem entsprechenden Fehlercode abschließen, indem WdfRequestComplete aufgerufen wird. Dadurch wird die UDE-Clienterweiterung über den Abschluss des Zurücksetzungsvorgangs mit dem Status benachrichtigt.

Hinweis : Wenn eine komplexe Lösung für die Fehlerwiederherstellung erforderlich ist, hat der Clienttreiber die Möglichkeit, den Hostcontroller zurückzusetzen. Diese Logik kann in der Rückruffunktion EVT_UDECX_WDF_DEVICE_RESET 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 muss, aber alle nachgeschalteten Geräte zurücksetzen muss, muss der Treiber UdeWdfDeviceResetActionResetEachUsbDevice in den Konfigurationsparametern während der Registrierung angeben. In diesem Fall ruft die Klassenerweiterung EVT_UDECX_WDF_DEVICE_RESET für jedes verbundene Gerät auf.

Implementieren der Warteschlangenstatusverwaltung

Der Status 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-Fluss des Endpunkts zu behandeln. Diese Rückruffunktionen werden bei UdecxUsbEndpointInitSetCallbacks registriert.

Endpunktlöschvorgang

in UDE-Client-Treiber mit einer Warteschlange pro Endpunkt kann EVT_UDECX_USB_ENDPOINT_PURGE wie in diesem Beispiel gezeigt implementieren:

In der Implementierung von EVT_UDECX_USB_ENDPOINT_PURGE muss der Client-Treiber sicherstellen, dass alle aus der Warteschlange des Endpunkts weitergeleiteten E/A-Vorgänge abgeschlossen wurden und dass neu weitergeleitete E/A-Vorgänge ebenfalls fehlschlagen, bis EVT_UDECX_USB_ENDPOINT_START des Client-Treibers aufgerufen wird. Diese Anforderungen werden durch Aufrufen von UdecxUsbEndpointPurgeComplete erfüllt, wodurch sichergestellt wird, dass alle weitergeleiteten E/A-Vorgänge abgeschlossen sind und zukünftige weitergeleitete E/A fehlgeschlagen sind.

Endpunktstartvorgang

In der EVT_UDECX_USB_ENDPOINT_START Implementierung ist der Clienttreiber erforderlich, um mit der Verarbeitung von E/A in der Warteschlange des Endpunkts und in allen Warteschlangen zu beginnen, die weitergeleitete E/A für den Endpunkt empfangen. Nachdem ein Endpunkt erstellt wurde, empfängt er erst nach dem Zurückgeben dieser Rückruffunktion E/A. Dieser Rückruf bringt den Endpunkt in einen Zustand der E/A-Verarbeitung zurück, nachdem EVT_UDECX_USB_ENDPOINT_PURGE abgeschlossen ist.

Verarbeiten von Datenübertragungsanforderungen (URBs)

Um an die Endpunkte des Clientgeräts gesendete USB-E/A-Anfragen zu verarbeiten, fangen Sie den Rückruf EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL für das Warteschlangenobjekt ab, das mit UdecxUsbEndpointInitSetCallbacks verwendet wird, wenn Sie die Warteschlange mit dem Endpunkt verknüpfen. Verarbeiten Sie in diesem Rückruf E/A für den 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 dieser 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 USB-Steuereinrichtungspaket aus einem angegebenen Framework-Anforderungsobjekt ab.

UdecxUrbRetrieveBuffer Ruft den Übertragungspuffer eines URB aus dem angegebenen Framework-Anforderungsobjekt ab, das an die Endpunktwarteschlange gesendet wird.

UdecxUrbSetBytesCompleted Legt die Anzahl der Bytes fest, die für die URB in einem Framework-Anforderungsobjekt übertragen werden.

UdecxUrbComplete schließt die URB-Anforderung mit einem USB-spezifischen Abschlussstatuscode ab.

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

Unten sehen Sie den Fluss 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 eine separate E/A-Anforderung mit einem DPC abschließen. Befolgen Sie diese bewährten Methoden:

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