Controladores USB sin dolores de cabeza: De la teoría al código con libusb
Escribir controladores para dispositivos USB a menudo intimida a los desarrolladores con la complejidad del código de bajo nivel. Sin embargo, con la herramienta adecuada como libusb, el proceso se vuelve tan sencillo como la programación de redes con sockets. En este artículo, recorreremos paso a paso la creación de un controlador para un dispositivo USB, usando un dispositivo Android en modo bootloader como ejemplo.
Conceptos básicos de USB: Lo que un desarrollador necesita saber
La especificación USB define un mecanismo estándar para identificar dispositivos mediante VID (Vendor ID) y PID (Product ID). Estos identificadores se asignan al fabricante (VID por USB-IF) y al producto específico (PID por el propio fabricante). Cuando un dispositivo se conecta, el host solicita descriptores: estructuras binarias en el firmware que contienen información sobre la clase del dispositivo, capacidades y controlador requerido. Punto clave: la mayoría de las tareas se pueden manejar en el espacio de usuario mediante controladores estándar como Winusb.sys (Windows) o usbfs (Linux), sin código del kernel.
Terminología importante:
- Endpoint: Canal lógico para la transferencia de datos (Control, Bulk, Interrupt, Isochronous).
- Device Descriptor: Estructura raíz con VID/PID y parámetros básicos.
- Device Class: Funcionalidad estandarizada (HID, Mass Storage), pero muchos dispositivos usan Vendor Specific Class.
Preparando el dispositivo: Selección y configuración
Usaremos un dispositivo Android en modo Bootloader (fastboot) como ejemplo. Razones para esta elección:
- Accesibilidad: La mayoría de los smartphones permiten cambiar a fastboot.
- Protocolo simple: La documentación de fastboot es abierta y minimalista.
- Sin controladores preinstalados: El SO no se apropia de la interacción.
Cambiar al modo Bootloader generalmente requiere mantener una combinación de botones durante el encendido (p. ej., volumen arriba + encendido). Para identificar el dispositivo en Linux, usa lsusb:
$ lsusb
Bus 008 Device 014: ID 18d1:4ee0 Google Inc. Nexus/Pixel Device (fastboot)
Aquí, 18d1 es el VID de Google, 4ee0 es el PID para fastboot. En Windows, USB Device Tree Viewer proporciona información similar. Punto clave: Si el SO carga un controlador (p. ej., aparece con un ⚠️ en el Administrador de dispositivos), deberás reemplazarlo con Winusb.sys usando Zadig.
Enumerando el dispositivo: De análisis manual a implementación programática
El proceso de enumeración comienza automáticamente al conectar. El SO analiza VID/PID y la clase del dispositivo para seleccionar un controlador. Para el espacio de usuario, lo implementamos con libusb. Aquí hay código para registrar un manejador de conexión:
#include <print>
#include <libusb-1.0/libusb.h>
auto hotplug_callback(
libusb_context *ctx,
libusb_device *device,
libusb_hotplug_event event,
void *user_data
) -> int {
std::println("Device plugged in!");
return 0;
}
auto main() -> int {
libusb_context *context = nullptr;
libusb_init(&context);
libusb_hotplug_callback_handle handle;
libusb_hotplug_register_callback(
context,
LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED,
LIBUSB_HOTPLUG_ENUMERATE,
0x18d1, 0x4ee0,
LIBUSB_HOTPLUG_MATCH_ANY,
hotplug_callback, nullptr,
&handle
);
while (true) {
if (libusb_handle_events(context) < 0) break;
}
libusb_hotplug_deregister_callback(context, handle);
libusb_exit(context);
}
Este código inicializa libusb, registra un callback para dispositivos con VID=0x18d1 y PID=0x4ee0, y espera la conexión. Al éxito, imprime "Device plugged in!". En Windows, podrías necesitar desconectar el controlador del kernel:
libusb_detach_kernel_driver(handle, 0);
Comunicando con el dispositivo vía Endpoint de Control
Para interacción básica, usa el Endpoint de Control (ID 0x00): el canal estándar para solicitudes de servicio. Aquí la implementación de una solicitud GET_STATUS:
libusb_device_handle *handle = nullptr;
libusb_open(device, &handle);
std::vector<std::uint8_t> data(0xFF);
const auto result = libusb_control_transfer(
handle,
LIBUSB_ENDPOINT_IN | LIBUSB_RECIPIENT_DEVICE | LIBUSB_REQUEST_TYPE_STANDARD,
LIBUSB_REQUEST_GET_STATUS,
0x00, 0x00,
data.data(), data.size(),
1000
);
if (result >= 0) print_bytes(std::span(data).subspan(0, result));
libusb_close(handle);
Una respuesta exitosa (p. ej., 01 00) se decodifica según la espec: primer byte: fuente de energía (1 = con batería), segundo: soporte para wakeup remoto (0 = no soportado). El Endpoint de Control permite otras solicitudes estándar también (GET_DESCRIPTOR, SET_CONFIGURATION), cruciales para obtener descriptores.
Solicitando descriptores: La clave para entender el dispositivo
Los descriptores son la base de la interacción con dispositivos USB. Solicita el descriptor de dispositivo vía GET_DESCRIPTOR:
const auto result = libusb_control_transfer(
handle,
LIBUSB_ENDPOINT_IN | LIBUSB_RECIPIENT_DEVICE | LIBUSB_REQUEST_TYPE_STANDARD,
LIBUSB_REQUEST_GET_DESCRIPTOR,
(LIBUSB_DT_DEVICE << 8), // Tip deskriptora
0x00,
data.data(), data.size(),
1000
);
Los datos recibidos coinciden con la estructura libusb_device_descriptor con campos como:
bcdUSB: versión de la especificación USBbMaxPacketSize0: tamaño máximo de paquete para Endpoint de ControlidVendor/idProduct: VID y PIDbNumConfigurations: número de configuraciones
Analizando descriptores revela interfaces y endpoints soportados. Para un descriptor de configuración, cambia el tipo de solicitud a LIBUSB_DT_CONFIG y especifica el índice de config en wIndex.
Puntos clave
- Operación en espacio de usuario: Usa libusb en lugar de código del kernel: más fácil de depurar y más seguro.
- VID/PID como identificadores principales: El enlace del controlador depende de ellos; la clase del dispositivo a menudo es poco informativa (Vendor Specific Class).
- Endpoint de Control como herramienta básica: Úsalo para obtener descriptores y controlar el dispositivo antes de configurar otros endpoints.
- Descriptores como fuente de verdad: Su estructura está definida en la espec de USB; decodificarlos proporciona una imagen completa de las capacidades del dispositivo.
— Editorial Team
Aún no hay comentarios.