Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym artykule opisano zachowanie rozszerzenia klasy emulacji urządzeń USB (UDE) i zadań, które sterownik klienta musi wykonać dla emulowanego kontrolera hosta i podłączonych do niego urządzeń. Zawiera informacje o sposobie, w jaki sterownik klasy i rozszerzenie klasy komunikują się ze sobą nawzajem za pomocą zestawu procedur i funkcji wywołań zwrotnych. Opisano w nim również funkcje, które należy zaimplementować przez sterownik klienta.
Podsumowanie
- Obiekty i uchwyty UDE używane przez rozszerzenie klasy i sterownik klienta.
- Tworzenie emulowanego kontrolera hosta z funkcjami umożliwiającymi wykonywanie zapytań dotyczących możliwości kontrolera i resetowanie kontrolera.
- Utworzenie wirtualnego urządzenia USB, skonfigurowanie go na potrzeby zarządzania energią i transferu danych za pośrednictwem punktów końcowych.
Ważne interfejsy API
Zanim rozpoczniesz
-
Zainstaluj najnowszy zestaw sterowników systemu Windows (WDK) na komputerze dewelopera. Zestaw zawiera wymagane pliki nagłówkowe i biblioteki do pisania sterownika klienta UDE, w szczególności potrzebne są następujące elementy:
- Biblioteka szkieletowa (Udecxstub.lib). Biblioteka tłumaczy wywołania wykonywane przez sterownik klienta i przekazuje je do UdeCx.
- Plik nagłówka Udecx.h.
- Zainstaluj system Windows 10 na komputerze docelowym.
- Zapoznaj się z UDE. Zobacz Architektura: emulacja urządzeń USB (UDE).
- Zapoznaj się z programem Windows Driver Foundation (WDF). Zalecane czytanie: Opracowywanie sterowników z Windows Driver Foundation, napisane przez Penny Orwick i Guy Smith.
Obiekty i uchwyty UDE
Rozszerzenie klasy UDE i sterownik klienta używają określonych obiektów WDF reprezentujących emulowany kontroler hosta i urządzenie wirtualne, w tym jego punkty końcowe i adresy URL używane do transferu danych między urządzeniem a hostem. Sterownik klienta żąda utworzenia obiektów, a czas istnienia obiektu jest zarządzany przez rozszerzenie klasy.
Emulowany obiekt kontrolera hosta (WDFDEVICE)
Reprezentuje emulowany kontroler hosta i jest głównym interfejsem między rozszerzeniem klasy UDE a sterownikiem klienta.
Obiekt urządzenia UDE (UDECXUSBDEVICE)
Reprezentuje wirtualne urządzenie USB podłączone do portu na emulowanym kontrolerze hosta.
Obiekt punktu końcowego UDE (UDECXUSBENDPOINT)
Reprezentują sekwencyjne potoki danych urządzeń USB. Służy do odbierania żądań oprogramowania do wysyłania lub odbierania danych w punkcie końcowym.
Inicjowanie emulowanego kontrolera hosta
Oto podsumowanie sekwencji, w której sterownik klienta pobiera dojście WDFDEVICE dla emulowanego kontrolera hosta. Zalecamy, aby sterownik wykonywał te zadania w funkcji EvtDriverDeviceAdd wywołania zwrotnego.
Wywołaj metodę UdecxInitializeWdfDeviceInit, przekazując odwołanie do WDFDEVICE_INIT przekazanego przez platformę.
Zainicjuj strukturę WDFDEVICE_INIT z informacjami o konfiguracji, tak aby urządzenie było podobne do innych kontrolerów hosta USB. Na przykład przypisz nazwę FDO i link symboliczny, zarejestruj interfejs urządzenia z udostępnionym przez firmę Microsoft identyfikatorem GUID_DEVINTERFACE_USB_HOST_CONTROLLER jako GUID interfejsu urządzenia, aby aplikacje mogły otworzyć uchwyt do urządzenia.
Wywołaj WdfDeviceCreate, aby utworzyć obiekt urządzenia ramy.
Wywołaj funkcję UdecxWdfDeviceAddUsbDeviceEmulation i zarejestruj funkcje wywołania zwrotnego sterownika klienta.
Poniżej przedstawiono funkcje wywołania zwrotnego skojarzone z obiektem kontrolera hosta, które są wywoływane przez rozszerzenie klasy UDE. Te funkcje muszą być implementowane przez sterownik klienta.
EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY
Określa możliwości obsługiwane przez kontroler hosta, które sterownik klienta musi zgłosić do rozszerzenia klasy.
-
Opcjonalny. Resetuje kontroler hosta i/lub podłączone urządzenia.
EVT_WDF_DRIVER_DEVICE_ADD Controller_WdfEvtDeviceAdd; #define BASE_DEVICE_NAME L"\\Device\\USBFDO-" #define BASE_SYMBOLIC_LINK_NAME L"\\DosDevices\\HCD" #define DeviceNameSize sizeof(BASE_DEVICE_NAME)+MAX_SUFFIX_SIZE #define SymLinkNameSize sizeof(BASE_SYMBOLIC_LINK_NAME)+MAX_SUFFIX_SIZE NTSTATUS Controller_WdfEvtDeviceAdd( _In_ WDFDRIVER Driver, _Inout_ PWDFDEVICE_INIT WdfDeviceInit ) { NTSTATUS status; WDFDEVICE wdfDevice; WDF_PNPPOWER_EVENT_CALLBACKS wdfPnpPowerCallbacks; WDF_OBJECT_ATTRIBUTES wdfDeviceAttributes; WDF_OBJECT_ATTRIBUTES wdfRequestAttributes; UDECX_WDF_DEVICE_CONFIG controllerConfig; WDF_FILEOBJECT_CONFIG fileConfig; PWDFDEVICE_CONTEXT pControllerContext; WDF_IO_QUEUE_CONFIG defaultQueueConfig; WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings; UNICODE_STRING refString; ULONG instanceNumber; BOOLEAN isCreated; DECLARE_UNICODE_STRING_SIZE(uniDeviceName, DeviceNameSize); DECLARE_UNICODE_STRING_SIZE(uniSymLinkName, SymLinkNameSize); UNREFERENCED_PARAMETER(Driver); ... WdfDeviceInitSetPnpPowerEventCallbacks(WdfDeviceInit, &wdfPnpPowerCallbacks); WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wdfRequestAttributes, REQUEST_CONTEXT); WdfDeviceInitSetRequestAttributes(WdfDeviceInit, &wdfRequestAttributes); // To distinguish I/O sent to GUID_DEVINTERFACE_USB_HOST_CONTROLLER, we will enable // enable interface reference strings by calling WdfDeviceInitSetFileObjectConfig // with FileObjectClass WdfFileObjectWdfXxx. WDF_FILEOBJECT_CONFIG_INIT(&fileConfig, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK // No cleanup callback function ); ... WdfDeviceInitSetFileObjectConfig(WdfDeviceInit, &fileConfig, WDF_NO_OBJECT_ATTRIBUTES); ... // Do additional setup required for USB controllers. status = UdecxInitializeWdfDeviceInit(WdfDeviceInit); ... WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wdfDeviceAttributes, WDFDEVICE_CONTEXT); wdfDeviceAttributes.EvtCleanupCallback = _ControllerWdfEvtCleanupCallback; // Call WdfDeviceCreate with a few extra compatibility steps to ensure this device looks // exactly like other USB host controllers. isCreated = FALSE; for (instanceNumber = 0; instanceNumber < ULONG_MAX; instanceNumber++) { status = RtlUnicodeStringPrintf(&uniDeviceName, L"%ws%d", BASE_DEVICE_NAME, instanceNumber); ... status = WdfDeviceInitAssignName(*WdfDeviceInit, &uniDeviceName); ... status = WdfDeviceCreate(WdfDeviceInit, WdfDeviceAttributes, WdfDevice); if (status == STATUS_OBJECT_NAME_COLLISION) { // This is expected to happen at least once when another USB host controller // already exists on the system. ... } else if (!NT_SUCCESS(status)) { ... } else { isCreated = TRUE; break; } } if (!isCreated) { ... } // Create the symbolic link (also for compatibility). status = RtlUnicodeStringPrintf(&uniSymLinkName, L"%ws%d", BASE_SYMBOLIC_LINK_NAME, instanceNumber); ... status = WdfDeviceCreateSymbolicLink(*WdfDevice, &uniSymLinkName); ... // Create the device interface. RtlInitUnicodeString(&refString, USB_HOST_DEVINTERFACE_REF_STRING); status = WdfDeviceCreateDeviceInterface(wdfDevice, (LPGUID)&GUID_DEVINTERFACE_USB_HOST_CONTROLLER, &refString); ... UDECX_WDF_DEVICE_CONFIG_INIT(&controllerConfig, Controller_EvtUdecxWdfDeviceQueryUsbCapability); status = UdecxWdfDeviceAddUsbDeviceEmulation(wdfDevice, &controllerConfig); // Create default queue. It only supports USB controller IOCTLs. (USB I/O will come through // in separate USB device queues.) // Shown later in this topic. WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&defaultQueueConfig, WdfIoQueueDispatchSequential); defaultQueueConfig.EvtIoDeviceControl = ControllerEvtIoDeviceControl; defaultQueueConfig.PowerManaged = WdfFalse; status = WdfIoQueueCreate(wdfDevice, &defaultQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, &pControllerContext->DefaultQueue); ... // Initialize virtual USB device software objects. // Shown later in this topic. status = Usb_Initialize(wdfDevice); ... exit: return status; } ```1.
Obsługa żądań IOCTL w trybie użytkownika wysyłanych do kontrolera hosta
Podczas inicjowania sterownik klienta UDE uwidacznia identyfikator GUID interfejsu GUID_DEVINTERFACE_USB_HOST_CONTROLLER urządzenia. Dzięki temu sterownik może odbierać żądania IOCTL z aplikacji, która otwiera dojście urządzenia przy użyciu tego identyfikatora GUID. Aby uzyskać listę kodów kontrolek IOCTL, zobacz IOCTLs USB z identyfikatorem GUID interfejsu urządzenia: GUID_DEVINTERFACE_USB_HOST_CONTROLLER.
Aby obsłużyć te żądania, sterownik klienta rejestruje zdarzenie zwrotne EvtIoDeviceControl. W implementacji, zamiast obsługiwać żądanie, sterownik może zdecydować się przekazać żądanie do rozszerzenia klasy UDE na potrzeby przetwarzania. Aby przesłać żądanie, sterownik musi wywołać UdecxWdfDeviceTryHandleUserIoctl. Jeśli otrzymany kod kontrolny IOCTL odpowiada standardowemu żądaniu, takie jak pobieranie deskryptorów urządzeń, rozszerzenie klasy przetwarza i kończy żądanie pomyślnie. W tym przypadku element UdecxWdfDeviceTryHandleUserIoctl kończy się wartością TRUE jako wartością zwracaną. W przeciwnym razie wywołanie zwraca wartość FALSE, a sterownik musi określić sposób ukończenia żądania. W najprostszej implementacji sterownik może ukończyć żądanie z odpowiednim kodem błędu, wywołując element WdfRequestComplete.
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL Controller_EvtIoDeviceControl;
VOID
Controller_EvtIoDeviceControl(
_In_
WDFQUEUE Queue,
_In_
WDFREQUEST Request,
_In_
size_t OutputBufferLength,
_In_
size_t InputBufferLength,
_In_
ULONG IoControlCode
)
{
BOOLEAN handled;
NTSTATUS status;
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);
handled = UdecxWdfDeviceTryHandleUserIoctl(WdfIoQueueGetDevice(Queue),
Request);
if (handled) {
goto exit;
}
// Unexpected control code.
// Fail the request.
status = STATUS_INVALID_DEVICE_REQUEST;
WdfRequestComplete(Request, status);
exit:
return;
}
Zgłaszanie możliwości kontrolera hosta
Aby sterowniki górnej warstwy mogły korzystać z możliwości kontrolera hosta USB, sterowniki muszą określić, czy te możliwości są obsługiwane przez kontroler. Sterowniki tworzą takie zapytania, wywołując funkcję WdfUsbTargetDeviceQueryUsbCapability i USBD_QueryUsbCapability. Te wywołania są przekazywane do rozszerzenia klasy emulacji urządzeń USB (UDE). Po otrzymaniu żądania rozszerzenie klasy wywołuje implementację EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY sterownika klienta. To wywołanie jest wykonywane tylko po zakończeniu EvtDriverDeviceAdd , zazwyczaj w EvtDevicePrepareHardware , a nie po EvtDeviceReleaseHardware. Ta funkcja wywołania zwrotnego jest wymagana.
W implementacji sterownik klienta musi zgłosić, czy obsługuje żądaną możliwość. Niektóre możliwości nie są obsługiwane przez funkcję UDE, taką jak strumienie statyczne.
NTSTATUS
Controller_EvtControllerQueryUsbCapability(
WDFDEVICE UdeWdfDevice,
PGUID CapabilityType,
ULONG OutputBufferLength,
PVOID OutputBuffer,
PULONG ResultLength
)
{
NTSTATUS status;
UNREFERENCED_PARAMETER(UdeWdfDevice);
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(OutputBuffer);
*ResultLength = 0;
if (RtlCompareMemory(CapabilityType,
&GUID_USB_CAPABILITY_CHAINED_MDLS,
sizeof(GUID)) == sizeof(GUID)) {
//
// TODO: Is GUID_USB_CAPABILITY_CHAINED_MDLS supported?
// If supported, status = STATUS_SUCCESS
// Otherwise, status = STATUS_NOT_SUPPORTED
}
else {
status = STATUS_NOT_IMPLEMENTED;
}
return status;
}
Tworzenie wirtualnego urządzenia USB
Wirtualne urządzenie USB zachowuje się podobnie do urządzenia USB. Obsługuje konfigurację z wieloma interfejsami, a każdy interfejs obsługuje ustawienia alternatywne. Każde ustawienie może mieć jeszcze jeden punkt końcowy, który jest używany do transferów danych. Wszystkie deskryptory (urządzenie, konfiguracja, interfejs, punkt końcowy) są ustawiane przez sterownik klienta UDE, aby urządzenie mogło zgłaszać informacje podobne do rzeczywistego urządzenia USB.
Uwaga
Sterownik klienta UDE nie obsługuje koncentratorów zewnętrznych
Poniżej przedstawiono podsumowanie sekwencji, w której sterownik klienta tworzy uchwyt UDECXUSBDEVICE dla obiektu urządzenia UDE. Sterownik musi wykonać te kroki po uzyskaniu uchwytu WDFDEVICE dla emulowanego kontrolera hosta. Zalecamy, aby sterownik wykonywał te zadania w funkcji EvtDriverDeviceAdd wywołania zwrotnego.
Wywołaj metodę UdecxUsbDeviceInitAllocate , aby uzyskać wskaźnik do parametrów inicjowania wymaganych do utworzenia urządzenia. Ta struktura jest przydzielana przez rozszerzenie klasy UDE.
Zarejestruj funkcje obsługi zdarzeń, ustawiając składowe UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS, a następnie wywołując funkcję UdecxUsbDeviceInitSetStateChangeCallbacks. Poniżej przedstawiono funkcje wywołania zwrotnego skojarzone z obiektem urządzenia UDE, które są wywoływane przez rozszerzenie klasy UDE.
Te funkcje są implementowane przez sterownik klienta w celu tworzenia lub konfigurowania punktów końcowych.
Wywołaj metodę UdecxUsbDeviceInitSetSpeed , aby ustawić szybkość urządzenia USB, a także typ urządzenia, USB 2.0 lub SuperSpeed.
Wywołaj metodę UdecxUsbDeviceInitSetEndpointsType , aby określić typ punktów końcowych, które obsługuje urządzenie: proste lub dynamiczne. Jeśli sterownik klienta zdecyduje się utworzyć proste punkty końcowe, sterownik musi utworzyć wszystkie obiekty punktu końcowego przed podłączeniem urządzenia. Urządzenie musi mieć tylko jedną konfigurację i tylko jedno ustawienie interfejsu dla każdego interfejsu. W przypadku dynamicznych punktów końcowych sterownik może tworzyć punkty końcowe w dowolnym momencie po podłączeniu urządzenia, gdy otrzyma wywołanie zwrotne zdarzenia EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE. Zobacz Tworzenie dynamicznych punktów końcowych.
Wywołaj dowolną z tych metod, aby dodać niezbędne deskryptory do urządzenia.
UdecxUsbDeviceInitAddStringDescriptorRaw
Jeśli rozszerzenie klasy UDE odbiera żądanie dla deskryptora standardowego, który sterownik klienta dostarczył podczas inicjowania przy użyciu jednej z powyższych metod, rozszerzenie klasy automatycznie ukończy żądanie. Rozszerzenie klasy nie przekazuje tego żądania do sterownika klienta. Ten projekt zmniejsza liczbę żądań, które sterownik musi przetworzyć na potrzeby żądań kontroli. Ponadto eliminuje również konieczność zaimplementowania logiki deskryptora sterownika, która wymaga obszernego analizowania pakietu instalacyjnego i prawidłowego obsługi wLength i TransferBufferLength . Ta lista zawiera standardowe żądania. Sterownik klienta nie musi sprawdzać tych żądań (tylko wtedy, gdy poprzednie metody zostały wywołane w celu dodania deskryptora):
USB_REQUEST_GET_DESCRIPTOR (żądanie uzyskania deskryptora USB)
USB_REQUEST_SET_CONFIGURATION
Żądanie USB Ustaw Interfejs
USB_REQUEST_SET_ADDRESS
USB_REQUEST_SET_FEATURE
ZAWIESZENIE_FUNKCJI_USB
USB_FUNKCJA_REMOTE_WAKEUP
Żądanie_USB_USUNIĘCIE_FUNKCJI
USB_CECHA_ZAWIESZENIE_PUNKTU_KOŃCOWEGO
USB_REQUEST_SET_SEL
USB_REQUEST_ISOCH_DELAY
Jednak żądania dotyczące interfejsu, specyficznego dla klasy lub deskryptora zdefiniowanego przez dostawcę, rozszerzenie klasy UDE przekazuje je do sterownika klienta. Sterownik musi obsługiwać te żądania GET_DESCRIPTOR.
Wywołaj metodę UdecxUsbDeviceCreate , aby utworzyć obiekt urządzenia UDE i pobrać uchwyt UDECXUSBDEVICE.
Tworzenie statycznych punktów końcowych przez wywołanie UdecxUsbEndpointCreate. Zobacz Tworzenie prostych punktów końcowych.
Wywołaj metodę UdecxUsbDevicePlugIn, aby wskazać rozszerzeniu klasy UDE, że urządzenie jest podłączone i umożliwia odbieranie żądań we/wy na punktach końcowych. Po wywołaniu tego wywołania rozszerzenie klasy może również wywoływać funkcje wywołania zwrotnego w punktach końcowych i urządzeniu USB. Nuta Jeśli urządzenie USB musi zostać usunięte w czasie wykonywania, sterownik klienta może wywołać UdecxUsbDevicePlugOutAndDelete. Jeśli sterownik chce użyć urządzenia, musi go utworzyć przez wywołanie UdecxUsbDeviceCreate.
W tym przykładzie przyjmuje się, że deklaracje deskryptora są zmiennymi globalnymi, zadeklarowane jak pokazano tutaj dla urządzenia HID, tak jak na przykład:
const UCHAR g_UsbDeviceDescriptor[] = {
// Device Descriptor
0x12, // Descriptor Size
0x01, // Device Descriptor Type
0x00, 0x03, // USB 3.0
0x00, // Device class
0x00, // Device sub-class
0x00, // Device protocol
0x09, // Maxpacket size for EP0 : 2^9
0x5E, 0x04, // Vendor ID
0x39, 0x00, // Product ID
0x00, // LSB of firmware version
0x03, // MSB of firmware version
0x01, // Manufacture string index
0x03, // Product string index
0x00, // Serial number string index
0x01 // Number of configurations
};
Oto przykład, w którym sterownik klienta określa parametry inicjowania, rejestrując funkcje wywołania zwrotnego, ustawiając szybkość urządzenia, wskazując typ punktów końcowych, a na koniec ustawiając niektóre deskryptory urządzeń.
NTSTATUS
Usb_Initialize(
_In_
WDFDEVICE WdfDevice
)
{
NTSTATUS status;
PUSB_CONTEXT usbContext; //Client driver declared context for the host controller object
PUDECX_USBDEVICE_CONTEXT deviceContext; //Client driver declared context for the UDE device object
UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS callbacks;
WDF_OBJECT_ATTRIBUTES attributes;
UDECX_USB_DEVICE_PLUG_IN_OPTIONS pluginOptions;
usbContext = WdfDeviceGetUsbContext(WdfDevice);
usbContext->UdecxUsbDeviceInit = UdecxUsbDeviceInitAllocate(WdfDevice);
if (usbContext->UdecxUsbDeviceInit == NULL) {
...
goto exit;
}
// State changed callbacks
UDECX_USB_DEVICE_CALLBACKS_INIT(&callbacks);
#ifndef SIMPLEENDPOINTS
callbacks.EvtUsbDeviceDefaultEndpointAdd = UsbDevice_EvtUsbDeviceDefaultEndpointAdd;
callbacks.EvtUsbDeviceEndpointAdd = UsbDevice_EvtUsbDeviceEndpointAdd;
callbacks.EvtUsbDeviceEndpointsConfigure = UsbDevice_EvtUsbDeviceEndpointsConfigure;
#endif
callbacks.EvtUsbDeviceLinkPowerEntry = UsbDevice_EvtUsbDeviceLinkPowerEntry;
callbacks.EvtUsbDeviceLinkPowerExit = UsbDevice_EvtUsbDeviceLinkPowerExit;
callbacks.EvtUsbDeviceSetFunctionSuspendAndWake = UsbDevice_EvtUsbDeviceSetFunctionSuspendAndWake;
UdecxUsbDeviceInitSetStateChangeCallbacks(usbContext->UdecxUsbDeviceInit, &callbacks);
// Set required attributes.
UdecxUsbDeviceInitSetSpeed(usbContext->UdecxUsbDeviceInit, UdecxUsbLowSpeed);
#ifdef SIMPLEENDPOINTS
UdecxUsbDeviceInitSetEndpointsType(usbContext->UdecxUsbDeviceInit, UdecxEndpointTypeSimple);
#else
UdecxUsbDeviceInitSetEndpointsType(usbContext->UdecxUsbDeviceInit, UdecxEndpointTypeDynamic);
#endif
// Add device descriptor
//
status = UdecxUsbDeviceInitAddDescriptor(usbContext->UdecxUsbDeviceInit,
(PUCHAR)g_UsbDeviceDescriptor,
sizeof(g_UsbDeviceDescriptor));
if (!NT_SUCCESS(status)) {
goto exit;
}
#ifdef USB30
// Add BOS descriptor for a SuperSpeed device
status = UdecxUsbDeviceInitAddDescriptor(pUsbContext->UdecxUsbDeviceInit,
(PUCHAR)g_UsbBOSDescriptor,
sizeof(g_UsbBOSDescriptor));
if (!NT_SUCCESS(status)) {
goto exit;
}
#endif
// String descriptors
status = UdecxUsbDeviceInitAddDescriptorWithIndex(usbContext->UdecxUsbDeviceInit,
(PUCHAR)g_LanguageDescriptor,
sizeof(g_LanguageDescriptor),
0);
if (!NT_SUCCESS(status)) {
goto exit;
}
status = UdecxUsbDeviceInitAddStringDescriptor(usbContext->UdecxUsbDeviceInit,
&g_ManufacturerStringEnUs,
g_ManufacturerIndex,
US_ENGLISH);
if (!NT_SUCCESS(status)) {
goto exit;
}
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, UDECX_USBDEVICE_CONTEXT);
status = UdecxUsbDeviceCreate(&usbContext->UdecxUsbDeviceInit,
&attributes,
&usbContext->UdecxUsbDevice);
if (!NT_SUCCESS(status)) {
goto exit;
}
#ifdef SIMPLEENDPOINTS
// Create the default control endpoint
// Shown later in this topic.
status = UsbCreateControlEndpoint(WdfDevice);
if (!NT_SUCCESS(status)) {
goto exit;
}
#endif
UDECX_USB_DEVICE_PLUG_IN_OPTIONS_INIT(&pluginOptions);
#ifdef USB30
pluginOptions.Usb30PortNumber = 2;
#else
pluginOptions.Usb20PortNumber = 1;
#endif
status = UdecxUsbDevicePlugIn(usbContext->UdecxUsbDevice, &pluginOptions);
exit:
if (!NT_SUCCESS(status)) {
UdecxUsbDeviceInitFree(usbContext->UdecxUsbDeviceInit);
usbContext->UdecxUsbDeviceInit = NULL;
}
return status;
}
Zarządzanie energią urządzenia USB
Rozszerzenie klasy UDE wywołuje funkcje zwrotne sterownika klienta po odebraniu żądania wysłania urządzenia do stanu niskiego zasilania lub przywrócenia do stanu pracy. Te funkcje wywołania zwrotnego są wymagane dla urządzeń USB, które obsługują wybudzanie. Sterownik klienta zarejestrował swoją implementację w poprzednim wywołaniu UdecxUsbDeviceInitSetStateChangeCallbacks.
Aby uzyskać więcej informacji, zobacz Stany zasilania urządzenia USB.
EVT_UDECX_USB_DEVICE_D0_ENTRY: sterownik klienta przenosi urządzenie ze stanu Dx na stan D0.
EVT_UDECX_USB_DEVICE_D0_EXIT: sterownik klienta przenosi urządzenie ze stanu D0 na stan Dx.
EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE: sterownik klienta zmienia stan funkcji określonego interfejsu wirtualnego urządzenia USB 3.0.
Urządzenie USB 3.0 umożliwia poszczególnym funkcjom wprowadzanie niższego stanu zasilania. Każda funkcja jest również w stanie wysłać sygnał wznawiania. Rozszerzenie klasy UDE powiadamia sterownik klienta poprzez wywołanie EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE. To zdarzenie wskazuje zmianę stanu zasilania funkcji i powiadamia sterownik klienta, czy funkcja może wybudzić się z nowego stanu. W funkcji rozszerzenie klasy przekazuje numer interfejsu funkcji, która budzi się.
Sterownik klienta może symulować działanie wirtualnego urządzenia USB, które inicjuje własne wznawianie z niskiego stanu zasilania łącza, zawieszenia funkcji lub obu stanów. W przypadku urządzenia USB 2.0 sterownik musi wywołać funkcję UdecxUsbDeviceSignalWake, jeśli sterownik włączył wznawianie na urządzeniu w najnowszym EVT_UDECX_USB_DEVICE_D0_EXIT. W przypadku urządzenia USB 3.0 sterownik musi wywołać UdecxUsbDeviceSignalFunctionWake, ponieważ funkcja wznawiania w USB 3.0 jest realizowana na poziomie pojedynczej funkcji. Jeśli całe urządzenie jest w stanie niskiego zasilania lub wchodzi w taki stan, UdecxUsbDeviceSignalFunctionWake wznawia urządzenie.
Tworzenie prostych punktów końcowych
Sterownik klienta tworzy obiekty punktu końcowego UDE do obsługi transferów danych do i z urządzenia USB. Sterownik tworzy proste punkty końcowe po stworzeniu urządzenia typu UDE i zanim zgłosi urządzenie jako podłączone.
Oto podsumowanie sekwencji, w której sterownik klienta tworzy dojście UDECXUSBENDPOINT dla obiektu punktu końcowego UDE. Sterownik musi wykonać te kroki po pobraniu uchwytu UDECXUSBDEVICE dla wirtualnego urządzenia USB. Zalecamy, aby sterownik wykonywał te zadania w funkcji EvtDriverDeviceAdd wywołania zwrotnego.
Wywołaj metodę UdecxUsbSimpleEndpointInitAllocate , aby uzyskać wskaźnik do parametrów inicjowania przydzielonych przez rozszerzenie klasy.
Wywołaj metodę UdecxUsbEndpointInitSetEndpointAddress , aby ustawić adres punktu końcowego w parametrach inicjowania.
Wywołaj UdecxUsbEndpointInitSetCallbacks w celu zarejestrowania funkcji wywołania zwrotnego zaimplementowanych przez sterownik klienta.
Te funkcje są implementowane przez sterownik klienta do obsługi kolejek i żądań w punkcie końcowym.
EVT_UDECX_USB_ENDPOINT_RESET: Resetuje punkt końcowy wirtualnego urządzenia USB.
EVT_UDECX_USB_ENDPOINT_START: opcjonalne. Rozpoczyna przetwarzanie żądań we/wy
EVT_UDECX_USB_ENDPOINT_PURGE: opcjonalny. Zatrzymaj kolejkowanie żądań we/wy do kolejki punktu końcowego i anuluj nieprzetworzone żądania.
Wywołaj UdecxUsbEndpointCreate, aby utworzyć obiekt punktu końcowego i pobrać uchwyt UDECXUSBENDPOINT.
Wywołaj metodę UdecxUsbEndpointSetWdfIoQueue , aby skojarzyć obiekt kolejki struktury z punktem końcowym. Jeśli ma to zastosowanie, może ustawić obiekt punktu końcowego jako obiekt nadrzędny kolejki WDF, ustawiając odpowiednie atrybuty.
Każdy obiekt punktu końcowego ma obiekt kolejki framework do obsługi żądań transferu. Dla każdego przekazanego żądania transferu, rozszerzenie klasy umieszcza w kolejce obiekt żądania ramowego. Stan kolejki (uruchomiono, przeczyszczone) jest zarządzany przez rozszerzenie klasy UDE, a sterownik klienta nie może zmienić tego stanu. Każdy obiekt żądania zawiera blok żądań USB (URB), który zawiera szczegóły transferu.
W tym przykładzie sterownik klienta tworzy domyślny punkt końcowy kontroli.
EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL IoEvtControlUrb;
EVT_UDECX_USB_ENDPOINT_RESET UsbEndpointReset;
EVT_UDECX_USB_ENDPOINT_PURGE UsEndpointEvtPurge;
EVT_UDECX_USB_ENDPOINT_START UsbEndpointEvtStart;
NTSTATUS
UsbCreateControlEndpoint(
_In_
WDFDEVICE WdfDevice
)
{
NTSTATUS status;
PUSB_CONTEXT pUsbContext;
WDF_IO_QUEUE_CONFIG queueConfig;
WDFQUEUE controlQueue;
UDECX_USB_ENDPOINT_CALLBACKS callbacks;
PUDECXUSBENDPOINT_INIT endpointInit;
pUsbContext = WdfDeviceGetUsbContext(WdfDevice);
endpointInit = NULL;
WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);
queueConfig.EvtIoInternalDeviceControl = IoEvtControlUrb;
status = WdfIoQueueCreate (Device,
&queueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&controlQueue);
if (!NT_SUCCESS(status)) {
goto exit;
}
endpointInit = UdecxUsbSimpleEndpointInitAllocate(pUsbContext->UdecxUsbDevice);
if (endpointInit == NULL) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto exit;
}
UdecxUsbEndpointInitSetEndpointAddress(endpointInit, USB_DEFAULT_ENDPOINT_ADDRESS);
UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, UsbEndpointReset);
UdecxUsbEndpointInitSetCallbacks(endpointInit, &callbacks);
callbacks.EvtUsbEndpointStart = UsbEndpointEvtStart;
callbacks.EvtUsbEndpointPurge = UsEndpointEvtPurge;
status = UdecxUsbEndpointCreate(&endpointInit,
WDF_NO_OBJECT_ATTRIBUTES,
&pUsbContext->UdecxUsbControlEndpoint);
if (!NT_SUCCESS(status)) {
goto exit;
}
UdecxUsbEndpointSetWdfIoQueue(pUsbContext->UdecxUsbControlEndpoint,
controlQueue);
exit:
if (endpointInit != NULL) {
NT_ASSERT(!NT_SUCCESS(status));
UdecxUsbEndpointInitFree(endpointInit);
endpointInit = NULL;
}
return status;
}
Tworzenie dynamicznych punktów końcowych
Sterownik klienta może tworzyć dynamiczne punkty końcowe na żądanie rozszerzenia klasy UDE (w imieniu sterowników centrum i sterowników klienta). Rozszerzenie klasy wysyła żądanie, wywołując dowolną z tych funkcji wywołania zwrotnego:
* EVT_UDECX_USB_DEVICE_DEFAULT_ENDPOINT_ADD Sterownik klienta tworzy domyślny punkt końcowy kontroli (punkt końcowy 0)
* EVT_UDECX_USB_DEVICE_ENDPOINT_ADD Sterownik klienta tworzy dynamiczny punkt końcowy.
* EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE Sterownik klienta zmienia konfigurację, wybierając ustawienie alternatywne, wyłączając bieżące punkty końcowe lub dodając dynamiczne punkty końcowe.
Sterownik klienta zarejestrował wymienione wywołanie zwrotne podczas wywołania funkcji UdecxUsbDeviceInitSetStateChangeCallbacks. Zobacz Tworzenie wirtualnego urządzenia USB. Ten mechanizm umożliwia sterownikowi klienta dynamiczne zmienianie ustawień konfiguracji i interfejsu USB na urządzeniu. Jeśli na przykład potrzebny jest obiekt punktu końcowego lub należy zwolnić istniejący obiekt punktu końcowego, rozszerzenie klasy wywołuje EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE.
Poniżej przedstawiono podsumowanie sekwencji, w której sterownik klienta tworzy dojście UDECXUSBENDPOINT dla obiektu punktu końcowego w implementacji funkcji wywołania zwrotnego.
Wywołaj metodę UdecxUsbEndpointInitSetEndpointAddress , aby ustawić adres punktu końcowego w parametrach inicjowania.
Wywołaj UdecxUsbEndpointInitSetCallbacks w celu zarejestrowania funkcji wywołania zwrotnego zaimplementowanych przez sterownik klienta. Podobnie jak w przypadku prostych punktów końcowych sterownik może zarejestrować te funkcje wywołania zwrotnego:
Wywołaj UdecxUsbEndpointCreate aby utworzyć obiekt punktu końcowego i pobrać uchwyt UDECXUSBENDPOINT.
Wywołaj metodę UdecxUsbEndpointSetWdfIoQueue , aby skojarzyć obiekt kolejki struktury z punktem końcowym.
W tej przykładowej implementacji sterownik klienta tworzy dynamiczny domyślny punkt końcowy kontroli.
NTSTATUS
UsbDevice_EvtUsbDeviceDefaultEndpointAdd(
_In_
UDECXUSBDEVICE UdecxUsbDevice,
_In_
PUDECXUSBENDPOINT_INIT UdecxUsbEndpointInit
)
{
NTSTATUS status;
PUDECX_USBDEVICE_CONTEXT deviceContext;
WDFQUEUE controlQueue;
WDF_IO_QUEUE_CONFIG queueConfig;
UDECX_USB_ENDPOINT_CALLBACKS callbacks;
deviceContext = UdecxDeviceGetContext(UdecxUsbDevice);
WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);
queueConfig.EvtIoInternalDeviceControl = IoEvtControlUrb;
status = WdfIoQueueCreate (deviceContext->WdfDevice,
&queueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&controlQueue);
if (!NT_SUCCESS(status)) {
goto exit;
}
UdecxUsbEndpointInitSetEndpointAddress(UdecxUsbEndpointInit, USB_DEFAULT_DEVICE_ADDRESS);
UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, UsbEndpointReset);
UdecxUsbEndpointInitSetCallbacks(UdecxUsbEndpointInit, &callbacks);
status = UdecxUsbEndpointCreate(UdecxUsbEndpointInit,
WDF_NO_OBJECT_ATTRIBUTES,
&deviceContext->UdecxUsbControlEndpoint);
if (!NT_SUCCESS(status)) {
goto exit;
}
UdecxUsbEndpointSetWdfIoQueue(deviceContext->UdecxUsbControlEndpoint,
controlQueue);
exit:
return status;
}
Wykonaj odzyskiwanie błędów przez zresetowanie punktu końcowego
Czasami transfery danych mogą zakończyć się niepowodzeniem z różnych powodów, takich jak zawieszenie w urządzeniu końcowym. W przypadku nieudanych transferów punkt końcowy nie może przetworzyć żądań, dopóki warunek błędu nie zostanie wyczyszczone. Gdy rozszerzenie klasy UDE nie powiodło się transfery danych, wywołuje EVT_UDECX_USB_ENDPOINT_RESET funkcji wywołania zwrotnego sterownika klienta, który sterownik zarejestrowany w poprzednim wywołaniu do UdecxUsbEndpointInitSetCallbacks. W implementacji sterownik może zdecydować się na wyczyszczenie stanu ZATRZYMANIA potoku i podjąć inne niezbędne kroki, aby usunąć stan błędu.
To wywołanie jest asynchroniczne. Po zakończeniu operacji resetowania klienta sterownik musi ukończyć żądanie z odpowiednim kodem błędu, wywołując polecenie WdfRequestComplete. To wywołanie powiadamia rozszerzenie klienta UDE o zakończeniu operacji resetowania ze stanem.
Nuta Jeśli do odzyskiwania błędów jest wymagane złożone rozwiązanie, sterownik klienta ma możliwość zresetowania kontrolera hosta. Tę logikę można zaimplementować w funkcji wywołania zwrotnego EVT_UDECX_WDF_DEVICE_RESET, którą sterownik zarejestrował w wywołaniu UdecxWdfDeviceAddUsbDeviceEmulation. Jeśli ma to zastosowanie, sterownik może zresetować kontroler hosta i wszystkie urządzenia podrzędne. Jeśli sterownik klienta nie musi resetować kontrolera, ale zresetować wszystkie urządzenia podrzędne, sterownik musi określić UdeWdfDeviceResetActionResetEachUsbDevice w parametrach konfiguracji podczas rejestracji. W takim przypadku rozszerzenie klasy wywołuje EVT_UDECX_WDF_DEVICE_RESET dla każdego połączonego urządzenia.
Implementowanie zarządzania stanem kolejki
Stan obiektu kolejki struktury skojarzonego z obiektem punktu końcowego UDE jest zarządzany przez rozszerzenie klasy UDE. Jeśli jednak sterownik klienta przekazuje żądania z kolejek punktów końcowych do innych kolejek wewnętrznych, klient musi zaimplementować logikę w celu obsługi zmian w przepływie we/wy punktu końcowego. Te funkcje wywołania zwrotnego są rejestrowane w funkcji UdecxUsbEndpointInitSetCallbacks.
Operacja przeczyszczania punktu końcowego
Sterownik klienta UDE z jedną kolejką na punkt końcowy może zaimplementować EVT_UDECX_USB_ENDPOINT_PURGE , jak pokazano w tym przykładzie:
W implementacji EVT_UDECX_USB_ENDPOINT_PURGE sterownik klienta jest wymagany, aby upewnić się, że wszystkie we/wy przekazane z kolejki punktu końcowego zostały ukończone i że nowo przekazane we/wy również zakończy się niepowodzeniem, dopóki nie zostanie wywołana EVT_UDECX_USB_ENDPOINT_START sterownika klienta. Te wymagania są spełnione przez wywołanie funkcji UdecxUsbEndpointPurgeComplete, co zapewnia, że wszystkie przekazane we/wy zostały ukończone, a przyszłe przekazane we/wy zakończyły się niepowodzeniem.
Operacja uruchamiania punktu końcowego
W implementacji EVT_UDECX_USB_ENDPOINT_START sterownik klienta jest wymagany do rozpoczęcia przetwarzania operacji we/wy w kolejce punktu końcowego oraz w kolejkach, które odbierają przekazane operacje we/wy dla punktu końcowego. Po utworzeniu punktu końcowego nie otrzyma żadnych operacji we/wy, dopóki ta funkcja wywołania zwrotnego nie zostanie zwrócona. To wywołanie zwrotne zwraca punkt końcowy do stanu przetwarzania operacji we/wy po zakończeniu EVT_UDECX_USB_ENDPOINT_PURGE .
Obsługa żądań transferu danych (URB)
Aby przetworzyć żądania we/wy USB wysyłane do punktów końcowych urządzenia klienckiego, przechwyć wywołanie zwrotne EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL w obiekcie kolejki używanym z elementem UdecxUsbEndpointInitSetCallbacks podczas kojarzenia kolejki z punktem końcowym. W tym wywołaniu zwrotnym przetwórz operacje we/wy dla IOCTL_INTERNAL_USB_SUBMIT_URB IoControlCode (zobacz przykładowy kod w sekcji Metody obsługi urB).
Metody obsługi urB
W ramach przetwarzania adresów URL za pośrednictwem IOCTL_INTERNAL_USB_SUBMIT_URB kolejki skojarzonej z punktem końcowym na urządzeniu wirtualnym sterownik klienta UDE może uzyskać wskaźnik do bufora transferu żądania we/wy przy użyciu następujących metod:
Te funkcje są implementowane przez sterownik klienta do obsługi kolejek i żądań w punkcie końcowym.
UdecxUrbRetrieveControlSetupPacket Pobiera pakiet instalacyjny formantu USB z określonego obiektu żądania struktury.
UdecxUrbRetrieveBuffer Pobiera bufor transferu urB z określonego obiektu żądania platformy wysyłanego do kolejki punktu końcowego.
UdecxUrbSetBytesCompleted Ustawia liczbę bajtów przesłanych dla identyfikatora URB zawartego w obiekcie żądania struktury.
UdecxUrbComplete Kończy żądanie URB z kodem stanu ukończenia specyficznym dla usb.
UdecxUrbCompleteWithNtStatus Kończy żądanie URB przy użyciu kodu NTSTATUS.
Poniżej znajduje się przepływ typowego przetwarzania we/wy dla urB transferu USB OUT.
static VOID
IoEvtSampleOutUrb(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode
)
{
PENDPOINTQUEUE_CONTEXT pEpQContext;
NTSTATUS status = STATUS_SUCCESS;
PUCHAR transferBuffer;
ULONG transferBufferLength = 0;
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);
// one possible way to get context info
pEpQContext = GetEndpointQueueContext(Queue);
if (IoControlCode != IOCTL_INTERNAL_USB_SUBMIT_URB)
{
LogError(TRACE_DEVICE, "WdfRequest %p Incorrect IOCTL %x, %!STATUS!",
Request, IoControlCode, status);
status = STATUS_INVALID_PARAMETER;
goto exit;
}
status = UdecxUrbRetrieveBuffer(Request, &transferBuffer, &transferBufferLength);
if (!NT_SUCCESS(status))
{
LogError(TRACE_DEVICE, "WdfRequest %p unable to retrieve buffer %!STATUS!",
Request, status);
goto exit;
}
if (transferBufferLength >= 1)
{
//consume one byte of output data
pEpQContext->global_storage = transferBuffer[0];
}
exit:
// writes never pended, always completed
UdecxUrbSetBytesCompleted(Request, transferBufferLength);
UdecxUrbCompleteWithNtStatus(Request, status);
return;
}
Sterownik klienta może ukończyć żądanie we/wy w osobnym przypadku z DPC. Postępuj zgodnie z najlepszymi rozwiązaniami:
- Aby zapewnić zgodność z istniejącymi sterownikami USB, klient UDE musi wywołać element WdfRequestComplete pod adresem DISPATCH_LEVEL.
- Jeśli URB został dodany do kolejki punktu końcowego, a sterownik zacznie przetwarzać go synchronicznie w wątku sterownika wywołującego lub DPC, żądanie nie może być zakończone synchronicznie. Do tego celu jest wymagany oddzielny DPC, który jest kolejowany przez sterownik przez wywołanie WdfDpcEnqueue.
- Gdy rozszerzenie klasy UDE wywołuje EvtIoCanceledOnQueue lub EvtRequestCancel, sterownik klienta musi ukończyć odebrany URB w osobnym DPC niż wątek lub DPC obiektu wywołującego. Aby to zrobić, sterownik musi zapewnić wywołanie zwrotne EvtIoCanceledOnQueue do obsługi kolejek URB.