![](https://habrastorage.org/getpro/habr/post_images/882/61a/ae8/88261aae82f0dfcb06d32f7bfd94c4c6.png)
usb_new_device
из bus/usb/protocol.inc.Сейчас устройство мало что может: оно ещё не получило адрес на шине, ещё не сконфигурировано, может использовать только 100 мА питания от шины. В общем-то, всё, что устройство может, — это рассказать о себе в ответ на подходящие вопросы. Рассказ устройства о себе организован в виде дескрипторов. Подходящим вопросом считается команда
GET_DESCRIPTOR
, отправленная нулевой конечной точке; в команде должны быть указаны тип дескриптора, порядковый номер дескриптора среди всех с таким типом, длина данных для передачи. Каждая команда к управляющей конечной точке занимает 8 байт и может иметь или не иметь дополнительных данных, в некоторых командах некоторые поля не используются. Структура команд по байтам и используемые поля расписаны в главе 9 спецификации USB, здесь я буду только описывать входные и выходные данные для команд.Уровень логического устройства имеет две задачи: во-первых, сконфигурировать устройство; во-вторых, расспросить его, загрузить соответствующий драйвер — или даже драйверы — и сообщить драйверу о новом устройстве.
Первые шаги
usb_new_device
начинает с создания канала к нулевой конечной точке устройства. Код поддержки хост-контроллеров перед вызовом usb_new_device
заполнил структуру, описывающую устройство, остаётся только указать параметры конечной точки: номер — нулевой — и максимальный размер пакета. Максимальный размер пакета для управляющей конечной точки может варьироваться от 8 до 64 байт и записан среди первых 8 байт дескриптора устройства. Однако, чтобы прочесть дескриптор, нужен уже открытый канал. К счастью, можно выбраться из цикла зависимостей: максимальный размер пакета нужен только для того, чтобы разбить одну передачу на транзакции; гарантируется, что максимальный размер пакета для управляющей конечной точки не меньше 8 байт; если на начальных стадиях конфигурации передавать только данные длиной 8 байт или меньше, то точное значение максимального размера пакета неважно. Поэтому в качестве максимального значения пакета можно установить любое число, не меньшее 8 байт.Впрочем, есть одна проблема: эмуляция EHCI в VirtualBox не совсем корректна и требует указания 64 байт для работы с HighSpeed-устройствами. Поскольку других требований нет, канал к нулевой конечной точке начинает существование с максимальным размером пакета в 64 байт.
Пока устройство не получило адрес на шине, оно отзывается на нулевой адрес и мешает сбросу последующих устройств — если прямо сейчас закончится сброс другого устройства, то оба устройства будут реагировать на пакеты с нулевым адресом, что приведёт к беспорядку на шине. Поэтому первое, что делает
usb_new_device
после открытия канала, — выбирает первый незанятый адрес и посылает команду SET_ADDRESS
. На входе у команды SET_ADDRESS
— адрес, выходных данных нет.После завершения команды
SET_ADDRESS
управление получает процедура usb_set_address_callback
. Если устройство не ответило на команду или ответило ошибкой, то единственный вариант действий — отключить устройство от шины на уровне хаба. Если команда выполнена успешно, то usb_set_address_callback
сообщает уровню поддержки хост-контроллеров, что адрес изменился; по причинам, о которых я упоминала в одной из предыдущих статей серии, изменение может занять некоторое время. В обоих случаях нулевой адрес на шине перестаёт быть занятым, поэтому можно приступать к сбросу последующих устройств, если они есть; за это отвечает вызов usb_test_pending_port
.Когда хост-контроллер подтверждает изменение адреса в структуре, описывающей устройство, код поддержки вызывает
usb_after_set_address
. Теперь представления хост-контроллера и устройства об адресе на шине вновь согласованы, можно продолжать настройку. Следующей командой usb_after_set_address
посылает вопрос GET_DESCRIPTOR
, запрашивая первые 8 байт дескриптора устройства — тип дескриптора 1, номер дескриптора 0. Последний из этих 8 байт задаёт максимальный размер пакета для нулевой конечной точки — последнее из того, чего не хватало для полноценного общения. Обработчик ответа usb_get_descr8_callback
вытаскивает максимальный размер пакета, сообщает о нём хост-контроллеру, дожидается подтверждения. После подтверждения происходит вызов usb_after_set_endpoint_size
, где вопрос GET_DESCRIPTOR
для дескриптора устройства повторяется, теперь запрашивая данные полностью.Дескриптор устройства
![](https://habrastorage.org/getpro/habr/post_images/77f/d4f/ddb/77fd4fddb4d82a58bd44505b186469a5.png)
На рисунке показаны шестнадцатеричные дампы дескрипторов двух различных устройств: сверху — виртуального хаба RMH, снизу — некоторой мышки. Дескриптор устройства состоит из следующих данных в little-endian:
- первый байт любого дескриптора — его размер, второй — его тип, дескриптор устройства имеет тип 1;
- версия спецификации USB, с которой совместимо устройство, в BCD,
110h
— версия 1.1,200h
— версия 2.0; - класс, подкласс и протокол устройства в целом; часто здесь бывают нули, тогда данные задаются на уровне отдельных интерфейсов — про них будет дальше;
- максимальный размер пакета для нулевой конечной точки;
- Vendor ID и Product ID устройства — ассоциация USB Implementers Forum выделяет каждому производителю USB-устройств двухбайтный Vendor ID, иногда даже не один, производитель назначает каждому своему продукту отдельный двухбайтный Product ID; например, Vendor ID
8087h
закреплён за Intel; - версия устройства — произвольное число, которое сюда записал производитель:
- три указателя на строки, каждый из которых может отсутствовать. Первые два — тексты для пользователя, строки-описания производителя и продукта соответственно, последний — серийный номер. Сами строки хранятся в отдельных дескрипторах типа 3;
- число конфигураций.
KolibriOS всегда выбирает первую конфигурацию устройства. Функция
usb_get_descr_callback
, вызываемая после чтения дескриптора устройства, отправляет команду GET_DESCRIPTOR
для первого дескриптора конфигурации — тип 2, номер 0.Вместе с дескриптором конфигурации устройство возвращает много других дескрипторов, задающих детали конфигурации. Общий объём данных, ассоциированных с дескриптором конфигурации, заранее неизвестен, но содержится в самом дескрипторе конфигурации. Поэтому запрос протекает в два этапа — на первом этапе функция
usb_get_descr_callback
запрашивает 8 байт, на втором этапе функция usb_know_length_callback
вытаскивает из прочитанных байт общий размер и запрашивает уже его.Дескриптор конфигурации
![](https://habrastorage.org/getpro/habr/post_images/48c/596/9a7/48c5969a7af056aad131b688d325093a.png)
Одна из самых простых конфигураций — у виртуального хаба RMH, она показана на рисунке. Вместе с дескриптором конфигурации устройство возвращает дескрипторы всех интерфейсов этой конфигурации, для каждого интерфейса — дескрипторы всех его конечных точек. Среди ассоциированных данных бывают и дескрипторы других типов — например, HID-устройства вроде мышек и клавиатур возвращают HID-дескриптор между дескриптором интерфейса и дескриптором конечной точки; их разбор — дело драйвера.
Дескриптор конфигурации состоит из следующих данных:
- размер и тип, как у всех дескрипторов; дескриптор конфигурации имеет тип 2;
- общий размер всех ассоциированных данных, в примере с RMH это 9+9+7=19h;
- число интерфейсов;
- байт-параметр команды
SET_CONFIGURATION
, соответствующий описываемой конфигурации; - индекс строки-описания конфигурации для пользователя, обычно отсутствует — задан как 0;
- байт атрибутов — старший бит и 5 младших бита зарезервированы, 5-й бит означает поддержку пробуждения из усыплённого состояния в ответ на какое-то внешнее действие, 6-й бит — что устройство имеет свой источник питания, независимый от шины USB;
- максимальное используемое питание от шины в этой конфигурации, 1 единица = 2 мА, значение 50 соответствует 100 мА.
Дескриптор интерфейса:
- размер и тип, как у всех дескрипторов; дескриптор конфигурации имеет тип 4;
- номер интерфейса и идентификатор режима работы интерфейса. У некоторых устройств один интерфейс может работать в нескольких режимах. Пример — веб-камеры: в зависимости от разрешения картинки и кодека скорость потока данных может существенно различаться, различные режимы резервируют различную пропускную способность конечной точки, передающей данные. В таких случаях для одного и того же интерфейса есть несколько дескрипторов, в которых поле номера одно и то же, но различаются идентификаторы режима. По умолчанию после выбора конфигурации интерфейсы работают в нулевом режиме, драйвер может послать команду
SET_INTERFACE
для переключения режима одного интерфейса; - число конечных точек;
- класс, подкласс и протокол интерфейса. Классы, кроме
0FFh
, определяются ассоциацией USB Implementers Forum, их описания собраны на этой странице. Например, к классу 9 относятся хабы. Класс0FFh
используется для устройств, не попавших ни в какой другой класс; - индекс строки-описания интерфейса для пользователя, обычно отсутствует — задан как 0.
Дескриптор конечной точки задаёт параметры, необходимые для открытия канала:
- размер и тип, как у всех дескрипторов; дескриптор конечной точки имеет тип 5;
- направление — старший бит — и номер — 4 младших бита — конечной точки; например,
81h
означает точку номер 1, передающую данные от устройства к хосту; - тип конечной точки: 0 = управляющая, 1 = изохронная, 2 = массивов данных, 3 = прерывания; для изохронных точек в этом же байте упакованы уточняющие параметры;
- 11 бит максимального размера пакета для этой конечной точки плюс ещё 2 бита для HighSpeed-точек, задающие максимальное число транзакций за один микрофрейм;
- желательный интервал опроса для изохронных точек и точек типа прерывания. Для LowSpeed/FullSpeed-точек типа прерывания интервал задаётся в миллисекундах, для HighSpeed-точек типа прерывания интервал равен 2значение-1 микрофреймов. В примере с RMH получается 211/8 миллисекунд = 0.256 секунд. Для изохронных точек интервал равен 2значение-1 в единицах измерения, соответствующих скорости: миллисекундах для FullSpeed, микрофреймах для HighSpeed.
Функция
usb_set_config_callback
, получив полные данные, ассоциированные с дескриптором конфигурации, подаёт команду SET_CONFIGURATION
с параметром, взятым из дескриптора; выходных данных у этой команды нет. После успешного завершения команды устройство становится полностью функциональным. Последняя из функций уровня логического устройства, usb_got_config_callback
, разбирает полученные данные конфигурации: убеждается в том, что устройство не пытается обмануть, подменив общий размер данных между командами GET_DESCRIPTOR
; проходит по списку дескрипторов, игнорируя все, кроме дескрипторов интерфейсов; для дескрипторов с нулевым режимом определяет драйвер по классу устройства, загружает драйвер и вызывает его функцию AddDevice
, передавая указатель на данные конфигурации и указатель на нужный дескриптор интерфейса внутри них. За дальнейшую работу с устройством отвечает загруженный драйвер. Я расскажу о существующих драйверах KolibriOS в последующих статьях.Все статьи серии
Часть 1: общая схема
Часть 2: основы работы с хост-контроллерами
Часть 3: код поддержки хост-контроллеров
Часть 4: уровень поддержки каналов
Часть 5: уровень логического устройства
Часть 6: драйвер хабов