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.
Llamar a UdecxInitializeWdfDeviceInit pasando la referencia a WDFDEVICE_INIT que ha pasado el marco.
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.
Llamar a WdfDeviceCreate para crear el objeto de dispositivo de marco.
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_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY
Determina las funcionalidades admitidas por el controlador de host que el controlador cliente debe informar a la extensión de clase.
-
Opcional. Restablece el controlador de host o los dispositivos conectados.
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.
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.
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.
Llamar a UdecxUsbDeviceInitSetSpeed para establecer la velocidad del dispositivo USB y también el tipo de dispositivo, USB 2.0 o un dispositivo SuperSpeed.
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.
Llamar a cualquiera de estos métodos para agregar descriptores necesarios al dispositivo.
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.
Llamar a UdecxUsbDeviceCreate para crear el objeto de dispositivo UDE y recuperar el identificador UDECXUSBDEVICE.
Crear puntos de conexión estáticos llamando a UdecxUsbEndpointCreate. Consulte Creación de puntos de conexión simples.
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.
EVT_UDECX_USB_DEVICE_D0_ENTRY: el controlador cliente realiza la transición del dispositivo de un estado Dx a D0.
EVT_UDECX_USB_DEVICE_D0_EXIT: el controlador cliente realiza la transición del dispositivo de un estado D0 a Dx.
EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE: el controlador cliente cambia el estado de función de la interfaz especificada del dispositivo USB 3.0 virtual.
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.
Llamar a UdecxUsbSimpleEndpointInitAllocate para obtener un puntero a los parámetros de inicialización asignados por la extensión de clase.
Llamar a UdecxUsbEndpointInitSetEndpointAddress para establecer la dirección del punto de conexión en los parámetros de inicialización.
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.
EVT_UDECX_USB_ENDPOINT_RESET: restablece un punto de conexión del dispositivo USB virtual.
EVT_UDECX_USB_ENDPOINT_START: opcional. Inicia el procesamiento de solicitudes de E/S
EVT_UDECX_USB_ENDPOINT_PURGE: opcional. Detiene la puesta en cola de las solicitudes de E/S a la cola del punto de conexión y cancela las solicitudes sin procesar.
Llamar a UdecxUsbEndpointCreate para crear el objeto de punto de conexión y recuperar el identificador UDECXUSBENDPOINT.
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.
Llamar a UdecxUsbEndpointInitSetEndpointAddress para establecer la dirección del punto de conexión en los parámetros de inicialización.
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:
Llamar a UdecxUsbEndpointCreate para crear el objeto de punto de conexión y recuperar el identificador UDECXUSBENDPOINT.
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.