Grundlegendes zur Codestruktur des USB-Client-Treibers (KMDF)
In diesem Thema erfahren Sie mehr über den Quellcode für einen KMDF-basierten USB-Clienttreiber. Die Codebeispiele werden von der vorlage für den USB-Benutzermodustreiber generiert, die in Microsoft Visual Studio 2019 enthalten ist.
Diese Abschnitte enthalten Informationen zum Vorlagencode.
Anweisungen zum Generieren des KMDF-Vorlagencodes finden Sie unter Schreiben des ersten USB-Clienttreibers (KMDF).
Treiberquellcode
Das Treiberobjekt stellt die Instanz des Clienttreibers dar, nachdem Windows den Treiber im Arbeitsspeicher geladen hat. Der vollständige Quellcode für das Treiberobjekt befindet sich in Driver.h und Driver.c.
Driver.h
Bevor wir uns mit den Details des Vorlagencodes befassen, sehen wir uns einige Deklarationen in der Headerdatei (Driver.h) an, die für die KMDF-Treiberentwicklung relevant sind.
Driver.h enthält diese Dateien, die im Windows Driver Kit (WDK) enthalten sind.
#include <ntddk.h>
#include <wdf.h>
#include <usb.h>
#include <usbdlib.h>
#include <wdfusb.h>
#include "device.h"
#include "queue.h"
#include "trace.h"
Ntddk.h- und Wdf.h-Headerdateien sind immer für die KMDF-Treiberentwicklung enthalten. Die Headerdatei enthält verschiedene Deklarationen und Definitionen von Methoden und Strukturen, die Sie zum Kompilieren eines KMDF-Treibers benötigen.
Usb.h und Usbdlib.h enthalten Deklarationen und Definitionen von Strukturen und Routinen, die von einem Clienttreiber für ein USB-Gerät benötigt werden.
Wdfusb.h enthält Deklarationen und Definitionen von Strukturen und Methoden, die für die Kommunikation mit den vom Framework bereitgestellten USB-I/O-Zielobjekten erforderlich sind.
"Device.h", "Queue.h" und "Trace.h" sind nicht im WDK enthalten. Diese Headerdateien werden von der Vorlage generiert und weiter unten in diesem Thema erläutert.
Der nächste Block in Driver.h stellt Funktionsrollentypdeklarationen für die DriverEntry-Routine sowie EvtDriverDeviceAdd- und EvtCleanupCallback-Ereignisrückrufroutinen bereit. Alle diese Routinen werden vom Treiber implementiert. Rollentypen helfen beim Analysieren des Quellcodes eines Treibers mit statischer Treiberüberprüfung (STATIC Driver Verifier, SDV). Weitere Informationen zu Rollentypen finden Sie unter Deklarieren von Funktionen mithilfe von Funktionsrollentypen für KMDF-Treiber.
DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD MyUSBDriver_EvtDeviceAdd;
EVT_WDF_OBJECT_CONTEXT_CLEANUP MyUSBDriver_EvtDriverContextCleanup;
Die Implementierungsdatei Driver.c enthält den folgenden Codeblock, der Pragma verwendet, alloc_text
um anzugeben, ob die DriverEntry-Funktion und Ereignisrückrufroutinen im ausgelagerten Speicher vorhanden sind.
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDeviceAdd)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDriverContextCleanup)
#endif
Beachten Sie, dass DriverEntry als INIT gekennzeichnet ist, während die Ereignisrückrufroutinen als PAGE gekennzeichnet sind. Der INIT-Abschnitt gibt an, dass der ausführbare Code für DriverEntry seitenfähig und verworfen werden kann, sobald der Treiber von seinem DriverEntry zurückgegeben wird. Der PAGE-Abschnitt gibt an, dass der Code nicht immer im physischen Speicher verbleiben muss; sie kann in die Seitendatei geschrieben werden, wenn sie nicht verwendet wird. Weitere Informationen finden Sie unter Sperren von ausgelagertem Code oder Daten.
Kurz nach dem Laden des Treibers weist Windows eine DRIVER_OBJECT Struktur zu, die Ihren Treiber darstellt. Anschließend wird die Einstiegspunktroutine Des Treibers, DriverEntry, aufgerufen und ein Zeiger an die Struktur übergeben. Da Windows anhand des Namens nach der Routine sucht, muss jeder Treiber eine Routine namens "DriverEntry" implementieren. Die Routine führt die Initialisierungsaufgaben des Treibers aus und gibt die Ereignisrückrufroutinen des Treibers für das Framework an.
Das folgende Codebeispiel zeigt die DriverEntry-Routine, die von der Vorlage generiert wird.
NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
WDF_DRIVER_CONFIG config;
NTSTATUS status;
WDF_OBJECT_ATTRIBUTES attributes;
//
// Initialize WPP Tracing
//
WPP_INIT_TRACING( DriverObject, RegistryPath );
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
//
// Register a cleanup callback so that we can call WPP_CLEANUP when
// the framework driver object is deleted during driver unload.
//
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.EvtCleanupCallback = MyUSBDriver_EvtDriverContextCleanup;
WDF_DRIVER_CONFIG_INIT(&config,
MyUSBDriver_EvtDeviceAdd
);
status = WdfDriverCreate(DriverObject,
RegistryPath,
&attributes,
&config,
WDF_NO_HANDLE
);
if (!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed %!STATUS!", status);
WPP_CLEANUP(DriverObject);
return status;
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
return status;
}
Die DriverEntry-Routine verfügt über zwei Parameter: einen Zeiger auf die DRIVER_OBJECT Struktur, die von Windows zugewiesen wird, und einen Registrierungspfad für den Treiber. Der Parameter RegistryPath stellt den treiberspezifischen Pfad in der Registrierung dar.
In der DriverEntry-Routine führt der Treiber die folgenden Aufgaben aus:
Ordnet globale Ressourcen zu, die während der Lebensdauer des Treibers erforderlich sind. Beispielsweise weist der Clienttreiber im Vorlagencode Ressourcen zu, die für die WPP-Softwareablaufverfolgung erforderlich sind, indem das WPP_INIT_TRACING-Makro aufgerufen wird.
Registriert bestimmte Ereignisrückrufroutinen mit dem Framework.
Um die Ereignisrückrufe zu registrieren, gibt der Clienttreiber zunächst Zeiger auf seine Implementierungen der EvtDriverXxx-Routinen in bestimmten WDF-Strukturen an. Der Treiber ruft dann die WdfDriverCreate-Methode auf und liefert diese Strukturen (im nächsten Schritt erläutert).
Ruft die WdfDriverCreate-Methode auf und ruft ein Handle für das Framework-Treiberobjekt ab.
Nachdem der Clienttreiber WdfDriverCreate aufgerufen hat, erstellt das Framework ein Frameworktreiberobjekt, das den Clienttreiber darstellt. Wenn der Aufruf abgeschlossen ist, empfängt der Clienttreiber ein WDFDRIVER-Handle und kann Informationen zum Treiber abrufen, z. B. seinen Registrierungspfad, Versionsinformationen usw. (siehe WDF Driver Object Reference).
Beachten Sie, dass sich das Frameworktreiberobjekt von dem windows-Treiberobjekt unterscheidet, das von DRIVER_OBJECT beschrieben wird. Jederzeit kann der Clienttreiber mithilfe des WDFDRIVER-Handles und aufrufen der WdfGetDriver-Methode einen Zeiger auf die WindowsDRIVER_OBJECT-Struktur abrufen.
Nach dem WdfDriverCreate-Aufruf arbeitet das Framework mit dem Clienttreiber zusammen, um mit Windows zu kommunizieren. Das Framework fungiert als Abstraktionsebene zwischen Windows und dem Treiber und behandelt die meisten komplizierten Treiberaufgaben. Der Clienttreiber registriert sich beim Framework für Ereignisse, an denen der Treiber interessiert ist. Wenn bestimmte Ereignisse auftreten, benachrichtigt Windows das Framework. Wenn der Treiber einen Ereignisrückruf für ein bestimmtes Ereignis registriert hat, benachrichtigt das Framework den Treiber durch Aufrufen des registrierten Ereignisrückrufs. Auf diese Weise erhält der Treiber bei Bedarf die Möglichkeit, das Ereignis zu behandeln. Wenn der Treiber den Ereignisrückruf nicht registriert hat, fährt das Framework mit der Standardbehandlung des Ereignisses fort.
Einer der Ereignisrückrufe, die der Treiber registrieren muss, ist EvtDriverDeviceAdd. Das Framework ruft die EvtDriverDeviceAdd-Implementierung des Treibers auf, wenn das Framework zum Erstellen eines Geräteobjekts bereit ist. In Windows ist ein Geräteobjekt eine logische Darstellung der Funktion des physischen Geräts, für das der Clienttreiber geladen wird (weiter unten in diesem Thema erläutert).
Andere Ereignisrückrufe, die der Treiber registrieren kann, sind EvtDriverUnload, EvtCleanupCallback und EvtDestroyCallback.
Im Vorlagencode registriert sich der Clienttreiber für zwei Ereignisse: EvtDriverDeviceAdd und EvtCleanupCallback. Der Treiber gibt einen Zeiger auf die Implementierung von EvtDriverDeviceAdd in der WDF_DRIVER_CONFIG Struktur und den EvtCleanupCallback-Ereignisrückruf in der WDF_OBJECT_ATTRIBUTES-Struktur an.
Wenn Windows bereit ist, die DRIVER_OBJECT Struktur freizugeben und den Treiber zu entladen, meldet das Framework dieses Ereignis an den Clienttreiber, indem er die EvtCleanupCallback-Implementierung des Treibers aufruft. Das Framework ruft diesen Rückruf direkt vor dem Löschen des Frameworktreiberobjekts auf. Der Clienttreiber kann alle globalen Ressourcen freigeben, die er in seinem DriverEntry zugeordnet hat. Beispielsweise stoppt der Clienttreiber im Vorlagencode die WPP-Ablaufverfolgung, die in DriverEntry aktiviert wurde.
Das folgende Codebeispiel zeigt die EvtCleanupCallback-Ereignisrückrufimplementierung des Clienttreibers.
VOID MyUSBDriver_EvtDriverContextCleanup(
_In_ WDFDRIVER Driver
)
{
UNREFERENCED_PARAMETER(Driver);
PAGED_CODE ();
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
//
// Stop WPP Tracing
//
WPP_CLEANUP( WdfDriverWdmGetDriverObject(Driver) );
}
Nachdem das Gerät vom USB-Treiberstapel erkannt wurde, erstellt der Bustreiber ein physisches Geräteobjekt (PDO) für das Gerät und ordnet die PDO dem Geräteknoten zu. Der Geräteknoten befindet sich in einer Stapelbildung, in der sich der PDO unten befindet. Jeder Stapel muss über einen PDO verfügen und kann Filtergeräteobjekte (Filter-DOs) und ein Funktionsgeräteobjekt (Function Device Object, FDO) darüber aufweisen. Weitere Informationen finden Sie unter Geräteknoten und Gerätestapel.
Diese Abbildung zeigt den Gerätestapel für den Vorlagentreiber MyUSBDriver_.sys.
Beachten Sie den Gerätestapel mit dem Namen "Mein USB-Gerät". Der USB-Treiberstapel erstellt den PDO für den Gerätestapel. Im Beispiel ist der PDO mit Usbhub3.sys verknüpft, bei dem es sich um einen der Treiber handelt, die im USB-Treiberstapel enthalten sind. Als Funktionstreiber für das Gerät muss der Clienttreiber zuerst den FDO für das Gerät erstellen und dann an den Anfang des Gerätestapels anfügen.
Für einen KMDF-basierten Clienttreiber führt das Framework diese Aufgaben im Auftrag des Clienttreibers aus. Um die FDO für das Gerät darzustellen, erstellt das Framework ein Framework-Geräteobjekt. Der Clienttreiber kann jedoch bestimmte Initialisierungsparameter angeben, die das Framework zum Konfigurieren des neuen Objekts verwendet. Diese Möglichkeit wird dem Clienttreiber gegeben, wenn das Framework die EvtDriverDeviceAdd-Implementierung des Treibers aufruft. Nachdem das Objekt erstellt wurde und der FDO oben im Gerätestapel angefügt ist, stellt das Framework dem Clienttreiber ein WDFDEVICE-Handle für das Framework-Geräteobjekt bereit. Mithilfe dieses Handles kann der Clienttreiber verschiedene gerätebezogene Vorgänge ausführen.
Das folgende Codebeispiel zeigt die EvtDriverDeviceAdd-Ereignisrückrufimplementierung des Clienttreibers.
NTSTATUS
MyUSBDriver_EvtDeviceAdd(
_In_ WDFDRIVER Driver,
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
NTSTATUS status;
UNREFERENCED_PARAMETER(Driver);
PAGED_CODE();
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
status = MyUSBDriver_CreateDevice(DeviceInit);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
return status;
}
Während der Laufzeit verwendet die Implementierung von EvtDriverDeviceAdd das PAGED_CODE Makro, um zu überprüfen, ob die Routine in einer geeigneten Umgebung für seitenfähigen Code aufgerufen wird. Stellen Sie sicher, dass Sie das Makro aufrufen, nachdem Sie alle Variablen deklariert haben. Andernfalls schlägt die Kompilierung fehl, da es sich bei den generierten Quelldateien um C-Dateien und nicht um .cpp Dateien handelt.
Die EvtDriverDeviceAdd-Implementierung des Clienttreibers ruft die MyUSBDriver_CreateDevice Hilfsfunktion auf, um die erforderlichen Aufgaben auszuführen.
Das folgende Codebeispiel zeigt die hilfsfunktion MyUSBDriver_CreateDevice. MyUSBDriver_CreateDevice ist in Device.c definiert.
NTSTATUS
MyUSBDriver_CreateDevice(
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
WDF_OBJECT_ATTRIBUTES deviceAttributes;
PDEVICE_CONTEXT deviceContext;
WDFDEVICE device;
NTSTATUS status;
PAGED_CODE();
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
pnpPowerCallbacks.EvtDevicePrepareHardware = MyUSBDriver_EvtDevicePrepareHardware;
WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);
status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
if (NT_SUCCESS(status)) {
//
// Get the device context and initialize it. WdfObjectGet_DEVICE_CONTEXT is an
// inline function generated by WDF_DECLARE_CONTEXT_TYPE macro in the
// device.h header file. This function will do the type checking and return
// the device context. If you pass a wrong object handle
// it will return NULL and assert if run under framework verifier mode.
//
deviceContext = WdfObjectGet_DEVICE_CONTEXT(device);
deviceContext->PrivateDeviceData = 0;
//
// Create a device interface so that applications can find and talk
// to us.
//
status = WdfDeviceCreateDeviceInterface(
device,
&GUID_DEVINTERFACE_MyUSBDriver_,
NULL // ReferenceString
);
if (NT_SUCCESS(status)) {
//
// Initialize the I/O Package and any Queues
//
status = MyUSBDriver_QueueInitialize(device);
}
}
return status;
}
EvtDriverDeviceAdd verfügt über zwei Parameter: ein Handle für das Frameworktreiberobjekt, das im vorherigen Aufruf von DriverEntry erstellt wurde, und einen Zeiger auf eine WDFDEVICE_INIT Struktur. Das Framework weist die WDFDEVICE_INIT Struktur zu und übergibt ihn einen Zeiger, damit der Clienttreiber die Struktur mit Initialisierungsparametern für das zu erstellende Framework-Geräteobjekt auffüllen kann.
In der EvtDriverDeviceAdd-Implementierung muss der Clienttreiber die folgenden Aufgaben ausführen:
Rufen Sie die WdfDeviceCreate-Methode auf, um ein WDFDEVICE-Handle für das neue Geräteobjekt abzurufen.
Die WdfDeviceCreate-Methode bewirkt, dass das Framework ein Framework-Geräteobjekt für den FDO erstellt und an den Anfang des Gerätestapels anfügt. Im WdfDeviceCreate-Aufruf muss der Clienttreiber die folgenden Aufgaben ausführen:
- Geben Sie Zeiger auf die Plug-and-Play-Routinen (Plug and Play) des Clienttreibers (PnP) in der vom Framework angegebenen WDFDEVICE_INIT Struktur an. Die Routinen werden zuerst in der WDF_PNPPOWER_EVENT_CALLBACKS-Struktur festgelegt und dann mit WDFDEVICE_INIT verknüpft, indem die WdfDeviceInitSetPnpPowerEventCallbacks-Methode aufgerufen wird.
Windows-Komponenten, PnP und Energiemanager senden gerätebezogene Anforderungen an Treiber als Reaktion auf Änderungen im PnP-Zustand (z. B. "Gestartet", "Angehalten" und "Entfernt") und "Energiezustand" (z. B. "Arbeiten" oder "Anhalten"). Bei KMDF-basierten Treibern fängt das Framework diese Anforderungen ab. Der Clienttreiber kann über die Anforderungen benachrichtigt werden, indem Rückrufroutinen registriert werden, die als PnP-Power-Ereignisrückrufe mit dem Framework bezeichnet werden, indem er den WdfDeviceCreate-Aufruf verwendet. Wenn Windows-Komponenten Anforderungen senden, verarbeitet das Framework sie und ruft den entsprechenden PnP-Power-Ereignisrückruf auf, wenn der Clienttreiber registriert wurde.
Eine der PnP-Ereignisrückrufroutinen, die der Clienttreiber implementieren muss, ist EvtDevicePrepareHardware. Dieser Ereignisrückruf wird aufgerufen, wenn der PnP-Manager das Gerät startet. Die Implementierung für EvtDevicePrepareHardware wird im folgenden Abschnitt erläutert.
- Geben Sie einen Zeiger auf die Gerätekontextstruktur des Treibers an. Der Zeiger muss in der WDF_OBJECT_ATTRIBUTES Struktur festgelegt werden, die durch Aufrufen des WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE-Makros initialisiert wird.
Ein Gerätekontext (manchmal als Geräteerweiterung bezeichnet) ist eine Datenstruktur (definiert durch den Clienttreiber), um Informationen zu einem bestimmten Geräteobjekt zu speichern. Der Clienttreiber übergibt einen Zeiger an den Gerätekontext an das Framework. Das Framework weist einen Speicherblock basierend auf der Größe der Struktur zu und speichert einen Zeiger auf diesen Speicherort im Framework-Geräteobjekt. Der Clienttreiber kann den Zeiger verwenden, um auf Informationen in Mitgliedern des Gerätekontexts zuzugreifen und diese zu speichern. Weitere Informationen zu Gerätekontexten finden Sie unter Framework-Objektkontextbereich.
Nach Abschluss des WdfDeviceCreate-Aufrufs empfängt der Clienttreiber ein Handle für das neue Framework-Geräteobjekt, das einen Zeiger auf den Speicherblock speichert, der vom Framework für den Gerätekontext zugewiesen wird. Der Clienttreiber kann nun einen Zeiger auf den Gerätekontext abrufen, indem das WdfObjectGet_DEVICE_CONTEXT-Makro aufgerufen wird.
Registrieren Sie eine Geräteschnittstellen-GUID für den Clienttreiber, indem Sie die WdfDeviceCreateDeviceInterface-Methode aufrufen. Anwendungen können mithilfe dieser GUID mit dem Treiber kommunizieren. Die GUID-Konstante wird im Header "public.h" deklariert.
Richten Sie Warteschlangen für E/A-Übertragungen an das Gerät ein. Der Vorlagencode definiert MyUSBDriver_QueueInitialize, eine Hilfsroutine zum Einrichten von Warteschlangen, die im Quellcodeabschnitt "Warteschlange" erläutert wird.
Gerätequellcode
Das Geräteobjekt stellt die Instanz des Geräts dar, für das der Clienttreiber im Arbeitsspeicher geladen wird. Der vollständige Quellcode für das Geräteobjekt befindet sich in "Device.h" und "Device.c".
Device.h
Die Headerdatei "Device.h" enthält "public.h", die allgemeine Deklarationen enthält, die von allen Dateien im Projekt verwendet werden.
Der nächste Block in Device.h deklariert den Gerätekontext für den Clienttreiber.
typedef struct _DEVICE_CONTEXT
{
WDFUSBDEVICE UsbDevice;
ULONG PrivateDeviceData; // just a placeholder
} DEVICE_CONTEXT, *PDEVICE_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT)
Die DEVICE_CONTEXT Struktur wird vom Clienttreiber definiert und speichert Informationen zu einem Framework-Geräteobjekt. Sie wird in Device.h deklariert und enthält zwei Member: ein Handle für das USB-Zielgerätobjekt eines Frameworks (weiter unten erläutert) und einen Platzhalter. Diese Struktur wird in späteren Übungen erweitert.
Device.h enthält auch das WDF_DECLARE_CONTEXT_TYPE Makro, das eine Inlinefunktion generiert, WdfObjectGet_DEVICE_CONTEXT. Der Clienttreiber kann diese Funktion aufrufen, um einen Zeiger auf den Speicherblock aus dem Framework-Geräteobjekt abzurufen.
Die folgende Codezeile deklariert MyUSBDriver_CreateDevice, eine Hilfsfunktion, die ein WDFUSBDEVICE-Handle an das USB-Zielgerätobjekt abruft.
NTSTATUS
MyUSBDriver_CreateDevice(
_Inout_ PWDFDEVICE_INIT DeviceInit
);
USBCreate verwendet einen Zeiger auf eine WDFDEVICE_INIT Struktur als Parameter. Dies ist derselbe Zeiger, der vom Framework übergeben wurde, als er die EvtDriverDeviceAdd-Implementierung des Clienttreibers aufgerufen hat . Im Grunde führt MyUSBDriver_CreateDevice die Aufgaben von EvtDriverDeviceAdd aus. Der Quellcode für die Implementierung von EvtDriverDeviceAdd wird im vorherigen Abschnitt erläutert.
Die nächste Zeile in Device.h deklariert eine Funktionsrollentypdeklaration für die Ereignisrückrufroutine EvtDevicePrepareHardware. Der Ereignisrückruf wird vom Clienttreiber implementiert und führt Aufgaben wie das Konfigurieren des USB-Geräts aus.
EVT_WDF_DEVICE_PREPARE_HARDWARE MyUSBDriver_EvtDevicePrepareHardware;
Device.c
Die Device.c-Implementierungsdatei enthält den folgenden Codeblock, der pragma verwendet, alloc_text
um anzugeben, dass die Implementierung des Treibers von EvtDevicePrepareHardware im ausgelagerten Speicher liegt.
#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_CreateDevice)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDevicePrepareHardware)
#endif
In der Implementierung für EvtDevicePrepareHardware führt der Clienttreiber die USB-spezifischen Initialisierungsaufgaben aus. Zu diesen Aufgaben gehören das Registrieren des Clienttreibers, das Initialisieren von USB-spezifischen E/A-Zielobjekten und das Auswählen einer USB-Konfiguration. In der folgenden Tabelle sind die speziellen E/A-Zielobjekte aufgeführt, die vom Framework bereitgestellt werden. Weitere Informationen finden Sie unter USB-E/A-Ziele.
USB-E/A-Zielobjekt (Handle) | Rufen Sie ein Handle ab, indem Sie anrufen... | Beschreibung |
---|---|---|
USB-Zielgerätobjekt (WDFUSBDEVICE) | WdfUsbTargetDeviceCreateWithParameters | Stellt ein USB-Gerät dar und stellt Methoden zum Abrufen der Gerätebeschreibung und zum Senden von Steuerelementanforderungen an das Gerät bereit. |
USB-Zielschnittstellenobjekt (WDFUSBINTERFACE) | WdfUsbTargetDeviceGetInterface | Stellt eine einzelne Schnittstelle dar und stellt Methoden bereit, die ein Clienttreiber aufrufen kann, um eine alternative Einstellung auszuwählen und Informationen zur Einstellung abzurufen. |
USB-Zielpipelineobjekt (WDFUSBPIPE) | WdfUsbInterfaceGetConfiguredPipe | Stellt eine einzelne Pipe für einen Endpunkt dar, der in der aktuellen alternativen Einstellung für eine Schnittstelle konfiguriert ist. Der USB-Treiberstapel wählt jede Schnittstelle in der ausgewählten Konfiguration aus und richtet einen Kommunikationskanal für jeden Endpunkt innerhalb der Schnittstelle ein. In der USB-Terminologie wird dieser Kommunikationskanal als Pipe bezeichnet. |
Dieses Codebeispiel zeigt die Implementierung für EvtDevicePrepareHardware.
NTSTATUS
MyUSBDriver_EvtDevicePrepareHardware(
_In_ WDFDEVICE Device,
_In_ WDFCMRESLIST ResourceList,
_In_ WDFCMRESLIST ResourceListTranslated
)
{
NTSTATUS status;
PDEVICE_CONTEXT pDeviceContext;
WDF_USB_DEVICE_CREATE_CONFIG createParams;
WDF_USB_DEVICE_SELECT_CONFIG_PARAMS configParams;
UNREFERENCED_PARAMETER(ResourceList);
UNREFERENCED_PARAMETER(ResourceListTranslated);
PAGED_CODE();
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
status = STATUS_SUCCESS;
pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
if (pDeviceContext->UsbDevice == NULL) {
//
// Specifying a client contract version of 602 enables us to query for
// and use the new capabilities of the USB driver stack for Windows 8.
// It also implies that we conform to rules mentioned in the documentation
// documentation for WdfUsbTargetDeviceCreateWithParameters.
//
WDF_USB_DEVICE_CREATE_CONFIG_INIT(&createParams,
USBD_CLIENT_CONTRACT_VERSION_602
);
status = WdfUsbTargetDeviceCreateWithParameters(Device,
&createParams,
WDF_NO_OBJECT_ATTRIBUTES,
&pDeviceContext->UsbDevice
);
if (!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"WdfUsbTargetDeviceCreateWithParameters failed 0x%x", status);
return status;
}
//
// Select the first configuration of the device, using the first alternate
// setting of each interface
//
WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES(&configParams,
0,
NULL
);
status = WdfUsbTargetDeviceSelectConfig(pDeviceContext->UsbDevice,
WDF_NO_OBJECT_ATTRIBUTES,
&configParams
);
if (!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"WdfUsbTargetDeviceSelectConfig failed 0x%x", status);
return status;
}
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
return status;
}
Hier sehen Sie sich die Aufgaben des Clienttreibers genauer an, wie sie durch den Vorlagencode implementiert werden:
Gibt die Vertragsversion des Clienttreibers zur Vorbereitung an, um sich mit dem zugrunde liegenden USB-Treiberstapel zu registrieren, der von Windows geladen wird.
Windows kann den USB 3.0- oder USB 2.0-Treiberstapel laden, abhängig vom Hostcontroller, an den das USB-Gerät angeschlossen ist. Der USB 3.0-Treiberstapel ist neu in Windows 8 und unterstützt mehrere neue Features, die durch die USB 3.0-Spezifikation definiert sind, z. B. die Streams-Funktion. Der neue Treiberstapel implementiert auch mehrere Verbesserungen, z. B. eine bessere Nachverfolgung und Verarbeitung von USB-Anforderungsblöcken (URBs), die über einen neuen Satz von URB-Routinen verfügbar sind. Ein Clienttreiber, der diese Features verwenden oder die neuen Routinen aufrufen möchte, muss die USBD_CLIENT_CONTRACT_VERSION_602 Vertragsversion angeben. Ein USBD_CLIENT_CONTRACT_VERSION_602 Clienttreiber muss einen bestimmten Satz von Regeln einhalten. Weitere Informationen zu diesen Regeln finden Sie unter Bewährte Methoden: Verwenden von URBs.
Um die Vertragsversion anzugeben, muss der Clienttreiber eine WDF_USB_DEVICE_CREATE_CONFIG Struktur mit der Vertragsversion initialisieren, indem das WDF_USB_DEVICE_CREATE_CONFIG_INIT-Makro aufgerufen wird.
Ruft die WdfUsbTargetDeviceCreateWithParameters-Methode auf. Die Methode erfordert ein Handle für das Framework-Geräteobjekt, das der Clienttreiber zuvor durch Aufrufen von WdfDeviceCreate in der Implementierung von EvtDriverDeviceAdd abgerufen hat. Die WdfUsbTargetDeviceCreateWithParameters-Methode :
- Registriert den Clienttreiber mit dem zugrunde liegenden USB-Treiberstapel.
- Ruft ein WDFUSBDEVICE-Handle zum USB-Zielgerätobjekt ab, das vom Framework erstellt wird. Der Vorlagencode speichert das Handle für das USB-Zielgerätobjekt im Gerätekontext. Mithilfe dieses Handles kann der Clienttreiber USB-spezifische Informationen über das Gerät abrufen.
Sie müssen WdfUsbTargetDeviceCreate anstelle von WdfUsbTargetDeviceCreateWithParameters aufrufen, wenn:
Der Clienttreiber ruft nicht die neuen URB-Routinen auf, die mit der WdK-Version vonWindows 8 verfügbar sind.
Wenn Ihr Clienttreiber WdfUsbTargetDeviceCreateWithParameters aufruft, geht der USB-Treiberstapel davon aus, dass alle URBs durch Aufrufen von WdfUsbTargetDeviceCreateUrb oder WdfUsbTargetDeviceCreateIsochUrb zugeordnet werden. URBs, die von diesen Methoden zugewiesen werden, weisen undurchsichtige URB-Kontextblöcke auf, die vom USB-Treiberstapel für eine schnellere Verarbeitung verwendet werden. Wenn der Clienttreiber eine URB verwendet, die von diesen Methoden nicht zugeordnet ist, generiert der USB-Treiber eine Fehlerüberprüfung.
Weitere Informationen zu URB-Zuordnungen finden Sie unter "Allocating" und "Building URBs".
Ihr Clienttreiber beabsichtigt nicht, den in best Practices beschriebenen Regelsatz einzuhalten: Verwenden von URBs.
Solche Treiber sind nicht erforderlich, um eine Clientvertragsversion anzugeben und daher Schritt 1 zu überspringen.
Wählt eine USB-Konfiguration aus.
Im Vorlagencode wählt der Clienttreiber die Standardkonfiguration auf dem USB-Gerät aus. Die Standardkonfiguration umfasst Configuration 0 des Geräts und die alternative Einstellung 0 jeder Schnittstelle innerhalb dieser Konfiguration.
Um die Standardkonfiguration auszuwählen, konfiguriert der Clienttreiber die WDF_USB_DEVICE_SELECT_CONFIG_PARAMS Struktur durch Aufrufen der WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES-Funktion. Die Funktion initialisiert das Type-Element in WdfUsbTargetDeviceSelectConfigTypeMultiInterface , um anzugeben, dass eine alternative Einstellung in jeder dieser Schnittstellen ausgewählt werden muss, wenn mehrere Schnittstellen verfügbar sind. Da der Aufruf die Standardkonfiguration auswählen muss, gibt der Clienttreiber NULL im Parameter SettingPairs und 0 im Parameter NumberInterfaces an. Nach Abschluss gibt das Element "MultiInterface.NumberOfConfiguredInterfaces" von WDF_USB_DEVICE_SELECT_CONFIG_PARAMS die Anzahl der Schnittstellen an, für die alternative Einstellung 0 ausgewählt wurde. Andere Mitglieder werden nicht geändert.
Hinweis : Wenn der Clienttreiber alternative Einstellungen als die Standardeinstellung auswählen möchte, muss der Treiber ein Array von WDF_USB_INTERFACE_SETTING_PAIR Strukturen erstellen. Jedes Element im Array gibt die gerätedefinierte Schnittstellennummer und den Index der auszuwählenden alternativen Einstellung an. Diese Informationen werden in den Konfigurations- und Schnittstellendeskriptoren des Geräts gespeichert, die durch Aufrufen der WdfUsbTargetDeviceRetrieveConfigDescriptor-Methode abgerufen werden können. Der Clienttreiber muss dann WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES aufrufen und das WDF_USB_INTERFACE_SETTING_PAIR Array an das Framework übergeben.
Quellcode der Warteschlange
Das Framework-Warteschlangenobjekt stellt die E/A-Warteschlange für ein bestimmtes Framework-Geräteobjekt dar. Der vollständige Quellcode für das Warteschlangenobjekt befindet sich in "Queue.h" und "Queue.c".
Queue.h
Deklariert eine Ereignisrückrufroutine für das Ereignis, das vom Warteschlangenobjekt des Frameworks ausgelöst wird.
Der erste Block in Queue.h deklariert einen Warteschlangenkontext.
typedef struct _QUEUE_CONTEXT {
ULONG PrivateDeviceData; // just a placeholder
} QUEUE_CONTEXT, *PQUEUE_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext)
Ähnlich wie bei einem Gerätekontext ist ein Warteschlangenkontext eine vom Client definierte Datenstruktur zum Speichern von Informationen zu einer bestimmten Warteschlange.
Die nächste Codezeile deklariert MyUSBDriver_QueueInitialize Funktion, die Hilfsfunktion, die das Framework-Warteschlangenobjekt erstellt und initialisiert.
NTSTATUS
MyUSBDriver_QueueInitialize(
_In_ WDFDEVICE Device
);
Im nächsten Codebeispiel wird eine Funktionsrollentypdeklaration für die Ereignisrückrufroutine EvtIoDeviceControl deklariert. Der Ereignisrückruf wird vom Clienttreiber implementiert und aufgerufen, wenn das Framework eine Geräte-E/A-Steuerungsanforderung verarbeitet.
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL MyUSBDriver_EvtIoDeviceControl;
Queue.c
Die Implementierungsdatei Queue.c enthält den folgenden Codeblock, der pragma verwendet, alloc_text
um anzugeben, dass die Implementierung von MyUSBDriver_QueueInitialize des Treibers im ausgelagerten Speicher liegt.
#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_QueueInitialize)
#endif
WDF stellt das Framework-Warteschlangenobjekt bereit, um den Anforderungsfluss an den Clienttreiber zu verarbeiten. Das Framework erstellt ein Framework-Warteschlangenobjekt, wenn der Clienttreiber die WdfIoQueueCreate-Methode aufruft. In diesem Aufruf kann der Clienttreiber bestimmte Konfigurationsoptionen angeben, bevor das Framework Warteschlangen erstellt. Diese Optionen umfassen, ob die Warteschlange energieverwaltet ist, 0-Länge-Anforderungen zulässt oder die Standardwarteschlange für den Treiber ist. Ein einzelnes Framework-Warteschlangenobjekt kann mehrere Arten von Anforderungen verarbeiten, z. B. Lese-, Schreib- und Geräte-E/A-Steuerelement. Der Clienttreiber kann Ereignisrückrufe für jede dieser Anforderungen angeben.
Der Clienttreiber muss auch den Verteilertyp angeben. Der Verteilertyp eines Warteschlangenobjekts bestimmt, wie das Framework Anforderungen an den Clienttreiber übermittelt. Der Übermittlungsmechanismus kann sequenziell, parallel oder durch einen vom Clienttreiber definierten benutzerdefinierten Mechanismus erfolgen. Bei einer sequenziellen Warteschlange wird eine Anforderung erst übermittelt, wenn der Clienttreiber die vorherige Anforderung abgeschlossen hat. Im parallelen Versandmodus leitet das Framework die Anforderungen weiter, sobald sie vom E/A-Manager empfangen werden. Dies bedeutet, dass der Clienttreiber eine Anforderung empfangen kann, während eine andere verarbeitet wird. Im benutzerdefinierten Mechanismus ruft der Client die nächste Anforderung aus dem Framework-Warteschlangenobjekt manuell ab, wenn der Treiber für die Verarbeitung bereit ist.
Normalerweise muss der Clienttreiber Warteschlangen im EvtDriverDeviceAdd-Ereignisrückruf des Treibers einrichten. Der Vorlagencode stellt die Hilfsroutine MyUSBDriver_QueueInitialize bereit, die das Framework-Warteschlangenobjekt initialisiert.
NTSTATUS
MyUSBDriver_QueueInitialize(
_In_ WDFDEVICE Device
)
{
WDFQUEUE queue;
NTSTATUS status;
WDF_IO_QUEUE_CONFIG queueConfig;
PAGED_CODE();
//
// Configure a default queue so that requests that are not
// configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
// other queues get dispatched here.
//
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
&queueConfig,
WdfIoQueueDispatchParallel
);
queueConfig.EvtIoDeviceControl = MyUSBDriver_EvtIoDeviceControl;
status = WdfIoQueueCreate(
Device,
&queueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&queue
);
if( !NT_SUCCESS(status) ) {
TraceEvents(TRACE_LEVEL_ERROR, TRACE_QUEUE, "WdfIoQueueCreate failed %!STATUS!", status);
return status;
}
return status;
}
Zum Einrichten von Warteschlangen führt der Clienttreiber die folgenden Aufgaben aus:
- Gibt die Konfigurationsoptionen der Warteschlange in einer WDF_IO_QUEUE_CONFIG Struktur an. Der Vorlagencode verwendet die WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE-Funktion , um die Struktur zu initialisieren. Die Funktion gibt das Warteschlangenobjekt als Standardwarteschlangenobjekt an, wird vom Netz verwaltet und empfängt Parallelanforderungen.
- Fügt die Ereignisrückrufe des Clienttreibers für E/A-Anforderungen für die Warteschlange hinzu. In der Vorlage gibt der Clienttreiber einen Zeiger auf den Ereignisrückruf für eine Geräte-E/A-Steuerelementanforderung an.
- Ruft WdfIoQueueCreate auf, um ein WDFQUEUE-Handle für das Framework-Warteschlangenobjekt abzurufen, das vom Framework erstellt wird.
Hier erfahren Sie, wie der Warteschlangenmechanismus funktioniert. Um mit dem USB-Gerät zu kommunizieren, öffnet eine Anwendung zunächst ein Handle für das Gerät durch Aufrufen der SetDixxx-Routinen und CreateHandle. Mithilfe dieses Handles ruft die Anwendung die DeviceIoControl-Funktion mit einem bestimmten Steuerelementcode auf. Je nach Typ des Steuerelementcodes kann die Anwendung Eingabe- und Ausgabepuffer in diesem Aufruf angeben. Der Anruf wird schließlich vom E/A-Manager empfangen, der dann eine Anforderung (IRP) erstellt und an den Clienttreiber weiterleitet. Das Framework fängt die Anforderung ab, erstellt ein Framework-Anforderungsobjekt und fügt es dem Framework-Warteschlangenobjekt hinzu. Da der Clienttreiber in diesem Fall den Ereignisrückruf für die Geräte-E/A-Steuerelementanforderung registriert hat, ruft das Framework den Rückruf auf. Da das Warteschlangenobjekt auch mit dem WdfIoQueueDispatchParpatchParallel-Flag erstellt wurde, wird der Rückruf aufgerufen, sobald die Anforderung der Warteschlange hinzugefügt wird.
VOID
MyUSBDriver_EvtIoDeviceControl(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode
)
{
TraceEvents(TRACE_LEVEL_INFORMATION,
TRACE_QUEUE,
"!FUNC! Queue 0x%p, Request 0x%p OutputBufferLength %d InputBufferLength %d IoControlCode %d",
Queue, Request, (int) OutputBufferLength, (int) InputBufferLength, IoControlCode);
WdfRequestComplete(Request, STATUS_SUCCESS);
return;
}
Wenn das Framework den Ereignisrückruf des Clienttreibers aufruft, übergibt es ein Handle an das Framework-Anforderungsobjekt, das die von der Anwendung gesendete Anforderung (und seine Eingabe- und Ausgabepuffer) enthält. Darüber hinaus sendet es ein Handle an das Framework-Warteschlangenobjekt, das die Anforderung enthält. Im Ereignisrückruf verarbeitet der Clienttreiber die Anforderung nach Bedarf. Der Vorlagencode schließt einfach die Anforderung ab. Der Clienttreiber kann mehr involvierte Aufgaben ausführen. Wenn eine Anwendung beispielsweise bestimmte Geräteinformationen anfordert, kann der Clienttreiber eine USB-Steuerelementanforderung erstellen und an den USB-Treiberstapel senden, um die angeforderten Geräteinformationen abzurufen. USB-Steuerungsanforderungen werden in der USB-Steuerungsübertragung erläutert.