![](https://habrastorage.org/getpro/habr/post_images/882/61a/ae8/88261aae82f0dfcb06d32f7bfd94c4c6.png)
Я рассказываю о программной поддержке USB, поэтому уровни ниже передач почти неинтересны: за них отвечает хост-контроллер. Зато важно, какой интерфейс представляет хост-контроллер софту. Сейчас распространены три интерфейса, и постепенно распространяется четвёртый:
Аббр. | Название интерфейса | Версия | Код поддержки контроллера в KolibriOS |
---|---|---|---|
UHCI | Universal Host Controller Interface | USB 1.1 | kernel/trunk/bus/usb/uhci.inc |
OHCI | Open Host Controller Interface | USB 1.1 | kernel/trunk/bus/usb/ohci.inc |
EHCI | Enhanced Host Controller Interface | USB 2.0 | kernel/trunk/bus/usb/ehci.inc |
XHCI | eXtensible Host Controller Interface (новый) | USB 3.0 | В KolibriOS ещё не поддерживается |
![](https://habrastorage.org/getpro/habr/post_images/ab5/093/0fd/ab50930fde89b3a78a05dce85352696d.png)
Немного теории
С программной точки зрения, любое USB-устройство представляет из себя набор конечных точек (endpoints), к которым можно открыть каналы (pipes) и организовывать передачи (transfers) различных типов. Каждая конечная точка имеет свой номер, от 0 до 15. Две конечные точки могут иметь один номер, только если они обе однонаправленные, причём одна из них предназначена для передач от хоста к устройству, а другая — в обратном направлении.
В зависимости от вида обрабатываемых передач, различают четыре вида конечных точек.
- Управляющие передачи (control transfers) — небольшие (как правило) передачи, используемые для конфигурирования, управляющих команд и запроса статуса. Единственный тип двунаправленного обмена: здесь передаётся информация и от хоста к устройству, и от устройства к хосту. Управляющая передача состоит из трёх этапов (stages), один из которых может отсутствовать:
- Setup stage передаёт 8 байт информации предписанного формата от хоста к устройству. Эти байты включают в себя направление и длину данных. Общий формат и стандартные запросы описаны в основной спецификации USB, и позднее я расскажу о некоторых из них.
- Data stage (отсутствует, если длина данных равна нулю) передаёт данные в направлении, указанном на предыдущем этапе.
- Status stage передаёт в обратном направлении признак успеха обработки.
Любое USB-устройство обязано иметь управляющую конечную точку с номером 0: с её помощью инфраструктура USB узнаёт общую информацию об устройстве (в том числе «какой драйвер загружать») и выполняет начальную настройку. Практически всем (если не вообще всем) устройствам хватает одной управляющей конечной точки, которая, возможно, обрабатывает ещё и специфические для устройства запросы. - Передачи массивов данных (bulk transfers). Основная «рабочая лошадка», когда данных много, и важно, чтобы они дошли без искажений, но не столь важно, сколько времени на это уйдёт, — флешки, принтеры etc. Например, типичная флешка располагает тремя конечными точками — обязательной нулевой управляющей и двумя для передачи данных в двух направлениях.
- Передачи по прерыванию (interrupt transfers). Используются, когда данных немного, но важно, чтобы они были обработаны за определённое время, — мышки, клавиатуры. Например, типичная мышка располагает двумя конечными точками — обязательной нулевой управляющей и одной типа прерывания с требованием опрашивать каждые 10 миллисекунд (можно чаще); требуемый интервал — свойство конечной точки, он может варьироваться.
- Изохронные передачи (isochronous transfers). Единственный тип, не гарантирующий доставку данных. Используется, когда данных много, и важно, чтобы они были обработаны за определённое время, но допускаются потери данных, — мультимедиа: веб-камеры, USB-колонки.
![](https://habrastorage.org/getpro/habr/post_images/fb3/937/9c4/fb39379c46ad2c84e8e99eda5f4c7526.gif)
Время на шине USB измеряется во фреймах или микрофреймах. Фреймы появились в USB1, один фрейм — одна миллисекунда. Микрофреймы появились в USB2, в одном фрейме 8 микрофреймов. Инфраструктура USB планирует выполнение передач по прерыванию и изохронных передач на конкретные (микро)фреймы так, чтобы гарантировать запрошенный интервал времени между передачами. Планирование не имеет права использовать более 90% от фрейма или более 80% от микрофрейма. Оставшееся время (минимум 10%/20%, хотя может быть и больше, если не всё время распланировано) занимают активные управляющие передачи и, по остаточному принципу, передачи массивов данных.
Уровень поддержки каналов
Файл kernel/trunk/bus/usb/pipe.inc содержит реализацию функций работы с каналами. Достаточно подробная документация есть в kernel/trunk/docs/usbapi.txt. Сейчас реализованы 4 функции. Первые две из них:
- Функция открытия канала USBOpenPipe, в исходном коде названная
usb_open_pipe
. Она принимает на вход ранее открытый канал, из которого копирует характеристики устройства, включая его координату на шине USB, и характеристики нового канала, и возвращает хэндл нового канала, или нуль при ошибке. Пока неважно, что из себя представляет этот хэндл. - Функция закрытия канала USBClosePipe, в исходном коде названная
usb_close_pipe
. Её назначение и единственный параметр достаточно очевидны.
Уровень поддержки каналов также обрабатывает событие отключения устройства, закрывая все каналы, связанные с устройством. Так что явным образом закрывать канал необязательно. Внимательный читатель здесь может возмутиться: «Получается, канал может внезапно закрыться сам по себе?» Но волноваться не стоит — событие отключения устройства, помимо обработки на текущем уровне, также транслируется «наверх». Канал окончательно закроется только после того, как всем будет дан шанс обработать событие отключения устройства. Точнее, хэндл канала можно использовать как минимум вплоть до того момента, как обработчики «сверху» закончат работу. Обработчик в драйвере описан в документации как DeviceDisconnected, хотя реальное имя может быть любым — драйвер предоставляет указатель на эту функцию.
Прежде, чем вводить следующие две функции, я должна отметить следующее. У каждого канала есть своя очередь передач. В одной очереди могут находиться несколько передач одновременно, но активной может быть только одна из них — та, которая находится в голове очереди. Следующая передача начнётся только после того, как активная передача полностью успешно закончится. Если какая-то передача закончится неуспешно, очередь остановится. Зачем нужна очередь? Для эффективности: программная обработка закончившейся передачи может потребовать некоторого времени, хост-контроллер вполне может не ждать реакции обработчика, а тем временем следовать дальше. Очереди разных каналов независимы, передачи для разных каналов выполняются параллельно.
- Функция передачи данных USBNormalTransferAsync, в исходниках названная
usb_normal_transfer_async
, обслуживает сразу два типа передач: передачи массивов данных и передачи по прерыванию. Для обоих типов нужно знать сами данные для передачи в виде указатель+длина, канал в виде хэндла, возвращённого USBOpenPipe, и некоторые флаги; пока что определён только один флаг, который разрешает или запрещает короткие передачи в направлении от устройства к хосту. Само направление указывать не нужно: оно однозначно определено в момент открытия канала. Суффикс «Async» указывает на то, что функция только ставит передачу в очередь, после чего немедленно возвращается; когда передача будет закончена — выполнена успешно, неуспешно или вообще отменена в связи с отключением устройства — будет вызвана callback-функция, указатель на которую также передаётся одним из аргументов USBNormalTransferAsync. Чтобы callback-функции можно было передать какую-нибудь дополнительную информацию, вместе с указателем на функцию передаётся произвольный параметр, который будет передан без изменений. - Функция управляющей передачи USBControlTransferAsync, в исходном коде названная
usb_control_async
. Интерфейс идентичен предыдущей функции с добавлением одного параметра — указателя на 8 байт для setup stage. Направление указывать по-прежнему не нужно, но уже по другой причине: оно извлекается из данных для setup stage. Строго говоря, длину данных тоже можно было бы не указывать по той же причине, но она оставлена для унификации интерфейса.
Внимательный читатель, несомненно, заметил нехватку функций. Я работаю над этим.
К уровню поддержки каналов также относится файл kernel/trunk/bus/usb/scheduler.inc; он отвечает за планирование передач, чувствительных ко времени обработки.
Уровень логического устройства
В сводке теории я уже сказала, что любое USB-устройство обязано иметь нулевую управляющую конечную точку, посредством которой инфраструктура опрашивает устройство и выполняет начальную настройку. Файл kernel/trunk/bus/usb/protocol.inc как раз этим и занимается, основываясь на уровне поддержки каналов. Результат работы этого уровня: загруженный драйвер устройства, получивший вызов функции, описанной в документации как AddDevice. Дальше всё в руках драйвера. Первый аргумент функции AddDevice — хэндл канала, открытого к нулевой конечной точке. Используя его, драйвер может открыть дополнительные каналы, нужные ему, а также проделать дополнительную настройку через нулевую конечную точку. Возвращаемое значение AddDevice — либо нуль при ошибке, либо абстрактный параметр, который USB-инфраструктура, никак не интерпретируя, — за исключением сравнения с нулём — сохраняет внутри информации об устройстве и потом передаёт функции DeviceDisconnected, про которую я уже говорила.
Для описания двух остальных параметров функции AddDevice мне понадобится ещё немного теории.
![переходник 2*PS/2 -> USB](https://habrastorage.org/getpro/habr/post_images/96e/f10/12a/96ef1012aa0f32506a0546aed52eb124.jpg)
Одно физическое устройство может предоставлять несколько интерфейсов, программируемых в той или иной степени независимо. В частности, у каждого интерфейса есть свой собственный набор конечных точек. Наглядный пример — многочисленные переходники с PS/2 на USB, предоставляющие два входа, для мыши и клавиатуры; такое USB-устройство предоставляет два несвязанных интерфейса. Менее наглядный, но более распространённый пример: USB-клавиатуры со специальными кнопками часто представляются как два независимых интерфейса, один — клавиатура со стандартными кнопками, другой — дополнительные кнопки.
USB-устройство обязано поддерживать запрос различных дескрипторов, в частности, дескриптора конфигурации. В ответ на запрос дескриптора конфигурации устройство также возвращает много связанной информации, в том числе дескрипторы всех интерфейсов и всех конечных точек, ассоциированных с интерфейсом. Дескриптор конечной точки содержит всю информацию, необходимую для открытия канала к ней.
Второй аргумент функции AddDevice — указатель на данные, ассоциированные с дескриптором конфигурации, начиная с самого дескриптора конфигурации; одно из его полей — общая длина данных. Третий аргумент функции AddDevice — указатель на дескриптор интерфейса, за который отвечает драйвер.
Если устройство реализует несколько интерфейсов, то уровень логического устройства вызовет драйвер — или несколько драйверов — несколько раз. Область ответственности одного вызова AddDevice простирается от того дескриптора интерфейса, который был ей передан, до следующего дескриптора интерфейса либо до конца данных; на этом интервале расположены дескрипторы всех конечных точек этого интерфейса.
Драйверы устройств
Это самый высокий уровень в архитектуре USB. Драйверы устройств используют API уровня поддержки каналов и информацию, собранную уровнем логического устройства, для поддержки нужной функциональности, зависящей от самого устройства.
Среди драйверов выделяется драйвер хабов kernel/trunk/bus/usb/hub.inc тем, что в некоторых аспектах он близок к уровню поддержки хост-контроллера и является составной частью инфраструктуры USB. Спецификация USB выделяет часть хост-контроллера, ответственную за контроль над USB-портами, в специальную сущность — корневой хаб; корневой хаб роднит с отдельными хабами интерфейс для других уровней, но они принципиально различны с точки зрения программирования.
Интерфейс кода поддержки хабов: при подключении устройства код сообщает коду поддержки хост-контроллера о новом устройстве; при отключении устройства код передаёт информацию об этом уровню поддержки каналов; для уровня логического устройства код предоставляет функции AddDevice+DeviceDisconnected, которые в исходном тексте называются
usb_hub_init
и usb_hub_disconnect
соответственно, а также функцию блокировки порта, к которому подключено новое устройство.Мышки и клавиатуры поддерживаются драйвером kernel/trunk/drivers/usbhid.asm. Поддержкой флешек занимается драйвер kernel/trunk/drivers/usbstor.asm.
Интерфейс кода поддержки хост-контроллеров
Наконец, я готова объяснить весь интерфейс, предоставляемый кодом поддержки хост-контроллеров прочим уровням. При подключении нового устройства вызывается функция
usb_new_device
уровня логического устройства. Здесь я должна отметить следующее: usb_new_device
попытается открыть канал к нулевой точке. Но функция открытия канала требует уже открытый канал, откуда она копирует характеристики устройства. Чтобы это работало, уровень хост-контроллера создаёт псевдо-канал, в структуре которого заполняет только поля с характеристиками устройства. Открытие канала к нулевой точке создаст уже полноценный канал. Когда хост-контроллер смирился с мыслью об исчезновении канала, вызывается функция usb_pipe_closed
уровня поддержки каналов. Кроме того, уровень логического устройства в ходе начальной настройки меняет параметры канала; когда хост-контроллер подтверждает, что изменения приняты, вызывается одна из функций usb_after_set_address
и usb_after_set_endpoint_size
уровня логического устройства. Подробнее о том, зачем это нужно, я расскажу в рамках разбора уровня логического устройства.Функции, специфичные для конкретного хост-контроллера и вызываемые из другого кода, собраны в структуру
usb_hardware_func
из hccommon.inc. Она включает в себя:- Функции для работы с аппаратной частью каналов и очередей, используемые уровнем поддержки каналов.
- Функции для получения и изменения адреса устройства на шине, а также размера пакета канала, используемые уровнем логического устройства.
- Функции для работы с USB-портами: блокировка, сброс, используемые уровнем логического устройства.
- Функцию, подготавливающая параметры для
usb_new_device
и вызывающаяusb_new_device
. Она вызывается из двух мест: изнутри этого же уровня и из кода поддержки хабов. - Пару функций для контроллеро-неспецифичной части этого же уровня.
Все статьи серии
Часть 1: общая схема
Часть 2: основы работы с хост-контроллерами
Часть 3: код поддержки хост-контроллеров
Часть 4: уровень поддержки каналов
Часть 5: уровень логического устройства
Часть 6: драйвер хабов
P.S. Если кто ещё не в курсе: мы собираем немного денег на Kickstarter, чтобы провести свой Summer of Code. Пока что собрано 65%, и сбор средств заканчивается 31 мая (завтра). Статья: habrahabr.ru/post/180197