Compartir tecnología

Desarrollo de controladores de dispositivos USB de Windows: procesamiento de flujos estáticos de transferencias por lotes

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

En dispositivos USB 2.0 y anteriores, un punto final masivo puede enviar o recibir un único flujo de datos a través del punto final. En los dispositivos USB 3.0, el punto final masivo es capaz de enviar y recibir múltiples flujos de datos a través del punto final.

La pila de controladores USB proporcionada por Microsoft en Windows admite múltiples transmisiones. Esto permite que los controladores del cliente envíen solicitudes de E/S independientes a cada secuencia asociada con un punto final masivo en un dispositivo USB 3.0, sin serializar solicitudes a diferentes secuencias.

Para un controlador de cliente, una secuencia representa múltiples puntos finales lógicos con el mismo conjunto de características. Para enviar una solicitud a una secuencia específica, el controlador del cliente necesita un identificador para esa secuencia (similar al identificador de canalización de un punto final). La URB para solicitudes de E/S de transmisión es similar a la URB para solicitudes de E/S para puntos finales masivos. La única diferencia es el mango del tubo. Para enviar una solicitud de E/S a una secuencia, el controlador especifica un identificador de tubería en la secuencia.

Durante la configuración del dispositivo, el controlador del cliente envía una solicitud de configuración seleccionada y, opcionalmente, una solicitud de interfaz seleccionada. Estas solicitudes recuperan un conjunto de identificadores de canalización para los puntos finales definidos en la configuración de actividad de la interfaz. Para los puntos finales que reconocen la transmisión, el identificador de canalización del punto final se puede usar para enviar solicitudes de E/S a la secuencia predeterminada (la primera secuencia) hasta que el controlador abra la secuencia.

Si un controlador de cliente desea enviar solicitudes a secuencias distintas a la secuencia predeterminada, el controlador debe abrir y obtener identificadores para todas las secuencias. Para hacer esto, el controlador del cliente envía una solicitud de transmisión abierta especificando la cantidad de transmisiones a abrir. Una vez que un controlador de cliente ha terminado de usar transmisiones, el controlador puede optar por cerrarlas enviando una solicitud de cierre de transmisión.

Kernel Mode Driver Framework (KMDF) no admite de forma nativa transmisiones estáticas. Los controladores de cliente deben utilizar el modelo de controlador de Windows (WDM) para abrir y cerrar transmisiones. Los controladores de cliente de User-mode Driver Framework (UMDF) no pueden utilizar la función de transmisión estática.

Lo siguiente puede contener algunos comentarios denominados Controladores WDM. Estas instrucciones describen rutinas para un controlador de cliente USB basado en WDM que desea enviar solicitudes de transmisión.

requisitos previos

Antes de que un controlador de cliente pueda abrir o cerrar una transmisión, el controlador debe tener:

1. Llame al método WdfUsbTargetDeviceCreateWithParameters. El método requiere la versión del protocolo del cliente USBD_CLIENT_CONTRACT_VERSION_602. Al especificar esta versión, el controlador del cliente debe cumplir con un conjunto de reglas.

Llame para recuperar el identificador WDFUSBDEVICE del objeto de dispositivo de destino USB del marco. El identificador es necesario para llamadas posteriores a la secuencia abierta. Normalmente, un controlador de cliente se registra en la rutina de devolución de llamada del evento EVT_WDF_DEVICE_PREPARE_HARDWARE del controlador.

Controlador WDM: llame a la rutina USBD_CreateHandle y obtenga el identificador USBD registrado por el controlador en la pila del controlador USB.

2. Configuró el dispositivo y obtuvo el identificador de tubería WDFUSBPIPE para el punto final masivo que admite la transmisión. Para obtener un identificador de tubería, llame al método WdfUsbInterfaceGetConfiguredPipe en la configuración alternativa actual de la interfaz en la configuración seleccionada.

Controlador WDM: obtiene el identificador de tubería USBD enviando una solicitud de configuración de selección o de interfaz de selección.

Cómo abrir una transmisión estática

1. Determine si la pila de controladores USB subyacente y el controlador de host admiten la función de transmisión estática llamando al método WdfUsbTargetDeviceQueryUsbCapability. Normalmente, los controladores del cliente llaman a rutinas en la rutina de devolución de llamada del evento EVT_WDF_DEVICE_PREPARE_HARDWARE del controlador.

Controlador WDM: llama a la rutina USBD_QueryUsbCapability. Normalmente, un controlador consulta la función que se utilizará en la rutina del dispositivo de inicio del controlador (IRP_MN_START_DEVICE).

Provee la siguiente informacion:

  • El identificador del objeto del dispositivo USB recuperado en una llamada anterior a WdfUsbTargetDeviceCreateWithParameters, utilizado para registrar el controlador del cliente.

Controlador WDM: pase el identificador USBD recuperado en la llamada anterior a USBD_CreateHandle.

Si un controlador cliente desea utilizar una función específica, primero debe consultar la pila de controladores USB subyacente para determinar si la pila de controladores y el controlador host admiten la función. Si la función es compatible, solo entonces el conductor debe enviar una solicitud para utilizarla. Ciertas solicitudes requieren URB, como la funcionalidad de transmisión que se analiza en el Paso 5. Para estas solicitudes, asegúrese de utilizar el mismo identificador para consultar la función y asignar la URB. Esto se debe a que la pila de controladores utiliza identificadores para realizar un seguimiento de las funciones compatibles que el controlador puede utilizar.

Por ejemplo, si se obtuvo USBD_HANDLE llamando a USBD_CreateHandle, la pila del controlador se consulta llamando a USBD_QueryUsbCapability y la URB se asigna llamando a USBD_UrbAllocate. Pase el mismo USBD_HANDLE en ambas llamadas.

Si llama a los métodos KMDF, WdfUsbTargetDeviceQueryUsbCapability y WdfUsbTargetDeviceCreateUrb, especifique el mismo identificador WDFUSBDEVICE para el objeto de destino del marco en estas llamadas a métodos.

  • GUID asignado a GUID_USB_CAPABILITY_STATIC_STREAMS;
  • Puntero al búfer de salida (apuntando a USHORT). Una vez completado, el búfer se llenará con (número máximo de transmisiones para) cada punto final admitido por el controlador del host;
  • La longitud del búfer de salida, en bytes. Para las transmisiones, la longitud es sizeof (USHORT);

2. Evalúe el valor NTSTATUS devuelto. Si la rutina se completa correctamente, se devuelve STATUS_SUCCESS y se admite la funcionalidad de transmisión estática. De lo contrario, el método devuelve el código de error apropiado.

3. Determine la cantidad de transmisiones que se abrirán. El número máximo de transmisiones que se pueden abrir está limitado por:

  • El número máximo de transmisiones admitidas por el controlador de host. WdfUsbTargetDeviceQueryUsbCapability (recibe WdfUsbTargetDeviceQueryUsbCapabilityUSBD_QueryUsbCapability) en el búfer de salida proporcionado por la persona que llama. La pila de controladores USB proporcionada por Microsoft admite hasta 255 transmisiones. WdfUsbTargetDeviceQueryUsbCapability tiene en cuenta este límite al calcular el número de transmisiones. El método nunca devuelve un valor mayor que 255.
  • El número máximo de transmisiones admitidas por el punto final en el dispositivo. Para obtener este número, verifique el descriptor complementario del punto final (en Usbspec.h) para USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR. Para obtener el descriptor complementario del punto final, se debe analizar el descriptor de configuración. Para obtener el descriptor de configuración, el controlador del cliente debe llamar al método WdfUsbTargetDeviceRetrieveConfigDescriptor. Se deben utilizar las rutinas auxiliares, USBD_ParseConfigurationDescriptorEx y USBD_ParseDescriptor.

Para determinar la cantidad máxima de flujos, elija el menor de los dos valores admitidos por el controlador del host y el punto final.

4. Asigne una matriz de estructuras USBD_STREAM_INFORMATION de n elementos, donde n es el número de secuencias que se abrirán. El controlador del cliente es responsable de liberar esta matriz una vez que el controlador termina de usar la transmisión.

5. Asigne una URB para la solicitud de transmisión abierta llamando al método WdfUsbTargetDeviceCreateUrb. Si la llamada se completa correctamente, el método recupera el objeto de memoria WDF y la dirección de la estructura URB asignada por la pila del controlador USB.

Controlador WDM: llama a la rutina USBD_UrbAllocate.

6. Configure el formato URB de la solicitud de transmisión abierta. URB utiliza la estructura _URB_OPEN_STATIC_STREAMS para definir solicitudes. Para formatear una URB, necesita:

  • Mango de tubería USBD apuntando al punto final. Si hay un objeto de tubería WDF, puede obtener el identificador de tubería USBD llamando al método WdfUsbTargetPipeWdmGetPipeHandle.
  • Creado en el paso 4 (matriz de flujo)
  • Puntero a la estructura URB creada en (paso 5).

Para formatear una URB, llame a UsbBuildOpenStaticStreamsRequest y pase la información requerida como valores de parámetros. Asegúrese de que la cantidad de transmisiones especificadas en UsbBuildOpenStaticStreamsRequest no exceda la cantidad máxima de transmisiones admitidas.

7. Envíe la URB como un objeto de solicitud WDF llamando al método WdfRequestSend. Para enviar la solicitud de forma sincrónica, llame al método WdfUsbTargetDeviceSendUrbSynchronfully.

Controlador WDM: asocia la URB con el IRP y envía el IRP a la pila del controlador USB.

8. Una vez completada la solicitud, verifique el estado de la solicitud.Si falla la solicitud de la pila del controlador USB, el estado de la URB contiene el código de error relevante.

Si el estado de la solicitud (objeto de solicitud IRP o WDF) indica USBD_STATUS_SUCCESS, la solicitud se completó correctamente. Conjunto de estructuras USBD_STREAM_INFORMATION recibidas cuando se completa la verificación. La matriz se completa con información sobre la secuencia solicitada. La pila del controlador USB completa cada estructura de la matriz con información de flujo, como el identificador de recepción USBD_PIPE_HANDLE, el identificador de flujo y el tamaño máximo de transferencia numérica. La transmisión ahora puede transferir datos.

Para solicitudes de transmisión abierta, es necesario asignar URB y matrices. Una vez completada la solicitud de transmisión abierta, el controlador del cliente debe liberar la URB llamando al método WdfObjectDelete en el objeto de memoria WDF asociado. Si el controlador envía la solicitud de forma sincrónica llamando a WdfUsbTargetDeviceSendUrbSynchronfully, el objeto de memoria WDF debe liberarse después de que regrese el método. Si un controlador de cliente envía una solicitud de forma asincrónica llamando a WdfRequestSend, el controlador debe liberar el objeto de memoria WDF en la rutina de finalización implementada por el controlador asociada con la solicitud.

La matriz de flujo se puede liberar después de que el controlador del cliente haya terminado de usar el flujo, o se puede almacenar para solicitudes de E/S. En el ejemplo de código que se incluye a continuación, el controlador almacena una matriz de flujo en el contexto del dispositivo. El controlador libera el contexto del dispositivo antes de liberar el objeto del dispositivo.

Cómo transferir datos a una secuencia específica
Para enviar una solicitud de transferencia de datos a una secuencia específica, se requiere un objeto de solicitud WDF. Normalmente, los controladores del cliente no necesitan asignar objetos de solicitud WDF. Cuando el administrador de E/S recibe una solicitud de una aplicación, crea un IRP para la solicitud. El IRP fue interceptado por el marco. Luego, el marco asigna un objeto de solicitud WDF para representar el IRP. Luego, el marco pasa el objeto de solicitud WDF al controlador del cliente. Luego, el controlador del cliente puede asociar el objeto de solicitud con la URB de transferencia de datos y enviarlo a la pila del controlador USB.

Si un controlador de cliente no recibe un objeto de solicitud WDF del marco y desea enviar la solicitud de forma asincrónica, el controlador debe asignar un objeto de solicitud WDF llamando al método WdfRequestCreate. Formatee el nuevo objeto llamando a WdfUsbTargetPipeFormatRequestForUrb y envíe la solicitud llamando a WdfRequestSend.

En el caso síncrono, pasar el objeto de solicitud WDF es opcional.

Para transferir datos a una secuencia, se debe utilizar URB. La URB se debe formatear llamando a WdfUsbTargetPipeFormatRequestForUrb.

Las transmisiones no admiten los siguientes métodos WDF:

  • Solicitud de lectura del formato de tubería de destino WdfUsb
  • Solicitud de escritura en formato de tubería de destino WdfUsb
  • WdfUsbTargetPipeReadSincronizado
  • WdfUsbTargetPipeWriteSynchronously

El siguiente procedimiento supone que el controlador del cliente recibe el objeto de solicitud del marco.

  1. Asigne la URB llamando a WdfUsbTargetDeviceCreateUrb. Este método asigna un objeto de memoria WDF que contiene la URB recién asignada. Los controladores del cliente pueden optar por asignar una URB para cada solicitud de E/S o asignar una URB y utilizarla para solicitudes del mismo tipo.
  2. Formatee la URB para una transferencia masiva llamando a UsbBuildInterruptOrBulkTransferRequest. En el parámetro PipeHandle, especifique el identificador de la secuencia. El identificador de flujo se obtiene en la solicitud anterior, como se describe en la sección Cómo abrir un flujo estático.
  3. Formatee el objeto de solicitud WDF llamando al método WdfUsbTargetPipeFormatRequestForUrb. En la llamada, especifique el objeto de memoria WDF que contiene la URB de transferencia de datos. El objeto de memoria se asignó en el paso 1.
  4. Envíe la URB como una solicitud WDF llamando a WdfRequestSend o WdfUsbTargetPipeSendUrbSynchronfully. Si llama a WdfRequestSend, debe especificar una rutina de finalización llamando a WdfRequestSetCompletionRoutine para que se pueda notificar al controlador del cliente cuando se complete la operación asincrónica. La transferencia de datos URB debe liberarse en la rutina de finalización.

Controlador WDM: asigna una URB llamando a USBD_UrbAllocate y la formatea para transferencia masiva (consulte _URB_BULK_OR_INTERRUPT_TRANSFER). Para formatear una URB, puede llamar a UsbBuildInterruptOrBulkTransferRequest o formatear manualmente la estructura URB. Especifique el identificador de la secuencia en el miembro UrbBulkOrInterruptTransfer.PipeHandle de la URB.

Cómo cerrar una transmisión estática

El controlador del cliente puede cerrar la transmisión una vez que haya terminado de usarla. Sin embargo, cerrar la solicitud de transmisión es opcional. La pila del controlador USB cierra todas las transmisiones cuando el punto final asociado con la transmisión no está configurado. Los puntos finales se desconfiguran al seleccionar una configuración o interfaz alternativa, eliminar un dispositivo, etc. Si el controlador del cliente desea abrir una cantidad diferente de transmisiones, debe cerrarlas. Enviar una solicitud de cierre de transmisión:

1. Asigne la estructura URB llamando a WdfUsbTargetDeviceCreateUrb.

2. Configure el formato URB para cerrar la solicitud de transmisión. El miembro UrbPipeRequest de la estructura URB es la estructura _URB_PIPE_REQUEST. Complete sus miembros de la siguiente manera:

  • Miembro Hdr requerido de URB_FUNCTION_CLOSE_STATIC_STREAMS_URB_PIPE_REQUEST
  • El miembro PipeHandle debe ser un identificador que contenga el punto final que se utiliza para abrir la secuencia.

3. Envíe la URB como una solicitud WDF llamando a WdfRequestSend o WdfUsbTargetDeviceSendUrbSynchronfully.

El controlador de cierre solicita el cierre de todas las secuencias abiertas previamente por el controlador del cliente. El controlador del cliente no puede utilizar solicitudes para cerrar una secuencia específica en el punto final.

Mejores prácticas para enviar solicitudes de transmisión estática

La pila del controlador USB realiza la verificación en la URB recibida. Para evitar errores de validación, haga lo siguiente:

  • No envíe solicitudes de transmisión abierta o cerrada a puntos finales que no admitan transmisiones. Llame a WdfUsbTargetDeviceQueryUsbCapability (, USBD_QueryUsbCapability) del controlador WDM para determinar la compatibilidad con la transmisión estática y solo envíe solicitudes de transmisión si el punto final lo admite.
  • No solicite más transmisiones (abiertas) que la cantidad máxima de transmisiones admitidas, ni envíe una solicitud sin especificar la cantidad de transmisiones. La cantidad de transmisiones se determina en función de la cantidad de transmisiones admitidas por la pila del controlador USB y el punto final del dispositivo.
  • No envíe una solicitud de transmisión abierta a un punto final que ya tenga una transmisión abierta.
  • No envíe una solicitud de cierre de transmisión a un punto final que no tenga una transmisión abierta.
  • Después de abrir una secuencia estática para un punto final, no envíe solicitudes de E/S utilizando el identificador de canalización del punto final obtenido a través de una configuración de selección o una solicitud de interfaz de selección. Esto es cierto incluso si la transmisión estática está cerrada.
Restablecer y cancelar operaciones de canalización

En ocasiones, las transferencias hacia o desde un punto final pueden fallar. Estas fallas pueden deberse a condiciones de error en el punto final o en el controlador del host, como una condición de detención o detención. Para borrar la condición de error, el controlador del cliente primero cancela la transferencia pendiente y luego restablece la tubería asociada con el punto final. Para cancelar una transferencia pendiente, el controlador del cliente puede enviar una solicitud de cancelación de canalización. Para restablecer una tubería, el controlador del cliente debe enviar una solicitud de reinicio de tubería.

Para la transmisión, las solicitudes de cancelación y reinicio de canalización no se admiten para transmisiones individuales asociadas con el punto final masivo. Si falla una transferencia en una tubería de flujo específica, el controlador del host detendrá las transferencias en todas las demás tuberías para otras transmisiones. Para recuperarse de la condición de error, el controlador del cliente debe cancelar manualmente la transferencia a cada transmisión. un identificador de tubería para enviar una solicitud de reinicio de tubería al punto final masivo. Para esta solicitud, el controlador del cliente debe especificar el identificador de tubería del punto final en la estructura _URB_PIPE_REQUEST y configurar la función URB (Hdr.Function) en URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL.

Ejemplo completo

El siguiente ejemplo de código demuestra cómo abrir una secuencia.

  1. NTSTATUS
  2. OpenStreams (
  3. _In_ WDFDEVICE Device,
  4. _In_ WDFUSBPIPE Pipe)
  5. {
  6. NTSTATUS status;
  7. PDEVICE_CONTEXT deviceContext;
  8. PPIPE_CONTEXT pipeContext;
  9. USHORT cStreams = 0;
  10. USBD_PIPE_HANDLE usbdPipeHandle;
  11. WDFMEMORY urbMemory = NULL;
  12. PURB urb = NULL;
  13. PAGED_CODE();
  14. deviceContext =GetDeviceContext(Device);
  15. pipeContext = GetPipeContext (Pipe);
  16. if (deviceContext->MaxStreamsController == 0)
  17. {
  18. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  19. "%!FUNC! Static streams are not supported.");
  20. status = STATUS_NOT_SUPPORTED;
  21. goto Exit;
  22. }
  23. // If static streams are not supported, number of streams supported is zero.
  24. if (pipeContext->MaxStreamsSupported == 0)
  25. {
  26. status = STATUS_DEVICE_CONFIGURATION_ERROR;
  27. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  28. "%!FUNC! Static streams are not supported by the endpoint.");
  29. goto Exit;
  30. }
  31. // Determine the number of streams to open.
  32. // Compare the number of streams supported by the endpoint with the
  33. // number of streams supported by the host controller, and choose the
  34. // lesser of the two values. The deviceContext->MaxStreams value was
  35. // obtained in a previous call to WdfUsbTargetDeviceQueryUsbCapability
  36. // that determined whether or not static streams is supported and
  37. // retrieved the maximum number of streams supported by the
  38. // host controller. The device context stores the values for IN and OUT
  39. // endpoints.
  40. // Allocate an array of USBD_STREAM_INFORMATION structures to store handles to streams.
  41. // The number of elements in the array is the number of streams to open.
  42. // The code snippet stores the array in its device context.
  43. cStreams = min(deviceContext->MaxStreamsController, pipeContext->MaxStreamsSupported);
  44. // Allocate an array of streams associated with the IN bulk endpoint
  45. // This array is released in CloseStreams.
  46. pipeContext->StreamInfo = (PUSBD_STREAM_INFORMATION) ExAllocatePoolWithTag (
  47. NonPagedPool,
  48. sizeof (USBD_STREAM_INFORMATION) * cStreams,
  49. USBCLIENT_TAG);
  50. if (pipeContext->StreamInfo == NULL)
  51. {
  52. status = STATUS_INSUFFICIENT_RESOURCES;
  53. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  54. "%!FUNC! Could not allocate stream information array.");
  55. goto Exit;
  56. }
  57. RtlZeroMemory (pipeContext->StreamInfo,
  58. sizeof (USBD_STREAM_INFORMATION) * cStreams);
  59. // Get USBD pipe handle from the WDF target pipe object. The client driver received the
  60. // endpoint pipe handles during device configuration.
  61. usbdPipeHandle = WdfUsbTargetPipeWdmGetPipeHandle (Pipe);
  62. // Allocate an URB for the open streams request.
  63. // WdfUsbTargetDeviceCreateUrb returns the address of the
  64. // newly allocated URB and the WDFMemory object that
  65. // contains the URB.
  66. status = WdfUsbTargetDeviceCreateUrb (
  67. deviceContext->UsbDevice,
  68. NULL,
  69. &urbMemory,
  70. &urb);
  71. if (status != STATUS_SUCCESS)
  72. {
  73. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  74. "%!FUNC! Could not allocate URB for an open-streams request.");
  75. goto Exit;
  76. }
  77. // Format the URB for the open-streams request.
  78. // The UsbBuildOpenStaticStreamsRequest inline function formats the URB by specifying the
  79. // pipe handle to the entire bulk endpoint, number of streams to open, and the array of stream structures.
  80. UsbBuildOpenStaticStreamsRequest (
  81. urb,
  82. usbdPipeHandle,
  83. (USHORT)cStreams,
  84. pipeContext->StreamInfo);
  85. // Send the request synchronously.
  86. // Upon completion, the USB driver stack populates the array of with handles to streams.
  87. status = WdfUsbTargetPipeSendUrbSynchronously (
  88. Pipe,
  89. NULL,
  90. NULL,
  91. urb);
  92. if (status != STATUS_SUCCESS)
  93. {
  94. goto Exit;
  95. }
  96. Exit:
  97. if (urbMemory)
  98. {
  99. WdfObjectDelete (urbMemory);
  100. }
  101. return status;
  102. }