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.
Rufen Sie UdecxInitializeWdfDeviceInit auf, indem Sie den Verweis auf WDFDEVICE_INIT übergeben, der vom Framework übergeben wurde.
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.
Rufen Sie WdfDeviceCreate auf , um das Framework-Geräteobjekt zu erstellen.
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_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY
Bestimmt die vom Hostcontroller unterstützten Funktionen, die der Clienttreiber an die Klassenerweiterung melden muss.
-
Optional. Setzt den Hostcontroller und/oder die verbundenen Geräte zurück.
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.
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.
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.
Rufen Sie UdecxUsbDeviceInitSetSpeed an, um die USB-Gerätegeschwindigkeit und auch den Gerätetyp, USB 2.0 oder ein SuperSpeed-Gerät festzulegen.
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.
Rufen Sie eine dieser Methoden auf, um dem Gerät die erforderlichen Deskriptoren hinzuzufügen.
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.
Rufen Sie UdecxUsbDeviceCreate auf , um das UDE-Geräteobjekt zu erstellen und das UDECXUSBDEVICE-Handle abzurufen.
Erstellen Sie statische Endpunkte, indem Sie UdecxUsbEndpointCreate aufrufen. Weitere Informationen finden Sie unter Erstellen einfacher Endpunkte.
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.
EVT_UDECX_USB_DEVICE_D0_ENTRY: Der Clienttreiber überwechselt das Gerät von einem Dx-Zustand in den D0-Zustand.
EVT_UDECX_USB_DEVICE_D0_EXIT: Der Clienttreiber wechselt das Gerät vom D0-Zustand in den Dx-Zustand.
EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE: Der Clienttreiber ändert den Funktionsstatus der angegebenen Schnittstelle des virtuellen USB 3.0-Geräts.
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.
Rufen Sie UdecxUsbSimpleEndpointInitAllocate auf, um einen Zeiger auf die Initialisierungsparameter abzurufen, die von der Klassenerweiterung zugewiesen werden.
Rufen Sie UdecxUsbEndpointInitSetEndpointAddress auf, um die Endpunktadresse in den Initialisierungsparametern festzulegen.
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.
EVT_UDECX_USB_ENDPOINT_RESET: Setzt einen Endpunkt des virtuellen USB-Geräts zurück.
EVT_UDECX_USB_ENDPOINT_START: Optional. Beginnt mit der Verarbeitung von E/A-Anforderungen
EVT_UDECX_USB_ENDPOINT_PURGE: Optional. Beenden Sie das Anstehen von E/A-Anforderungen an die Warteschlange des Endpunkts, und brechen Sie nicht verarbeitete Anforderungen ab.
Rufen Sie UdecxUsbEndpointCreate auf , um das Endpunktobjekt zu erstellen und das UDECXUSBENDPOINT-Handle abzurufen.
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.
Rufen Sie UdecxUsbEndpointInitSetEndpointAddress auf, um die Endpunktadresse in den Initialisierungsparametern festzulegen.
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:
Rufen Sie UdecxUsbEndpointCreate auf , um das Endpunktobjekt zu erstellen und das UDECXUSBENDPOINT-Handle abzurufen.
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.
Feedback
https://aka.ms/ContentUserFeedback.
Bald verfügbar: Im Laufe des Jahres 2024 werden wir GitHub-Issues stufenweise als Feedbackmechanismus für Inhalte abbauen und durch ein neues Feedbacksystem ersetzen. Weitere Informationen finden Sie unterFeedback senden und anzeigen für