Последняя часть инфраструктуры USB — хабы. Хотя хабы — отдельные USB-устройства, они достаточно тесно связаны с другими частями инфраструктуры, чтобы спецификация хабов была частью основной спецификации USB, а код поддержки — частью ядра, расположенной в файле bus/usb/hub.inc.
Задачи хабов таковы.
- Хабы предоставляют питание всем подключённым устройствам.
- Хабы оповещают хост о подключении и отключении устройств.
- Хабы делают сброс подключённого устройства, попутно определяя его скорость, по команде с хоста.
- Хабы транслируют весь трафик, приходящий от хоста, подключённым устройствам в период после сброса и до отключения, а также трафик от устройств в обратную сторону.
- HighSpeed-хабы содержат Transaction Translator, связывающий HighSpeed-шину с низкоскоростной USB1-шиной.
Трансляция трафика без переключения скорости происходит полностью прозрачно для хоста. Расщеплёнными транзакциями занимается хост-контроллер EHCI, здесь от софта важно только заполнить те поля в аппаратной части структуры канала, которые содержат адрес TT-хаба и порт в TT-хабе — и, разумеется, учитывать время транзакций при планировании. Драйвер хабов управляет остальными пунктами списка задач.
Хабы имеют код класса устройства 9, код подкласса устройства 0 и три варианта 0, 1, 2 для кода протокола. Согласно спецификации USB, HighSpeed-хаб обязан поддерживать режим работы с единым TT для всех своих портов, и дополнительно может, но не обязан, поддерживать режим работы с отдельным TT для каждого порта. Типичный случай — режим с различными TT отсутствует, тогда код протокола равен 0. В случае поддержки такого режима в данных конфигурации должны быть два варианта дескриптора интерфейса с одинаковым номером интерфейса. Тогда код протокола 1 идентифицирует режим с единым TT, который должен быть принят по умолчанию, а код протокола 2 — режим с различными TT, включаемый командой SET_INTERFACE
. Существование в живой природе хабов, поддерживающих режим с различными TT, не подтверждено, как и польза от этого режима, поэтому драйвер хабов даже не пытается его обнаружить и включить и просто использует режим единого TT, включённый по умолчанию.
Обнаружив интерфейс класса 9, уровень логического устройства читает структуру usb_hub_callbacks
, содержащую указатели на функции драйвера usb_hub_init
и usb_hub_disconnect
. Работа драйвера начинается, когда уровень логического устройства вызывает usb_hub_init
, и заканчивается, когда уровень поддержки каналов вызывает usb_hub_disconnect
в ответ на отключение устройства.
Инициализация
Инициализация хаба начинается в usb_hub_init
и продолжается в других функциях по мере получения ответов от устройства. usb_hub_init
открывает канал к конечной точке типа прерывания, по которой хаб будет уведомлять драйвер об изменениях статуса подключённых устройств. Далее usb_hub_init
посылает запрос GET_DESCRIPTOR
для дескриптора хаба.
На рисунке показаны шестнадцатеричные дампы дескрипторов трёх различных хабов: клавиатуры со встроенным хабом на два порта, двух RMH на 6 портов и на 8 портов. Клавиатура со встроенным хабом представляется системе как хаб, к одному из портов которого подключено несъёмное устройство — собственно клавиатура.
Дескриптор хаба содержит следующие поля:
- размер и тип, как у всех дескрипторов; дескриптор хаба имеет тип
29h
; - число портов;
- биты атрибутов. Единственный атрибут, важный для инфраструктуры, — пауза, вносимая TT при трансляции пакетов с переключением скорости, она вычисляется как 8*(1+значение бит 6:5) бит на скорости FullSpeed; во всех дескрипторах на рисунке она равна 8 FS-бит;
- время, необходимое для стабилизации питания на портах после команды включения, измеряемое в 2-миллисекундных интервалах, значение
32h=50
соответствует 100 мс; - питание, необходимое электронике хаба, в мА;
- битовая маска несъёмных устройств. Размер — по одному биту на каждый порт, плюс зарезервированный младший бит, с выравниванием вверх на границу байта. Для клавиатуры-хаба с рисунка бит 3 — считая с нуля — установлен, что соответствует несъёмному устройству на порту 3 — считая с единицы — собственно клавиатуре. Для 3 и 6 портов нужно соответственно 4 и 7 бит, что округляется до байта; для 8 портов нужно 9 бит, что округляется до двух байт, и так далее;
- ещё одно поле переменного размера, такого же, как и предыдущее, и заполненное FF, для совместимости со старым софтом.
Дескриптор имеет переменный размер. Максимальный размер получается при 255 портах и равен 40 байт. usb_hub_init
сохраняет размер пакета конечной точки — он понадобится дальше — и запрашивает 40 байт дескриптора хаба, разрешая короткий ответ. Получив дескриптор хаба, usb_hub_got_config
выделяет и заполняет память, достаточную для хранения интересных данных, плюс две переменные на каждый порт: указатель на подключённое устройство и время подключения устройства.
Прежде чем с портами хаба можно будет работать, нужно включить их питание командой SET_FEATURE(PORT_POWER)
. На вход команда принимает номер порта, считая от 1. Физически хаб может поддерживать включение и отключение питания для каждого порта либо ограничиваться двумя состояниями «питания нет на всех портах» и «питание есть на всех портах» — организацию можно узнать из двух младших бит атрибутов хаба, 00 соответствует общему питанию, 01 — отдельному, 10 и 11 зарезервированы. Но в обоих случаях хаб поддерживает отдельное логическое состояние для каждого порта и не будет сообщать о событиях на портах с логически выключенным питанием, поэтому независимо от физической организации нужно включать питание на каждом порту. usb_hub_got_config
и usb_hub_port_powered
последовательно подают команду включения питания для всех портов.
После успешного включения питания на всех портах следует подождать, пока питание стабилизируется, время ожидания указано в дескрипторе хаба. Как и другими действиями, требующими отложенной во времени реакции, контролем ожидания питания занимается функция usb_hub_process_deferred
. По истечении отведённого интервала usb_hub_process_deferred
начинает работу с хабом.
Работа
Вкратце схема опроса хаба описана на следующем рисунке, взятом из спецификации USB:
Работа с хабом начинается вызовом usb_hub_wait_change
, которая запрашивает данные от конечной точки типа прерывания. Пока с хабом не происходит событий, требующих внимания, запрос ожидает, не требуя ресурсов CPU: хост-контроллер без вмешательства CPU периодически опрашивает конечную точку, нет ли данных. Я напомню, что интервал опроса кодируется последним байтом дескриптора конечной точки и задаётся при открытии канала. Когда состояние хаба изменилось, хаб возвращает битовую маску, показывающую порты, состояние которых изменилось, плюс младший бит, показывающий, изменилось ли состояние самого хаба. Если при инициализации хаба к нему уже были подключены какие-то устройства, при первом запросе хаб сразу же возвращает изменение относительно «нулевого» состояния.
Размер значимой части ответа в битах равен количеству портов плюс один. В большинстве случаев полный размер ответа получается дополнением до целого числа байт. Но тесты показывают, что иногда хаб может добавлять ещё незначимые байты, поэтому реальный размер ответа нужно брать как максимальный размер пакета из дескриптора конечной точки — вот зачем usb_hub_init
сохранила его, не ограничившись передачей в функцию открытия канала.
Хаб отслеживает изменения в состоянии портов. Способ извещения хоста об этих изменениях несколько запутан. Состояние порта содержит несколько различных бит: устройство подключено/отключено, трафик к устройству транслируется/не транслируется, устройство приостановлено/работает, перегрузка по току зарегистрирована/нет, процесс сброса на порту идёт/нет. Для каждого из перечисленных бит хаб поддерживает парный бит изменения в состоянии; хаб устанавливает бит автоматически при любом изменении состояния, при последующих изменениях бит остаётся установленным. Хаб также поддерживает две команды GET_STATUS
, возвращающую одновременно биты текущего состояния и биты изменения, и CLEAR_FEATURE
, один из способов использовать которую — сбросить указанный бит изменения. Из-за того, что это две разные команды, возможна следующая ситуация:
- к порту подключается устройство,
- хаб устанавливает бит подключённого устройства и бит изменения состояния,
- хаб сигнализирует хосту об изменении на таком-то порту,
- хост подаёт команду
GET_STATUS
и видит подключение устройства в текущем состоянии и в изменении состояния, - устройство отключается,
- хаб сбрасывает бит подключённого устройства, бит изменения состояния остаётся установленным,
- хост подаёт команду
CLEAR_FEATURE
, хаб сбрасывает бит изменения, - хаб считает, что хост знает обо всех изменениях, у хоста неверное представление о текущем состоянии.
Поэтому при обработке изменения состояния после команды GET_STATUS
для выяснения, что произошло, и команды CLEAR_FEATURE
для очистки изменений нужно ещё раз отправить команду GET_STATUS
для получения актуального состояния. Вполне возможно, что вторая команда GET_STATUS
снова сообщит об изменениях в состоянии, произошедших после CLEAR_FEATURE
; хотя остановить обработку в этот момент корректно — при следующем чтении данных от конечной точки типа прерывания хаб сразу же сообщит о новом изменении — эффективнее, раз уж хост узнал об изменении, обновить представление хоста о мире за хабом и сделать цикл: послать вторую команду CLEAR_FEATURE
и третью GET_STATUS
и так далее, пока GET_STATUS
не сообщит об отсутствии новых изменений.
Данные от конечной точки типа прерывания, поступающие при изменении состояния хаба или какого-либо его порта, обрабатывает функция usb_hub_changed
. Она смотрит, состояние каких портов изменилось, для каждого порта запускает вышеописанный цикл запроса-статуса-и-подтверждения-изменения, накапливая поступающие изменения. Реакция на большинство изменений — соответствующая отладочная печать. Необходимо обрабатывать только уведомления о подключении/отключении устройства.
При подключении устройства usb_hub_changed
запоминает время подключения, после чего usb_hub_process_deferred
планирует пробуждение через 100 миллисекунд. Если в течение 100 миллисекунд приходит сигнал об отключении устройства на том же порту — дальнейшие действия отменяются. К сожалению, отсутствие такого сигнала ещё не означает, что устройство было подключено все эти 100 миллисекунд, — возможно, интервал опроса достаточно большой, и сигнал ещё не дошёл. Для надёжности по истечении 100-миллисекундного интервала usb_hub_process_deferred
посылает явный запрос GET_STATUS
. Если он показывает, что изменения в статусе подключения не было, — включается дальнейшая инициализация. Нельзя сбрасывать два устройства на одной шине параллельно; если идёт сброс какого-то другого устройства, подключённого к тому же контроллеру — необязательно к тому же хабу, — то сначала устройство ждёт своей очереди на сброс. Сброс включается командой SET_FEATURE(PORT_RESET)
и отключается хабом автоматически по прошествии положенных по спецификации 10 мс, что приводит к установке бита изменения состояния сброса, которое может быть, а может и не быть вовремя замечено при чтении конечной точки — в зависимости от интервала опроса. Из-за такой неопределённости usb_hub_process_deferred
самостоятельно посылает GET_STATUS
через положенное время и CLEAR_FEATURE
, а код обработки изменений, если он всё же видит изменение состояния сброса, просто игнорирует его. Окончание сброса в хабе одновременно включает трансляцию трафика к устройству. После сброса в соответствии со спецификацией usb_hub_process_deferred
выжидает ещё 10 миллисекунд, в течение которых устройство может адаптироваться, после чего передаёт управление коду поддержки хост-контроллеров вызовом usb_hardware_func.NewDevice
. Скорость устройства, нужную для usb_hardware_func.NewDevice
, хаб определил в процессе сброса и вернул вместе с прочим состоянием устройства в ответ на GET_STATUS
. Указатель на подключённое устройство, возвращаемый из usb_hardware_func.NewDevice
, сохраняется в структуре хаба.
При отключении устройства usb_hub_changed
вызывает функцию usb_device_disconnected
уровня поддержки каналов, передавая ей ранее сохранённый указатель от NewDevice
. Если устройство находилось в процессе инициализации, процесс обрывается.
Отключение
Обработчик usb_hub_disconnected
, вызываемый при отключении хаба, вызывает usb_device_disconnected
для всех подключённых устройств, продвигает очередь устройств, ожидающих сброса — если при отключении хаба шёл сброс одного из подключённых к нему устройств — и освобождает выделенную память под структуру хаба.
Что дальше?
Драйвер хабов — последняя часть инфраструктуры USB. После реализации всего, о чём я писала в первых 6 частях серии — заканчивая этой статьёй — можно говорить о поддержке системой работы с USB. К сожалению, важно это только программистам драйверов, а пользователю пока мало что заметно.
Дальше начинается мир бесконечно разнообразных драйверов. К счастью, при всём разнообразии устройств многие попадают в классы, для всех устройств в пределах одного класса может подойти один и тот же драйвер. Хабы — первый пример класса. Два других распространённых класса, для которых KolibriOS предоставляет драйвер класса, — HID-устройства, типичными представителями которых являются мыши и клавиатуры, и устройства Mass Storage, типичными представителями которых являются флешки, а вымирающими — внешние USB-флоппи-дисководы. В следующий раз я расскажу о HID-устройствах.
Все статьи серии
Часть 1: общая схема
Часть 2: основы работы с хост-контроллерами
Часть 3: код поддержки хост-контроллеров
Часть 4: уровень поддержки каналов
Часть 5: уровень логического устройства
Часть 6: драйвер хабов
Автор: CleverMouse