Opis struktury kodu sterownika klienta USB (KMDF)
W tym temacie dowiesz się więcej o kodzie źródłowym sterownika klienta USB opartego na usłudze KMDF. Przykłady kodu są generowane przez szablon sterownika trybu użytkownika USB dołączony do programu Microsoft Visual Studio 2019.
Te sekcje zawierają informacje o kodzie szablonu.
Aby uzyskać instrukcje dotyczące generowania kodu szablonu usługi KMDF, zobacz Jak napisać pierwszy sterownik klienta USB (KMDF).
Kod źródłowy sterownika
Obiekt sterownika reprezentuje wystąpienie sterownika klienta po załadowaniu sterownika w pamięci przez system Windows. Kompletny kod źródłowy obiektu sterownika znajduje się w driver.h i Driver.c.
Driver.h
Przed omówieniem szczegółów kodu szablonu przyjrzyjmy się niektórym deklaracjom w pliku nagłówka (Driver.h), które są istotne dla opracowywania sterowników kmDF.
Driver.h zawiera te pliki zawarte w zestawie sterowników systemu Windows (WDK).
#include <ntddk.h>
#include <wdf.h>
#include <usb.h>
#include <usbdlib.h>
#include <wdfusb.h>
#include "device.h"
#include "queue.h"
#include "trace.h"
Pliki nagłówkowe Ntddk.h i Wdf.h są zawsze dołączane do programowania sterowników KMDF. Plik nagłówka zawiera różne deklaracje i definicje metod i struktur, które są potrzebne do skompilowania sterownika KMDF.
Usb.h i Usbdlib.h zawierają deklaracje i definicje struktur i procedur, które są wymagane przez sterownik klienta dla urządzenia USB.
Wdfusb.h zawiera deklaracje i definicje struktur i metod, które są wymagane do komunikowania się z obiektami docelowymi we/wy USB dostarczonymi przez platformę.
Elementy Device.h, Queue.h i Trace.h nie są uwzględnione w zestawie WDK. Te pliki nagłówkowe są generowane przez szablon i zostały omówione w dalszej części tego tematu.
Następny blok w Driver.h zawiera deklaracje typów ról funkcji dla funkcji DriverEntry oraz EvtDriverDeviceAdd i procedur wywołań zwrotnych zdarzeń EvtCleanupCallback. Wszystkie te procedury są implementowane przez kierowcę. Typy ról ułatwiają statycznemu weryfikatorowi sterowników (SDV) analizowanie kodu źródłowego sterownika. Aby uzyskać więcej informacji na temat typów ról, zobacz Deklarowanie funkcji przy użyciu typów ról funkcji dla sterowników KMDF.
DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD MyUSBDriver_EvtDeviceAdd;
EVT_WDF_OBJECT_CONTEXT_CLEANUP MyUSBDriver_EvtDriverContextCleanup;
Plik implementacji Driver.c zawiera następujący blok kodu, który używa dyrektywy alloc_text
pragma w celu określenia, czy funkcja DriverEntry oraz procedury obsługi wywołań zwrotnych zdarzeń znajdują się w pamięci stronicowalnej.
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDeviceAdd)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDriverContextCleanup)
#endif
Zwróć uwagę, że DriverEntry jest oznaczona jako INIT, podczas gdy procedury wywołania zwrotnego zdarzeń są oznaczone jako PAGE. Sekcja INIT wskazuje, że kod wykonywalny dla DriverEntry jest stronicowalny i odrzucany natychmiast po powrocie sterownika z DriverEntry. Sekcja PAGE wskazuje, że kod nie musi pozostawać w pamięci fizycznej przez cały czas; Można go zapisać w pliku stronicowania, gdy nie jest używany. Aby uzyskać więcej informacji, zobacz Blokowanie kodu lub danych stronicowalnych.
Wkrótce po załadowaniu sterownika system Windows przydziela DRIVER_OBJECT strukturę reprezentującą sterownik. Następnie wywołuje procedurę punktu wejścia kierowcy, DriverEntryi przekazuje wskaźnik do struktury. Ponieważ system Windows szuka procedury według nazwy, każdy sterownik musi zaimplementować procedurę o nazwie DriverEntry. Procedura wykonuje zadania inicjowania sterownika i określa procedury wywołania zwrotnego zdarzeń sterownika do struktury.
Poniższy przykład kodu przedstawia procedurę DriverEntry wygenerowaną przez szablon.
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;
}
Procedura DriverEntry ma dwa parametry: wskaźnik do struktury DRIVER_OBJECT przydzielonej przez system Windows oraz ścieżkę rejestru dla sterownika. Parametr RegistryPath reprezentuje ścieżkę specyficzną dla sterownika w rejestrze.
W procedurze DriverEntry sterownik wykonuje następujące zadania:
Przydziela zasoby globalne, które są wymagane w trakcie działania sterownika. Na przykład w kodzie szablonu sterownik klienta przydziela zasoby wymagane do śledzenia oprogramowania WPP przez wywołanie makra WPP_INIT_TRACING.
Rejestruje pewne procedury wywołania zwrotnego zdarzeń w strukturze.
Aby zarejestrować wywołania zwrotne zdarzeń, sterownik klienta najpierw określa wskaźniki do jego implementacji EvtDriverXxx procedur w niektórych strukturach WDF. Następnie sterownik wywołuje metodę WdfDriverCreate i dostarcza te struktury (omówione w następnym kroku).
Wywołuje metodę WdfDriverCreate i pobiera uchwyt do obiektu sterownika framework .
Po wywołaniu przez sterownik klienta funkcji WdfDriverCreate, framework tworzy obiekt sterownika frameworku, aby reprezentować sterownik klienta. Po zakończeniu wywołania sterownik klienta odbiera dojście WDFDRIVER i może pobrać informacje o sterowniku, takie jak ścieżka rejestru, informacje o wersji itd. (zobacz odwołanie do obiektu sterownika WDF).
Należy pamiętać, że obiekt sterownika struktury różni się od obiektu sterownika systemu Windows opisanego przez DRIVER_OBJECT. Sterownik klienta może w dowolnym momencie uzyskać wskaźnik do strukturyDRIVER_OBJECT systemu Windows, używając uchwytu WDFDRIVER i wywołując metodę WdfGetDriver.
Po wywołaniu WdfDriverCreate, platforma nawiązuje współpracę ze sterownikiem klienta w celu komunikacji z systemem Windows. Struktura działa jako warstwa abstrakcji między systemem Windows a sterownikiem i obsługuje większość skomplikowanych zadań sterowników. Sterownik klienta rejestruje się w ramach dla zdarzeń, które go interesują. W przypadku wystąpienia niektórych wydarzeń system Windows powiadamia platformę. Jeśli sterownik zarejestrował wywołanie zwrotne dla określonego zdarzenia, framework powiadamia sterownik, wywołując zarejestrowane wywołanie zwrotne. Dzięki temu sterownik ma możliwość obsługi zdarzenia, jeśli jest to konieczne. Jeśli sterownik nie zarejestrował wywołania zwrotnego dla zdarzenia, framework kontynuuje domyślną obsługę zdarzenia.
Jednym z wywołań zwrotnych zdarzeń, które sterownik musi zarejestrować, jest EvtDriverDeviceAdd. Platforma wywołuje implementację EvtDriverDeviceAdd sterownika, gdy jest gotowa do utworzenia obiektu urządzenia. W systemie Windows obiekt urządzenia jest logiczną reprezentacją funkcji urządzenia fizycznego, dla którego jest ładowany sterownik klienta (omówiony w dalszej części tego tematu).
Inne wywołania zwrotne zdarzeń, które może zarejestrować sterownik, to EvtDriverUnload, EvtCleanupCallbacki EvtDestroyCallback.
W kodzie szablonu sterownik klienta rejestruje się dla dwóch zdarzeń: EvtDriverDeviceAdd i EvtCleanupCallback. Sterownik określa wskaźnik do implementacji EvtDriverDeviceAdd w strukturze WDF_DRIVER_CONFIG oraz wywołanie zwrotne zdarzenia EvtCleanupCallback w strukturze WDF_OBJECT_ATTRIBUTES.
Gdy system Windows jest gotowy do zwolnienia struktury DRIVER_OBJECT i odciążenia sterownika, framework zgłasza to zdarzenie do sterownika klienta, wywołując implementację EvtCleanupCallback sterownika. Struktura wywołuje wywołanie zwrotne tuż przed usunięciem obiektu sterownika struktury. Sterownik klienta może zwolnić wszystkie zasoby globalne, które przydzielił w DriverEntry. Na przykład w kodzie szablonu sterownik klienta zatrzymuje śledzenie WPP, które zostało aktywowane w DriverEntry.
Poniższy przykład kodu przedstawia implementację wywołania zwrotnego zdarzeń EvtCleanupCallback sterownika klienta.
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) );
}
Po rozpoznaniu urządzenia przez stos sterowników USB sterownik magistrali tworzy obiekt urządzenia fizycznego (PDO) dla urządzenia i kojarzy pdO z węzłem urządzenia. Węzeł urządzenia znajduje się w formacji stosu, gdzie PDO znajduje się na dole. Każdy stos musi mieć jeden PDO i może mieć obiekty filtrów (filter DOs) i obiekt urządzenia funkcjonalnego (FDO) powyżej niego. Aby uzyskać więcej informacji, zobacz Węzły urządzeń i Stosy urządzeń.
Na tej ilustracji przedstawiono stos urządzeń dla sterownika szablonu, MyUSBDriver_.sys.
Zwróć uwagę na stos urządzenia o nazwie "Moje urządzenie USB". Stos sterowników USB tworzy PDO dla stosu urządzenia. W tym przykładzie PDO jest skojarzone z Usbhub3.sys, który jest jednym ze sterowników dołączonych do stosu sterowników USB. Jako sterownik funkcji dla urządzenia sterownik klienta musi najpierw utworzyć FDO dla urządzenia, a następnie dołączyć go do góry stosu urządzenia.
W przypadku sterownika klienta opartego na usłudze KMDF struktura wykonuje te zadania w imieniu sterownika klienta. Aby reprezentować FDO dla urządzenia, struktura tworzy obiekt urządzenia platformy . Sterownik klienta może jednak określić pewne parametry inicjowania używane przez platformę do konfigurowania nowego obiektu. Ta możliwość jest udzielana sterownikowi klienta, gdy system wywołuje implementację funkcji EvtDriverDeviceAdd sterownika. Po utworzeniu obiektu i dołączeniu FDO do górnej części stosu urządzenia platforma udostępnia sterownik klienta z uchwytem WDFDEVICE do obiektu urządzenia struktury. Za pomocą tego uchwytu sterownik klienta może wykonywać różne operacje związane z urządzeniem.
Poniższy przykład kodu pokazuje, jak zaimplementować wywołanie zwrotne zdarzenia EvtDriverDeviceAdd w sterowniku klienta.
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 czasie wykonywania implementacja EvtDriverDeviceAdd używa makra PAGED_CODE w celu sprawdzenia, czy procedura jest wywoływana w odpowiednim środowisku dla kodu stronicowalnego. Upewnij się, że makro jest wywoływane po zadeklarowaniu wszystkich zmiennych; W przeciwnym razie kompilacja kończy się niepowodzeniem, ponieważ wygenerowane pliki źródłowe są plikami .c, a nie .cpp plików.
Sterownik klienta EvtDriverDeviceAdd implementacji wywołuje funkcję pomocnika MyUSBDriver_CreateDevice w celu wykonania wymaganych zadań.
Poniższy przykład kodu przedstawia funkcję pomocnika MyUSBDriver_CreateDevice. MyUSBDriver_CreateDevice jest definiowana w pliku Device.c.
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 ma dwa parametry: dojście do obiektu sterownika platformy utworzonego w poprzednim wywołaniu DriverEntry, wskaźnik do struktury WDFDEVICE_INIT. Framework przydziela strukturę WDFDEVICE_INIT i przekazuje wskaźnik, aby sterownik klienta mógł wypełnić tę strukturę parametrami inicjalizacji dla tworzonego obiektu urządzenia frameworka.
W implementacji EvtDriverDeviceAdd sterownik klienta musi wykonać następujące zadania:
Wywołaj metodę WdfDeviceCreate w celu pobrania dojścia WDFDEVICE do nowego obiektu urządzenia.
Metoda WdfDeviceCreate powoduje, że struktura tworzy obiekt urządzenia dla FDO i dołącza go na szczyt stosu urządzeń. W wywołaniu WdfDeviceCreate sterownik klienta musi wykonać następujące zadania:
- Określ wskaźniki do procedur zwrotnego wywołania zasilania Plug and Play (PnP) sterownika klienta w strukturze określonej przez platformę WDFDEVICE_INIT. Procedury są najpierw ustawiane w strukturze WDF_PNPPOWER_EVENT_CALLBACKS, a następnie skojarzone z WDFDEVICE_INIT przez wywołanie metody WdfDeviceInitSetPnpPowerEventCallbacks.
Składniki systemu Windows, pnP i menedżery zasilania, wysyłają do sterowników żądania związane z urządzeniem w odpowiedzi na zmiany stanu PnP (takie jak uruchomione, zatrzymane i usunięte) i stan zasilania (np. praca lub zawieszenie). W przypadku sterowników opartych na KMDF, framework przechwytuje te żądania. Sterownik klienta może otrzymywać powiadomienia o żądaniach, rejestrując procedury wywołania zwrotnego o nazwie PnP wywołania zwrotne zdarzeń zasilania w ramach platformy, korzystając z wywołania WdfDeviceCreate. Kiedy składniki systemu Windows wysyłają żądania, platforma obsługuje je i, jeśli sterownik klienta został zarejestrowany, wywołuje odpowiednie wywołanie zwrotne PnP związane ze zdarzeniem zasilania.
Jedną z funkcji wywołania zwrotnego zdarzeń PnP związanych z zasilaniem, które sterownik klienta musi zaimplementować, jest EvtDevicePrepareHardware. Procedura wywołania zwrotnego jest uruchamiana, gdy menedżer PnP włącza urządzenie. Implementacja EvtDevicePrepareHardware została omówiona w poniższej sekcji.
- Określ wskaźnik do struktury kontekstu urządzenia sterownika. Wskaźnik musi być ustawiony w strukturze WDF_OBJECT_ATTRIBUTES, która jest inicjowana przez wywołanie makra WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE.
Kontekst urządzenia (czasami nazywany rozszerzeniem urządzenia) to struktura danych (zdefiniowana przez sterownik klienta) do przechowywania informacji o określonym obiekcie urządzenia. Sterownik klienta przekazuje wskaźnik do kontekstu urządzenia do frameworku. Rama alokuje blok pamięci na podstawie rozmiaru struktury, a następnie przechowuje wskaźnik do tej lokalizacji pamięci w obiekcie urządzenia ramy. Sterownik klienta może używać wskaźnika, aby uzyskać dostęp do informacji i przechowywać je w składowych kontekstu urządzenia. Aby uzyskać więcej informacji na temat kontekstów urządzeń, zobacz Framework Object Context Space.
Po zakończeniu wywołania WdfDeviceCreate sterownik klienta otrzymuje uchwyt do nowego obiektu urządzenia frameworku, który przechowuje wskaźnik do bloku pamięci przydzielonej przez framework dla kontekstu urządzenia. Sterownik klienta może teraz uzyskać wskaźnik do kontekstu urządzenia, wywołując makro WdfObjectGet_DEVICE_CONTEXT.
Zarejestruj identyfikator GUID interfejsu urządzenia dla sterownika klienta, wywołując metodę WdfDeviceCreateDeviceInterface. Aplikacje mogą komunikować się ze sterownikiem przy użyciu tego identyfikatora GUID. Stała GUID jest zadeklarowana w nagłówku public.h.
Skonfiguruj kolejki dla transferów I/O na urządzenie. Kod szablonu definiuje MyUSBDriver_QueueInitialize, procedurę pomocniczą do konfigurowania kolejek, która została omówiona w sekcji kodu źródłowego kolejki.
Kod źródłowy urządzenia
Obiekt urządzenia reprezentuje instancję urządzenia, dla którego sterownik klienta jest załadowany do pamięci. Kompletny kod źródłowy obiektu urządzenia znajduje się w plikach Device.h i Device.c.
Device.h
Plik nagłówkowy Device.h zawiera plik public.h, który zawiera wspólne deklaracje używane przez wszystkie pliki w projekcie.
Następny blok w pliku Device.h deklaruje kontekst urządzenia dla sterownika klienta.
typedef struct _DEVICE_CONTEXT
{
WDFUSBDEVICE UsbDevice;
ULONG PrivateDeviceData; // just a placeholder
} DEVICE_CONTEXT, *PDEVICE_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT)
Struktura DEVICE_CONTEXT jest definiowana przez sterownik klienta i przechowuje informacje o obiekcie urządzenia struktury. Jest zadeklarowany w pliku Device.h i zawiera dwóch członków: uchwyt do obiektu urządzenia docelowego USB platformy (omówionego w dalszej części) i element zastępczy. Ta struktura zostanie rozszerzona w kolejnych ćwiczeniach.
Device.h zawiera również makro WDF_DECLARE_CONTEXT_TYPE, które generuje funkcję śródliniową WdfObjectGet_DEVICE_CONTEXT. Sterownik klienta może wywołać tę funkcję, aby pobrać wskaźnik do bloku pamięci z obiektu urządzenia struktury.
Poniższy wiersz kodu deklaruje MyUSBDriver_CreateDevice, funkcję pomocniczą, która pobiera uchwyt WDFUSBDEVICE do obiektu urządzenia docelowego USB.
NTSTATUS
MyUSBDriver_CreateDevice(
_Inout_ PWDFDEVICE_INIT DeviceInit
);
Funkcja USBCreate przyjmuje wskaźnik do struktury WDFDEVICE_INIT jako parametru. Jest to ten sam wskaźnik, który został przekazany przez framework, gdy wywoływał implementację sterownika klienckiego EvtDriverDeviceAdd. Zasadniczo MyUSBDriver_CreateDevice wykonuje zadania EvtDriverDeviceAdd. Kod źródłowy implementacji EvtDriverDeviceAdd omówiono w poprzedniej sekcji.
Następny wiersz w pliku Device.h deklaruje deklarację typu roli funkcji dla procedury wywołania zwrotnego zdarzeń EvtDevicePrepareHardware. Wywołanie zwrotne zdarzeń jest implementowane przez sterownik klienta i wykonuje zadania, takie jak konfigurowanie urządzenia USB.
EVT_WDF_DEVICE_PREPARE_HARDWARE MyUSBDriver_EvtDevicePrepareHardware;
Device.c
Plik implementacji Device.c zawiera następujący blok kodu, który używa pragmy alloc_text
do określenia, że implementacja sterownika EvtDevicePrepareHardware znajduje się w pamięci stronicowalnej.
#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_CreateDevice)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDevicePrepareHardware)
#endif
W implementacji EvtDevicePrepareHardwaresterownik klienta wykonuje zadania inicjowania specyficzne dla usb. Zadania te obejmują rejestrowanie sterownika klienta, inicjowanie obiektów docelowych we/wy specyficznych dla USB i wybieranie konfiguracji USB. W poniższej tabeli przedstawiono wyspecjalizowane obiekty docelowe we/wy udostępniane przez framework. Aby uzyskać więcej informacji, zobacz docelowe urządzenia wejścia/wyjścia USB.
Obiekt docelowy we/wy USB (uchwyt) | Uzyskaj uchwyt, wywołując... | Opis |
---|---|---|
obiekt docelowy urządzenia USB (WDFUSBDEVICE) | WdfUsbTargetDeviceCreateWithParameters | Reprezentuje urządzenie USB i udostępnia metody pobierania deskryptora urządzenia i wysyłania żądań kontroli do urządzenia. |
obiekt interfejsu docelowego USB (WDFUSBINTERFACE ) | WdfUsbTargetDeviceGetInterface | Reprezentuje pojedynczy interfejs i udostępnia metody, które sterownik klienta może wywołać, aby wybrać alternatywne ustawienie i pobrać informacje o ustawieniu. |
obiekt docelowego kanału USB (WDFUSBPIPE) | WdfUsbInterfaceGetConfiguredPipe | Reprezentuje pojedynczy kanał dla punktu końcowego skonfigurowanego w aktualnie skonfigurowanym alternatywnym ustawieniu interfejsu. Stos sterowników USB wybiera każdy interfejs w wybranej konfiguracji i konfiguruje kanał komunikacyjny do każdego punktu końcowego w interfejsie. W terminologii USB kanał komunikacyjny jest nazywany potokiem . |
W tym przykładzie kodu przedstawiono implementację 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;
}
Poniżej przedstawiono bliżej zadania sterownika klienta implementowane przez kod szablonu:
Określa wersję kontraktu sterownika klienta w ramach przygotowania do zarejestrowania się w bazowym stosie sterowników USB załadowanym przez system Windows.
System Windows może załadować stos sterowników USB 3.0 lub USB 2.0, w zależności od kontrolera hosta, do którego jest podłączone urządzenie USB. Pakiet sterowników USB 3.0 jest nowy w systemie Windows 8 i obsługuje kilka nowych funkcji zdefiniowanych przez specyfikację USB 3.0, takich jak obsługa strumieni danych. Nowy stos sterowników implementuje również kilka ulepszeń, takich jak lepsze śledzenie i przetwarzanie bloków żądań USB (URB), które są dostępne za pośrednictwem nowego zestawu procedur URB. Sterownik klienta, który zamierza używać tych funkcji lub wywoływać nowe procedury, musi określić USBD_CLIENT_CONTRACT_VERSION_602 wersję kontraktu. Sterownik klienta USBD_CLIENT_CONTRACT_VERSION_602 musi być zgodny z określonym zestawem reguł. Aby uzyskać więcej informacji na temat tych reguł, zobacz Najlepsze rozwiązania: Używanie adresów URL.
Aby określić wersję kontraktu, sterownik klienta musi zainicjować strukturę WDF_USB_DEVICE_CREATE_CONFIG z wersją kontraktu, wywołując makro WDF_USB_DEVICE_CREATE_CONFIG_INIT.
Wywołuje metodę WdfUsbTargetDeviceCreateWithParameters. Metoda wymaga dojścia do obiektu urządzenia struktury uzyskanego wcześniej przez wywołanie WdfDeviceUtwórz w implementacji sterownika EvtDriverDeviceAdd. Metoda WdfUsbTargetDeviceCreateWithParameters:
- Rejestruje sterownik klienta przy użyciu bazowego stosu sterowników USB.
- Pobiera dojście WDFUSBDEVICE do obiektu urządzenia docelowego USB utworzonego przez platformę. Kod szablonu przechowuje uchwyt do urządzenia docelowego USB w kontekście jego urządzenia. Za pomocą tego uchwytu sterownik klienta może uzyskać informacje specyficzne dla usb dotyczące urządzenia.
Należy wywołać WdfUsbTargetDeviceCreate zamiast WdfUsbTargetDeviceCreateWithParameters, jeśli:
Sterownik klienta nie wywołuje nowego zestawu procedur URB dostępnych w Windows 8 w wersji WDK.
Jeśli sterownik klienta wywołuje WdfUsbTargetDeviceCreateWithParameters, stos sterowników USB zakłada, że wszystkie URB są przydzielane przez wywołanie WdfUsbTargetDeviceCreateUrb lub WdfUsbTargetDeviceCreateIsochUrb. URB-y przydzielone przez te metody posiadają nieprzezroczyste bloki kontekstu, które są używane przez stos sterowników USB do szybszego przetwarzania. Jeśli sterownik klienta używa identyfikatora URB, który nie jest przydzielany przez te metody, sterownik USB generuje sprawdzanie usterek.
Aby uzyskać więcej informacji na temat alokacji URB, zobacz Alokowanie i Budowanie URB.
Sterownik klienta nie zamierza przestrzegać zestawu reguł opisanych w ramach najlepszych praktyk : korzystanie z URB.
Takie sterowniki nie są wymagane do określenia wersji umowy klienta i dlatego należy pominąć krok 1.
Wybiera konfigurację USB.
W kodzie szablonu sterownik klienta na urządzeniu USB wybiera domyślną konfigurację . Domyślna konfiguracja obejmuje konfigurację 0 urządzenia i ustawienie alternatywne 0 każdego interfejsu w ramach tej konfiguracji.
Aby wybrać konfigurację domyślną, sterownik klienta konfiguruje strukturę WDF_USB_DEVICE_SELECT_CONFIG_PARAMS przez wywołanie funkcji WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES. Funkcja inicjuje element członkowski typu, aby WdfUsbTargetDeviceSelectConfigTypeMultiInterface wskazać, że jeśli dostępnych jest wiele interfejsów, należy wybrać alternatywne ustawienie w każdym z tych interfejsów. Ponieważ wywołanie musi wybrać konfigurację domyślną, sterownik klienta określa wartość NULL w parametrze SettingPairs i 0 w parametrze NumberInterfaces. Po zakończeniu, członek struktury MultiInterface.NumberOfConfiguredInterfaces w WDF_USB_DEVICE_SELECT_CONFIG_PARAMS określa liczbę interfejsów, dla których wybrano alternatywne ustawienie 0. Inni członkowie nie są modyfikowani.
Uwaga Jeśli sterownik klienta chce wybrać alternatywne ustawienia inne niż ustawienie domyślne, sterownik musi utworzyć tablicę struktur WDF_USB_INTERFACE_SETTING_PAIR. Każdy element w tablicy określa numer interfejsu zdefiniowanego przez urządzenie i indeks ustawienia alternatywnego do wybrania. Te informacje są przechowywane w deskryptorach konfiguracji i interfejsu urządzenia, które można uzyskać, wywołując metodę WdfUsbTargetDeviceRetrieveConfigDescriptor. Następnie klient sterownika musi wywołać WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES i przekazać tablicę WDF_USB_INTERFACE_SETTING_PAIR do frameworku.
Kod źródłowy kolejki
Obiekt kolejki platformy reprezentuje kolejkę we/wy dla określonego obiektu urządzenia platformy. Kompletny kod źródłowy obiektu kolejki znajduje się w plikach Queue.h i Queue.c.
Queue.h
Deklaruje rutynę wywołania zwrotnego dla zdarzenia zgłaszanego przez obiekt kolejki frameworka.
Pierwszy blok w pliku Queue.h deklaruje kontekst kolejki.
typedef struct _QUEUE_CONTEXT {
ULONG PrivateDeviceData; // just a placeholder
} QUEUE_CONTEXT, *PQUEUE_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext)
Podobnie jak w kontekście urządzenia, kontekst kolejki to struktura danych zdefiniowana przez klienta do przechowywania informacji o określonej kolejce.
Następny wiersz kodu deklaruje funkcję MyUSBDriver_QueueInitialize, funkcję pomocniczą, która tworzy i inicjuje obiekt kolejki frameworku.
NTSTATUS
MyUSBDriver_QueueInitialize(
_In_ WDFDEVICE Device
);
Następny przykład kodu składa deklarację typu roli funkcji dla procedury obsługi zdarzenia EvtIoDeviceControl. Wywołanie zwrotne zdarzenia jest implementowane przez sterownik klienta i jest wywoływane, gdy struktura przetwarza żądanie kontroli I/O urządzenia.
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL MyUSBDriver_EvtIoDeviceControl;
Queue.c
Plik implementacji Queue.c zawiera następujący blok kodu używający dyrektywy alloc_text
pragma do określenia, że implementacja funkcji MyUSBDriver_QueueInitialize przez sterownik znajduje się w pamięci stronicowanej.
#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_QueueInitialize)
#endif
WDF udostępnia obiekt kolejki ram do obsługi przepływu żądań do sterownika klienta. Struktura tworzy obiekt kolejki struktury, gdy sterownik klienta wywołuje metodę WdfIoQueueCreate. W tym wywołaniu sterownik klienta może określić pewne opcje konfiguracji przed utworzeniem kolejek przez framework. Te opcje obejmują to, czy kolejka jest zarządzana energią, czy zezwala na żądania o zerowej długości, oraz czy jest domyślną kolejką dla sterownika. Pojedynczy obiekt kolejki może obsługiwać kilka typów żądań, takich jak odczyt, zapis oraz kontrola we/wy urządzenia. Sterownik klienta może określać wywołania zwrotne zdarzeń dla każdego z tych żądań.
Sterownik klienta musi również określać typ wysyłki. Typ wysyłania obiektu kolejki określa, w jaki sposób platforma dostarcza żądania do sterownika klienta. Mechanizm dostarczania może być sekwencyjny, równoległy lub przez niestandardowy mechanizm zdefiniowany przez sterownik klienta. W przypadku kolejki sekwencyjnej żądanie nie zostanie dostarczone, dopóki sterownik klienta nie zakończy poprzedniego żądania. W trybie równoległego wysyłania framework przekazuje żądania natychmiast po ich nadejściu z menedżera I/O. Oznacza to, że sterownik klienta może odbierać jedno żądanie podczas przetwarzania drugiego. W mechanizmie niestandardowym klient ręcznie pobiera następne żądanie z obiektu kolejki frameworka, gdy sterownik jest gotowy do jego przetworzenia.
Zazwyczaj sterownik klienta musi skonfigurować kolejki w evtDriverDeviceAdd wywołania zwrotnego zdarzeń. Kod szablonu zawiera funkcję pomocniczą MyUSBDriver_QueueInitialize, która inicjalizuje obiekt kolejki w ramach.
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;
}
Aby skonfigurować kolejki, sterownik klienta wykonuje następujące zadania:
- Określa opcje konfiguracji kolejki w strukturze WDF_IO_QUEUE_CONFIG. Kod szablonu używa funkcji WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE do inicjowania struktury. Funkcja określa obiekt kolejki jako domyślny obiekt kolejki, jest zarządzany energetycznie, odbierając żądania równolegle.
- Dodaje wywołania zwrotne zdarzeń sterownika klienta dla żądań I/O w ramach kolejki. W szablonie sterownik klienta określa wskaźnik do wywołania zwrotnego zdarzeń dla żądania sterowania we/wy urządzenia.
- Wywołuje WdfIoQueueCreate, aby uzyskać uchwyt WDFQUEUE do obiektu kolejki systemu utworzonego przez framework.
Oto jak działa mechanizm kolejki. Aby komunikować się z urządzeniem USB, aplikacja najpierw otwiera dojście do urządzenia, wywołując procedury SetDixxx oraz CreateHandle. Za pomocą tego uchwytu aplikacja wywołuje funkcję DeviceIoControl z określonym kodem sterującym. W zależności od typu kodu sterującego aplikacja może określać bufory wejściowe i wyjściowe w tym wywołaniu. Wywołanie jest ostatecznie odbierane przez menedżera we/wy, który następnie tworzy żądanie (IRP) i przekazuje je do sterownika klienta. Struktura przechwytuje żądanie, tworzy obiekt żądania platformy i dodaje go do obiektu kolejki platformy. W takim przypadku, ponieważ sterownik klienta zarejestrował wywołanie zwrotne zdarzeń dla żądania sterowania wejścia/wyjścia urządzenia, framework wywołuje wywołanie zwrotne. Ponadto, ponieważ obiekt kolejki został utworzony za pomocą flagi WdfIoQueueDispatchParallel, wywołanie zwrotne następuje natychmiast po dodaniu żądania do kolejki.
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;
}
Gdy platforma wywołuje wywołanie zwrotne zdarzeń sterownika klienta, przekazuje dojście do obiektu żądania platformy, który przechowuje żądanie (oraz jego wejściowe i wyjściowe) wysyłane przez aplikację. Ponadto wysyła dojście do obiektu kolejki struktury, który zawiera żądanie. W przypadku wywołania zwrotnego sterownik klienta przetwarza żądanie według wymagań. Kod szablonu po prostu kończy żądanie. Sterownik klienta może wykonywać bardziej skomplikowane zadania. Jeśli na przykład aplikacja żąda pewnych informacji o urządzeniu, w wywołaniu zwrotnym zdarzenia sterownik klienta może utworzyć żądanie sterowania USB i wysłać je do stosu sterowników USB w celu pobrania żądanych informacji o urządzeniu. Żądania kontroli USB są omówione w Przesyłanie Kontroli USB.