- PVSM.RU - https://www.pvsm.ru -

Поддержка USB в KolibriOS: что внутри? Часть 6: драйвер хабов

Поддержка USB в KolibriOS: что внутри? Часть 6: драйвер хабовПоследняя часть инфраструктуры USB — хабы. Хотя хабы — отдельные USB-устройства, они достаточно тесно связаны с другими частями инфраструктуры, чтобы спецификация хабов была частью основной спецификации USB, а код поддержки — частью ядра, расположенной в файле bus/usb/hub.inc [1].

Задачи хабов таковы.

  • Хабы предоставляют питание всем подключённым устройствам.
  • Хабы оповещают хост о подключении и отключении устройств.
  • Хабы делают сброс подключённого устройства, попутно определяя его скорость, по команде с хоста.
  • Хабы транслируют весь трафик, приходящий от хоста, подключённым устройствам в период после сброса и до отключения, а также трафик от устройств в обратную сторону.
  • HighSpeed-хабы содержат Transaction Translator [2], связывающий HighSpeed-шину с низкоскоростной USB1-шиной.

Трансляция трафика без переключения скорости происходит полностью прозрачно для хоста. Расщеплёнными транзакциями занимается хост-контроллер EHCI, здесь от софта важно только заполнить те поля в аппаратной части структуры канала, которые содержат адрес TT-хаба и порт в TT-хабе — и, разумеется, учитывать время транзакций при планировании [3]. Драйвер хабов управляет остальными пунктами списка задач.

Поддержка USB в KolibriOS: что внутри? Часть 6: драйвер хабов

Хабы имеют код класса устройства 9, код подкласса устройства 0 и три варианта 0, 1, 2 для кода протокола. Согласно спецификации USB, HighSpeed-хаб обязан поддерживать режим работы с единым TT для всех своих портов, и дополнительно может, но не обязан, поддерживать режим работы с отдельным TT для каждого порта. Типичный случай — режим с различными TT отсутствует, тогда код протокола равен 0. В случае поддержки такого режима в данных конфигурации должны быть два варианта дескриптора интерфейса с одинаковым номером интерфейса [4]. Тогда код протокола 1 идентифицирует режим с единым TT, который должен быть принят по умолчанию, а код протокола 2 — режим с различными TT, включаемый командой SET_INTERFACE. Существование в живой природе хабов, поддерживающих режим с различными TT, не подтверждено, как и польза от этого режима, поэтому драйвер хабов даже не пытается его обнаружить и включить и просто использует режим единого TT, включённый по умолчанию.

Обнаружив интерфейс класса 9, уровень логического устройства [4] читает структуру usb_hub_callbacks, содержащую указатели на функции драйвера usb_hub_init и usb_hub_disconnect. Работа драйвера начинается, когда уровень логического устройства вызывает usb_hub_init, и заканчивается, когда уровень поддержки каналов [3] вызывает usb_hub_disconnect в ответ на отключение устройства.

Инициализация

Инициализация хаба начинается в usb_hub_init и продолжается в других функциях по мере получения ответов от устройства. usb_hub_init открывает канал к конечной точке типа прерывания, по которой хаб будет уведомлять драйвер об изменениях статуса подключённых устройств. Далее usb_hub_init посылает запрос GET_DESCRIPTOR для дескриптора хаба.

Поддержка USB в KolibriOS: что внутри? Часть 6: драйвер хабов

На рисунке показаны шестнадцатеричные дампы дескрипторов трёх различных хабов: клавиатуры со встроенным хабом на два порта, двух 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 в KolibriOS: что внутри? Часть 6: драйвер хабов

Работа с хабом начинается вызовом usb_hub_wait_change, которая запрашивает данные от конечной точки типа прерывания. Пока с хабом не происходит событий, требующих внимания, запрос ожидает, не требуя ресурсов CPU: хост-контроллер без вмешательства CPU периодически опрашивает конечную точку, нет ли данных. Я напомню, что интервал опроса кодируется последним байтом дескриптора конечной точки и задаётся при открытии канала. Когда состояние хаба изменилось, хаб возвращает битовую маску, показывающую порты, состояние которых изменилось, плюс младший бит, показывающий, изменилось ли состояние самого хаба. Если при инициализации хаба к нему уже были подключены какие-то устройства, при первом запросе хаб сразу же возвращает изменение относительно «нулевого» состояния.

Размер значимой части ответа в битах равен количеству портов плюс один. В большинстве случаев полный размер ответа получается дополнением до целого числа байт. Но тесты [5] показывают, что иногда хаб может добавлять ещё незначимые байты, поэтому реальный размер ответа нужно брать как максимальный размер пакета из дескриптора конечной точки — вот зачем 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 миллисекунд, в течение которых устройство может адаптироваться, после чего передаёт управление коду поддержки хост-контроллеров [6] вызовом usb_hardware_func.NewDevice. Скорость устройства, нужную для usb_hardware_func.NewDevice, хаб определил в процессе сброса и вернул вместе с прочим состоянием устройства в ответ на GET_STATUS. Указатель на подключённое устройство, возвращаемый из usb_hardware_func.NewDevice, сохраняется в структуре хаба.

При отключении устройства usb_hub_changed вызывает функцию usb_device_disconnected уровня поддержки каналов [3], передавая ей ранее сохранённый указатель от NewDevice. Если устройство находилось в процессе инициализации, процесс обрывается.

Отключение

Обработчик usb_hub_disconnected, вызываемый при отключении хаба, вызывает usb_device_disconnected для всех подключённых устройств, продвигает очередь устройств, ожидающих сброса — если при отключении хаба шёл сброс одного из подключённых к нему устройств — и освобождает выделенную память под структуру хаба.

Что дальше?

Драйвер хабов — последняя часть инфраструктуры USB. После реализации всего, о чём я писала в первых 6 частях серии — заканчивая этой статьёй — можно говорить о поддержке системой работы с USB. К сожалению, важно это только программистам драйверов, а пользователю пока мало что заметно.

Поддержка USB в KolibriOS: что внутри? Часть 6: драйвер хабов
Дальше начинается мир бесконечно разнообразных драйверов. К счастью, при всём разнообразии устройств многие попадают в классы, для всех устройств в пределах одного класса может подойти один и тот же драйвер. Хабы — первый пример класса. Два других распространённых класса, для которых KolibriOS предоставляет драйвер класса, — HID-устройства, типичными представителями которых являются мыши и клавиатуры, и устройства Mass Storage, типичными представителями которых являются флешки, а вымирающими — внешние USB-флоппи-дисководы. В следующий раз я расскажу о HID-устройствах.

Все статьи серии

Часть 1: общая схема [7]
Часть 2: основы работы с хост-контроллерами [2]
Часть 3: код поддержки хост-контроллеров [6]
Часть 4: уровень поддержки каналов [3]
Часть 5: уровень логического устройства [4]
Часть 6: драйвер хабов

Автор: CleverMouse

Источник [8]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/usb/49540

Ссылки в тексте:

[1] bus/usb/hub.inc: http://websvn.kolibrios.org/filedetails.php?repname=Kolibri+OS&path=%2Fkernel%2Ftrunk%2Fbus%2Fusb%2Fhub.inc

[2] Transaction Translator: http://habrahabr.ru/company/kolibrios/blog/183184/

[3] учитывать время транзакций при планировании: http://habrahabr.ru/company/kolibrios/blog/186276/

[4] одинаковым номером интерфейса: http://habrahabr.ru/company/kolibrios/blog/200172/

[5] тесты: http://board.kolibrios.org/viewtopic.php?p=50568#p50568

[6] коду поддержки хост-контроллеров: http://habrahabr.ru/company/kolibrios/blog/183284/

[7] Часть 1: общая схема: http://habrahabr.ru/company/kolibrios/blog/181586/

[8] Источник: http://habrahabr.ru/post/203918/