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.
Rufen Sie UdecxInitializeWdfDeviceInit auf, indem Sie die Referenz an WDFDEVICE_INIT übergeben, die vom Framework übergeben wurde.
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.
Rufen Sie WdfDeviceCreate auf, um das Framework-Geräteobjekt zu erstellen.
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_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY
Bestimmt die vom Hostcontroller unterstützten Funktionen, die der Clienttreiber der 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.
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.
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.
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.
Rufen Sie UdecxUsbDeviceInitSetSpeed auf, 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 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.
Rufen Sie eine dieser Methoden auf, um dem Gerät erforderliche 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 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.
Rufen Sie UdecxUsbDeviceCreate auf, um das UDE-Geräteobjekt zu erstellen und den UDECXUSBDEVICE-Handle abzurufen.
Erstellen Sie statische Endpunkte, indem Sie UdecxUsbEndpointCreate aufrufen. Siehe Erstellen einfacher Endpunkte.
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.
EVT_UDECX_USB_DEVICE_D0_ENTRY: Der Client-Treiber versetzt das Gerät vom Dx-Zustand in den D0-Zustand.
EVT_UDECX_USB_DEVICE_D0_ENTRY: Der Client-Treiber versetzt das Gerät vom D0-Zustand in den Dx-Zustand.
EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE: Der Clienttreiber ändert den Funktionszustand der angegebenen Schnittstelle des virtuellen USB 3.0-Geräts.
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.
Rufen Sie UdecxUsbSimpleEndpointInitAllocate auf, um einen Zeiger auf die initialisierungsparameter abzurufen, die der Klassenerweiterung zugeordnet sind.
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 die Warteschlangen-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 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.
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:
- EVT_UDECX_USB_ENDPOINT_RESET (erforderlich).
- EVT_UDECX_USB_ENDPOINT_START
- EVT_UDECX_USB_ENDPOINT_PURGE
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 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.