24 puntos por GN⁺ 20 일 전 | 1 comentarios | Compartir por WhatsApp
  • Aunque el desarrollo de controladores USB suele considerarse trabajo a nivel de kernel, en la práctica también puede implementarse en espacio de usuario con una dificultad similar a la programación con sockets
  • Con libusb es posible realizar enumeración de dispositivos, transferencias de control y envío/recepción de datos sin escribir código de kernel
  • La comunicación USB se compone de cuatro tipos de transferencia: Control, Bulk, Interrupt, Isochronous, además de la dirección IN/OUT; cada endpoint funciona como un canal unidireccional
  • Usando el protocolo Fastboot de dispositivos Android como ejemplo, se muestra en código cómo intercambiar comandos y respuestas a través de endpoints Bulk
  • Incluso en espacio de usuario es posible implementar un controlador USB completo, y todos los protocolos USB comparten la misma estructura básica

Introducción

  • Los controladores para dispositivos USB suelen parecer difíciles porque existe la idea de que hay que trabajar con código de kernel, pero en realidad tienen una complejidad comparable a la de una aplicación que usa sockets
  • Incluso desarrolladores sin mucha experiencia en hardware pueden aprender cómo manejar USB desde espacio de usuario
  • Existen materiales que cubren el funcionamiento detallado de USB, pero para principiantes suelen ser poco accesibles
  • Usar USB no requiere conocimientos de nivel de sistemas embebidos y puede abordarse de forma similar a los sockets de red

Dispositivos USB

  • Como ejemplo se usa un smartphone Android en modo bootloader
    • Es fácil de conseguir, el protocolo es simple y, como el sistema operativo no incluye un controlador predeterminado, resulta ideal para experimentar
  • La forma de entrar al modo bootloader varía según el dispositivo, pero por lo general se logra con una combinación del botón de encendido y los botones de volumen

Enumeración manual del dispositivo

  • La enumeración (Enumeration) es el proceso mediante el cual el host solicita información del dispositivo para identificarlo, y se realiza automáticamente al conectarlo
  • Los dispositivos estándar cargan controladores automáticamente según su clase USB, mientras que los dispositivos específicos del fabricante usan VID (Vendor ID) y PID (Product ID)
  • En Linux se puede consultar la información del dispositivo con el comando lsusb
    • Ejemplo: ID 18d1:4ee0 Google Inc. Nexus/Pixel Device (fastboot)
    • 18d1 es el VID de Google y 4ee0 es el PID del bootloader de Nexus/Pixel
  • Con el comando lsusb -t se puede revisar la clase y el estado del controlador
    • Si aparece Class=Vendor Specific Class, Driver=[none], significa que el sistema operativo no cargó ningún controlador
  • En Windows se puede verificar la misma información con Device Manager o USB Device Tree Viewer

Enumeración del dispositivo con libusb

  • La biblioteca libusb permite comunicarse con dispositivos USB desde espacio de usuario sin escribir código de kernel
  • Con libusb_hotplug_register_callback() se puede configurar un callback para ejecutarse cuando se conecte un dispositivo con una combinación específica de VID:PID
  • Al conectar el dispositivo después de ejecutar el programa, se muestra el mensaje "Device plugged in!"
  • En Linux funciona de forma predeterminada y, si hace falta, se puede desacoplar el controlador del kernel con libusb_detach_kernel_driver()
  • En Windows se necesita el controlador Winusb.sys; si no está disponible, puede reemplazarse manualmente con la herramienta Zadig

Comunicación con el dispositivo

  • La primera comunicación con un dispositivo USB se realiza mediante el endpoint de Control (dirección 0x00)
  • Con libusb_control_transfer() se envía una solicitud estándar (GET_STATUS) para leer el estado del dispositivo
    • Ejemplo de respuesta: 01 00 → el primer byte indica Self-Powered, y el segundo que no es compatible con Remote Wakeup
  • Después se puede obtener el descriptor del dispositivo mediante una solicitud GET_DESCRIPTOR
    • Los datos devueltos incluyen información como idVendor, idProduct, bDeviceClass, entre otros
  • Con el comando lsusb -v se pueden consultar en detalle todos los descriptores (dispositivo, configuración, interfaz, endpoint, etc.)
    • Ejemplo: la interfaz Android Fastboot tiene endpoints Bulk IN (0x81) y Bulk OUT (0x02)

Endpoints

  • Un endpoint es un concepto similar a un puerto de red: un canal por el que el dispositivo envía y recibe datos
  • En el descriptor se define el tipo y la dirección de cada endpoint
  • Tipo de transferencia Control

    • Todos los dispositivos tienen uno y su dirección siempre es 0x00
    • Se usa para la configuración inicial y para solicitar información del dispositivo
    • No pertenece a una interfaz, sino que existe como parte del propio dispositivo
  • Tipo de transferencia Bulk

    • Se usa para transferencia de grandes volúmenes de datos no sensibles al tiempo real
    • Ejemplos: Mass Storage, CDC-ACM (serial), RNDIS (Ethernet)
    • Tiene gran ancho de banda, pero baja prioridad
  • Tipo de transferencia Interrupt

    • Se usa para transferencias de poca cantidad de datos y baja latencia
    • En teclados y ratones, por ejemplo, sirve para sondear rápidamente la entrada de botones
    • No es una interrupción de hardware real; el host hace solicitudes periódicas
  • Tipo de transferencia Isochronous

    • Se usa para datos voluminosos sensibles al tiempo (streaming de audio y video)
    • Si hay latencia, la pérdida de calidad se nota de inmediato
    • En libusb se maneja de forma asíncrona
  • Dirección IN / OUT

    • USB tiene una arquitectura centrada en el host, por lo que el dispositivo no transmite datos hasta recibir una solicitud
    • IN: dirección en la que el host recibe datos
    • OUT: dirección en la que el host envía datos
    • Si el bit más significativo (MSB) de la dirección del endpoint es 1, es IN; si es 0, es OUT
    • Se pueden usar hasta 127 endpoints definidos por el usuario (0x00 está reservado para Control)
    • Los endpoints son unidireccionales y suelen organizarse en pares IN/OUT, como en la interfaz Fastboot

Protocolo Fastboot

  • Fastboot es el protocolo de comunicación del bootloader de Android; su estructura consiste en enviar una cadena de comando y recibir un código de estado de 4 bytes junto con datos
    • Ejemplos:
      • Host: "getvar:version"Client: "OKAY0.4"
      • Host: "getvar:nonexistant"Client: "OKAY"
  • Se muestra un ejemplo de código que envía comandos Fastboot usando libusb
    • Se toma control de la interfaz 0 con libusb_claim_interface()
    • El comando "getvar:version" se envía al endpoint Bulk OUT (0x02)
    • La respuesta se recibe desde el endpoint Bulk IN (0x81)
    • Ejemplo de salida:
      Request: getvar:version
      Response: OKAY0.4
      
    • OKAY indica éxito y 0.4 es la versión de Fastboot

Cierre

  • Es posible implementar un controlador USB completo desde espacio de usuario sin escribir código de kernel
  • Todos los controladores USB siguen los mismos principios básicos; lo que cambia es el protocolo
  • Incluso protocolos complejos (como MTP) comparten la misma estructura básica y pueden abordarse con una idea similar a la comunicación por sockets

1 comentarios

 
GN⁺ 20 일 전
Comentarios en Hacker News
  • Justo llegó en el momento perfecto. Pronto voy a recoger un MOTU MIDI Express XT en el Guitar Center local
    Como es equipo usado, por ley tienen que retenerlo cierto tiempo, así que estoy esperando. El problema es que este equipo no usa MIDI-over-USB estándar, sino un protocolo propietario, así que no puedo usarlo directamente por USB en mis sistemas como Linux, OpenBSD o Haiku
    Por ahora está bien porque solo necesito enrutar entre módulos de sintetizador y controladores, pero estaría bueno lograr que también funcione del lado de la PC
    Sí existe un driver de Linux, pero no está clara su estabilidad ni si soporta el XT. Dicen que ya se resolvió el problema de kernel panic, pero siguen quedando issues
    Así que estoy pensando en hacer yo mismo un driver en espacio de usuario basado en LibUSB. Si expone puertos MIDI y además agrega herramientas de routing, podría quedar bastante útil

    • El período de espera en Guitar Center no es solo para verificar si fue robado. Legalmente tienen la obligación de no venderlo durante cierto tiempo, como una casa de empeño (pawn shop), para que pase el plazo en el que el dueño original podría recuperarlo
    • Yo uso el mismo equipo y empaqueté ese driver en AUR. El blob binario no funcionó, pero alcanza para usarlo como router MIDI simple
  • Si quieres probar algo así en Go, hice la librería go-usb, que permite acceso USB sin cgo
    Con eso también desarrollé go-uvc para manejar dispositivos UVC

    • En Rust recomiendo nusb
  • Yo también estoy implementando recientemente un sistema usbip de forma parecida en una Macbook M3
    Pero en las versiones recientes de macOS hay limitaciones. Para dispositivos USB que el sistema reconoce, no puedes construir un driver en espacio de usuario basado en libusb a menos que desactives manualmente funciones de seguridad

    • El override del driver se puede mitigar porque solo hay que ajustar una sola capa
  • Este enfoque al final hace que el driver USB también cumpla el papel de código de aplicación. O sea, se parece más a una librería + programa que a un driver
    Por ejemplo, me pregunto cómo harías para conectar un dispositivo USB-Ethernet como adaptador de red del sistema operativo

    • Los dispositivos estandarizados normalmente usan USB/CDC/ECM o RNDIS, así que se reconocen automáticamente. El acceso desde espacio de usuario es más útil para dispositivos no estándar. En Windows se puede implementar de forma portable con libusb sin firma de drivers
    • En Linux tendrías que crear un dispositivo tun/tap para comunicarte con el kernel desde espacio de usuario, o ejecutar también otros subsistemas en espacio de usuario
  • Si hubiera leído este artículo hace unos años, me habría sido mucho más fácil hacer ingeniería inversa de funciones de una laptop. En especial, el programa de control de LEDs del teclado sigue siendo uno de mis proyectos favoritos

  • Fue una introducción realmente útil. Trabajar con APIs de hardware de bajo nivel es difícil, pero muy gratificante. Las capas de abstracción de los sistemas operativos modernos lo han hecho más fácil, pero igual sigue siendo importante entender lo que hay debajo

  • El código en C++ se veía raro. Nunca he visto un teclado donde puedas escribir directamente el carácter de flecha

    • Eso es una ligadura de una fuente de programación. Si lo copias, en realidad se ve como ->. Es la sintaxis de trailing return type de C++ moderno
    • Algunos desarrolladores prefieren fuentes con ligaduras. Unen dos caracteres en un solo glifo
    • Si configuras una tecla Compose, puedes escribir “→” con cualquier teclado
    • Al final no es más que "->". La fuente solo lo renderiza como una flecha
  • Me preguntaba si un dispositivo USB soporta DMA. Quería saber si solo es posible a través del host o si el dispositivo también puede acceder directamente a la memoria

    • Los dispositivos USB no acceden directamente a la memoria del host como PCIe o FireWire. En su lugar, el controlador XHCI es el que hace DMA, y la mayoría de los controladores de dispositivos sí soportan DMA entre su propia RAM y USB
    • Todas las transferencias están dirigidas por el host. Aunque parezca que el dispositivo envía datos primero, en realidad es el host quien los solicita. El DMA directo sería un gran riesgo de seguridad
  • Hace tiempo intenté crear un dispositivo USB sencillo, pero casi no había información sobre cómo escribir descriptores (descriptors). Casi todo era del estilo “busca un dispositivo parecido, cópialo y ajústalo”. Me hizo dudar de si USB de verdad es un estándar tan bueno

    • A mí también me parecían misteriosos los descriptores, pero al final entendí que son estructuras binarias fijas. Mientras coincidan los campos y endpoints que define cada clase USB, el dispositivo será reconocido
    • USB está bien, pero en la parte eléctrica USB 1/2 no es una señal diferencial verdadera
    • Casi no hay tutoriales, pero para ser un estándar de grandes empresas, es bastante razonable. Eso sí, hay demasiadas opciones, así que toca leer muchas especificaciones relacionadas
  • Si me pidieran “escribe tú mismo el driver de este dispositivo USB”, yo devolvería el dispositivo y primero vería si no se puede resolver con un puerto COM virtual