Share via


Grundlegendes zur Codestruktur des USB-Clienttreibers (KMDF)

In diesem Thema erfahren Sie mehr über den Quellcode für einen KMDF-basierten USB-Clienttreiber. Die Codebeispiele werden von der USB-Benutzermodustreibervorlage 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 Ihres ersten USB-Clienttreibers (KMDF).

Treiberquellcode

Das Driver-Objekt stellt die instance des Clienttreibers dar, nachdem Windows den Treiber in den Arbeitsspeicher geladen hat. Der vollständige Quellcode für das Treiberobjekt befindet sich in Driver.h und Driver.c.

Driver.h

Bevor wir die Details des Vorlagencodes besprechen, 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"

Die Headerdateien "Ntddk.h" und "Wdf.h" sind für die KMDF-Treiberentwicklung immer 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-E/A-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 und Die Rückrufroutinen EvtDriverDeviceAdd und EvtCleanupCallback bereit. Alle diese Routinen werden vom Treiber implementiert. Rollentypen helfen beim Static Driver Verifier (SDV) beim Analysieren des Quellcodes eines Treibers. 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 sich die DriverEntry-Funktion und Ereignisrückrufroutinen im auslagerungsfähigen Speicher befinden.

#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 ABSCHNITT INIT gibt an, dass der ausführbare Code für DriverEntry ausgelagert und verworfen wird, sobald der Treiber von driverEntry zurückgibt. Im Abschnitt PAGE wird angegeben, dass der Code nicht ständig im physischen Arbeitsspeicher verbleiben muss. Sie kann in die Auslagerungsdatei 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 ruft sie die Einstiegspunktroutine Ihres Treibers auf, DriverEntry, und übergibt einen Zeiger an die Struktur. Da Windows die Routine anhand des Namens 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 an das Framework an.

Das folgende Codebeispiel zeigt die Von der Vorlage generierte DriverEntry-Routine.

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 RegistryPath-Parameter 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 die für die WPP-Softwareablaufverfolgung erforderlichen Ressourcen zu, indem er das makro WPP_INIT_TRACING aufruft.

  • Registriert bestimmte Ereignisrückrufroutinen beim 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 stellt diese Strukturen bereit (im nächsten Schritt erläutert).

  • Ruft die WdfDriverCreate-Methode auf und ruft ein Handle für das Frameworktreiberobjekt ab.

    Nachdem der Clienttreiber WdfDriverCreate aufgerufen hat, erstellt das Framework ein Frameworktreiberobjekt, das den Clienttreiber darstellt. Nach Abschluss des Aufrufs erhält der Clienttreiber ein WDFDRIVER-Handle und kann Informationen zum Treiber abrufen, z. B. registrierungspfad, Versionsinformationen usw. (siehe WDF-Treiberobjektreferenz).

    Beachten Sie, dass sich das Frameworktreiberobjekt von dem windows-Treiberobjekt unterscheidet, das von DRIVER_OBJECT beschrieben wird. Der Clienttreiber kann jederzeit einen Zeiger auf dieWindows-DRIVER_OBJECT-Struktur abrufen, indem er das WDFDRIVER-Handle verwendet und die WdfGetDriver-Methode aufruft.

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 übernimmt 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 die Möglichkeit, das Ereignis bei Bedarf zu behandeln. Wenn der Treiber seinen 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 bereit ist, ein Geräteobjekt zu erstellen. 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 es die EvtCleanupCallback-Implementierung des Treibers aufruft. Das Framework ruft diesen Rückruf auf, bevor es das Frameworktreiberobjekt löscht. Der Clienttreiber kann alle globalen Ressourcen freigeben, die er in seinem DriverEntry zugeordnet hat. Beispielsweise beendet 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 das PDO dem Geräteknoten zu. Der Geräteknoten befindet sich in einer Stapelbildung, wobei sich der PDO unten befindet. Jeder Stapel muss über ein PDO verfügen und kann Filtergeräteobjekte (Filter-DOs) und ein Funktionsgeräteobjekt (FDO) darüber haben. Weitere Informationen finden Sie unter Geräteknoten und Gerätestapel.

Diese Abbildung zeigt den Gerätestapel für den Vorlagentreiber MyUSBDriver_.sys.

Gerätestapel für Vorlagentreiber.

Beachten Sie den Gerätestapel "Mein USB-Gerät". Der USB-Treiberstapel erstellt das PDO für den Gerätestapel. Im Beispiel ist das PDO Usbhub3.sys zugeordnet, einer der Treiber, die im USB-Treiberstapel enthalten sind. Als Funktionstreiber für das Gerät muss der Clienttreiber zuerst die FDO für das Gerät erstellen und dann an den oberen Rand des Gerätestapels anfügen.

Bei einem 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 gewährt, wenn das Framework die EvtDriverDeviceAdd-Implementierung des Treibers aufruft. Nachdem das Objekt erstellt und die FDO am oberen Rand des Gerätestapels angefügt ist, stellt das Framework dem Clienttreiber ein WDFDEVICE-Handle an das Framework-Geräteobjekt bereit. Mit diesem Handle 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 makro PAGED_CODE , um zu überprüfen, ob die Routine in einer geeigneten Umgebung für auslagerungsfä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 MyUSBDriver_CreateDevice Hilfsfunktion. 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 ihr 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 die FDO erstellt und es am anfang des Gerätestapels anfügt. Im WdfDeviceCreate-Aufruf muss der Clienttreiber die folgenden Aufgaben ausführen:

    Windows-Komponenten, PnP- und Power-Manager senden gerätebezogene Anforderungen an Treiber als Reaktion auf Änderungen im PnP-Zustand (z. B. gestartet, beendet 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 er Rückrufroutinen namens PnP-Powerereignisrückrufe mit dem Framework registriert, indem er den WdfDeviceCreate-Aufruf verwendet. Wenn Windows-Komponenten Anforderungen senden, verarbeitet das Framework diese und ruft den entsprechenden PnP-Power-Ereignisrückruf auf, wenn der Clienttreiber registriert ist.

    Eine der PnP-Powerereignisrü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.

    Ein Gerätekontext (manchmal auch als Geräteerweiterung bezeichnet) ist eine Datenstruktur (vom Clienttreiber definiert) zum Speichern von Informationen zu einem bestimmten Geräteobjekt. Der Clienttreiber übergibt einen Zeiger auf seinen Gerätekontext an das Framework. Das Framework ordnet 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 Membern des Gerätekontexts zuzugreifen und diese zu speichern. Weitere Informationen zu Gerätekontexten finden Sie unter Framework Object Context Space.

    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 jetzt einen Zeiger auf den Gerätekontext abrufen, indem er das Makro WdfObjectGet_DEVICE_CONTEXT aufruft.

  • 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 Abschnitt Warteschlangenquellcode erläutert wird.

Gerätequellcode

Das Geräteobjekt stellt die instance des Geräts dar, für das der Clienttreiber in den Arbeitsspeicher geladen wird. Der vollständige Quellcode für das Geräteobjekt befindet sich in Device.h und Device.c.

Device.h

Die Device.h-Headerdatei 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. Es ist in Device.h deklariert und enthält zwei Member: ein Handle für das USB-Zielgerätobjekt eines Frameworks (später erläutert) und einen Platzhalter. Diese Struktur wird in späteren Übungen erweitert.

Device.h enthält auch das makro WDF_DECLARE_CONTEXT_TYPE , 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 es die EvtDriverDeviceAdd-Implementierung des Clienttreibers aufgerufen hat. Grundsätzlich führt MyUSBDriver_CreateDevice die Aufgaben von EvtDriverDeviceAdd aus. Der Quellcode für die EvtDriverDeviceAdd-Implementierung 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 sich die Implementierung von EvtDevicePrepareHardware im Auslagerungsspeicher des Treibers befindet.

#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. Die folgende Tabelle zeigt die spezialisierten E/A-Zielobjekte, die vom Framework bereitgestellt werden. Weitere Informationen finden Sie unter USB-E/A-Ziele.

USB-E/A-Zielobjekt (Handle) Rufen Sie einen Handle ab, indem Sie aufrufen... BESCHREIBUNG
USB-Zielgerätobjekt (WDFUSBDEVICE ) WdfUsbTargetDeviceCreateWithParameters Stellt ein USB-Gerät dar und stellt Methoden zum Abrufen des Gerätedeskriptors und zum Senden von Steuerungsanforderungen 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 über die Einstellung abzurufen.
USB-Zielpipeobjekt (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 zu jedem 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:

  1. Gibt die Vertragsversion des Clienttreibers als Vorbereitung für die Registrierung beim zugrunde liegenden USB-Treiberstapel an, der von Windows geladen wird.

    Windows kann den USB 3.0- oder USB 2.0-Treiberstapel laden, je nachdem, an welchen Hostcontroller 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 Makro WDF_USB_DEVICE_CREATE_CONFIG_INIT aufgerufen wird.

  2. 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 des Treibers von EvtDriverDeviceAdd abgerufen hat. Die WdfUsbTargetDeviceCreateWithParameters-Methode :

    • Registriert den Clienttreiber beim zugrunde liegenden USB-Treiberstapel.
    • Ruft ein WDFUSBDEVICE-Handle für das USB-Zielgerätobjekt ab, das vom Framework erstellt wird. Der Vorlagencode speichert das Handle für das USB-Zielgerätobjekt im zugehörigen 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:

    • Ihr Clienttreiber ruft nicht die neuen URB-Routinen auf, die mit der Windows 8-Version des WDK 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 zugeordnet werden, verfügen über undurchsichtige URB-Kontextblöcke, die vom USB-Treiberstapel für eine schnellere Verarbeitung verwendet werden. Wenn der Clienttreiber eine URB verwendet, die nicht von diesen Methoden zugeordnet wird, generiert der USB-Treiber eine Fehlerüberprüfung.

      Weitere Informationen zu URB-Zuordnungen finden Sie unter Zuordnen und Erstellen von URBs.

    • Ihr Clienttreiber beabsichtigt nicht, die unter Bewährte Methoden: Verwenden von URBs beschriebenen Regeln einzuhalten.

    Solche Treiber sind nicht erforderlich, um eine Clientvertragsversion anzugeben, und müssen daher Schritt 1 überspringen.

  3. Wählt eine USB-Konfiguration aus.

    Im Vorlagencode wählt der Clienttreiber die Standardkonfiguration auf dem USB-Gerät aus. Die Standardkonfiguration umfasst Konfiguration 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 den Typmember für WdfUsbTargetDeviceSelectConfigTypeMultiInterface , um anzugeben, dass, wenn mehrere Schnittstellen verfügbar sind, eine alternative Einstellung in jeder dieser Schnittstellen ausgewählt werden muss. Da der Aufruf die Standardkonfiguration auswählen muss, gibt der Clienttreiber null im Parameter SettingPairs und 0 im Parameter NumberInterfaces an. Nach Abschluss gibt der MultiInterface.NumberOfConfiguredInterfaces-Member von WDF_USB_DEVICE_SELECT_CONFIG_PARAMS die Anzahl der Schnittstellen an, für die alternative Einstellung 0 ausgewählt wurde. Andere Member werden nicht geändert.

    Hinweis Wenn der Clienttreiber alternative Einstellungen als die Standardeinstellung auswählen möchte, muss der Treiber ein Array mit 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.

Warteschlangenquellcode

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 Frameworkwarteschlangenobjekt 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 sich die Implementierung von MyUSBDriver_QueueInitialize des Treibers im ausgelagerten Speicher befindet.

#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, Anforderungen der Länge null 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-Steuerung. Der Clienttreiber kann Ereignisrückrufe für jede dieser Anforderungen angeben.

Der Clienttreiber muss auch den Verteilungstyp angeben. Der Dispatchtyp 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 Verteilmodus leitet das Framework die Anforderungen weiter, sobald sie vom E/A-Manager eingehen. 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 manuell aus dem Framework-Warteschlangenobjekt ab, wenn der Treiber bereit ist, sie zu verarbeiten.

In der Regel muss der Clienttreiber Warteschlangen im EvtDriverDeviceAdd-Ereignisrückruf des Treibers einrichten. Der Vorlagencode stellt die Hilfsroutine MyUSBDriver_QueueInitialize bereit, die das Frameworkwarteschlangenobjekt 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:

  1. 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, ist energieverwaltet und empfängt Anforderungen parallel.
  2. 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 seinen Ereignisrückruf für eine Geräte-E/A-Steuerungsanforderung an.
  3. 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, indem sie die SetDixxx-Routinen und CreateHandle aufruft. Mithilfe dieses Handles ruft die Anwendung die DeviceIoControl-Funktion mit einem bestimmten Steuerungscode auf. Abhängig vom Typ des Steuerelementcodes kann die Anwendung Eingabe- und Ausgabepuffer in diesem Aufruf angeben. Der Aufruf 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 Frameworkanforderungsobjekt und fügt es dem Framework-Warteschlangenobjekt hinzu. In diesem Fall ruft das Framework den Rückruf auf, da der Clienttreiber seinen Ereignisrückruf für die Anforderung der Geräte-E/A-Steuerung registriert hat. Da das Warteschlangenobjekt mit dem WdfIoQueueDispatchParallel-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 ihre Eingabe- und Ausgabepuffer) enthält. Darüber hinaus wird ein Handle an das Frameworkwarteschlangenobjekt gesendet, das die Anforderung enthält. Im Ereignisrückruf verarbeitet der Clienttreiber die Anforderung nach Bedarf. Der Vorlagencode schließt die Anforderung einfach ab. Der Clienttreiber kann komplexere Aufgaben ausführen. Wenn eine Anwendung für instance bestimmte Geräteinformationen anfordert, kann der Clienttreiber im Fall eines Rückrufs eine USB-Steuerungsanforderung erstellen und an den USB-Treiberstapel senden, um die angeforderten Geräteinformationen abzurufen. USB-Steuerungsanforderungen werden unter USB-Steuerungsübertragung erläutert.