Поделиться через


Написание драйвера клиента UDE

В этой статье описывается поведение расширения класса эмуляции USB-устройства (UDE) и задач, которые драйвер клиента должен выполнять для эмулированного контроллера узла и устройств, подключенных к нему. В ней содержатся сведения о том, как расширение драйвера классов и класса взаимодействуют с каждым из них с помощью набора подпрограмм и функций обратного вызова. В нем также описываются функции, которые, как ожидается, будет реализован драйвер клиента.

Итоги

  • Объекты и дескриптор UDE, используемые расширением класса и драйвером клиента.
  • Создание эмулированного контроллера узла с функциями для запроса возможностей контроллера и сброса контроллера.
  • Создание виртуального USB-устройства, настройка его для управления питанием и передачи данных через конечные точки.

Важные API

Подготовка к работе

  • Установите последний комплект драйверов Windows (WDK) на компьютере разработки. В комплекте есть необходимые файлы заголовков и библиотеки для написания клиентского драйвера UDE, в частности, вам потребуется:
    • Библиотека заглушки (Udecxstub.lib). Библиотека преобразует вызовы, выполненные драйвером клиента, и передает их в UdeCx.
    • Файл заголовка Udecx.h.
  • Установите Windows 10 на целевом компьютере.
  • Ознакомьтесь с UDE. См . архитектуру: эмуляция USB-устройства (UDE).
  • Ознакомьтесь с Windows Driver Foundation (WDF). Рекомендуемое чтение: разработка драйверов с помощью Windows Driver Foundation, написанная Пенни Орвик и Гай Смит.

Объекты и дескриптор UDE

Расширение класса UDE и драйвер клиента используют определенные объекты WDF, представляющие эмулированный контроллер узла и виртуальное устройство, включая конечные точки и URI- объекты, используемые для передачи данных между устройством и узлом. Драйвер клиента запрашивает создание объектов и время существования объекта управляется расширением класса.

  • Эмулированный объект контроллера узла (WDFDEVICE)

    Представляет эмулированный контроллер узла и является основным дескриптором между расширением класса UDE и драйвером клиента.

  • Объект устройства UDE (UDECXUSBDEVICE)

    Представляет виртуальное USB-устройство, подключенное к порту на эмулированном контроллере узла.

  • Объект конечной точки UDE (UDECXUSBENDPOINT)

    Представляет последовательные каналы данных USB-устройств. Используется для получения запросов программного обеспечения для отправки или получения данных в конечной точке.

Инициализация эмулированного контроллера узла

Ниже приведена сводка последовательности, в которой драйвер клиента получает дескриптор WDFDEVICE для эмулированного контроллера узла. Мы рекомендуем драйверу выполнять эти задачи в функции обратного вызова EvtDriverDeviceAdd.

  1. Вызовите UdecxInitializeWdfDeviceInit , передав ссылку на WDFDEVICE_INIT , переданную платформой.

  2. Инициализировать структуру WDFDEVICE_INIT с информацией о настройке, чтобы это устройство отображалось аналогично другим контроллерам USB-узла. Например, назначьте имя FDO и символьную ссылку, зарегистрируйте интерфейс устройства с помощью предоставленного корпорацией Майкрософт GUID_DEVINTERFACE_USB_HOST_CONTROLLER GUID в качестве GUID интерфейса устройства, чтобы приложения могли открывать дескриптор на устройстве.

  3. Вызовите WdfDeviceCreate , чтобы создать объект устройства платформы.

  4. Вызовите UdecxWdfDeviceAddUsbDeviceEmulation и зарегистрируйте функции обратного вызова драйвера клиента.

    Ниже приведены функции обратного вызова, связанные с объектом контроллера узла, которые вызываются расширением класса UDE. Эти функции должны быть реализованы драйвером клиента.

    • EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY

      Определяет возможности, поддерживаемые контроллером узла, который драйвер клиента должен сообщать расширению класса.

    • EVT_UDECX_WDF_DEVICE_RESET

      Необязательно. Сбрасывает контроллер узла и (или) подключенные устройства.

    
    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.
    
    

Обработка запросов IOCTL в режиме пользователя, отправленных контроллеру узла

Во время инициализации драйвер клиента UDE предоставляет guid интерфейса устройства GUID_DEVINTERFACE_USB_HOST_CONTROLLER. Это позволяет драйверу получать запросы IOCTL от приложения, которое открывает дескриптор устройства с помощью этого GUID. Список кодов элементов управления IOCTL см. в разделе USB IOCTLs с GUID интерфейса устройства: GUID_DEVINTERFACE_USB_HOST_CONTROLLER.

Для обработки этих запросов драйвер клиента регистрирует обратный вызов события EvtIoDeviceControl. В реализации вместо обработки запроса драйвер может перенаправить запрос в расширение класса UDE для обработки. Чтобы перенаправить запрос, драйвер должен вызвать UdecxWdfDeviceTryHandleUserIoctl. Если полученный код элемента управления IOCTL соответствует стандартному запросу, например получение дескрипторов устройства, процесс расширения класса и успешное завершение запроса. В этом случае UdecxWdfDeviceTryHandleUserIoctl завершается значением TRUE в качестве возвращаемого значения. В противном случае вызов возвращает значение FALSE, и драйвер должен определить, как завершить запрос. В самой простой реализации драйвер может завершить запрос с соответствующим кодом сбоя, вызвав 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;
}

Сообщите о возможностях контроллера узла

Прежде чем драйверы верхнего слоя могут использовать возможности контроллера USB-узла, драйверы должны определить, поддерживаются ли эти возможности контроллером. Драйверы делают такие запросы путем вызова WdfUsbTargetDeviceQueryUsbCapability и USBD_QueryUsbCapability. Эти вызовы перенаправляются в расширение класса эмуляции (UDE) USB-устройства. После получения запроса расширение класса вызывает реализацию EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY драйвера клиента. Этот вызов выполняется только после завершения EvtDriverDeviceAdd, как правило, в EvtDevicePrepareHardware, а не после EvtDeviceReleaseHardware. Это требуется функция обратного вызова.

В реализации драйвер клиента должен сообщить, поддерживает ли он запрошенную возможность. Некоторые возможности не поддерживаются UDE, например статическими потоками.

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;
}

Создание виртуального USB-устройства

Виртуальное USB-устройство работает аналогично USB-устройству. Она поддерживает конфигурацию с несколькими интерфейсами, и каждый интерфейс поддерживает альтернативные параметры. Каждый параметр может иметь еще одну конечную точку, используемую для передачи данных. Все дескрипторы (устройство, конфигурация, интерфейс, конечная точка) задаются драйвером клиента UDE, чтобы устройство могли сообщать информацию, как реальное USB-устройство.

Примечание.

Драйвер клиента UDE не поддерживает внешние центры

Ниже приведена сводка последовательности, в которой драйвер клиента создает дескриптор UDECXUSBDEVICE для объекта устройства UDE. Драйвер должен выполнить эти действия после получения дескриптора WDFDEVICE для эмулированного контроллера узла. Мы рекомендуем драйверу выполнять эти задачи в функции обратного вызова EvtDriverDeviceAdd.

  1. Вызовите UdecxUsbDeviceInitAllocate , чтобы получить указатель на параметры инициализации, необходимые для создания устройства. Эта структура выделяется расширением класса UDE.

  2. Зарегистрируйте функции обратного вызова событий, задав элементы UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS, а затем вызовите UdecxUsbDeviceInitSetStateChangeCallbacks. Ниже приведены функции обратного вызова, связанные с объектом устройства UDE, который вызывается расширением класса UDE.

    Эти функции реализуются драйвером клиента для создания или настройки конечных точек.

  3. Вызовите UdecxUsbDeviceInitSetSpeed , чтобы установить скорость USB-устройства, а также тип устройства, USB 2.0 или superSpeed.

  4. Вызовите UdecxUsbDeviceInitSetEndpointsType , чтобы указать тип конечных точек, поддерживаемых устройством: простое или динамическое. Если драйвер клиента выбирает создание простых конечных точек, драйвер должен создать все объекты конечных точек перед подключением к устройству. Устройство должно иметь только одну конфигурацию и только один параметр интерфейса для каждого интерфейса. В случае динамических конечных точек драйвер может создавать конечные точки в любое время после подключения устройства при получении обратного вызова события EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE . См. статью "Создание динамических конечных точек".

  5. Вызовите любой из этих методов, чтобы добавить необходимые дескрипторы на устройство.

    • UdecxUsbDeviceInitAddDescriptor

    • UdecxUsbDeviceInitAddDescriptorWithIndex

    • UdecxUsbDeviceInitAddStringDescriptor

    • UdecxUsbDeviceInitAddStringDescriptorRaw

      Если расширение класса UDE получает запрос на стандартный дескриптор, предоставленный драйвером клиента во время инициализации с помощью одного из предыдущих методов, расширение класса автоматически завершает запрос. Расширение класса не пересылает этот запрос драйверу клиента. Эта конструкция уменьшает количество запросов, необходимых драйверу для обработки запросов управления. Кроме того, это также устраняет необходимость реализации логики дескриптора драйвера, требующей обширного анализа пакета установки и правильной обработки wLength и TransferBufferLength. Этот список включает стандартные запросы. Драйвер клиента не должен проверка для этих запросов (только если предыдущие методы были вызваны для добавления дескриптора):

    • USB_REQUEST_GET_DESCRIPTOR

    • USB_REQUEST_SET_CONFIGURATION

    • USB_REQUEST_SET_INTERFACE

    • USB_REQUEST_SET_ADDRESS

    • USB_REQUEST_SET_FEATURE

    • USB_FEATURE_FUNCTION_SUSPEND

    • USB_FEATURE_REMOTE_WAKEUP

    • USB_REQUEST_CLEAR_FEATURE

    • USB_FEATURE_ENDPOINT_STALL

    • USB_REQUEST_SET_SEL

    • USB_REQUEST_ISOCH_DELAY

      Однако запросы к дескриптору интерфейса, определенного класса или определяемого поставщиком дескриптора, расширение класса UDE перенаправит их в драйвер клиента. Драйвер должен обрабатывать эти GET_DESCRIPTOR запросы.

  6. Вызовите UdecxUsbDeviceCreate , чтобы создать объект устройства UDE и получить дескриптор UDECXUSBDEVICE.

  7. Создайте статические конечные точки, вызвав UdecxUsbEndpointCreate. См. статью "Создание простых конечных точек".

  8. Вызовите UdecxUsbDevicePlugIn , чтобы указать расширение класса UDE, которое подключено устройство и может получать запросы ввода-вывода на конечных точках. После этого вызова расширение класса также может вызывать функции обратного вызова на конечных точках и USB-устройстве. Обратите внимание , что при удалении USB-устройства во время выполнения драйвер клиента может вызвать UdecxUsbDevicePlugOutAndDelete. Если драйвер хочет использовать устройство, он должен создать его, вызвав UdecxUsbDeviceCreate.

В этом примере объявления дескриптора считаются глобальными переменными, объявленными здесь для устройства HID, как показано в примере:

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
};

Ниже приведен пример, в котором драйвер клиента задает параметры инициализации, регистрируя функции обратного вызова, устанавливая скорость устройства, указывая тип конечных точек и, наконец, устанавливая некоторые дескрипторы устройств.


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;
}

Управление питанием USB-устройства

Расширение класса UDE вызывает функции обратного вызова драйвера клиента при получении запроса на отправку устройства в состояние низкой мощности или возвратить его в рабочее состояние. Эти функции обратного вызова необходимы для USB-устройств, поддерживающих пробуждение. Драйвер клиента зарегистрировал свою реализацию в предыдущем вызове UdecxUsbDeviceInitSetStateChangeCallbacks.

Дополнительные сведения см. в разделе "Состояния питания USB-устройства".

  • EVT_UDECX_USB_DEVICE_D0_ENTRY: драйвер клиента перемещает устройство из состояния Dx в состояние D0.

  • EVT_UDECX_USB_DEVICE_D0_EXIT. Драйвер клиента перемещает устройство из состояния D0 в состояние Dx.

  • EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE. Драйвер клиента изменяет состояние функции указанного интерфейса виртуального устройства USB 3.0.

Устройство USB 3.0 позволяет отдельным функциям ввести меньшее состояние питания. Каждая функция также может отправлять сигнал пробуждения. Расширение класса UDE уведомляет драйвер клиента, вызвав EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE. Это событие указывает на изменение состояния питания функции и сообщает драйверу клиента о том, может ли функция проснуться из нового состояния. В функции расширение класса передает номер интерфейса функции, которая просыпается.

Клиентский драйвер может имитировать действие виртуального USB-устройства, инициирующего собственное пробуждение от состояния низкой мощности, приостановки функции или обоих. Для устройства USB 2.0 драйвер должен вызвать UdecxUsbDeviceSignalWake, если драйвер включил пробуждение на устройстве в последней EVT_UDECX_USB_DEVICE_D0_EXIT. Для устройства USB 3.0 драйвер должен вызвать UdecxUsbDeviceSignalFunctionWake , так как функция пробуждения USB 3.0 является функцией. Если все устройство находится в состоянии низкой мощности или входит в такое состояние, UdecxUsbDeviceSignalFunctionWake проснет устройство.

Создание простых конечных точек

Драйвер клиента создает объекты конечной точки UDE для обработки передачи данных на USB-устройство и из нее. Драйвер создает простые конечные точки после создания устройства UDE и перед тем, как сообщить об устройстве как подключенном.

Ниже приведена сводка последовательности, в которой драйвер клиента создает дескриптор UDECXUSBENDPOINT для объекта конечной точки UDE. Драйвер должен выполнить эти действия после получения дескриптора UDECXUSBDEVICE для виртуального USB-устройства. Мы рекомендуем драйверу выполнять эти задачи в функции обратного вызова EvtDriverDeviceAdd.

  1. Вызовите UdecxUsbSimpleEndpointInitAllocate , чтобы получить указатель на параметры инициализации, выделенные расширением класса.

  2. Вызовите UdecxUsbEndpointInitSetEndpointAddress , чтобы задать адрес конечной точки в параметрах инициализации.

  3. Вызовите UdecxUsbEndpointInitSetCallbacks , чтобы зарегистрировать функции обратного вызова, реализованные драйвером клиента.

    Эти функции реализуются драйвером клиента для обработки очередей и запросов в конечной точке.

    • EVT_UDECX_USB_ENDPOINT_RESET: сбрасывает конечную точку виртуального USB-устройства.

    • EVT_UDECX_USB_ENDPOINT_START: необязательно. Начинает обработку запросов ввода-вывода

    • EVT_UDECX_USB_ENDPOINT_PURGE: необязательно. Остановите очереди запросов ввода-вывода в очередь конечной точки и отмените необработанные запросы.

  4. Вызовите UdecxUsbEndpointCreate , чтобы создать объект конечной точки и получить дескриптор UDECXUSBENDPOINT.

  5. Вызовите UdecxUsbEndpointSetWdfIoQueue , чтобы связать объект очереди платформы с конечной точкой. Если применимо, объект конечной точки может быть родительским объектом WDF очереди, задав соответствующие атрибуты.

    Каждый объект конечной точки имеет объект очереди платформы для обработки запросов на передачу. Для каждого запроса на передачу, который получает расширение класса, он помещает объект запроса платформы. Состояние очереди (запущено, очистка) управляется расширением класса UDE, а драйвер клиента не должен изменять это состояние. Каждый объект запроса содержит блок USB-запроса (URB), содержащий сведения о передаче.

В этом примере драйвер клиента создает конечную точку управления по умолчанию.

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;
}

Создание динамических конечных точек

Драйвер клиента может создавать динамические конечные точки по запросу расширения класса UDE (от имени драйвера концентратора и драйверов клиента). Расширение класса делает запрос путем вызова любой из этих функций обратного вызова:

* EVT_UDECX_USB_DEVICE_DEFAULT_ENDPOINT_ADD Драйвер клиента создает конечную точку управления по умолчанию (конечная точка 0)

* EVT_UDECX_USB_DEVICE_ENDPOINT_ADD драйвер клиента создает динамическую конечную точку.

* EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE Драйвер клиента изменяет конфигурацию, выбрав альтернативный параметр, отключив текущие конечные точки или добавив динамические конечные точки.

Драйвер клиента зарегистрировал предыдущий обратный вызов во время вызова UdecxUsbDeviceInitSetStateChangeCallbacks. См. статью "Создание виртуального USB-устройства". Этот механизм позволяет драйверу клиента динамически изменять параметры конфигурации и интерфейса USB на устройстве. Например, если требуется объект конечной точки или должен быть освобожден существующий объект конечной точки, расширение класса вызывает EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE.

Ниже приведена сводка последовательности, в которой драйвер клиента создает дескриптор UDECXUSBENDPOINT для объекта конечной точки в реализации функции обратного вызова.

  1. Вызовите UdecxUsbEndpointInitSetEndpointAddress , чтобы задать адрес конечной точки в параметрах инициализации.

  2. Вызовите UdecxUsbEndpointInitSetCallbacks , чтобы зарегистрировать функции обратного вызова, реализованные драйвером клиента. Как и в простых конечных точках, драйвер может зарегистрировать эти функции обратного вызова:

  3. Вызовите UdecxUsbEndpointCreate , чтобы создать объект конечной точки и получить дескриптор UDECXUSBENDPOINT.

  4. Вызовите UdecxUsbEndpointSetWdfIoQueue , чтобы связать объект очереди платформы с конечной точкой.

В этом примере реализации драйвер клиента создает динамическую конечную точку управления по умолчанию.

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;
}

Выполнение восстановления ошибок путем сброса конечной точки

Иногда передача данных может завершиться сбоем из-за различных причин, таких как состояние остановки в конечной точке. В случае неудачной передачи конечная точка не может обрабатывать запросы до очистки условия ошибки. При сбое передачи данных расширения класса UDE вызывает функцию обратного вызова EVT_UDECX_USB_ENDPOINT_RESET драйвера клиента, которая зарегистрирована в предыдущем вызове UdecxUsbEndpointInitSetCallbacks. В реализации драйвер может очистить состояние канала HALT и выполнить другие необходимые действия для очистки условия ошибки.

Этот вызов является асинхронным. После завершения операции сброса драйвер должен завершить запрос с соответствующим кодом сбоя, вызвав WdfRequestComplete. Этот вызов уведомляет расширение клиента UDE о завершении операции сброса с состоянием.

Примечание. Если сложное решение требуется для восстановления ошибок, драйвер клиента имеет возможность сброса контроллера узла. Эту логику можно реализовать в функции обратного вызова EVT_UDECX_WDF_DEVICE_RESET, зарегистрированной драйвером в вызове UdecxWdfDeviceAddUsbDeviceEmulation. Если применимо, драйвер может сбросить контроллер узла и все подчиненные устройства. Если драйвер клиента не должен сбрасывать контроллер, но сбрасывать все подчиненные устройства, драйвер должен указать UdeWdfDeviceResetActionResetEachUsbDevice в параметрах конфигурации во время регистрации. В этом случае расширение класса вызывает EVT_UDECX_WDF_DEVICE_RESET для каждого подключенного устройства.

Реализация управления состоянием очереди

Состояние объекта очереди платформы, связанного с объектом конечной точки UDE, управляется расширением класса UDE. Однако если драйвер клиента перенаправит запросы из очередей конечных точек в другие внутренние очереди, клиент должен реализовать логику для обработки изменений в потоке ввода-вывода конечной точки. Эти функции обратного вызова регистрируются в UdecxUsbEndpointInitSetCallbacks.

Операция очистки конечной точки

Драйвер клиента UDE с одной очередью на конечную точку может реализовать EVT_UDECX_USB_ENDPOINT_PURGE , как показано в этом примере:

В реализации EVT_UDECX_USB_ENDPOINT_PURGE драйвер клиента необходим, чтобы убедиться, что все операции ввода-вывода, переадресованные из очереди конечной точки, завершены, и что недавно переадресованный ввод-вывод также завершается сбоем до вызова EVT_UDECX_USB_ENDPOINT_START драйвера клиента. Эти требования выполняются путем вызова UdecxUsbEndpointPurgeComplete, что гарантирует, что все переадресованные операции ввода-вывода завершены и будущие перенаправленные операции ввода-вывода завершаются сбоем.

Операция запуска конечной точки

В реализации EVT_UDECX_USB_ENDPOINT_START драйвер клиента должен начать обработку операций ввода-вывода в очереди конечной точки и в любых очередях, которые получают перенаправленные операции ввода-вывода для конечной точки. После создания конечной точки он не получает никаких операций ввода-вывода до тех пор, пока эта функция обратного вызова не возвращается. Этот обратный вызов возвращает конечную точку в состояние обработки ввода-вывода после завершения EVT_UDECX_USB_ENDPOINT_PURGE.

Обработка запросов на передачу данных (URI)

Чтобы обработать запросы USB-ввода-вывода, отправленные конечным точкам клиентского устройства, перехватите обратный вызов EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL в объекте очереди, используемом с UdecxUsbEndpointInitSetCallbacks при связывании очереди с конечной точкой. В этом обратном вызове обработайте операции ввода-вывода для IOCTL_INTERNAL_USB_SUBMIT_URB IoControlCode (см. пример кода в методах обработки URB).

Методы обработки URB

В рамках обработки URI через IOCTL_INTERNAL_USB_SUBMIT_URB очереди, связанной с конечной точкой на виртуальном устройстве, драйвер клиента UDE может получить указатель на буфер передачи запроса ввода-вывода с помощью следующих методов:

Эти функции реализуются драйвером клиента для обработки очередей и запросов в конечной точке.

UdecxUrbRetrieveControlSetupPacket извлекает пакет установки USB-элемента управления из указанного объекта запроса платформы.

UdecxUrbRetrieveBuffer извлекает буфер передачи URB из указанного объекта запроса платформы, отправленного в очередь конечной точки.

UdecxUrbSetBytesCompleted Задает количество байтов, передаваемых для URB, содержащегося в объекте запроса платформы.

UdecxUrbComplete завершает запрос URB с кодом состояния завершения для конкретного USB.

UdecxUrbCompleteWithNtStatus завершает запрос URB с кодом NTSTATUS.

Ниже приведен поток типичной обработки ввода-вывода для URB передачи 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;
}

Драйвер клиента может завершить запрос ввода-вывода отдельно с помощью DPC. Следуйте приведенным ниже рекомендациям.

  • Чтобы обеспечить совместимость с существующими USB-драйверами, клиент UDE должен вызвать WdfRequestComplete по DISPATCH_LEVEL.
  • Если URB был добавлен в очередь конечной точки и драйвер начинает синхронно обрабатывать его в потоке вызывающего драйвера или DPC, запрос не должен быть синхронно завершен. Для этой цели требуется отдельный DPC, для которого очередь драйверов вызывает WdfDpcEnqueue.
  • Когда расширение класса UDE вызывает EvtIoCanceledOnQueue или EvtRequestCancel, драйвер клиента должен завершить полученный URB на отдельном DPC от потока вызывающего или DPC. Для этого драйвер должен предоставить обратный вызов EvtIoCanceledOnQueue для очередей URB.