Оригинальная статья | Разработка USB-устройства: от вступления к практическому руководству (1)
Оригинальная статья | Разработка USB-устройства: от вступления к практическому руководству (1)

Автор: Hcamael@knowchuangyu 404 лаборатория

Время: 27 февраля 2024 г.

В процессе использования Google для поиска соответствующих учебных материалов я нашел книгу - «Научимся играть по USB по кругу». Во время чтения я обнаружил, что мне необходимо приобрести соответствующее аппаратное оборудование.

Что касается разработки аппаратного обеспечения, мы рассмотрим возможность проведения обучения и исследований на более позднем этапе, а на раннем этапе мы рассмотрим возможность использования существующего оборудования для обучения и исследований. Первым решением было использование мобильного телефона в качестве USB-устройства для подключения к компьютеру. Однако после исследования выяснилось, что поддержка разработки USB-устройств в Android не очень удобна. Кроме того, разработка USB инкапсулирована на уровне Android. , что затруднит понимание основных деталей USB.

Впоследствии, учитывая, что у меня под рукой было устройство Raspberry Pi 4b, я решил попробовать использовать это устройство для разработки USB-устройств. Выполнив поиск в Google, я нашел проект под названием key-mime-pi, который послужил для меня отправной точкой.

1 Инструменты для разработки USB-устройств

Ссылки

После некоторых исследований, поскольку визуализация инструментов, связанных с USB, в Windows лучше, я наконец выбрал устройство Windows в качестве USB-хоста.

1.1 USB Tree View

Первый инструмент — USB Tree View, который хорошо отображает статус главного-подчиненного дерева USB-устройств на хосте, информацию о дескрипторе USB и другие данные. Как показано на рисунке 1:

Рис. 1. Интерфейс USB Tree View

Как видно из дерева USB-устройств на рисунке выше, самым низким уровнем хоста является хост-контроллер USB, который напрямую подключен к ЦП. Затем верхний уровень подключается к корневому концентратору USB (HUB), который можно сравнить с док-станцией USB. Его функция заключается в расширении нескольких интерфейсов USB, но общая пропускная способность по-прежнему определяется хост-контроллером USB.

Например, на рисунке выше версия хост-контроллера USB — USB3.1, тогда корневой концентратор USB на верхнем уровне может расширять порты USB уровня USB3.1 и ниже, например порт USB2.0. Однако независимо от того, сколько портов USB будет расширено, общая пропускная способность будет зафиксирована на уровне 10 Гбит/с (поскольку пропускная способность USB3.1 составляет 10 Гбит/с). Кроме того, к концентратору можно подключить несколько концентраторов. Например, на рисунке выше показан универсальный USB-концентратор, который на самом деле представляет собой интерфейс USB, расширенный шасси на примере хоста.

Кроме того, нажмите на подключенное USB-устройство, чтобы просмотреть более подробную информацию о USB-устройстве, как показано на рисунке 2:

Рис. 2. Просмотр сведений об устройстве USB в дереве USB.

Если вы поняли информацию, содержащуюся в каждой части изображения выше, она будет объяснена позже. Однако этот инструмент также имеет некоторые ошибки. Инструмент не может нормально анализировать информацию HID, как показано на рисунке 3:

Рисунок 3: HID-дескриптор

1.2 Wireshark

После установки USBPcap на Wireshark в Windows он может захватывать USB-трафик на хост-контроллере. Например, на хосте есть три хост-контроллера, поэтому в Wireshark отображаются три USBPcap, как показано на рисунке 4.

Рисунок 4. Интерфейс Wireshark.

Если вы не знаете, какому хост-контроллеру принадлежит USB-порт, вставленный в настройки USB, вы можете использовать USBTree View для его проверки. Однако, поскольку Wireshark захватывает трафик на хост-контроллере, а хост-контроллер USB может подключаться к нескольким USB-устройствам, когда я хочу изучить определенное USB-устройство, мне нужно использовать выражение фильтра Wireshark для фильтрации трафика хост-контроллера. другие USB-устройства фильтруются.

1.3 Bus Hound

Bus HoundМожно захватить указанныйUSBТрафик устройства,Однако недостатком этого инструмента является то, что он стоит денег.,В противном случае размер и количество перехваченных пакетов ограничены.,Взломанная версия этого ПО пока не найдена. На рисунке 5 показан интерфейс программного обеспечения:

Рисунок 5: Интерфейс Bus Hound

2 Использование Raspberry Pi 4b в качестве USB-клавиатуры для ПК

Ссылки

Следующий,читаяkey-mime-piИсходный код проекта,Я обнаружил, что эмулировать USB-устройство с помощью устройства Raspberry Pi 4b очень легко. По коду проекта,Его можно разбить на следующие два этапа:

1.Нужно включитьdwc2водить машину:на малине пиconfig.txtДобавитьdtoverlay=dwc2,После запуска устройства,Проверьте, включен ли автозапуск:

Язык кода:javascript
копировать
$ lsmod|grep dwc2
dwc2                  196608  0

2.Запустите, как показано нижеbashСкрипт:

Язык кода:javascript
копировать
#!/usr/bin/env bash

# Adapted from https://github.com/girst/hardpass-sendHID/blob/master/README.md

# Exit on first error.
set -e

# Treat undefined environment variables as errors.
set -u

modprobe libcomposite

cd /sys/kernel/config/usb_gadget/
mkdir -p g1
cd g1

echo 0x1d6b > idVendor  # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB    # USB2

STRINGS_DIR="strings/0x409"
mkdir -p "$STRINGS_DIR"
echo "6b65796d696d6570690" > "${STRINGS_DIR}/serialnumber"
echo "keymimepi" > "${STRINGS_DIR}/manufacturer"
echo "Generic USB Keyboard" > "${STRINGS_DIR}/product"

FUNCTIONS_DIR="functions/hid.usb0"
mkdir -p "$FUNCTIONS_DIR"
echo 1 > "${FUNCTIONS_DIR}/protocol" # Keyboard
echo 0 > "${FUNCTIONS_DIR}/subclass" # No subclass
echo 8 > "${FUNCTIONS_DIR}/report_length"
# Write the report descriptor
# Source: https://www.kernel.org/doc/html/latest/usb/gadget_hid.html
echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > "${FUNCTIONS_DIR}/report_desc"

CONFIG_INDEX=1
CONFIGS_DIR="configs/c.${CONFIG_INDEX}"
mkdir -p "$CONFIGS_DIR"
echo 250 > "${CONFIGS_DIR}/MaxPower"

CONFIGS_STRINGS_DIR="${CONFIGS_DIR}/strings/0x409"
mkdir -p "$CONFIGS_STRINGS_DIR"
echo "Config ${CONFIG_INDEX}: ECM network" > "${CONFIGS_STRINGS_DIR}/configuration"

ln -s "$FUNCTIONS_DIR" "${CONFIGS_DIR}/"
ls /sys/class/udc > UDC

chmod 777 /dev/hidg0

Изучив вышеизложенноеbashСкрипт,Выяснилось, что в этом проекте используется драйвер USB-гаджета системы Linux.,При необходимости вы можете просмотреть исходный код этой части самостоятельно.,родыLinuxЯдро:linux/drivers/usb/dwc2иlinux/drivers/usb/gadgetв каталоге。

该водить машину细节существовать本文暂不进行研究。Запуск вышеизложенногоbashСкрипт后,Если не случайно,Можно найти вUSB Tree ViewПросмотрите смоделированный вывод устройства Raspberry Pi вUSBклавиатура。

2.1 Понимание протокола USB посредством повторного изучения трафика

Вот необходимые знания: USB — это структура «главный-подчиненный». Должен быть один USB-хост и одно USB-устройство, и связь всегда инициируется хостом.

В контексте этой статьи сначала откройте Wireshark, начните захват трафика USBPcap1, а затем подключите порт type-c (порт источника питания) Raspberry Pi. Четыре гнездовых порта USB устройства Raspberry Pi 4b можно использовать только в качестве. USB-хосты могут использоваться только в качестве USB-устройства) подключен к USB-порту хоста. В настоящее время хост не распознает Raspberry Pi как USB-устройство и подает только питание. поэтому Raspberry Pi начинает запускаться в это время.

Затем начните фильтровать все адреса трафика, которые может перехватить Wireshark. Они не принадлежат USB-трафику Raspberry Pi. Как показано на рисунке 6, синтаксис фильтрации используется в этой статье:

Рисунок 6. Синтаксис фильтра Wireshark.

Затем на Raspberry Pi используйтеrootРазрешения на запуск указанного вышеbashСкрипт,Еще раз проверьте трафик, перехваченный Wireshark.,Как показано на рисунке 7:

Рисунок 7. Трафик, перехваченный Wireshark на основе синтаксиса фильтра.

Адрес USB-трафика, захватываемый с помощью USBPcap, обычно имеет вид: x.y.z, x — номер шины USB (обычно номер хост-контроллера). Для трафика USBPcap1, перехваченного в этом примере, значение x равно 1.

y - номер устройства,Наведите указатель на устройство в шине,ноUSBPcapиUSBTreeViewСпособы нумерации устройств разные.。USBPcapНомера устройств определяются по порядку,иUSBTreeViewто в начале это уже для всехUSBИнтерфейсы пронумерованы,Независимо от того, подключено USB-устройство или нет.

По моему разумению,USBPcapМожет больше соответствовать реальной ситуации,Потому что он не составил свой собственный номер,Вместо этого он анализирует захваченный USB-трафик.

Значение z представляет номер конечной точки (конечная точка).,Я думаю, это немного похоже на файловый дескриптор программы (fd).,Связь между USB Хозяином и устройством осуществляется через номер конечной точки.,Когда USB-устройство не зарегистрировано на Хозяине,Используется по умолчанию0номер конечной точки для связи。

2.1.1 Дескриптор устройства

Далее мы изучим протокол USB. После исследования мы обнаружили, что USB-устройство сообщает USB-хосту свою собственную информацию, отправляя хосту различные дескрипторы. Эти дескрипторы определяются через структуры в исходном коде Linux. Поле структуры. Вы можете обратиться к соответствующим статьям. Давайте проанализируем несколько важных дескрипторов USB.

Первый - дескриптор устройства,该描述符из结构体定义роды:linux/include/uapi/linux/usb/ch9.h,Структура следующая:

Язык кода:javascript
копировать
/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;

    __le16 bcdUSB;
    __u8  bDeviceClass;
    __u8  bDeviceSubClass;
    __u8  bDeviceProtocol;
    __u8  bMaxPacketSize0;
    __le16 idVendor;
    __le16 idProduct;
    __le16 bcdDevice;
    __u8  iManufacturer;
    __u8  iProduct;
    __u8  iSerialNumber;
    __u8  bNumConfigurations;
} __attribute__ ((packed));

Сначала проверьте дескриптор устройства, записанный на USBPcap, как показано на рисунке 8:

Рисунок 8. Просмотр дескриптора устройства в Wireshark.

Сравнить еще разUSBTree Viewпоказано надескриптор Информация об устройстве, как показано на рисунке 9:

Рис. 9. Просмотр дескриптора устройства в дереве USB

Путем сравнения установлено, что контрольный дескриптор устройстваизродыbashСкриптиз以下几行代码:

Язык кода:javascript
копировать
echo 0x1d6b > idVendor  # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB    # USB2

STRINGS_DIR="strings/0x409"
mkdir -p "$STRINGS_DIR"
echo "6b65796d696d6570690" > "${STRINGS_DIR}/serialnumber"
echo "keymimepi" > "${STRINGS_DIR}/manufacturer"
echo "Generic USB Keyboard" > "${STRINGS_DIR}/product"

Сначала определите протокол устройства как USB2.0.,Затем установите идентификатор поставщика (0x1d6b) и идентификатор продукта (0x0104).,Затем определилиbNumConfigurationsдескриптор конфигурации Количество0x01,Остальное — это некоторая строковая идентификационная информация.

Информация о поставщике в большинстве случаев используется в качестве идентификационной информации, но в последующих статьях вы обнаружите, что некоторые драйверы запускаются при идентификации указанного поставщика и идентификаторов продукта. В настоящее время они только имитируют устройства с мышью и клавиатурой, они не полагаются на эти триггеры идентификатора, поэтому их можно изменять по желанию.

Затем хост запросит у устройства количество дескрипторов конфигурации, указанное в дескрипторе устройства.

2.1.2 Дескриптор конфигурации

дескриптор конфигурации Структура определяется как:linux/include/uapi/linux/usb/ch9.h,Структура следующая:

Язык кода:javascript
копировать
struct usb_config_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;

    __le16 wTotalLength;
    __u8  bNumInterfaces;
    __u8  bConfigurationValue;
    __u8  iConfiguration;
    __u8  bmAttributes;
    __u8  bMaxPower;
} __attribute__ ((packed));

Сначала проверьте дескриптор конфигурации, записанный на USBPcap, как показано на рисунке 10:

Рис. 10. Инициируемый хостом запрос на получение дескриптора конфигурации.

Хост сначала запросит дескриптор конфигурации фиксированной длины 9, как показано на рисунке 11:

Рис. 11. Просмотр дескриптора конфигурации в Wireshark.

Затем Хозяинпроходитьдескриптор конфигурацииизwTotalLengthПоле,узналдескриптор Фактическая длина конфигурации, затем Хозяин запросит полный дескриптор с USB-устройства. конфигурации,Как показано на рисунке 12.,На рисунке 13 показано:

Рисунок 12. Инициируемый хостом запрос на получение дескриптора конфигурации.

Рис. 13. USB-устройство отвечает полным пакетом дескриптора конфигурации.

Из трафика, перехваченного USBPcap, можно обнаружить, что ответный пакет дескриптора конфигурации, помимо информации дескриптора конфигурации, также содержит дескриптор интерфейса и дескриптор конечной точки, а поскольку USB-клавиатура зарегистрирована как USB HID-устройство, Итак, дескриптор конфигурации также содержит дескриптор HID, как показано на рисунке 14:

Рисунок 14. Просмотр дескриптора конфигурации в Wireshark.

Используйте USB Tree View для просмотра дескриптора конфигурации, как показано на рисунке 15:

Рис. 15. Просмотр дескрипторов конфигурации в дереве USB.

Далее мы объясним несколько полей в дескрипторе конфигурации:

  • bmAttributes:该Поледа控制电源из相关属性,Сообщает ли он Хозяину, имеет ли устройство автономное питание или требуется USB-питание Хозяину?,Можно ли разбудить устройство удаленно через USB.
  • MaxPower,Цель этого поля — сообщить Хозяину,Максимальный ток, который может принять USB-устройство.
  • bNumInterfaces:该Поле再次让Хозяин Знать,Существует несколько дескрипторов конфигурации USB-устройств.
  • bConfigurationValue,Это значение представляет собой серийный номер конфигурации дескриптора.,используется вSet Configuration Requestвнутри,Используется для уведомления о настройках USB,Какой USBдескриптор конфигурация успешно зарегистрирована в ядре Хозяин.

Полный анализдескриптор После конфигурирования вы можете узнать об управляющем дескрипторе. конфигурациисуществоватьbashСкриптсерединаиз代码如下所示:

Язык кода:javascript
копировать
CONFIG_INDEX=1
CONFIGS_DIR="configs/c.${CONFIG_INDEX}"
mkdir -p "$CONFIGS_DIR"
echo 250 > "${CONFIGS_DIR}/MaxPower"
CONFIGS_STRINGS_DIR="${CONFIGS_DIR}/strings/0x409"
mkdir -p "$CONFIGS_STRINGS_DIR"
echo "Config ${CONFIG_INDEX}: ECM network" > "${CONFIGS_STRINGS_DIR}/configuration"

2.1.3 Дескриптор интерфейса

дескриптор интерфейса Структура определяется как:linux/include/uapi/linux/usb/ch9.h,Структура следующая:

Язык кода:javascript
копировать
/* USB_DT_INTERFACE: Interface descriptor */
struct usb_interface_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;

    __u8  bInterfaceNumber;
    __u8  bAlternateSetting;
    __u8  bNumEndpoints;
    __u8  bInterfaceClass;
    __u8  bInterfaceSubClass;
    __u8  bInterfaceProtocol;
    __u8  iInterface;
} __attribute__ ((packed));

Сначала проверьте дескриптор интерфейса, записанный на USBPcap, как показано на рисунке 16:

Рис. 16. Просмотр дескриптора интерфейса в Wireshark.

Проверьте еще разUSBTree View上издескриптор Информация о интерфейсе, как показано на рисунке 17:

Рис. 17. Просмотр дескриптора интерфейса в виде дерева USB.

В дескрипторе интерфейса значение полей следующее:

  • bNumEndpoints定义了设备конечная точкаиз数量,в протоколе USB,Связь между конечными точками односторонняя.,Здесь определены два дескриптора конечной точки.,Один представляет ввод,Один представляет собой результат.
  • bInterfaceClass定义了接口из类型,На рисунке выше указано HID-устройство.,Таким образом, Хозяин снова прочитает HID-дескриптор.
  • bInterfaceProtocol定义了接口协议为клавиатура,Это позволит HID-драйверу клавиатуры обрабатывать последующую связь.

bInterfaceClass值对应из含义可以参考LinuxИсходный код ядра(同样дасуществоватьch9.hпо определению):

Язык кода:javascript
копировать
/*
 * Device and/or Interface Class codes
 * as found in bDeviceClass or bInterfaceClass
 * and defined by www.usb.org documents
 */
#define USB_CLASS_PER_INTERFACE     0   /* for DeviceClass */
#define USB_CLASS_AUDIO         1
#define USB_CLASS_COMM          2
#define USB_CLASS_HID           3
#define USB_CLASS_PHYSICAL      5
#define USB_CLASS_STILL_IMAGE       6
#define USB_CLASS_PRINTER       7
#define USB_CLASS_MASS_STORAGE      8
#define USB_CLASS_HUB           9
#define USB_CLASS_CDC_DATA      0x0a
#define USB_CLASS_CSCID         0x0b    /* chip+ smart card */
#define USB_CLASS_CONTENT_SEC       0x0d    /* content security */
#define USB_CLASS_VIDEO         0x0e
#define USB_CLASS_WIRELESS_CONTROLLER   0xe0
#define USB_CLASS_PERSONAL_HEALTHCARE   0x0f
#define USB_CLASS_AUDIO_VIDEO       0x10
#define USB_CLASS_BILLBOARD     0x11
#define USB_CLASS_USB_TYPE_C_BRIDGE 0x12
#define USB_CLASS_MISC          0xef
#define USB_CLASS_APP_SPEC      0xfe
#define USB_CLASS_VENDOR_SPEC       0xff

#define USB_SUBCLASS_VENDOR_SPEC    0xff

bInterfaceProtocolсуществоватьLinux源码середина只定义了鼠标иклавиатура,Как показано ниже,Значение остальных устройств установлено на 0,Или определяется драйвером производителя,Как показано на рисунке 18:

Рис. 18. Макроопределение значения bInterfaceProtocol в исходном коде Linux.

2.1.4 Дескриптор конечной точки

дескриптор конечной точки Структура определяется как:linux/include/uapi/linux/usb/ch9.h,Структура следующая:

Язык кода:javascript
копировать
/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;

    __u8  bEndpointAddress;
    __u8  bmAttributes;
    __le16 wMaxPacketSize;
    __u8  bInterval;

    /* NOTE:  these two are _only_ in audio endpoints. */
    /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
    __u8  bRefresh;
    __u8  bSynchAddress;
} __attribute__ ((packed));

существоватьUSBPcap上查看捕获到издескриптор конечной Информация о точках, как показано на рисунке 19:

Рисунок 19. Просмотр дескриптора конечной точки в Wireshark.

Номер порта и направление порта определяются в дескрипторе конечной точки, а также метод связи с использованием порта (метод связи, определенный на рисунке выше, представляет собой передачу с прерыванием) и максимальный размер пакета данных связи (8). байты.

Затем я перечислил файловую структуру каталога гаджетов Linux следующим образом:

Язык кода:javascript
копировать
$ tree
.
├── bcdDevice
├── bcdUSB
├── bDeviceClass
├── bDeviceProtocol
├── bDeviceSubClass
├── bMaxPacketSize0
├── configs
│?? └── c.1
│??     ├── bmAttributes
│??     ├── hid.usb0 -> ../../../../usb_gadget/g1/functions/hid.usb0
│??     ├── MaxPower
│??     └── strings
│??         └── 0x409
│??             └── configuration
├── functions
│?? └── hid.usb0
│??     ├── dev
│??     ├── no_out_endpoint
│??     ├── protocol
│??     ├── report_desc
│??     ├── report_length
│??     └── subclass
├── idProduct
├── idVendor
├── max_speed
├── os_desc
│?? ├── b_vendor_code
│?? ├── qw_sign
│?? └── use
├── strings
│?? └── 0x409
│??     ├── manufacturer
│??     ├── product
│??     └── serialnumber
└── UDC

Найден после исследования,可以проходитьno_out_endpointдокумент,Количество конечных точек управления,По умолчанию есть две конечные точки IN/OUT.,еслиno_out_endpointдокументиз值为1,дескриптор конечной точки Есть только одинINконечная точка。другойreport_lengthдокумент,Используется для контроля максимальной длины передаваемых пакетов данных.,То естьbMaxPacketSizeПоле。

2.1.5 Строковый дескриптор

Последний строковый дескриптор,结构体из定义也дародыch9.h

Язык кода:javascript
копировать
struct usb_string_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;

    __le16 wData[1];        /* UTF-16LE encoded */
} __attribute__ ((packed));

Основная функция строкового дескриптора — идентифицировать информацию.,напримерсуществоватьUSB Tree Viewпоказано наUSBИнформация об устройстве,都дапроходитьстроковый Дескриптор получен.

Можно найти вUSBTree ViewПосмотреть все встроковый дескриптор, как показано на рисунке 20:

Рис. 20. Просмотр строкового дескриптора в дереве USB

в дескрипторе интерфейсасередина,iInterfaceПолеиз值就дастроковый Смещение дескриптора.

2.1.6 Дескриптор отчета HID

Когда USB-хост узнает, что USB-устройство является USB HID-устройством, через дескриптор интерфейса, он получит дескриптор отчета HID. Дескриптор отчета HID, записанный в USBPcap, показан на рисунке 21:

Рис. 21. Дескриптор отчета HID просмотра Wireshark

Определение Дескриптор отчета HIDиз代码существоватьbashСкриптсередина如下所示:

Язык кода:javascript
копировать
# Write the report descriptor
# Source: https://www.kernel.org/doc/html/latest/usb/gadget_hid.html
echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > "${FUNCTIONS_DIR}/report_desc"

Дескриптор отчета HID в этом примере взят из примера ядра Linux, как показано на рисунке 22:

Рис. 22. Документация по драйверу гаджета USB HID для Linux.

Итак, на следующем этапе нам нужно иметь возможность плавно читать дескриптор отчета HID. Вы можете обратиться к официальному документу. Преимущество официального документа в том, что он является полным по содержанию. Содержание для новичков не подходит для начала. Официальный документ больше подходит в качестве справочного руководства после начала работы.

2.1.6.1 Анализ дескриптора конфигурации HID

Давайте сначала проверимkey-mime-pi项目из通信代码,Как узнать Хозяина на Raspberry Pi,Какие клавиши управляются,Соответствующий код выглядит следующим образом:

Язык кода:javascript
копировать
def send(hid_path, control_keys, hid_keycode):
    with open(hid_path, 'wb+') as hid_handle:   # hid_path = "/dev/hidg0"
        buf = [0] * 8
        buf[0] = control_keys
        buf[2] = hid_keycode
        hid_handle.write(bytearray(buf))
        hid_handle.write(bytearray([0] * 8))

Каждая операция кнопки требует отправки двух 8-байтовых буферов на USB-хост (максимальный размер пакета ограничен 8 байтами в дескрипторе конечной точки). Все 8 байтов второго buf установлены в 0, а первый байт первого buf является управляющим символом. После исследования выяснилось, что установлены 8 управляющих символов, как показано ниже:

Язык кода:javascript
копировать
#define KEY_LEFTCTRL 0xe0 // Keyboard Left Control
#define KEY_LEFTSHIFT 0xe1 // Keyboard Left Shift
#define KEY_LEFTALT 0xe2 // Keyboard Left Alt
#define KEY_LEFTMETA 0xe3 // Keyboard Left GUI
#define KEY_RIGHTCTRL 0xe4 // Keyboard Right Control
#define KEY_RIGHTSHIFT 0xe5 // Keyboard Right Shift
#define KEY_RIGHTALT 0xe6 // Keyboard Right Alt
#define KEY_RIGHTMETA 0xe7 // Keyboard Right GUI

GUIдаwindowsизwinключ,Мак-команда.

Второй байт первого buf не установлен и по умолчанию равен 0. Байты с третьего по 8-й имеют длину 6 байт и являются ключами ввода.

Имея общее представление о том, как отправлять данные на USB-хост, давайте взглянем на дескриптор отчета HID:

Язык кода:javascript
копировать
static struct hidg_func_descriptor my_hid_data = {
      .subclass               = 0, /* No subclass */
      .protocol               = 1, /* Keyboard */
      .report_length          = 8,
      .report_desc_length     = 63,
      .report_desc            = {
              0x05, 0x01,     /* USAGE_PAGE (Generic Desktop)           */
              0x09, 0x06,     /* USAGE (Keyboard)                       */
              0xa1, 0x01,     /* COLLECTION (Application)               */
              0x05, 0x07,     /*   USAGE_PAGE (Keyboard)                */
              0x19, 0xe0,     /*   USAGE_MINIMUM (Keyboard LeftControl) */
              0x29, 0xe7,     /*   USAGE_MAXIMUM (Keyboard Right GUI)   */
              0x15, 0x00,     /*   LOGICAL_MINIMUM (0)                  */
              0x25, 0x01,     /*   LOGICAL_MAXIMUM (1)                  */
              0x75, 0x01,     /*   REPORT_SIZE (1)                      */
              0x95, 0x08,     /*   REPORT_COUNT (8)                     */
              0x81, 0x02,     /*   INPUT (Data,Var,Abs)                 */
              0x95, 0x01,     /*   REPORT_COUNT (1)                     */
              0x75, 0x08,     /*   REPORT_SIZE (8)                      */
              0x81, 0x03,     /*   INPUT (Cnst,Var,Abs)                 */
              0x95, 0x05,     /*   REPORT_COUNT (5)                     */
              0x75, 0x01,     /*   REPORT_SIZE (1)                      */
              0x05, 0x08,     /*   USAGE_PAGE (LEDs)                    */
              0x19, 0x01,     /*   USAGE_MINIMUM (Num Lock)             */
              0x29, 0x05,     /*   USAGE_MAXIMUM (Kana)                 */
              0x91, 0x02,     /*   OUTPUT (Data,Var,Abs)                */
              0x95, 0x01,     /*   REPORT_COUNT (1)                     */
              0x75, 0x03,     /*   REPORT_SIZE (3)                      */
              0x91, 0x03,     /*   OUTPUT (Cnst,Var,Abs)                */
              0x95, 0x06,     /*   REPORT_COUNT (6)                     */
              0x75, 0x08,     /*   REPORT_SIZE (8)                      */
              0x15, 0x00,     /*   LOGICAL_MINIMUM (0)                  */
              0x25, 0x65,     /*   LOGICAL_MAXIMUM (101)                */
              0x05, 0x07,     /*   USAGE_PAGE (Keyboard)                */
              0x19, 0x00,     /*   USAGE_MINIMUM (Reserved)             */
              0x29, 0x65,     /*   USAGE_MAXIMUM (Keyboard Application) */
              0x81, 0x00,     /*   INPUT (Data,Ary,Abs)                 */
              0xc0            /* END_COLLECTION                         */
      }
};

首先даUSAGE_PAGEиUSAGE,Эти два поля можно найти в справочной документации.,В настоящее время мы можем выбрать только те приложения, которые были определены.,Это повлияет на идентификацию функций некоторых драйверов.

В основном смотрите на содержимое части коллекции,Собрать сCOLLECTIONначинатьEND_COLLECTIONЗаканчивать。

Содержимое сборника можно разделить на четыре части, первая часть выглядит следующим образом:

Язык кода:javascript
копировать
0x05, 0x07,     /*   USAGE_PAGE (Keyboard)                */
0x19, 0xe0,     /*   USAGE_MINIMUM (Keyboard LeftControl) */
0x29, 0xe7,     /*   USAGE_MAXIMUM (Keyboard Right GUI)   */
0x15, 0x00,     /*   LOGICAL_MINIMUM (0)                  */
0x25, 0x01,     /*   LOGICAL_MAXIMUM (1)                  */
0x75, 0x01,     /*   REPORT_SIZE (1)                      */
0x95, 0x08,     /*   REPORT_COUNT (8)                     */
0x81, 0x02,     /*   INPUT (Data,Var,Abs)                 */

Первая часть определяет функциональные клавиши клавиатуры.,Минимальное значениеKeyboard LeftControl(0xe0),Максимальное значениеKeyboard Right GUI(0xe7)。逻辑Минимальное значение0,Максимальное значение – 1.,1 означает нажатие,0 означает выпуск. Одна кнопка занимает 1 бит,Есть 8 кнопок,Всего пришлось1байт。например:0b00000001выражатьLeftControlключ被按下了。Смотри отсюда,Мы можем нажать все 8 клавиш управления одновременно.

Вторая часть выглядит так:

Язык кода:javascript
копировать
0x95, 0x01,     /*   REPORT_COUNT (1)                     */
0x75, 0x08,     /*   REPORT_SIZE (8)                      */
0x81, 0x03,     /*   INPUT (Cnst,Var,Abs)                 */

Существует 8 1-битных значений, всего 1 байт, и они являются константами (Cnst везде постоянен), поэтому контекст не видит заданного значения, поэтому по умолчанию оно считается значением 0. При отправке данных оно является значением. не повлияет, даже если 0 не будет отправлен, т.к. драйвер не будет активно распознавать этот байт данных.

Третья часть выглядит так:

Язык кода:javascript
копировать
0x95, 0x05,     /*   REPORT_COUNT (5)                     */
0x75, 0x01,     /*   REPORT_SIZE (1)                      */
0x05, 0x08,     /*   USAGE_PAGE (LEDs)                    */
0x19, 0x01,     /*   USAGE_MINIMUM (Num Lock)             */
0x29, 0x05,     /*   USAGE_MAXIMUM (Kana)                 */
0x91, 0x02,     /*   OUTPUT (Data,Var,Abs)                */
0x95, 0x01,     /*   REPORT_COUNT (1)                     */
0x75, 0x03,     /*   REPORT_SIZE (3)                      */
0x91, 0x03,     /*   OUTPUT (Cnst,Var,Abs)                */

Приведенный выше дескриптор HID определяет функцию светодиода, которая занимает в общей сложности 1 байт. Старшие 3 бита представляют собой постоянный 0, а младшие 5 бит представляют собой индикаторы на клавиатуре. К общим относятся: индикатор блокировки номера клавиатуры, индикатор блокировки верхнего регистра. , индикатор блокировки прокрутки. Подождите. И он отправляется на устройство хостом. Включение и выключение подсветки клавиатуры контролируется хостом USB.

Четвертая часть выглядит так:

Язык кода:javascript
копировать
0x95, 0x06,     /*   REPORT_COUNT (6)                     */
0x75, 0x08,     /*   REPORT_SIZE (8)                      */
0x15, 0x00,     /*   LOGICAL_MINIMUM (0)                  */
0x25, 0x65,     /*   LOGICAL_MAXIMUM (101)                */
0x05, 0x07,     /*   USAGE_PAGE (Keyboard)                */
0x19, 0x00,     /*   USAGE_MINIMUM (Reserved)             */
0x29, 0x65,     /*   USAGE_MAXIMUM (Keyboard Application) */
0x81, 0x00,     /*   INPUT (Data,Ary,Abs)                 */

Функция клавиатуры определена. Значение клавиши находится в диапазоне 0–0x65. Всего имеется 102 клавиши. Логическое значение также находится в диапазоне 0–0x65. Одна клавиша может занимать до 6 клавиш, общий размер которых составляет 6 байт.

На этом этапе было проанализировано определение дескриптора отчета HID клавиатуры. Мы обнаружили, что содержание определения дескриптора соответствует формату наших входных данных.

Первый отправленный байт buf представляет собой 8 кнопок управления, второй байт имеет фиксированное значение 0, а следующие 6 байтов представляют собой кнопки ввода.

В это время возникают две проблемы:

1. Клавиатура, смоделированная в тесте, представляет собой 104-клавишную клавиатуру. Почему имеется 102+8=110 значений? После исследования выяснилось следующее:

Язык кода:javascript
копировать
#define KEY_NONE 0x00 // No key pressed
#define KEY_ERR_OVF 0x01 //  Keyboard Error Roll Over - used for all slots if too many keys are pressed ("Phantom key")
// 0x02 //  Keyboard POST Fail
// 0x03 //  Keyboard Error Undefined

#define KEY_102ND 0x64 // Keyboard Non-US \ and |
#define KEY_BACKSLASH 0x31 // Keyboard \ and |
// Вышеупомянутые два конфликта, есть еще один
#define KEY_HASHTILDE 0x32 // Keyboard Non-US # and ~
// Я думаю, на моей клавиатуре нет клавиш, не связанных с США.

По приведенному выше расчету это именно 104-клавишная клавиатура.

2. Почему необходимо отправлять пакет данных со всеми нулями. После исследования выяснилось, что пакет данных, отправленный USB-устройством на USB-хост, представляет собой клавиатуру, информирующую USB-хост о текущем состоянии клавиатуры. Полная операция с ключом заключается в нажатии клавиши, а затем ее отпускании. Один отправленный пакет данных сообщает хосту, какие клавиши были нажаты, а второй пакет данных, все 0, сообщает хосту, что все клавиши были отпущены.

3 Следующие направления исследований

Справочная информация

Исследование данной статьи подошло к концу. В следующих статьях будут рассмотрены следующие направления исследований:

  • Выполните точную настройку Дескриптора отчета HID и посмотрите, какое влияние это окажет на фактическое использование.
  • Имитируйте мышь, изменив поле «Дескриптор интерфейса» и поле «Дескриптор отчета» HID.
  • Исследуйте контроллер. Разумеется, контроллер также использует протокол HID, но соответствующего определения в коде Linux я не вижу.
  • Изучите протоколы, отличные от HID, такие как USB-накопители, сетевые карты, принтеры и т. д.
  • Изучите детали системы привода,В каталоге драйверов ядра Linux,Строку модуля_usb_driver можно найти,Это макроопределенная функция,Все драйверы на стороне USB регистрируются в ядре с помощью этой функции.

4 Справочные ссылки

Ссылки

[1] https://github.com/mtlynch/key-mime-pi

[2] https://zhuanlan.zhihu.com/p/558716468

[3] https://www.kernel.org/doc/html/latest/usb/gadget_hid.html

[4] https://usb.org/sites/default/files/hut1_4.pdf

boy illustration
Используйте Rclone для переноса данных в MinIO
boy illustration
Enterprise WeChat, еще одно «поле битвы» для Double 11
boy illustration
Подробное руководство по созданию личного блога с использованием облачного сервера + WordPress.
boy illustration
Основы обучения серии napi — как разработать проект NAPI с помощью DevEco Studio
boy illustration
Консолидируйте и удалите кластер Kubernetes: изучите RKE2 и системный агент Rancher
boy illustration
[Машинное обучение] Dify: обновление версии платформы разработки агентов искусственного интеллекта
boy illustration
Вот как должна быть устроена платежная система, чтобы она была стабильной! !
boy illustration
Лучшая практика Docker: настройка универсального шаблона Docker для создания
boy illustration
Изучите ES от входа до практики
boy illustration
Обзор облачной архитектуры
boy illustration
Серия Cloud Native: Контейнеры и Docker
boy illustration
Распаковка Double Eleven Goodies|Легкий сервер приложений
boy illustration
Практическая информация! Статья на 3000 слов, которая научит вас шаг за шагом! развертывание Zabbix!
boy illustration
Как создать онлайн-банк вопросов для тестов и помочь пользователям бесплатно готовить и тестировать работы.
boy illustration
Ubuntu 22.04 настраивает вспомогательную сетевую карту и несколько сетевых карт.
boy illustration
Используйте ChatGPT для реализации активно-активного развертывания в одном городе.
boy illustration
Сколько стоит игровой сервер Идеальное руководство по ценам и характеристикам?
boy illustration
установка мини-кластера
boy illustration
Будущие тенденции развития: какой основной язык программирования станет лидером этой тенденции?
boy illustration
Дополнительные правила стимулирования CPS от 22 января
boy illustration
[Baidu Apollo] Изучение автономного вождения: блог-руководство, в котором глубоко анализируется архитектура открытой платформы Apollo.
boy illustration
Легко отлаживайте и разрабатывайте приложения Kubernetes локально с помощью Telepresence.
boy illustration
Создание серии платформ автоматизированной эксплуатации и обслуживания корпоративного уровня (1): Подробное введение в основы облачных технологий.
boy illustration
В чем разница между Docker и виртуальной машиной?
boy illustration
компоненты nodelocaldns и CoreDNS k8s
boy illustration
Унификация облачной наблюдения: руководство по передовому опыту для Elastic и OpenTelemetry
boy illustration
Kubernetes 1.28: Знакомство с нативными сопроводительными контейнерами
boy illustration
«Пятьдесят лет волнений» 1920-е годы, когда сотни научных школ соперничали друг с другом.
boy illustration
Облачный сервер с измеренными графическими процессорами под управлением Swordsman Love Online, версия 3
boy illustration
Практика k8s (3) — подробное объяснение установки кластера k8s.