Compartir a través de


Escritura de un controlador de cliente UDE

En este artículo se describe el comportamiento de la extensión de clase y las tareas de emulación de dispositivo USB (UDE) que un controlador cliente debe realizar para un controlador host emulado y los dispositivos conectados a él. Proporciona información sobre cómo el controlador de clase y la extensión de clase se comunican entre sí a través de un conjunto de rutinas y funciones de devolución de llamada. También describe las características que se espera que implemente el controlador cliente.

Resumen

  • Objetos UDE e identificadores usados por la extensión de clase y el controlador cliente.
  • Creación de un controlador de host emulado con funciones para consultar las funcionalidades del controlador y restablecer el controlador.
  • Creación de un dispositivo USB virtual, configurarlo para la administración de energía y las transferencias de datos a través de puntos de conexión.

API importantes

Antes de empezar

  • Instale el Kit para controladores de Windows (WDK) más reciente en el equipo de desarrollo. El kit tiene los archivos y bibliotecas de encabezado necesarios para escribir un controlador cliente UDE, en concreto, necesitará:
    • La biblioteca de código auxiliar (Udecxstub.lib). La biblioteca traduce las llamadas realizadas por el controlador cliente y las pasa a UdeCx.
    • El archivo de encabezado, Udecx.h.
  • Instale Windows 10 en su computadora de destino.
  • Familiarícese con la UDE. Consulte Arquitectura: Emulación de dispositivo USB (UDE).
  • Familiarícese con Windows Driver Foundation (WDF). Lectura recomendada: Developing Drivers with Windows Driver Foundation, de Penny Orwick y Guy Smith.

Objetos e identificadores de UDE

La extensión de clase UDE y el controlador de cliente usan objetos WDF concretos que representan el controlador de host emulado y el dispositivo virtual, incluidos sus puntos de conexión y direcciones URL que se usan para transferir datos entre el dispositivo y el host. El controlador cliente solicita la creación de los objetos y la duración del objeto se administra mediante la extensión de clase.

  • Objeto de controlador de host emulado (WDFDEVICE)

    Representa el controlador host emulado y es el identificador principal entre la extensión de clase UDE y el controlador cliente.

  • Objeto de dispositivo UDE (UDECXUSBDEVICE)

    Representa un dispositivo USB virtual que está conectado a un puerto en el controlador de host emulado.

  • Objeto de punto de conexión UDE (UDECXUSBENDPOINT)

    Representa canalizaciones de datos secuenciales de dispositivos USB. Se usa para recibir solicitudes de software para enviar o recibir datos en un punto de conexión.

Inicialización del controlador de host emulado

Este es el resumen de la secuencia en la que el controlador cliente recupera un identificador WDFDEVICE para el controlador host emulado. Se recomienda que el controlador realice estas tareas en su función de devolución de llamada EvtDriverDeviceAdd.

  1. Llamar a UdecxInitializeWdfDeviceInit pasando la referencia a WDFDEVICE_INIT que ha pasado el marco.

  2. Inicializar la estructura WDFDEVICE_INIT con información de configuración de modo que este dispositivo parezca similar a otros controladores de host USB. Por ejemplo, asignar un nombre de FDO y un vínculo simbólico, registrar una interfaz de dispositivo con el GUID de GUID_DEVINTERFACE_USB_HOST_CONTROLLER proporcionado por Microsoft como GUID de interfaz de dispositivo para que las aplicaciones puedan abrir un identificador para el dispositivo.

  3. Llamar a WdfDeviceCreate para crear el objeto de dispositivo de marco.

  4. Llamar a UdecxWdfDeviceAddUsbDeviceEmulation y registrar las funciones de devolución de llamada del controlador cliente.

    Estas son las funciones de devolución de llamada asociadas al objeto de controlador de host que se invocan mediante la extensión de clase UDE. El controlador cliente debe implementar estas funciones.

    
    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.
    
    

Control de las solicitudes IOCTL en modo de usuario enviadas al controlador de host

Durante la inicialización, el controlador cliente UDE expone el GUID de interfaz de dispositivo GUID_DEVINTERFACE_USB_HOST_CONTROLLER. Esto permite al controlador recibir solicitudes IOCTL de una aplicación que abre un identificador de dispositivo mediante ese GUID. Para obtener una lista de códigos de control IOCTL, consulte IOCTL USB con el GUI de interfaz de dispositivo: GUID_DEVINTERFACE_USB_HOST_CONTROLLER.

Para controlar esas solicitudes, el controlador cliente registra la devolución de llamada de eventos EvtIoDeviceControl. En la implementación, en lugar de controlar la solicitud, el controlador puede optar por reenviar la solicitud a la extensión de clase UDE para su procesamiento. Para reenviar la solicitud, el controlador debe llamar a UdecxWdfDeviceTryHandleUserIoctl. Si el código de control IOCTL recibido corresponde a una solicitud estándar, como recuperar descriptores de dispositivo, la extensión de clase procesa y completa correctamente la solicitud. En este caso, UdecxWdfDeviceTryHandleUserIoctl se completa con TRUE como valor devuelto. De lo contrario, la llamada devuelve FALSE y el controlador debe determinar cómo completar la solicitud. En una implementación más sencilla, el controlador puede completar la solicitud con un código de error adecuado llamando a 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;
}

Notificación de las funcionalidades del controlador de host

Antes de que los controladores de nivel superior puedan usar las funcionalidades de un controlador host USB, los controladores deben determinar si esas funcionalidades son compatibles con el controlador. Los controladores realizan estas consultas llamando a WdfUsbTargetDeviceQueryUsbCapability y USBD_QueryUsbCapability. Esas llamadas se reenvían a la extensión de clase de emulación de dispositivo USB (UDE). Al obtener la solicitud, la extensión de clase invoca la implementación de EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY del controlador cliente. Esta llamada solo se realiza después de que EvtDriverDeviceAdd finalice, normalmente en EvtDevicePrepareHardware y no después de EvtDeviceReleaseHardware. Esta función de devolución de llamada es necesaria.

En la implementación, el controlador cliente debe notificar si admite la funcionalidad solicitada. Algunas funcionalidades no son compatibles con UDE, como secuencias estáticas.

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

Creación de un dispositivo USB virtual

Un dispositivo USB virtual se comporta de forma similar a un dispositivo USB. Admite una configuración con varias interfaces y cada interfaz admite opciones alternativas. Cada configuración puede tener uno más puntos de conexión que se usan para las transferencias de datos. Todos los descriptores (dispositivo, configuración, interfaz, punto de conexión) se establecen mediante el controlador cliente UDE para que el dispositivo pueda notificar información de forma muy similar a un dispositivo USB real.

Nota:

El controlador cliente UDE no admite centros externos

Este es el resumen de la secuencia en la que el controlador cliente crea un identificador UDECXUSBDEVICE para un objeto de dispositivo UDE. El controlador debe realizar estos pasos después de recuperar el identificador WDFDEVICE para el controlador host emulado. Se recomienda que el controlador realice estas tareas en su función de devolución de llamada EvtDriverDeviceAdd.

  1. Llamar a UdecxUsbDeviceInitAllocate para obtener un puntero a los parámetros de inicialización necesarios para crear el dispositivo. Esta estructura se asigna mediante la extensión de clase UDE.

  2. Registrar las funciones de devolución de llamada de eventos estableciendo miembros de UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS y llamando después a UdecxUsbDeviceInitSetStateChangeCallbacks. Estas son las funciones de devolución de llamada asociadas al objeto de dispositivo UDE, que la extensión de clase UDE invoca.

    El controlador cliente implementa estas funciones para crear o configurar puntos de conexión.

  3. Llamar a UdecxUsbDeviceInitSetSpeed para establecer la velocidad del dispositivo USB y también el tipo de dispositivo, USB 2.0 o un dispositivo SuperSpeed.

  4. Llamar a UdecxUsbDeviceInitSetEndpointsType para especificar el tipo de puntos de conexión que admite el dispositivo: simple o dinámico. Si el controlador cliente decide crear puntos de conexión simples, el controlador debe crear todos los objetos de punto de conexión antes de conectar el dispositivo. El dispositivo debe tener solo una configuración y solo una configuración de interfaz por interfaz. En el caso de los puntos de conexión dinámicos, el controlador puede crear puntos de conexión en cualquier momento después de conectar el dispositivo cuando recibe una devolución de llamada de evento EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE. Consulte Creación de puntos de conexión dinámicos.

  5. Llamar a cualquiera de estos métodos para agregar descriptores necesarios al dispositivo.

    • UdecxUsbDeviceInitAddDescriptor

    • UdecxUsbDeviceInitAddDescriptorWithIndex

    • UdecxUsbDeviceInitAddStringDescriptor

    • UdecxUsbDeviceInitAddStringDescriptorRaw

      Si la extensión de clase UDE recibe una solicitud de un descriptor estándar que el controlador cliente ha proporcionado durante la inicialización mediante uno de los métodos anteriores, la extensión de clase completa automáticamente la solicitud. La extensión de clase no reenvía esa solicitud al controlador cliente. Este diseño reduce el número de solicitudes que el controlador necesita procesar para las solicitudes de control. Además, también elimina la necesidad de que el controlador implemente la lógica del descriptor que requiere un análisis exhaustivo del paquete de instalación y control de wLength y TransferBufferLength correctamente. Esta lista incluye las solicitudes estándar. El controlador cliente no necesita comprobar estas solicitudes (solo si se llamó a los métodos anteriores para agregar descriptor):

    • 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

      Sin embargo, para las solicitudes del descriptor de interfaz, específico de clase o definido por el proveedor, la extensión de clase UDE las reenvía al controlador cliente. El controlador debe controlar esas solicitudes GET_DESCRIPTOR.

  6. Llamar a UdecxUsbDeviceCreate para crear el objeto de dispositivo UDE y recuperar el identificador UDECXUSBDEVICE.

  7. Crear puntos de conexión estáticos llamando a UdecxUsbEndpointCreate. Consulte Creación de puntos de conexión simples.

  8. Llamar a UdecxUsbDevicePlugIn para indicar a la extensión de clase UDE que el dispositivo está conectado y puede recibir solicitudes de E/S en puntos de conexión. Después de esta llamada, la extensión de clase también puede invocar funciones de devolución de llamada en puntos de conexión y el dispositivo USB. Nota Si el dispositivo USB debe quitarse en tiempo de ejecución, el controlador cliente puede llamar a UdecxUsbDevicePlugOutAndDelete. Si el controlador quiere usar el dispositivo, debe crearlo llamando a UdecxUsbDeviceCreate.

En este ejemplo, se supone que las declaraciones de descriptores son variables globales, declaradas como se muestra aquí para un dispositivo HID como ejemplo:

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

Este es un ejemplo en el que el controlador cliente especifica parámetros de inicialización registrando funciones de devolución de llamada, estableciendo la velocidad del dispositivo, indicando el tipo de puntos de conexión y, por último, estableciendo algunos descriptores de dispositivo.


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

Administración de energía del dispositivo USB

La extensión de clase UDE invoca las funciones de devolución de llamada del controlador cliente cuando recibe una solicitud para enviar el dispositivo a un estado de bajo consumo o devolverlo al estado de trabajo. Estas funciones de devolución de llamada son necesarias para dispositivos USB que admiten reactivación. El controlador cliente registró su implementación en la llamada anterior a UdecxUsbDeviceInitSetStateChangeCallbacks.

Para obtener más información, consulte Estados de energía del dispositivo USB.

Un dispositivo USB 3.0 permite que las funciones individuales entren en estado de energía inferior. Cada función también es capaz de enviar una señal de reactivación. La extensión de clase UDE notifica al controlador cliente invocando EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE. Este evento indica un cambio de estado de energía de función e informa al controlador cliente de si la función puede reactivarse desde el nuevo estado. En la función, la extensión de clase pasa el número de interfaz de la función que se está reactivando.

El controlador cliente puede simular la acción de un dispositivo USB virtual que inicia su propia reactivación desde un estado de baja energía de enlace, suspensión de funciones, o ambos. Para un dispositivo USB 2.0, el controlador debe llamar a UdecxUsbDeviceSignalWake, si el controlador habilitado se reactiva en el dispositivo en el EVT_UDECX_USB_DEVICE_D0_EXIT más reciente. Para un dispositivo USB 3.0, el controlador debe llamar a UdecxUsbDeviceSignalFunctionWake porque la característica de reactivación USB 3.0 es por función. Si todo el dispositivo está en un estado de baja energía o entra en este estado, UdecxUsbDeviceSignalFunctionWake reactiva el dispositivo.

Creación de puntos de conexión simples

El controlador cliente crea objetos de punto de conexión UDE para controlar las transferencias de datos hacia y desde el dispositivo USB. El controlador crea puntos de conexión simples después de crear el dispositivo UDE y antes de notificarlo como conectado.

Este es el resumen de la secuencia en la que el controlador cliente crea un identificador UDECXUSBENDPOINT para un objeto de punto de conexión UDE. El controlador debe realizar estos pasos después de recuperar el identificador UDECXUSBDEVICE para el dispositivo USB virtual. Se recomienda que el controlador realice estas tareas en su función de devolución de llamada EvtDriverDeviceAdd.

  1. Llamar a UdecxUsbSimpleEndpointInitAllocate para obtener un puntero a los parámetros de inicialización asignados por la extensión de clase.

  2. Llamar a UdecxUsbEndpointInitSetEndpointAddress para establecer la dirección del punto de conexión en los parámetros de inicialización.

  3. Llamar a UdecxUsbEndpointInitSetCallbacks para registrar las funciones de devolución de llamada implementadas por el controlador cliente.

    El controlador cliente implementa estas funciones para controlar las colas y las solicitudes en un punto de conexión.

  4. Llamar a UdecxUsbEndpointCreate para crear el objeto de punto de conexión y recuperar el identificador UDECXUSBENDPOINT.

  5. Llamar a UdecxUsbEndpointSetWdfIoQueue para asociar un objeto de cola de marco al punto de conexión. Si procede, puede establecer el objeto de punto de conexión como el objeto primario WDF de la cola estableciendo los atributos adecuados.

    Cada objeto de punto de conexión tiene un objeto de cola de marco para controlar las solicitudes de transferencia. Para cada solicitud de transferencia que recibe la extensión de clase, pone en cola un objeto de solicitud de marco. El estado de la cola (iniciado, purgado) se administra mediante la extensión de clase UDE y el controlador cliente no debe cambiar ese estado. Cada objeto de solicitud contiene un bloque de solicitud USB (URB) que contiene detalles de la transferencia.

En este ejemplo, el controlador cliente crea el punto de conexión de control predeterminado.

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

Creación de puntos de conexión dinámicos

El controlador cliente puede crear puntos de conexión dinámicos a petición de la extensión de clase UDE (en nombre del controlador del concentrador y los controladores de cliente). La extensión de clase realiza la solicitud invocando cualquiera de estas funciones de devolución de llamada:

*EVT_UDECX_USB_DEVICE_DEFAULT_ENDPOINT_ADD El controlador cliente crea el punto de conexión de control predeterminado (punto de conexión 0)

*EVT_UDECX_USB_DEVICE_ENDPOINT_ADD El controlador cliente crea un punto de conexión dinámico.

*EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE El controlador cliente cambia la configuración seleccionando una configuración alternativa, deshabilitando los puntos de conexión actuales o agregando puntos de conexión dinámicos.

El controlador cliente registró la devolución de llamada anterior durante su llamada a UdecxUsbDeviceInitSetStateChangeCallbacks. Consulte Creación de un dispositivo USB virtual. Este mecanismo permite al controlador cliente cambiar dinámicamente la configuración USB y la configuración de la interfaz en el dispositivo. Por ejemplo, cuando se necesita un objeto de punto de conexión o se debe liberar un objeto de punto de conexión existente, la extensión de clase llama a EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE.

Este es el resumen de la secuencia en la que el controlador cliente crea un identificador UDECXUSBENDPOINT para un objeto de punto de conexión en su implementación de la función de devolución de llamada.

  1. Llamar a UdecxUsbEndpointInitSetEndpointAddress para establecer la dirección del punto de conexión en los parámetros de inicialización.

  2. Llamar a UdecxUsbEndpointInitSetCallbacks para registrar las funciones de devolución de llamada implementadas por el controlador cliente. De forma similar a los puntos de conexión simples, el controlador puede registrar estas funciones de devolución de llamada:

  3. Llamar a UdecxUsbEndpointCreate para crear el objeto de punto de conexión y recuperar el identificador UDECXUSBENDPOINT.

  4. Llamar a UdecxUsbEndpointSetWdfIoQueue para asociar un objeto de cola de marco al punto de conexión.

En esta implementación de ejemplo, el controlador cliente crea un punto de conexión de control predeterminado dinámico.

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

Realización de la recuperación de errores mediante el restablecimiento de un punto de conexión

A veces, las transferencias de datos pueden generar un error por varios motivos, como una condición de bloqueo en el punto de conexión. En el caso de las transferencias con errores, el punto de conexión no puede procesar las solicitudes hasta que se elimine la condición de error. Cuando la extensión de clase UDE experimenta transferencias de datos con errores, invoca la función de devolución de llamada EVT_UDECX_USB_ENDPOINT_RESET del controlador cliente, que el controlador registró en la llamada anterior a UdecxUsbEndpointInitSetCallbacks. En la implementación, el controlador puede elegir eliminar el estado HALT de la canalización y realizar otros pasos necesarios para eliminar la condición de error.

Esta llamada es asincrónica. Una vez que el cliente finaliza la operación de restablecimiento, el controlador debe completar la solicitud con un código de error adecuado llamando a WdfRequestComplete. Esa llamada notifica a la extensión de cliente UDE la finalización de la operación de reinicio con estado.

Nota Si se requiere una solución compleja para la recuperación de errores, el controlador cliente tiene la opción de restablecer el controlador de host. Esta lógica se puede implementar en la función de devolución de llamada EVT_UDECX_WDF_DEVICE_RESET que el controlador registró en su llamada UdecxWdfDeviceAddUsbDeviceEmulation. Si procede, el controlador puede restablecer el controlador de host y todos los dispositivos de bajada. Si el controlador cliente no necesita restablecer el controlador, pero sí todos los dispositivos de bajada, debe especificar UdeWdfDeviceResetActionResetEachUsbDevice en los parámetros de configuración durante el registro. En ese caso, la extensión de clase invoca EVT_UDECX_WDF_DEVICE_RESET para cada dispositivo conectado.

Implementación de la administración de estado de cola

La extensión de clase UDE administra el estado del objeto de cola de marco asociado a un objeto de punto de conexión UDE. Sin embargo, si el controlador cliente reenvía las solicitudes de las colas de punto de conexión a otras colas internas, el cliente debe implementar lógica para controlar los cambios en el flujo de E/S del punto de conexión. Estas funciones de devolución de llamada se registran con UdecxUsbEndpointInitSetCallbacks.

Operación de purga de puntos de conexión

Un controlador cliente UDE con una cola por punto de conexión puede implementar EVT_UDECX_USB_ENDPOINT_PURGE como se muestra en este ejemplo:

En la implementación de EVT_UDECX_USB_ENDPOINT_PURGE, se requiere el controlador cliente para asegurarse de que se han completado todas las E/S reenviadas desde la cola del punto de conexión y que la E/S reenviada también genera un error hasta que se invoca EVT_UDECX_USB_ENDPOINT_START del controlador cliente. Estos requisitos se cumplen llamando a UdecxUsbEndpointPurgeComplete, lo que garantiza que se completen todas las E/S reenviadas y se hayan generado errores en las E/S reenviadas futuras.

Operación de inicio del punto de conexión

En la implementación de EVT_UDECX_USB_ENDPOINT_START, es necesario que el controlador cliente comience a procesar la E/S en la cola del punto de conexión y en las colas que reciben E/S reenviadas para el punto de conexión. Después de crear un punto de conexión, no recibe ninguna E/S hasta que se devuelva esta función de devolución de llamada. Esta devolución de llamada devuelve el punto de conexión a un estado de E/S de procesamiento una vez que finaliza EVT_UDECX_USB_ENDPOINT_PURGE.

Control de solicitudes de transferencia de datos (URB)

Para procesar las solicitudes de E/S USB enviadas a los puntos de conexión del dispositivo cliente, intercepte la devolución de llamada EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL en el objeto de cola usado con UdecxUsbEndpointInitSetCallbacks al asociar la cola con el punto de conexión. En esa devolución de llamada, procese la E/S para el IoControlCode IOCTL_INTERNAL_USB_SUBMIT_URB (consulte el código de ejemplo en Métodos de control de URB).

Métodos de control de URB

Como parte del procesamiento de URB a través de IOCTL_INTERNAL_USB_SUBMIT_URB de una cola asociada a un punto de conexión en un dispositivo virtual, un controlador cliente UDE puede obtener un puntero al búfer de transferencia de una solicitud de E/S mediante estos métodos:

El controlador cliente implementa estas funciones para controlar las colas y las solicitudes en un punto de conexión.

UdecxUrbRetrieveControlSetupPacket Recupera un paquete de configuración de control USB de un objeto de solicitud de marco especificado.

UdecxUrbRetrieveBuffer Recupera el búfer de transferencia de un URB del objeto de solicitud de marco especificado enviado a la cola del punto de conexión.

UdecxUrbSetBytesCompleted Establece el número de bytes transferidos para el URB contenido en un objeto de solicitud de marco.

UdecxUrbComplete Completa la solicitud URB con un código de estado de finalización específico de USB.

UdecxUrbCompleteWithNtStatus Completa la solicitud URB con un código NTSTATUS.

A continuación se muestra el flujo de procesamiento de E/S típico para el URB de una transferencia 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;
}

El controlador cliente puede completar una solicitud de E/S en un elemento independiente con un DPC. Siga estos procedimientos recomendados:

  • Para garantizar la compatibilidad con los controladores USB existentes, el cliente UDE debe llamar a WdfRequestComplete en DISPATCH_LEVEL.
  • Si el URB se agregó a la cola de un punto de conexión y el controlador comienza a procesarlo sincrónicamente en el subproceso del controlador que realiza la llamada o DPC, la solicitud no se debe completar de forma sincrónica. Se requiere un DPC independiente para ese propósito, que el controlador pone en cola llamando a WdfDpcEnqueue.
  • Cuando la extensión de clase UDE invoca EvtIoCanceledOnQueue o EvtRequestCancel, el controlador cliente debe completar el URB recibido en un DPC independiente del subproceso o DPC del llamador. Para ello, el controlador debe proporcionar una devolución de llamada EvtIoCanceledOnQueue para sus colas de URB.