Los cuatro tipos de transferencia USB
Control, bulk, interrupt e isochronous: los cuatro compromisos entre ancho de banda garantizado, latencia acotada y corrección de errores que USB ofrece sobre un mismo cable. El paquete setup de las transferencias de control, el emparejamiento de cada tipo con su clase de dispositivo, y cómo elegir el pipe y el helper de relleno correctos.
USB fue diseñado para llevar, por el mismo cable, el clic de un ratón, un firmware hacia una impresora, un terabyte desde un SSD y el vídeo en vivo de una webcam. Esas necesidades son irreconciliables bajo una sola política, así que USB define cuatro tipos de transferencia, cada uno un pacto distinto entre ancho de banda garantizado, latencia acotada y corrección de errores. Cada endpoint declara en su descriptor cuál de los cuatro habla, y esa declaración es, en el fondo, una petición de calidad de servicio.
- Distinguir los cuatro tipos —control, bulk, interrupt, isochronous— y su compromiso característico.
- Emparejar cada tipo con las clases de dispositivo que lo usan.
- Construir el paquete setup de una transferencia de control.
- Elegir el pipe y el helper de relleno correctos para cada tipo.
Control: el canal de mando
Una transferencia de control tiene una estructura única: una etapa setup de ocho bytes, una etapa de datos opcional y una etapa de estado. Discurre por el endpoint 0 bidireccional, tiene ancho de banda reservado y comprobación de errores, y es el vehículo de toda la enumeración (nivel 31.2) y de cualquier petición de configuración. El paquete setup es literalmente esta estructura:
struct usb_ctrlrequest {
__u8 bRequestType; /* direccion, tipo (standard/class/vendor), destinatario */
__u8 bRequest; /* el codigo de peticion */
__le16 wValue;
__le16 wIndex;
__le16 wLength; /* bytes de la etapa de datos */
} __attribute__ ((packed));
Para una petición puntual, usb_control_msg arma el setup, lo envía y espera de forma síncrona:
ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
USB_REQ_GET_STATUS, /* bRequest */
USB_DIR_IN | USB_TYPE_STANDARD, /* bRequestType */
0, 0, buf, 2, USB_CTRL_GET_TIMEOUT);
Bulk: datos masivos sin prisa
El tipo bulk mueve grandes volúmenes con fiabilidad garantizada —CRC y retransmisión automática hasta que los datos llegan intactos— pero sin ninguna garantía de tiempo ni de ancho de banda: usa el que sobra tras servir al tráfico periódico. Da el máximo caudal cuando el bus está libre y sufre latencia impredecible cuando está saturado. Es la elección del almacenamiento masivo, las impresoras y las redes CDC. No existe en dispositivos de baja velocidad.
int actual;
ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, dev->bulk_out_addr),
data, len, &actual, 5000 /* ms */);
Para alto rendimiento se usa la versión asíncrona con URBs y usb_fill_bulk_urb del nivel 31.4; usb_bulk_msg es el atajo síncrono para transferencias sencillas.
Interrupt: poco dato, latencia acotada
El nombre engaña: una transferencia interrupt no es una interrupción que el dispositivo lance, sino un endpoint que el host sondea a un intervalo máximo garantizado, fijado por bInterval. Ofrece latencia acotada y comprobación de errores, pero un caudal minúsculo. Es el tipo de los dispositivos de interacción humana (HID): teclados, ratones. El patrón de driver es un URB de interrupt que se reenvía sin fin desde su callback (nivel 31.4).
usb_fill_int_urb(urb, udev,
usb_rcvintpipe(udev, ep->bEndpointAddress),
buf, len, kbd_irq_complete, dev,
ep->bInterval); /* periodo de sondeo del descriptor */
usb_submit_urb(urb, GFP_KERNEL);
Isochronous: streaming con tiempo, sin garantía de entrega
El tipo isochronous invierte por completo el pacto de bulk: garantiza ancho de banda y cadencia constante, pero no retransmite nunca. Si un paquete llega corrupto o tarde, se descarta y la vida sigue, porque en audio y vídeo una muestra vieja es peor que una muestra perdida: un chasquido momentáneo es preferible a que todo el flujo se congele esperando un dato que ya no sirve. Es el tipo de las webcams (UVC) y las tarjetas de sonido (UAC). Un URB isócrono transporta muchos paquetes, cada uno con su propio estado:
urb = usb_alloc_urb(NPACKETS, GFP_KERNEL); /* reserva N paquetes iso */
urb->dev = udev;
urb->pipe = usb_rcvisocpipe(udev, ep->bEndpointAddress);
urb->interval = ep->bInterval;
urb->transfer_flags = URB_ISO_ASAP; /* programar en cuanto se pueda */
urb->number_of_packets = NPACKETS;
for (i = 0; i < NPACKETS; i++) {
urb->iso_frame_desc[i].offset = i * psize;
urb->iso_frame_desc[i].length = psize; /* tam. esperado por paquete */
}
Tras el completado, cada iso_frame_desc[i].status y .actual_length te dicen cómo fue ese paquete concreto: unos pueden llegar íntegros y otros perderse, y el driver de vídeo lo tolera por diseño.
Control e interrupt
Los dos tipos con garantías de tiempo o de mando: control reserva banda para la enumeración; interrupt garantiza un sondeo periódico de baja latencia para teclados y ratones.
Bulk e isochronous
Los dos tipos de volumen: bulk prioriza la integridad sin plazo (SSD, impresoras); isochronous prioriza la cadencia sin reintento (audio, webcam).
Un driver no elige el tipo de transferencia a su antojo: lo dicta el endpoint en los dos bits bajos de su bmAttributes (nivel 31.2). Tu trabajo es leer ese campo, construir el pipe adecuado (usb_rcvbulkpipe, usb_rcvintpipe, usb_rcvisocpipe) y usar el helper de relleno que le corresponde. Programar un endpoint bulk como si fuera interrupt sencillamente no funciona.
El host reparte cada microtrama sirviendo primero a quien prometió un plazo y dejando para el final a quien no prometió nada:
flowchart LR MF[Microtrama de 125 us] --> P[Periodico iso e interrupt con plazo] P --> C[Control porcion reservada] C --> B[Bulk usa el ancho de banda sobrante]
Detrás de estos cuatro nombres se esconde uno de los teoremas no escritos de los sistemas: no puedes maximizar a la vez el caudal, acotar la latencia y garantizar la entrega. Son objetivos en tensión, y cualquier sistema que mueva datos —una CPU repartiendo su tiempo, un router aplicando QoS, un planificador de disco ordenando peticiones— tiene que elegir cuál sacrificar en cada caso. USB hace esa elección explícita y la graba en el hardware. El host reparte cada microtrama de 125 microsegundos con un orden de prioridades que es puro sentido común una vez lo ves: primero sirve el tráfico periódico —isochronous e interrupt— porque ambos prometieron plazos que no pueden incumplirse; luego concede una porción reservada al control, para que la enumeración nunca se ahogue; y con lo que sobra alimenta al bulk, que no prometió tiempo alguno y por eso puede esperar. Esa jerarquía convierte a bmAttributes en algo mucho más profundo que un campo de configuración: es una declaración de calidad de servicio, la forma en que cada endpoint negocia su lugar en un recurso compartido y finito. Y aquí está la lección que trasciende a USB: cuando entiendes que isochronous descarta en vez de retransmitir no por descuido sino por una decisión deliberada de que la puntualidad importa más que la integridad, dejas de ver cuatro APIs distintas y empiezas a ver cuatro puntos de un mismo espacio de compromisos. El tiempo real frente al mejor esfuerzo, la entrega garantizada frente a la cadencia garantizada: los mismos ejes que gobiernan las redes, los planificadores y los sistemas de tiempo real, condensados en un solo cable y en dos bits de un descriptor.
- Con
lsusb -vsobre un teclado, un SSD y unos auriculares, identifica el tipo de cada endpoint leyendobmAttributesy empareja cada uno con su compromiso característico. - Construye una transferencia de control con
usb_control_msgque pida el estado del dispositivo, y explica el significado de cada campo del paquete setup. - Calcula el ancho de banda de un endpoint interrupt a partir de su
wMaxPacketSizey subInterval, y razona por qué un teclado no necesita más. - Argumenta por qué una webcam usa isochronous y toleraría perder un fotograma, mientras que un SSD usa bulk y jamás toleraría perder un sector.