Udostępnij przez


Tworzenie sterownika klienta UDE

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.

  1. Wywołaj metodę UdecxInitializeWdfDeviceInit, przekazując odwołanie do WDFDEVICE_INIT przekazanego przez platformę.

  2. 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.

  3. Wywołaj WdfDeviceCreate, aby utworzyć obiekt urządzenia ramy.

  4. 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_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.

  1. 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.

  2. 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.

  3. Wywołaj metodę UdecxUsbDeviceInitSetSpeed , aby ustawić szybkość urządzenia USB, a także typ urządzenia, USB 2.0 lub SuperSpeed.

  4. 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.

  5. Wywołaj dowolną z tych metod, aby dodać niezbędne deskryptory do urządzenia.

    • UdecxUsbDeviceInitAddDescriptor

    • UdecxUsbDeviceInitAddDescriptorWithIndex

    • UdecxUsbDeviceInitAddStringDescriptor

    • 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.

  6. Wywołaj metodę UdecxUsbDeviceCreate , aby utworzyć obiekt urządzenia UDE i pobrać uchwyt UDECXUSBDEVICE.

  7. Tworzenie statycznych punktów końcowych przez wywołanie UdecxUsbEndpointCreate. Zobacz Tworzenie prostych punktów końcowych.

  8. 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.

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.

  1. Wywołaj metodę UdecxUsbSimpleEndpointInitAllocate , aby uzyskać wskaźnik do parametrów inicjowania przydzielonych przez rozszerzenie klasy.

  2. Wywołaj metodę UdecxUsbEndpointInitSetEndpointAddress , aby ustawić adres punktu końcowego w parametrach inicjowania.

  3. 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.

  4. Wywołaj UdecxUsbEndpointCreate, aby utworzyć obiekt punktu końcowego i pobrać uchwyt UDECXUSBENDPOINT.

  5. 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.

  1. Wywołaj metodę UdecxUsbEndpointInitSetEndpointAddress , aby ustawić adres punktu końcowego w parametrach inicjowania.

  2. 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:

  3. Wywołaj UdecxUsbEndpointCreate aby utworzyć obiekt punktu końcowego i pobrać uchwyt UDECXUSBENDPOINT.

  4. 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.