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 dispositivos 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 con cada uno 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 y identificadores usados por la extensión de clase y el controlador cliente.
  • Crear un controlador de host emulado con características para las funcionalidades del controlador de consultas y restablecer el controlador.
  • Crear 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 de 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á lo siguiente:
    • La biblioteca de código auxiliar (Udecxstub.lib). La biblioteca traduce las llamadas realizadas por el controlador cliente y las pasa hasta UdeCx.
    • El archivo de encabezado, Udecx.h.
  • Instale Windows 10 en el equipo de destino.
  • Familiarícese con UDE. Consulte Arquitectura: emulación de dispositivos USB(UDE).
  • Familiarícese con Windows Driver Foundation (WDF). Lectura recomendada: Desarrollar controladores con Windows Driver Foundation, escrita por Penny Orwick y Guy Smith.

Objetos y 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 de 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 extremo 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 de host emulado. Se recomienda que el controlador realice estas tareas en su función de devolución de llamada EvtDriverDeviceAdd .

  1. Llame a UdecxInitializeWdfDeviceInit pasando la referencia a WDFDEVICE_INIT pasado por el marco.

  2. Inicialice 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, asigne un nombre de FDO y un vínculo simbólico, registre 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. Llame a WdfDeviceCreate para crear el objeto de dispositivo de marco.

  4. Llame a UdecxWdfDeviceAddUsbDeviceEmulation y registre 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 de 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 ioCTLs with Device interface GUID: GUID_DEVINTERFACE_USB_HOST_CONTROLLER.

Para controlar esas solicitudes, el controlador cliente registra la devolución de llamada del evento 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 la solicitud correctamente. 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 mediante una llamada 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;
}

Notificar las funcionalidades del controlador de host

Antes de que los controladores de nivel superior puedan usar las funcionalidades de un controlador de host USB, los controladores deben determinar si esas funcionalidades son compatibles con el controlador. Los controladores realizan estas consultas mediante una llamada a WdfUsbTargetDeviceQueryUsbCapability y USBD_QueryUsbCapability. Esas llamadas se reenvieron a la extensión de clase 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 se realiza solo después de que EvtDriverDeviceAdd se complete, normalmente en EvtDevicePrepareHardware y no después de EvtDeviceReleaseHardware. Se requiere esta función de devolución de llamada.

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 de 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. Llame a UdecxUsbDeviceInitAllocate para obtener un puntero a los parámetros de inicialización necesarios para crear el dispositivo. La extensión de clase UDE asigna esta estructura.

  2. Registre las funciones de devolución de llamada de eventos estableciendo miembros de UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS y, a continuación, llamando 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. Llama a UdecxUsbDeviceInitSetSpeed para establecer la velocidad del dispositivo USB y también el tipo de dispositivo, USB 2.0 o un dispositivo SuperSpeed.

  4. Llame a UdecxUsbDeviceInitSetEndpointsType para especificar el tipo de extremos 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. Llame a cualquiera de estos métodos para agregar los 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 lógica de 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, las solicitudes para la interfaz, el descriptor específico de la clase o el descriptor definido por el proveedor, la extensión de clase UDE los reenvía al controlador cliente. El controlador debe controlar esas solicitudes de GET_DESCRIPTOR.

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

  7. Cree puntos de conexión estáticos mediante una llamada a UdecxUsbEndpointCreate. Consulte Creación de puntos de conexión simples.

  8. Llame 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 los parámetros de inicialización mediante el registro de funciones de devolución de llamada, la configuración de la velocidad del dispositivo, el tipo de puntos de conexión y, por último, la configuración de 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 alimentación 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 la función e informa al controlador cliente de si la función puede reactivar 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á despertando.

El controlador cliente puede simular la acción de un dispositivo USB virtual que inicia su propia reactivación desde un estado de energía de vínculo bajo, suspensión de la función o ambos. Para un dispositivo USB 2.0, el controlador debe llamar a UdecxUsbDeviceSignalWake, si el controlador habilitado se activa 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 bajo consumo o si 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 extremo UDE. El controlador debe realizar estos pasos después de recuperar el controlador 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. Llame a UdecxUsbSimpleEndpointInitAllocate para obtener un puntero a los parámetros de inicialización asignados por la extensión de clase.

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

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

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

  4. Llame a UdecxUsbEndpointCreate para crear el objeto de extremo y recuperar el identificador UDECXUSBENDPOINT.

  5. Llame a UdecxUsbEndpointSetWdfIoQueue para asociar un objeto de cola de marco con el punto de conexión. Si procede, puede establecer el objeto de punto de conexión para que sea 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 centro de conectividad 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 de 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 de USB y la configuración de la interfaz en el dispositivo. Por ejemplo, cuando se necesita un objeto de extremo o se debe liberar un objeto de extremo existente, la extensión de clase llama al 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 extremo en su implementación de la función de devolución de llamada.

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

  2. Llame 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. Llame a UdecxUsbEndpointCreate para crear el objeto de extremo y recuperar el identificador UDECXUSBENDPOINT.

  4. Llame a UdecxUsbEndpointSetWdfIoQueue para asociar un objeto de cola de marco con el 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;
}

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

En ocasiones, las transferencias de datos pueden producir errores debido a diversos motivos, como una condición de de bloqueo en el punto de conexión. En el caso de transferencias con errores, el punto de conexión no puede procesar las solicitudes hasta que se borre 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 borrar el estado HALT de la canalización y realizar otros pasos necesarios para borrar la condición de error.

Esta llamada es asincrónica. Una vez finalizado el cliente con 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 del cliente UDE la finalización de la operación de restablecimiento con el 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 restablecer todos los dispositivos de bajada, el controlador 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 del estado de la cola

El estado del objeto de cola de marco asociado a un objeto de extremo UDE se administra mediante la extensión de clase 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 punto 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 recientemente también produce un error hasta que se invoca la EVT_UDECX_USB_ENDPOINT_START del controlador cliente. Estos requisitos se cumplen mediante una llamada a UdecxUsbEndpointPurgeComplete, que garantiza que se completen todas las E/S reenviadas y que se haya producido un error en la E/S reenviada futura.

Operación de inicio del punto de conexión

En la implementación de EVT_UDECX_USB_ENDPOINT_START , el controlador de cliente debe comenzar 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. Una vez creado un punto de conexión, no recibe ninguna E/S hasta que esta función de devolución de llamada vuelva. Esta devolución de llamada devuelve el punto de conexión a un estado de E/S de procesamiento después de que se complete 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 del IOCTL_INTERNAL_USB_SUBMIT_URB IoControlCode (consulte el código de ejemplo en métodos de control de URB).

Métodos de control de URB

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

El controlador de 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 o DPC del controlador que realiza la llamada, la solicitud no se debe completar sincrónicamente. Se requiere un DPC independiente para ese propósito, que la cola de controladores mediante una llamada 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 autor de la llamada. Para ello, el controlador debe proporcionar una devolución de llamada EvtIoCanceledOnQueue para sus colas URB .