Envío de solicitudes de transferencia masiva USB
En este tema se proporciona información general breve sobre las transferencias masivas USB. También proporciona instrucciones paso a paso sobre cómo un controlador cliente puede enviar y recibir datos masivos del dispositivo.
Acerca de los puntos de conexión masivos
Un punto de conexión masivo USB puede transferir grandes cantidades de datos. Las transferencias masivas son confiables que permiten la detección de errores de hardware e implican un número limitado de reintentos en el hardware. Para las transferencias a puntos de conexión masivos, el ancho de banda no está reservado en el bus. Cuando hay varias solicitudes de transferencia que tienen como destino diferentes tipos de puntos de conexión, el controlador programa primero las transferencias de datos críticos para el tiempo, como paquetes isócronos e interrupciones. Solo si hay ancho de banda sin usar disponible en el bus, el controlador programa transferencias masivas. Cuando no hay otro tráfico significativo en el autobús, la transferencia masiva puede ser rápida. Sin embargo, cuando el bus está ocupado con otras transferencias, los datos masivos pueden esperar indefinidamente.
Estas son las características clave de un punto de conexión masivo:
- Los puntos de conexión masivos son opcionales. Son compatibles con un dispositivo USB que quiere transferir grandes cantidades de datos. Por ejemplo, transferir archivos a una unidad flash, datos hacia o desde una impresora o un escáner.
- Los dispositivos USB de velocidad completa, alta velocidad y SuperSpeed admiten puntos de conexión masivos. Los dispositivos de baja velocidad no admiten puntos de conexión masivos.
- El punto de conexión es unidireccional y los datos se pueden transferir en una dirección IN o OUT. El punto de conexión bulk IN se usa para leer datos del dispositivo al host y el punto de conexión out masivo se usa para enviar datos del host al dispositivo.
- El punto de conexión tiene bits CRC para comprobar si hay errores y, por tanto, proporciona integridad de los datos. En el caso de los errores crc, los datos se retransmiten automáticamente.
- Un punto de conexión masivo SuperSpeed puede admitir flujos. Las secuencias permiten al host enviar transferencias a canalizaciones de flujo individuales.
- El tamaño máximo de paquete de un punto de conexión masivo depende de la velocidad del bus del dispositivo. Para velocidad completa, alta velocidad y SuperSpeed; los tamaños máximos de paquete son 64, 512 y 1024 bytes respectivamente.
Transacciones masivas
Al igual que todas las demás transferencias USB, el host siempre inicia una transferencia masiva. La comunicación tiene lugar entre el host y el punto de conexión de destino. El protocolo USB no aplica ningún formato en los datos enviados en una transacción masiva.
La forma en que el host y el dispositivo se comunican en el bus depende de la velocidad a la que está conectado el dispositivo. En esta sección se describen algunos ejemplos de transferencias masivas de alta velocidad y SuperSpeed que muestran la comunicación entre el host y el dispositivo.
Puede ver la estructura de transacciones y paquetes mediante cualquier analizador USB, como Beagle, Ellisys, analizadores del protocolo USB LeCroy. Un dispositivo analizador muestra cómo se envían o reciben los datos de un dispositivo USB a través de la conexión. En este ejemplo, vamos a examinar algunos seguimientos capturados por un analizador USB LeCroy. Este ejemplo es solo para información. No es una aprobación de Microsoft.
Ejemplo de transacción bulk OUT
Este seguimiento del analizador muestra un ejemplo de transacción OUT masiva a alta velocidad.
En el seguimiento anterior, el host inicia una transferencia de SALIDA masiva a un punto de conexión masivo de alta velocidad mediante el envío de un paquete de token con PID establecido en OUT (token OUT). El paquete contiene la dirección del dispositivo y el punto de conexión de destino. Después del paquete OUT, el host envía un paquete de datos que contiene la carga masiva. Si el punto de conexión acepta los datos entrantes, envía un paquete ACK. En este ejemplo, podemos ver que el host envió 31 bytes a la dirección del dispositivo:1; dirección del punto de conexión: 2.
Si el punto de conexión está ocupado en el momento en que llega el paquete de datos y no puede recibir datos, el dispositivo puede enviar un paquete NAK. En ese caso, el host comienza a enviar paquetes PING al dispositivo. El dispositivo responde con paquetes NAK siempre que el dispositivo no esté listo para recibir datos. Cuando el dispositivo está listo, responde con un paquete ACK. Después, el host puede reanudar la transferencia OUT.
Este seguimiento del analizador muestra una transacción superspeed bulk OUT de ejemplo.
En el seguimiento anterior, el host inicia una transacción OUT en un punto de conexión masivo SuperSpeed mediante el envío de un paquete de datos. El paquete de datos contiene las direcciones de carga masiva, dispositivo y punto de conexión. En este ejemplo, podemos ver que el host envió 31 bytes a la dirección del dispositivo:4; dirección del punto de conexión: 2.
El dispositivo recibe y confirma el paquete de datos y envía un paquete ACK al host. Si el punto de conexión está ocupado en el momento en que llega el paquete de datos y no puede recibir datos, el dispositivo puede enviar un paquete NRDY. A diferencia de la alta velocidad, después de recibir el paquete NRDY, el host no sondea repetidamente el dispositivo. En su lugar, el host espera un ERDY desde el dispositivo. Cuando el dispositivo está listo, envía un paquete ERDY y el host puede enviar datos al punto de conexión.
Ejemplo de transacción bulk IN
Este seguimiento del analizador muestra un ejemplo de transacción IN masiva a alta velocidad.
En el seguimiento anterior, el host inicia la transacción mediante el envío de un paquete de token con PID establecido en IN (token IN). A continuación, el dispositivo envía un paquete de datos con carga masiva. Si el punto de conexión no tiene datos para enviar o aún no está listo para enviar datos, el dispositivo puede enviar un paquete de protocolo de enlace NAK. El host reintenta la transferencia IN hasta que recibe un paquete ACK del dispositivo. Ese paquete ACK implica que el dispositivo ha aceptado los datos.
Este seguimiento del analizador muestra una transacción superspeed bulk IN de ejemplo.
Para iniciar una transferencia masiva in desde un punto de conexión de SuperSpeed, el host inicia una transacción masiva mediante el envío de un paquete ACK. La especificación USB versión 3.0 optimiza esta parte inicial de la transferencia mediante la combinación de paquetes ACK e IN en un paquete ACK. En lugar de un token IN, para SuperSpeed, el host envía un token de ACK para iniciar una transferencia masiva. El dispositivo responde con un paquete de datos. A continuación, el host confirma el paquete de datos mediante el envío de un paquete ACK. Si el punto de conexión está ocupado y no pudo enviar datos, el dispositivo puede enviar el estado de NRDY. En ese caso, el host espera hasta que obtiene un paquete ERDY del dispositivo.
Tareas del controlador de cliente USB para una transferencia masiva
Una aplicación o un controlador en el host siempre inicia una transferencia masiva para enviar o recibir datos. El controlador cliente envía la solicitud a la pila del controlador USB. La pila de controladores USB programa la solicitud en el controlador host y, a continuación, envía los paquetes de protocolo (como se describe en la sección anterior) a través de la conexión al dispositivo.
Veamos cómo el controlador cliente envía la solicitud de una transferencia masiva como resultado de la solicitud de una aplicación u otra solicitud del controlador. Como alternativa, el controlador puede iniciar la transferencia por su cuenta. Independientemente del enfoque, un controlador debe tener el búfer de transferencia y la solicitud para iniciar la transferencia masiva.
Para un controlador KMDF, la solicitud se describe en un objeto de solicitud de marco (consulte Referencia de objetos de solicitud de WDF). El controlador cliente llama a métodos del objeto de solicitud especificando el identificador WDFREQUEST para enviar la solicitud a la pila del controlador USB. Si el controlador cliente envía una transferencia masiva en respuesta a una solicitud de una aplicación u otro controlador, el marco crea un objeto de solicitud y entrega la solicitud al controlador cliente mediante un objeto de cola de marco. En ese caso, el controlador de cliente puede usar esa solicitud con el fin de enviar la transferencia masiva. Si el controlador cliente inició la solicitud, el controlador puede optar por asignar su propio objeto de solicitud.
Si la aplicación u otro controlador envió o solicitó datos, el marco pasa el búfer de transferencia al controlador. Como alternativa, el controlador cliente puede asignar el búfer de transferencia y crear el objeto de solicitud si el controlador inicia la transferencia por su cuenta.
Estas son las tareas principales para el controlador cliente:
- Obtenga el búfer de transferencia.
- Obtenga, dé formato y envíe un objeto de solicitud de marco a la pila del controlador USB.
- Implemente una rutina de finalización para recibir notificaciones cuando la pila del controlador USB complete la solicitud.
En este tema se describen esas tareas mediante un ejemplo en el que el controlador inicia una transferencia masiva como resultado de la solicitud de una aplicación para enviar o recibir datos.
Para leer datos del dispositivo, el controlador cliente puede usar el objeto de lector continuo proporcionado por el marco. Para obtener más información, consulte Uso del lector continuo para leer datos de una canalización USB.
Ejemplo de solicitud de transferencia masiva
Considere un escenario de ejemplo, donde una aplicación quiere leer o escribir datos en el dispositivo. La aplicación llama a las API de Windows para enviar estas solicitudes. En este ejemplo, la aplicación abre un identificador para el dispositivo mediante el GUID de interfaz de dispositivo publicado por el controlador en modo kernel. A continuación, la aplicación llama a ReadFile o WriteFile para iniciar una solicitud de lectura o escritura. En esa llamada, la aplicación también especifica un búfer que contiene los datos que se van a leer o escribir y la longitud de ese búfer.
El Administrador de E/S recibe la solicitud, crea un paquete de solicitud de E/S (IRP) y lo reenvía al controlador cliente.
El marco intercepta la solicitud, crea un objeto de solicitud de marco y lo agrega al objeto de cola del marco. A continuación, el marco notifica al controlador de cliente que hay una nueva solicitud a la espera de procesarse. Esa notificación se realiza invocando las rutinas de devolución de llamada de la cola del controlador para EvtIoRead o EvtIoWrite.
Cuando el marco entrega la solicitud al controlador cliente, recibe estos parámetros:
- Identificador WDFQUEUE para el objeto de cola del marco que contiene la solicitud.
- WDFREQUEST controla el objeto de solicitud de marco que contiene detalles sobre esta solicitud.
- Longitud de transferencia, es decir, el número de bytes que se van a leer o escribir.
En la implementación del controlador cliente de EvtIoRead o EvtIoWrite, el controlador inspecciona los parámetros de solicitud y, opcionalmente, puede realizar comprobaciones de validación.
Si usa secuencias de un punto de conexión masivo SuperSpeed, enviará la solicitud en un URB porque KMDF no admite secuencias intrínsecamente. Para obtener información sobre cómo enviar una solicitud de transferencia a flujos de un punto de conexión masivo, consulte Cómo abrir y cerrar secuencias estáticas en un punto de conexión masivo USB.
Si no usa secuencias, puede usar métodos definidos de KMDF para enviar la solicitud como se describe en el procedimiento siguiente:
Requisitos previos
Antes de empezar, asegúrese de que tiene esta información:
El controlador cliente debe haber creado el objeto de dispositivo de destino USB de la plataforma y obtenido el identificador WDFUSBDEVICE llamando al método WdfUsbTargetDeviceCreateWithParameters .
Si usa las plantillas USB que se proporcionan con Microsoft Visual Studio Professional 2012, el código de plantilla realiza esas tareas. El código de plantilla obtiene el identificador del objeto de dispositivo de destino y almacena en el contexto del dispositivo. Para obtener más información, vea "Código fuente del dispositivo" en Descripción de la estructura de código del controlador de cliente USB (KMDF).
WDFREQUEST controla el objeto de solicitud de marco que contiene detalles sobre esta solicitud.
Número de bytes que se van a leer o escribir.
El controlador WDFUSBPIPE para el objeto de canalización de marco asociado al punto de conexión de destino. Debe haber obtenido identificadores de canalización durante la configuración del dispositivo mediante la enumeración de canalizaciones. Para obtener más información, vea Cómo enumerar canalizaciones USB.
Si el punto de conexión masivo admite secuencias, debe tener el identificador de canalización en la secuencia. Para obtener más información, consulte Cómo abrir y cerrar secuencias estáticas en un punto de conexión masivo USB.
Paso 1: Obtener el búfer de transferencia
El búfer de transferencia o el MDL del búfer de transferencia contiene los datos que se van a enviar o recibir. En este tema se supone que está enviando o recibiendo datos en un búfer de transferencia. El búfer de transferencia se describe en un objeto de memoria WDF (consulte Referencia de objetos de memoria de WDF). Para obtener el objeto de memoria asociado al búfer de transferencia, llame a uno de estos métodos:
- Para una solicitud de transferencia in masiva, llame al método WdfRequestRetrieveOutputMemory .
- Para una solicitud de transferencia out masiva, llame al método WdfRequestRetrieveInputMemory .
El controlador cliente no necesita liberar esta memoria. La memoria está asociada al objeto de solicitud principal y se libera cuando se libera el elemento primario.
Paso 2: Dar formato y enviar un objeto de solicitud de marco a la pila del controlador USB
Puede enviar la solicitud de transferencia de forma asincrónica o sincrónica.
Estos son los métodos asincrónicos:
Los métodos de esta lista da formato a la solicitud. Si envía la solicitud de forma asincrónica, establezca un puntero a la rutina de finalización implementada por el controlador llamando al método WdfRequestSetCompletionRoutine (descrito en el paso siguiente). Para enviar la solicitud, llame al método WdfRequestSend .
Si envía la solicitud de forma sincrónica, llame a estos métodos:
Para obtener ejemplos de código, vea la sección Ejemplos de los temas de referencia de esos métodos.
Paso 3: Implementar una rutina de finalización para la solicitud
Si la solicitud se envía de forma asincrónica, debe implementar una rutina de finalización para recibir una notificación cuando la pila del controlador USB complete la solicitud. Tras la finalización, el marco invoca la rutina de finalización del controlador. El marco pasa estos parámetros:
- Identificador WDFREQUEST para el objeto de solicitud.
- Identificador WDFIOTARGET para el objeto de destino de E/S de la solicitud.
- Puntero a una estructura WDF_REQUEST_COMPLETION_PARAMS que contiene información de finalización. La información específica de USB se encuentra en el miembro CompletionParams-Parameters.Usb>.
- WDFCONTEXT controla el contexto que el controlador especificó en su llamada a WdfRequestSetCompletionRoutine.
En la rutina de finalización, realice estas tareas:
Compruebe el estado de la solicitud obteniendo el valor CompletionParams-IoStatus.Status>.
Compruebe el estado de USBD establecido por la pila de controladores USB.
En caso de errores de canalización, realice operaciones de recuperación de errores. Para obtener más información, consulte Recuperación de errores de canalización USB.
Compruebe el número de bytes transferidos.
Una transferencia masiva se completa cuando se ha transferido o desde el dispositivo el número solicitado de bytes. Si envía el búfer de solicitudes llamando al método KMDF, compruebe el valor recibido en los miembros CompletionParams-Parameters.Usb.Completion-Parameters.PipeWrite.Length>> o CompletionParams-Parameters.Usb.Completion-Parameters.PipeRead.Length>>.
En una transferencia sencilla en la que la pila del controlador USB envía todos los bytes solicitados en un paquete de datos, puede comprobar la comparación del valor length con el número de bytes solicitados. Si la pila del controlador USB transfiere la solicitud en varios paquetes de datos, debe realizar un seguimiento del número de bytes transferidos y el número restante de bytes.
Si se transfirió el número total de bytes, complete la solicitud. Si se ha producido una condición de error, complete la solicitud con el código de error devuelto. Complete la solicitud llamando al método WdfRequestComplete . Si desea establecer información, como el número de bytes transferidos, llame a WdfRequestCompleteWithInformation.
Asegúrese de que, al completar la solicitud con información, el número de bytes debe ser igual o menor que el número de bytes solicitado. El marco valida esos valores. Si la longitud establecida en la solicitud completada es mayor que la longitud de la solicitud original, se puede producir una comprobación de errores.
En este código de ejemplo se muestra cómo el controlador de cliente puede enviar una solicitud de transferencia masiva. El controlador establece una rutina de finalización. Esa rutina se muestra en el siguiente bloque de código.
/*++
Routine Description:
This routine sends a bulk write request to the
USB driver stack. The request is sent asynchronously and
the driver gets notified through a completion routine.
Arguments:
Queue - Handle to a framework queue object.
Request - Handle to the framework request object.
Length - Number of bytes to transfer.
Return Value:
VOID
--*/
VOID Fx3EvtIoWrite(
IN WDFQUEUE Queue,
IN WDFREQUEST Request,
IN size_t Length
)
{
NTSTATUS status;
WDFUSBPIPE pipe;
WDFMEMORY reqMemory;
PDEVICE_CONTEXT pDeviceContext;
pDeviceContext = GetDeviceContext(WdfIoQueueGetDevice(Queue));
pipe = pDeviceContext->BulkWritePipe;
status = WdfRequestRetrieveInputMemory(
Request,
&reqMemory
);
if (!NT_SUCCESS(status))
{
goto Exit;
}
status = WdfUsbTargetPipeFormatRequestForWrite(
pipe,
Request,
reqMemory,
NULL
);
if (!NT_SUCCESS(status))
{
goto Exit;
}
WdfRequestSetCompletionRoutine(
Request,
BulkWriteComplete,
pipe
);
if (WdfRequestSend( Request,
WdfUsbTargetPipeGetIoTarget(pipe),
WDF_NO_SEND_OPTIONS) == FALSE)
{
status = WdfRequestGetStatus(Request);
goto Exit;
}
Exit:
if (!NT_SUCCESS(status)) {
WdfRequestCompleteWithInformation(
Request,
status,
0
);
}
return;
}
Este código de ejemplo muestra la implementación de rutina de finalización para una transferencia masiva. El controlador cliente completa la solicitud en la rutina de finalización y establece esta información de solicitud: el estado y el número de bytes transferidos.
/*++
Routine Description:
This completion routine is invoked by the framework when
the USB drive stack completes the previously sent
bulk write request. The client driver completes the
the request if the total number of bytes were transferred
to the device.
In case of failure it queues a work item to start the
error recovery by resetting the target pipe.
Arguments:
Queue - Handle to a framework queue object.
Request - Handle to the framework request object.
Length - Number of bytes to transfer.
Pipe - Handle to the pipe that is the target for this request.
Return Value:
VOID
--*/
VOID BulkWriteComplete(
_In_ WDFREQUEST Request,
_In_ WDFIOTARGET Target,
PWDF_REQUEST_COMPLETION_PARAMS CompletionParams,
_In_ WDFCONTEXT Context
)
{
PDEVICE_CONTEXT deviceContext;
size_t bytesTransferred=0;
NTSTATUS status;
UNREFERENCED_PARAMETER (Target);
UNREFERENCED_PARAMETER (Context);
KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"In completion routine for Bulk transfer.\n"));
// Get the device context. This is the context structure that
// the client driver provided when it sent the request.
deviceContext = (PDEVICE_CONTEXT)Context;
// Get the status of the request
status = CompletionParams->IoStatus.Status;
if (!NT_SUCCESS (status))
{
// Get the USBD status code for more information about the error condition.
status = CompletionParams->Parameters.Usb.Completion->UsbdStatus;
KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Bulk transfer failed. 0x%x\n",
status));
// Queue a work item to start the reset-operation on the pipe
// Not shown.
goto Exit;
}
// Get the actual number of bytes transferred.
bytesTransferred =
CompletionParams->Parameters.Usb.Completion->Parameters.PipeWrite.Length;
KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Bulk transfer completed. Transferred %d bytes. \n",
bytesTransferred));
Exit:
// Complete the request and update the request with
// information about the status code and number of bytes transferred.
WdfRequestCompleteWithInformation(Request, status, bytesTransferred);
return;
}