Cómo abrir y cerrar secuencias estáticas en un punto de conexión masivo USB
En este artículo se describe la funcionalidad de secuencias estáticas y se explica cómo un controlador de cliente USB puede abrir y cerrar secuencias en un punto de conexión masivo de un dispositivo USB 3.0.
En los dispositivos USB 2.0 y anteriores, un punto de conexión masivo puede enviar o recibir un único flujo de datos a través del punto de conexión. En los dispositivos USB 3.0, los puntos de conexión masivos tienen la capacidad de enviar y recibir varios flujos de datos a través del punto de conexión.
La pila de controladores USB proporcionada por Microsoft en Windows admite varias secuencias. Esto permite a un controlador de cliente enviar solicitudes de E/S independientes a cada secuencia asociada a un punto de conexión masivo en un dispositivo USB 3.0. Las solicitudes a diferentes secuencias no se serializan.
Para un controlador cliente, los flujos representan varios puntos de conexión lógicos que tienen el mismo conjunto de características. Para enviar una solicitud a una secuencia determinada, el controlador de cliente necesita un identificador para esa secuencia (similar a un identificador de canalización para un punto de conexión). El URB de una solicitud de E/S a una secuencia es similar a un URB para una solicitud de E/S a un punto de conexión masivo. La única diferencia es el controlador de canalización. Para enviar una solicitud de E/S a una secuencia, el controlador especifica el identificador de canalización para la secuencia.
Durante la configuración del dispositivo, el controlador cliente envía una solicitud de configuración de selección y, opcionalmente, una solicitud select-interface. Esas solicitudes recuperan un conjunto de identificadores de canalización a los puntos de conexión definidos en la configuración activa de una interfaz. En el caso de un punto de conexión que admita flujos, el identificador de canalización del punto de conexión se puede usar para enviar solicitudes de E/S a la secuencia predeterminada (la primera secuencia) hasta que el controlador haya abierto secuencias (se describe a continuación).
Si el controlador cliente desea enviar solicitudes a secuencias distintas de la secuencia predeterminada, el controlador debe abrir y obtener identificadores para todas las secuencias. Para ello, el controlador cliente envía una solicitud de flujos abiertos especificando el número de secuencias que se van a abrir. Una vez que el controlador de cliente haya terminado de usar secuencias, el controlador puede cerrarlos si envía una solicitud de secuencias de cierre.
Kernel Mode Driver Framework (KMDF) no admite secuencias estáticas intrínsecamente. El controlador cliente debe enviar direcciones URL de estilo del modelo de controlador de Windows (WDM) que abran y cierren secuencias. En este artículo se describe cómo dar formato y enviar esas direcciones URL. Un controlador de marco de controlador de modo de usuario (UMDF)-client no puede usar la funcionalidad de secuencias estáticas.
El artículo contiene algunas notas etiquetadas como controladores WDM. Estas notas describen rutinas para un controlador de cliente USB basado en WDM que quiere enviar solicitudes de transmisión.
Requisitos previos
Para que un controlador cliente pueda abrir o cerrar secuencias, el controlador debe tener:
Se llamó al método WdfUsbTargetDeviceCreateWithParameters .
El método requiere que la versión del contrato de cliente sea USBD_CLIENT_CONTRACT_VERSION_602. Al especificar esa versión, el controlador cliente debe cumplir un conjunto de reglas. Para obtener más información, consulte Procedimientos recomendados: Uso de direcciones URL.
La llamada recupera un identificador WDFUSBDEVICE al objeto de dispositivo de destino USB del marco. Ese identificador es necesario para realizar llamadas posteriores a flujos abiertos. Normalmente, el controlador cliente se registra en la rutina de devolución de llamada de eventos EVT_WDF_DEVICE_PREPARE_HARDWARE del controlador.
Controladores WDM: Llame a la rutina USBD_CreateHandle y obtenga un identificador USBD para el registro del controlador con la pila de controladores USB.
Configuró el dispositivo y obtuvo un identificador de canalización WDFUSBPIPE en el punto de conexión masivo que admite flujos. Para obtener el identificador de canalización, llame al método WdfUsbInterfaceGetConfiguredPipe en la configuración alternativa actual de una interfaz en la configuración seleccionada.
Controladores WDM: Obtenga un identificador de canalización USBD enviando una solicitud select-configuration o select-interface. Para obtener más información, vea Cómo seleccionar una configuración para un dispositivo USB.
Cómo abrir secuencias estáticas
Determine si la pila de controladores USB subyacente y el controlador de host admiten la funcionalidad de secuencias estáticas mediante una llamada al método WdfUsbTargetDeviceQueryUsbCapability . Normalmente, el controlador cliente llama a la rutina en la rutina de devolución de llamada de eventos EVT_WDF_DEVICE_PREPARE_HARDWARE del controlador.
Controladores WDM: Llame a la rutina USBD_QueryUsbCapability . Normalmente, el controlador consulta las funcionalidades que quiere usar en la rutina de inicio del controlador (IRP_MN_START_DEVICE). Para ver el ejemplo de código, consulte USBD_QueryUsbCapability.
Proporcione la siguiente información:
Identificador del objeto de dispositivo USB que se recuperó, en una llamada anterior a WdfUsbTargetDeviceCreateWithParameters, para el registro de controladores de cliente.
Controladores WDM: Pase el identificador USBD que se recuperó en la llamada anterior a USBD_CreateHandle.
Si el controlador cliente desea usar una funcionalidad determinada, el controlador primero debe consultar la pila de controladores USB subyacente para determinar si la pila de controladores y el controlador host admiten la funcionalidad. Si solo se admite la funcionalidad, el controlador debe enviar una solicitud para usar la funcionalidad. Algunas solicitudes requieren direcciones URL, como la funcionalidad de flujos (que se describe en el paso 5). Para esas solicitudes, asegúrese de usar el mismo identificador para consultar funcionalidades y asignar direcciones URL. Esto se debe a que la pila de controladores usa identificadores para realizar un seguimiento de las funcionalidades admitidas que puede usar un controlador.
Por ejemplo, si obtuvo un USBD_HANDLE (llamando a USBD_CreateHandle), consulte la pila de controladores llamando a USBD_QueryUsbCapability y asigne el URB llamando a USBD_UrbAllocate. Pase el mismo USBD_HANDLE en ambas llamadas.
Si llama a métodos KMDF, WdfUsbTargetDeviceQueryUsbCapability y WdfUsbTargetDeviceCreateUrb, especifique el mismo identificador WDFUSBDEVICE para el objeto de destino de la plataforma en esas llamadas de método.
GUID asignado a GUID_USB_CAPABILITY_STATIC_STREAMS.
Un búfer de salida (puntero a USHORT). Tras la finalización, el búfer se rellena con el número máximo de secuencias (por punto de conexión) compatibles con el controlador de host.
Longitud, en bytes, del búfer de salida. En el caso de las secuencias, la longitud es
sizeof (USHORT)
.
Evalúe el valor NTSTATUS devuelto. Si la rutina se completa correctamente, devuelve STATUS_SUCCESS, se admite la funcionalidad de secuencias estáticas. De lo contrario, el método devuelve un código de error adecuado.
Determine el número de secuencias que se van a abrir. El número máximo de secuencias que se pueden abrir está limitado por:
- Número máximo de secuencias admitidas por el controlador de host. WdfUsbTargetDeviceQueryUsbCapability recibe ese número (para controladores WDM, USBD_QueryUsbCapability), en el búfer de salida proporcionado por el autor de la llamada. La pila de controladores USB proporcionada por Microsoft admite hasta 255 secuencias. WdfUsbTargetDeviceQueryUsbCapability tiene en cuenta esa limitación al calcular el número de secuencias. El método nunca devuelve un valor mayor que 255.
- Número máximo de secuencias admitidas por el punto de conexión en el dispositivo. Para obtener ese número, inspeccione el descriptor complementario del punto de conexión (consulte USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR en Usbspec.h). Para obtener el descriptor complementario del punto de conexión, debe analizar el descriptor de configuración. Para obtener el descriptor de configuración, el controlador de cliente debe llamar al método WdfUsbTargetDeviceRetrieveConfigDescriptor . Debe usar las rutinas auxiliares, USBD_ParseConfigurationDescriptorEx y USBD_ParseDescriptor. Para obtener un ejemplo de código, vea la función de ejemplo denominada RetrieveStreamInfoFromEndpointDesc en Cómo enumerar canalizaciones USB.
Para determinar el número máximo de secuencias, elija el menor de dos valores admitidos por el controlador de host y el punto de conexión.
Asigne una matriz de estructuras de USBD_STREAM_INFORMATION con n elementos, donde n es el número de secuencias que se van a abrir. El controlador cliente es responsable de liberar esta matriz después de que el controlador haya terminado de usar secuencias.
Asigne un URB para la solicitud de flujos abiertos llamando al método WdfUsbTargetDeviceCreateUrb . Si la llamada se completa correctamente, el método recupera un objeto de memoria WDF y la dirección de la estructura URB asignada por la pila de controladores USB.
Controladores WDM: Llame a la rutina USBD_UrbAllocate .
Dar formato al URB para la solicitud de flujo abierto. El URB usa la estructura _URB_OPEN_STATIC_STREAMS para definir la solicitud. Para dar formato a la URB, necesita:
- Identificador de canalización USBD al punto de conexión. Si tiene un objeto de canalización WDF, puede obtener el identificador de canalización USBD llamando al método WdfUsbTargetPipeWdmGetPipeHandle .
- Matriz de secuencia (creada en el paso 4)
- Puntero a la estructura URB (creada en el paso 5).
Para dar formato a la URB, llame a UsbBuildOpenStaticStreamsRequest y pase la información necesaria como valores de parámetro. Asegúrese de que el número de secuencias especificadas en UsbBuildOpenStaticStreamsRequest no supera el número máximo de secuencias admitidas.
Envíe el URB como un objeto de solicitud WDF llamando al método WdfRequestSend . Para enviar la solicitud de forma sincrónica, llame al método WdfUsbTargetDeviceSendUrbSynchronously en su lugar.
Controladores WDM: Asocie el URB a un IRP y envíe el IRP a la pila del controlador USB. Para obtener más información, vea Cómo enviar un URB.
Una vez completada la solicitud, compruebe el estado de la solicitud.
Si se produce un error en la pila del controlador USB, el estado urB contiene el código de error correspondiente. Algunas condiciones de error comunes se describen en la sección Comentarios.
Si el estado de la solicitud (IRP o el objeto de solicitud WDF) indica USBD_STATUS_SUCCESS, la solicitud se completó correctamente. Inspeccione la matriz de estructuras de USBD_STREAM_INFORMATION recibidas al finalizar. La matriz se rellena con información sobre las secuencias solicitadas. La pila del controlador USB rellena cada estructura de la matriz con información de flujo, como identificadores (recibidos como USBD_PIPE_HANDLE), identificadores de flujo y el tamaño máximo de transferencia de número. Las secuencias ahora están abiertas para transferir datos.
Para una solicitud de flujos abiertos, deberá asignar un URB y una matriz. El controlador cliente debe liberar el URB llamando al método WdfObjectDelete en el objeto de memoria WDF asociado, una vez completada la solicitud de secuencias abiertas. Si el controlador envió la solicitud de forma sincrónica llamando a WdfUsbTargetDeviceSendUrbSynchronousmente, debe liberar el objeto de memoria WDF, después de que el método devuelva. Si el controlador cliente envió la 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 a la solicitud.
La matriz de secuencias se puede liberar después de que el controlador de cliente haya terminado de usar secuencias o las haya almacenado para las solicitudes de E/S. En el ejemplo de código incluido en este artículo, el controlador almacena la matriz de secuencias en el contexto del dispositivo. El controlador libera el contexto del dispositivo justo antes de liberar el objeto de dispositivo.
Transferencia de datos a una secuencia determinada
Para enviar una solicitud de transferencia de datos a una secuencia determinada, necesitará un objeto de solicitud WDF. Normalmente, el controlador de cliente no es necesario para asignar un objeto de solicitud WDF. Cuando el Administrador de E/S recibe una solicitud de una aplicación, el Administrador de E/S crea un IRP para la solicitud. El marco intercepta ese IRP. A continuación, el marco asigna un objeto de solicitud WDF para representar el IRP. Después, el marco pasa el objeto de solicitud WDF al controlador de cliente. Después, el controlador cliente puede asociar el objeto de solicitud con el URB de transferencia de datos y enviarlo a la pila del controlador USB.
Si el controlador cliente no recibe un objeto de solicitud WDF del marco de trabajo y quiere enviar la solicitud de forma asincrónica, el controlador debe asignar un objeto de solicitud WDF llamando al método WdfRequestCreate . Dé formato al nuevo objeto llamando a WdfUsbTargetPipeFormatRequestForUrb y envíe la solicitud llamando a WdfRequestSend.
En los casos sincrónicos, pasar un objeto de solicitud WDF es opcional.
Para transferir datos a flujos, debe usar direcciones URL. El URB debe tener formato llamando a WdfUsbTargetPipeFormatRequestForUrb.
No se admiten los métodos WDF siguientes para las secuencias:
- WdfUsbTargetPipeFormatRequestForRead
- WdfUsbTargetPipeFormatRequestForWrite
- WdfUsbTargetPipeReadSynchronously
- WdfUsbTargetPipeWriteSynchronously
En el procedimiento siguiente se supone que el controlador cliente recibe el objeto de solicitud del marco de trabajo.
Asigne un URB llamando a WdfUsbTargetDeviceCreateUrb. Este método asigna un objeto de memoria WDF que contiene el URB recién asignado. El controlador cliente puede optar por asignar un URB para cada solicitud de E/S o asignar un URB y reutilizarlo para el mismo tipo de solicitud.
Dé formato al URB para una transferencia masiva mediante una llamada a UsbBuildInterruptOrBulkTransferRequest. En el parámetro PipeHandle , especifique el identificador de la secuencia. Los identificadores de flujo se obtuvieron en una solicitud anterior, que se describe en la sección Cómo abrir secuencias estáticas .
Dé formato al objeto de solicitud WDF llamando al método WdfUsbTargetPipeFormatRequestForUrb . En la llamada, especifique el objeto de memoria WDF que contiene el URB de transferencia de datos. El objeto de memoria se asignó en el paso 1.
Envíe el URB como una solicitud WDF llamando a WdfRequestSend o WdfUsbTargetPipeSendUrbSynchronously. Si llama a WdfRequestSend, debe especificar una rutina de finalización llamando a WdfRequestSetCompletionRoutine para que el controlador cliente pueda recibir una notificación cuando se complete la operación asincrónica. Debe liberar el URB de transferencia de datos en la rutina de finalización.
Controladores WDM: Asigne un URB llamando a USBD_UrbAllocate y dé formato para la transferencia masiva (consulte _URB_BULK_OR_INTERRUPT_TRANSFER). Para dar formato al URB, puede llamar a UsbBuildInterruptOrBulkTransferRequest o dar formato manualmente a la estructura URB. Especifique el identificador de la secuencia en el miembro UrbBulkOrInterruptTransfer.PipeHandle de urB .
Cómo cerrar secuencias estáticas
El controlador cliente puede cerrar secuencias una vez que el controlador haya terminado de usarlos. Sin embargo, la solicitud close-stream es opcional. La pila del controlador USB cierra todas las secuencias cuando el punto de conexión asociado a las secuencias está desconfigurado. Se desconfigura un punto de conexión cuando se selecciona una interfaz o una configuración alternativa, se quita el dispositivo, etc. Un controlador cliente debe cerrar las secuencias si el controlador desea abrir un número diferente de secuencias. Para enviar una solicitud de secuencia de cierre:
Asigne una estructura URB llamando a WdfUsbTargetDeviceCreateUrb.
Dé formato al URB para la solicitud de secuencias de cierre. El miembro UrbPipeRequest de la estructura URB es una estructura _URB_PIPE_REQUEST . Rellene sus miembros de la siguiente manera:
- El miembro Hdr de _URB_PIPE_REQUEST debe ser URB_FUNCTION_CLOSE_STATIC_STREAMS
- El miembro PipeHandle debe ser el identificador del punto de conexión que contiene las secuencias abiertas en uso.
Envíe el URB como una solicitud WDF llamando a WdfRequestSend o WdfUsbTargetDeviceSendUrbSynchronously.
La solicitud close-handle cierra todas las secuencias que el controlador de cliente abrió anteriormente. El controlador de cliente no puede usar la solicitud para cerrar flujos específicos en el punto de conexión.
Procedimientos recomendados para enviar una solicitud de secuencias estáticas
La pila del controlador USB realiza validaciones en el URB recibido. Para evitar errores de validación:
- No envíe una solicitud de flujo abierto o de cierre a un punto de conexión que no admita secuencias. Llame a WdfUsbTargetDeviceQueryUsbCapability (para controladores WDM, USBD_QueryUsbCapability) para determinar la compatibilidad con flujos estáticos y enviar solo solicitudes de secuencias si el punto de conexión las admite.
- No solicite un número (de secuencias para abrir) que supere el número máximo de secuencias admitidas o envíe una solicitud sin especificar el número de secuencias. Determine el número de secuencias en función del número de flujos admitidos por la pila de controladores USB y el punto de conexión del dispositivo.
- No envíe una solicitud de flujo abierto a un punto de conexión que ya tenga flujos abiertos.
- No envíe una solicitud de flujo de cierre a un punto de conexión que no tenga secuencias abiertas.
- Después de que las secuencias estáticas estén abiertas para un punto de conexión, no envíe solicitudes de E/S mediante el identificador de canalización de punto de conexión que se obtuvo a través de una configuración de selección o solicitudes de interfaz de selección. Esto es cierto incluso si se han cerrado las secuencias estáticas.
Operaciones de restablecimiento y anulación de canalización
En ocasiones, se pueden producir errores en las transferencias hacia o desde un punto de conexión. Estos errores pueden deberse a una condición de error en el punto de conexión o el controlador de host, como una condición de detención o detención. Para borrar la condición de error, el controlador cliente cancela primero las transferencias pendientes y, a continuación, restablece la canalización con la que está asociado el punto de conexión. Para cancelar transferencias pendientes, el controlador cliente puede enviar una solicitud de canalización de anulación. Para restablecer una canalización, el controlador de cliente debe enviar una solicitud de restablecimiento de canalización.
En el caso de las transferencias de flujos, las solicitudes de canalización de anulación y restablecimiento no se admiten para flujos individuales asociados al punto de conexión masivo. Si se produce un error en una transferencia en una canalización de flujo determinada, el controlador de host detiene las transferencias en todas las demás canalizaciones (para otras secuencias). Para recuperarse de la condición de error, el controlador cliente debe cancelar manualmente las transferencias a cada secuencia. A continuación, el controlador de cliente debe enviar una solicitud de canalización de restablecimiento mediante el identificador de canalización al punto de conexión masivo. Para esa solicitud, el controlador cliente debe especificar el identificador de canalización para el punto de conexión en una estructura de _URB_PIPE_REQUEST y establecer la función URB (Hdr.Function) en URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL.
Ejemplo completo
En el ejemplo de código siguiente se muestra cómo abrir secuencias.
NTSTATUS
OpenStreams (
_In_ WDFDEVICE Device,
_In_ WDFUSBPIPE Pipe)
{
NTSTATUS status;
PDEVICE_CONTEXT deviceContext;
PPIPE_CONTEXT pipeContext;
USHORT cStreams = 0;
USBD_PIPE_HANDLE usbdPipeHandle;
WDFMEMORY urbMemory = NULL;
PURB urb = NULL;
PAGED_CODE();
deviceContext =GetDeviceContext(Device);
pipeContext = GetPipeContext (Pipe);
if (deviceContext->MaxStreamsController == 0)
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Static streams are not supported.");
status = STATUS_NOT_SUPPORTED;
goto Exit;
}
// If static streams are not supported, number of streams supported is zero.
if (pipeContext->MaxStreamsSupported == 0)
{
status = STATUS_DEVICE_CONFIGURATION_ERROR;
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Static streams are not supported by the endpoint.");
goto Exit;
}
// Determine the number of streams to open.
// Compare the number of streams supported by the endpoint with the
// number of streams supported by the host controller, and choose the
// lesser of the two values. The deviceContext->MaxStreams value was
// obtained in a previous call to WdfUsbTargetDeviceQueryUsbCapability
// that determined whether or not static streams is supported and
// retrieved the maximum number of streams supported by the
// host controller. The device context stores the values for IN and OUT
// endpoints.
// Allocate an array of USBD_STREAM_INFORMATION structures to store handles to streams.
// The number of elements in the array is the number of streams to open.
// The code snippet stores the array in its device context.
cStreams = min(deviceContext->MaxStreamsController, pipeContext->MaxStreamsSupported);
// Allocate an array of streams associated with the IN bulk endpoint
// This array is released in CloseStreams.
pipeContext->StreamInfo = (PUSBD_STREAM_INFORMATION) ExAllocatePoolWithTag (
NonPagedPool,
sizeof (USBD_STREAM_INFORMATION) * cStreams,
USBCLIENT_TAG);
if (pipeContext->StreamInfo == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not allocate stream information array.");
goto Exit;
}
RtlZeroMemory (pipeContext->StreamInfo,
sizeof (USBD_STREAM_INFORMATION) * cStreams);
// Get USBD pipe handle from the WDF target pipe object. The client driver received the
// endpoint pipe handles during device configuration.
usbdPipeHandle = WdfUsbTargetPipeWdmGetPipeHandle (Pipe);
// Allocate an URB for the open streams request.
// WdfUsbTargetDeviceCreateUrb returns the address of the
// newly allocated URB and the WDFMemory object that
// contains the URB.
status = WdfUsbTargetDeviceCreateUrb (
deviceContext->UsbDevice,
NULL,
&urbMemory,
&urb);
if (status != STATUS_SUCCESS)
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not allocate URB for an open-streams request.");
goto Exit;
}
// Format the URB for the open-streams request.
// The UsbBuildOpenStaticStreamsRequest inline function formats the URB by specifying the
// pipe handle to the entire bulk endpoint, number of streams to open, and the array of stream structures.
UsbBuildOpenStaticStreamsRequest (
urb,
usbdPipeHandle,
(USHORT)cStreams,
pipeContext->StreamInfo);
// Send the request synchronously.
// Upon completion, the USB driver stack populates the array of with handles to streams.
status = WdfUsbTargetPipeSendUrbSynchronously (
Pipe,
NULL,
NULL,
urb);
if (status != STATUS_SUCCESS)
{
goto Exit;
}
Exit:
if (urbMemory)
{
WdfObjectDelete (urbMemory);
}
return status;
}