USB для разработчиков: как написать драйвер из пользовательского пространства
USB-драйвер можно написать целиком из userspace — без кода ядра. Endpoint как порты, 4 типа передачи, libusb API и Fastboot-клиент за 50 строк.
Вам вручили USB-устройство и попросили написать для него драйвер. Звучит страшно? На самом деле это не сложнее, чем написать приложение с сокетами. Не нужно лезть в ядро — всё можно сделать из пользовательского пространства с помощью библиотеки libusb.
Это перевод статьи WerWolv — введение в USB для разработчиков, которые не работали с железом. Концепции объясняются через аналогию с сетевым программированием, а практический пример — реализация протокола Android Fastboot.
Ключевые выводы
- USB-драйвер можно написать целиком в userspace через libusb — без кода ядра
- Endpoint в USB — аналог порта в TCP: устройство сообщает, какие «порты» открыты и какой протокол используют
- 4 типа передачи: Control (конфигурация), Bulk (большие данные), Interrupt (низкая задержка), Isochronous (аудио/видео)
- USB — master-slave: хост всегда инициирует обмен, устройство только отвечает
- Практический пример: полноценный Fastboot-клиент на C++ за ~50 строк кода
USB — это как сеть
Ключевая мысль статьи: USB можно понимать через аналогию с сетевым программированием. Endpoint — это порт, на который устройство принимает данные. Дескриптор устройства — аналог DNS, который говорит, кто это и какие «сервисы» доступны. Вам не нужно быть инженером встраиваемых систем, чтобы работать с USB — как не нужно быть сетевым специалистом, чтобы работать с сокетами.
Шаг 1: узнаём, что за устройство
При подключении устройства хост запрашивает у него информацию — это называется enumeration. На Linux это можно увидеть через lsusb:
Здесь 18d1 — Vendor ID (Google), 4ee0 — Product ID (загрузчик Pixel). Это уникальные идентификаторы, по которым хост находит нужный драйвер. VID выдаётся организацией USB-IF компаниям за деньги, PID назначает сам производитель.
Более подробный вывод:
Driver=[none] означает, что ОС не загрузила драйвер — именно то, что нужно для написания своего.
Шаг 2: общаемся через libusb
Библиотека libusb позволяет общаться с USB-устройствами из пользовательского пространства. Она предоставляет универсальный драйвер и API для отправки запросов — без написания кода ядра.
Пример: обнаружение устройства по VID:PID и получение дескриптора через Control endpoint:
Каждое USB-устройство имеет Control endpoint на адресе 0x00 — это стандартизированная «точка входа», через которую хост получает дескрипторы (кто устройство, какие интерфейсы, какие endpoint'ы). Аналогия: это как DNS-запрос перед подключением.
Endpoint'ы: порты USB-устройства
Endpoint'ы — аналог портов на сетевом устройстве. Устройство описывает в дескрипторе, какие endpoint'ы доступны и какой протокол каждый из них использует. Четыре типа:
- Control — один на устройство, адрес 0x00. Для конфигурации и запроса информации. Решает проблему курицы и яйца: чтобы узнать endpoint'ы, нужен endpoint — и это он
- Bulk — большие объёмы данных, низкий приоритет. Используется в Mass Storage (флешки), CDC-ACM (Serial over USB), RNDIS (Ethernet over USB)
- Interrupt — маленькие данные, минимальная задержка. Клавиатуры и мыши опрашиваются 1000+ раз в секунду через HID. Название обманчивое: реальных прерываний нет, хост просто очень часто опрашивает
- Isochronous — потоковые данные с гарантией тайминга. Аудио и видео, где задержка означает заикание
IN и OUT: USB — master-slave протокол, устройство никогда не говорит первым. IN = хост просит данные «к себе», OUT = хост отправляет данные «от себя». Направление кодируется в старшем бите адреса endpoint'а.
Практика: Fastboot-клиент за 50 строк
Протокол Fastboot (документация) предельно прост: хост отправляет строковую команду, устройство отвечает 4-символьным статусом + данные:
Реализация через libusb — отправляем команду на OUT Bulk endpoint, читаем ответ с IN Bulk endpoint:
Это всё. Два вызова libusb — и мы общаемся с загрузчиком Android без единой строки кода ядра.
Частые вопросы
Работает ли libusb на Windows?
Да, но с нюансами. Если устройство имеет Microsoft OS Descriptor, Windows автоматически загрузит WinUSB-драйвер. Если нет — придётся использовать утилиту Zadig для установки совместимого драйвера. На Linux и macOS libusb работает из коробки.
Нужно ли разбираться в электронике?
Нет. USB абстрагирует физический уровень. Для написания userspace-драйвера достаточно понимать endpoint'ы и типы передачи — всё это логические концепции, не электрические.
Зачем писать свой драйвер, если есть готовые?
Кастомные устройства (IoT-сенсоры, программаторы, специализированное оборудование) часто используют Vendor Specific Class и не имеют стандартных драйверов. Userspace-драйверы через libusb — самый быстрый способ начать с ними работать.
Полный текст с исходным кодом: USB for Software Developers, WerWolv.