В этом году интерфейсу USB исполнилось ровно 25-лет, а потому предлагаю рассмотреть его реализацию как на уровне архитектуры, так и программную составляющую. Сырая версия 1.0 была опубликована в начале 1996 года, а уже осенью 98-го, мир увидел первую стабильную редакцию v1.1. Однако на практике оказалось, что из шины USB можно выжать намного больший потенциал. Так, в 2000 году была опубликована очередная спецификация USB 2.0, в которой предусмотрено повышение пропускной способности сразу в десятки раз. Это позволило существенно расширить круг подключаемых к шине высоко-скоростных устройств:
Содержание:
1. Топология шины: хост-контролёр, Root-Hub, режимы работы Control-Bulk-Interrupt;
2. Логическая организация: конечные точки Endpoint, протокол шины USB;
3. Типы дескрипторов устройств.
------------------------------------------------------------
1. Топология шины USB
Начнём с того, что USB поддерживает некоммерческая организация под названием USB-IF, или "Implementers Forum". Ребята из этого форума поставили перед собой задачу, найти универсальное решение для подключения к компьютеру гремучей смеси разнообразных устройств. В приоритете было удовлетворить основные требования подсистемы Рlug-and-Рlay (подключи и играй), которая используется для простой настройки вновь обнаруженных устройств.
В результате, не знакомые на тот момент с технологией PnP внешние порты теперь остались в прошлом, в числе которых последовательный RS-232, параллельный LPT, порты PS/2 клавиатуры и мыши, Gameport, и т.д. Весь этот хлам съедал и без того ограниченный пул системных ресурсов, например аппаратные прерывания IRQ, и каналы прямого доступа к памяти DMA. С приходом универсальной последовательной шины USB удалось избавиться от этих проблем + организовать поддержку "горячего" подключения девайсов, без катастрофических для платы последствий. Только ознакомившись с технической стороной данного усовершенствования понимаешь, какой самоотдачи это требовало от инженеров
1.1. Хост-контролёр и Root-Hub
Внутри системной архитектуры, USB-интерфейс придерживается топологии "многоуровневая звезда". Как правило, на одной материнской плате имеется сразу несколько шин USB и каждую из них обслуживает свой хост-контролёр, который способен адресовать не более 127 физических устройств. Непосредственно к хосту, сразу-же присавокупляется корневой концентратор Root-Hub, в тушке которого уже парочка Usb-портов.
Таким образом, отсчёт 127-ми устройств на одной шине начинается именно с корневого хаба, и именно он в топологии является центром звезды. Подключив в порт рута внешний концентратор с несколькими портами, получим ещё одну подчинённую звезду и т.д. Подобная схема физ.устройств чем-то напоминает размножение "методом почкования" и у него имеются свои ограничения – общее число концентраторов на одной шине, не должно превышать шести (каскадирование хабов).
В терминологии USB, все подключаемые в порты концентраторов устройства принято называть конечной точкой, что на их манер звучит как Endpoint. В свою очередь шинный контролёр и вся его подчинённая свита собираются под общим названием Usb-Node – физический узел. Вот как эту картину можно представить в графике:
Здесь видно, что Usb-контролёры в качестве магистральной используют параллельную PCI-шину компьютера. Это действительно только для скоростей USB-1 и 2. Если-же в системную архитектуру включён хост USB-3 с поддержкой SuperSpeed, он подключается уже к высокоскоростной последовательной трассе PCI-Express. В остальном, на программном уровне все три контролёра и внутренняя организация их узлов мало чем отличаются – в спецификациях USB-1,2,3 фундаментальных различий нет и они лишь дополняют некоторыми особенностями одна другую.
Выше упоминалось, что в одной системе присутствуют сразу несколько независимых шин USB, и каждой заправляет собственный хост-контролёр. Ознакомиться с топологией конкретной системы позволит как-минимум её диспетчер устройств, если раскрыть в нём соответствующую ветвь. На скрине ниже представлен вариант моей тестовой платы где видно, что данный боард с чипсетом G41 имеет целых 5-шин и столько-же корневых хабов, причём всего один из контролёров поддерживает USB-2:
Здесь может возникнуть резонный вопрос: -"Если на каждую шину USB можно повесить до 127 девайсов, то зачем нужны ещё доп.четыре шины?". Всё верно, ..только в пользу зоопарка имеется весомый аргумент! Дело в том, что число 127 здесь чисто теоретическое и привязано исключительно к 7-битной адресации устройств внутри одного узла Node. В реальной-же ситуации, шина USB является разделяемым ресурсом, что в конечном счёте отрицательно сказывается на её пропускной способности.
К примеру, если USB-2 имеет заявленную скорость 60 Мбайт/с (480 Мбит), то это лишь с расчётом на один девайс. Если к этой-же шине подключить второе устройство, они разделят транспорт "по-братски" и каждое из устройств получит уже по 30 Мбайт/с. Связано это с тем, что USB последовательная шина, шириной всего 1-бит, и соответственно контролёру придётся чередовать информационные пакеты двух девайсов по очереди. Так-что архитектурное решение с несколькими шинами вполне оправдано, чтобы каждая из конечных точек имела возможность работать на макс.скорости.
Другой особенностью интерфейса USB является то, что практически всегда шинные транзакации инициирует исключительно хост-контролёр. Такая тактика была выбрана не случайно, ведь усмотреть сразу за сотней резвящимися на одной шине "разнополыми" пакетами – задача не простая. Конечная точка Endpoint (в лице нашего Usb-брелка или иного девайса) лишь отвечает на запросы, и лишена возможности самостоятельно начинать диалог. Это накладывает ограничение на прямую связь между двумя устройствами – в любом случае трафик проходит через посредника Hub.
Все девайсы томятся в ожидании команды от хоста на передачу данных, однако исключением является ситуация, когда девайс по истечении определённого таймаута был вогнан контролёром в режим низкого энергопотребления. Тогда устройству разрешается подать хосту сигнал "Wakeup" о своём пробуждении. Данный алгоритм является типичным при взаимодействии хоста с бездействующей Usb-клавой или мышью.
1.2. Режимы работы хост-контролёра
Спецификация USB строго гласит, что любой хост-контролёр обязан поддерживать 4 базовых режима передачи данных, которые далее могут комбинироваться:
Программная модель взаимодействия хост-контролёра с подчинёнными устройствами построена так, что именно устройство запрашивает у контролёра требуемые ему ресурсы, а хост обязан их предоставить. В общем случае дела обстоят следующим образом..
При первом обнаружении устройства в системе, контролёр замораживает все текущие транзакации и назначает обнаруженному устройству адрес(0). Этот адрес зарезервирован для служебных целей, и не используется в уже сконфигурированной системе, поэтому адреса всех активных на данный момент устройств начинаются с единицы и до 127. Теперь, в режиме "Control Transfer", устройству с нулевым адресом контролёр посылает серию управляющих запросов Setup, на что обнаруженное устройство возвращает контролёру все свои характеристики в виде дескрипторов: Device, Configuration, Interface, Endpoint, Power и String (позже мы их программно считаем).
На основании этих данных, контролёр выделяет устройству затребованную полосу пропускания шины, определяет режим работы с устройством (Bulk, Int, Isochronous), скорость обмена и потребляемый от шины ток. Только полностью сконфигурировав устройство, контролёр назначает ему свободный адрес в диапазоне 1..127, после чего продолжает приостановленные задачи.
Сканирование устройств осуществляется постоянно, в реальном времени отслеживая изменения физической топологии шины. Например, концентраторы Hub фиксируют и сразу сообщают контролёру о подключение/отключение устройств к своим портам. Тот, в свою очередь определяет, кем/чем является неизвестное устройство – хабом или обычным девайсом, и в зависимости от этого создает определённый канал обмена с ним, Pipe. В момент, когда устройство отключается, хаб прихлопывает соответствующий свой порт и сразу-же докладывает контролёру, который удаляет сведения о данном устройстве из всех служебных структур.
2. Логическая организация шины USB
Мы выяснили, что на одной шине контролёр может обслуживать макс 127 устройств, которые в топологии называют конечными точками Endpoint. Однако на логическом уровне, каждый Endpoint это целый набор независимых буферов, общее число которых может достигать 16. Эти буферы имеют размеры до 64-байт и нумеруются как ЕР0..ЕР16. Другими словами, одна физическая точка Endpoint в виде нашего USB-брелка, на логическом уровне может содержать в себе до 16-ти логических точек 0..16. В программировании интерфейса USB логический уровень играет огромную роль, поэтому разберёмся хотя-бы с основными его понятиями.
2.1. Конечные точки "Endpoint"
Выше упоминалось, что на этапе конфигурирования нового устройства, хост запрашивает все его дескрипторы, куда производитель обязан прошить полный паспорт своего продукта. Иначе контролёр просто не сможет опознать USB-девайс и настроить с ним связь должным образом. Среди полученных данных (а точнее в дескрипторе интерфейса) устройство вернёт и кол-во своих логических точек(ЕР), а контролёр привяжет к каждой из них свой пайп.
Здесь мы рассматриваем устройства класса Mass-Storage (т.е. накопители) поэтому авансом скажу, что у этого класса устройств, логических точек всегда только три: ЕР1 служит каналом чтения данных, ЕР2 канал для записи во флеш, и нулевой канал ЕР0. Вне зависимости от класса, ЕР0 присутствует буквально во всех USB-устройствах, т.к. именно через него контролёр посылает девайсу сервисные запросы при настройке в режиме "Control". Надеюсь схема ниже немного прояснит обстановку дел на этом фронте:
Канал конфигурации ЕР0 – это единственный двунаправленный пайп и активен он всегда, в то время-как информационные каналы In/Out могут быть отключены в какой-либо из конфигураций, или-же при первом обнаружении устройства. Набор конечных точек начиная с ЕР1 напрямую зависит от типа устройства, но точки In/Out всегда ходят парами. К примеру у девайса может быть один обязательный канал ЕР0 и пара инфо-каналов, как у второго устройства с адресом[127] на рисунке выше. Не точно, но 4-канальную схему с ЕР1..ЕР4 вроде используют девайсы USB-3, но это лишь предположение, т.к. проверить сейчас не могу. У всех устройств USB-2 всегда только одна пара In/Out.
Каждая точка ЕРх имеет свой набор характеристик, которые хранятся в дескрипторе "Usb-Endpoint" устройства. Битовая маска описывает: поддерживаемый точкой тип передачи данных (изохронный, Control, Bulk, Int, или все сразу CBI), размер инфо-пакета в байтах 64-512, и требования к частоте обслуживания. В практической части мы попытаемся сдампить на консоль всю эту инфу, а пока достаточно лишь знать об этом.
Устройство может выполнять несколько различных функциональных задач. Например, райдер DVD может исполнять роль плеера и работать как устройство хранения данных. Для решения каждой задачи в устройстве определяется Interface – набор и правила использования точек, для выполнения конкретной задачи. У многофункциональных устройств может быть несколько интерфейсов. Набор одновременно поддерживаемых интерфейсов составляет "конфигурацию" устройства. В свою очередь, устройство может иметь сразу несколько конфигураций, из которых на этапе инициализации хост выбирает одну, делая её активной.
2.2. Протокол шины USB
Прежде чем перейти к программной поддержки интерфейса, не помешает коротко рассмотреть протокол шины, т.е. какого вида информацию гоняет по шине контролёр и чем ему отвечают устройства. Как и принято в этой "науке", здесь всё продумано до мелочей, однако некоторую путаницу вносит зоопарк режимов работы хост-контролёра, таких как Control, Bulk, Interrupt. Поскольку это принципиально разные диалоги, то и протоколы взаимодействия с устройствами у них разные, но некоторая стабильность всё-же просматривается – на ней и остановимся.
Сердцебиение шины USB в непрерывном режиме поддерживают кадры Frame, которые хост выставляет на шину с периодом 1 мс. Каждый фрейм начинается с маркера SOF (Start of Frame) и заканчивается маркером EOF. По большому счёту, фремы играют роль синхроимпульсов для концентраторов Hub. Однако для непосредственно Usb-устройств, такие периоды слишком большие, поэтому каждый кадр делится ещё и на 8 микрокадров длительностью по 125 мкс. Теперь эти микрокадры можно рассматривать как транспорт для передачи упакованных в пакеты данных.
Во время передачи хостом маркеров ЕOF, все устройства на шине должны молчать. Если это не так и какой-либо девайс позволит себе выставить на шину хоть 1-бит, то его называют "болтливым", и к нему применяются жёсткие меры, вплоть до полного отключения соответствующего порта.
Неотъемлемой частью интерфейса USB является способность хоста усыплять неактивные устройства. Это достигается за счёт того, что на время 3 мс его канал Pipe просто запирается для микро-фреймов. В результате девайс автоматически уходит в спячку с энергопотреблением от шины ~0.5 мA, хотя рабочий ток большинства устройств составляет около 500 мA для USB-2, и порядка 800 мA для USB-3.
Один цикл-обмена контролёра с конечной точкой назвали транзакацией. В зависимости от режима обмена данными, транзакции состоят из двух (при изохронных передачах) или трёх пакетов для Bulk и прочих. Трёхпакетная передача подразумевает гарантированную доставку данных, для чего в протокол добавляется третий пакет размером в байт – Handshake (рукопожатие).
В любом случае, транзакации начинаются с пакет-маркера под названием Token, после которого идут уже полезные данные. В рассматриваемом нами режиме Bulk (передача массивов данных) размер пакета Token составляет 24-бита, куда запрессована наиболее ценная информация, например 7-битный адрес получателя на шине (итого 127), и 4-битный номер его конечной точки ЕРх (итого 16-точек). На рисунке ниже представлен формат одной транзакации, и битовая карта её пакета Token.
Контролёр выставляет информацию на шину начиная с младшего бита, и заканчивая старшим (справа-налево). Каждый из трёх пакетов предваряет неуказанная здесь 32-битная последовательность Sync (синхронизация), и 8-битный EOP (End-of-Packet):
По сути, этот запрос от контролёра является широковещательным. Приняв его по шине, все устройства начинают судорожно сравнивать поле "Device Address" в пакете токена со своим, и при его не совпадении обязаны проигнорировать транзакацию. Как видим, в токене сначала идёт тетрада "PID", что подразумевает идентификатор пакета. Следующее поле "Check" является копией PID и введено для надёжности. Далее следуют адрес USB-устройства на шине, номер его конечной точки ЕР и контрольная сумма токена. В тетраде PID закодирована следующая информация:
3. Дескрипторы USB-устройств
Все USB-устройства имеют иерархию дескрипторов, которые хранятся в постоянной памяти ROM, контролёра самого Usb-устройства. Производитель прошивает эту информацию для хоста, чтобы он мог правильно идентифицировать подключённый девайс. Имеются стандартные и расширенные дескрипторы. Стандартные должны поддерживать буквально все классы USB-устройств, в то время как вторые весьма специфичны и могут отсутствовать в таблице.
Для чтения дескрипторов, в режиме Control хост посылает конечной точке ЕР0 устройства, пакет под названием "Setup", в котором указывается тип дескриптора и его размер. В ответ на этот запрос устройство возвращает хосту требуемую информацию. Общая схема инициализации устройства и типы дескрипторов выглядит примерно так:
Как видим, стандартных дескрипторов всего 5, и они содержат базовые сведения об устройстве.
В следующих таблицах представлена детальная информация, которую несёт в себе каждый из них.
3.1. Дескриптор физического устройства
Все дескрипторы имеют общий формат. Первый байт указывает длину дескриптора, а второй – его тип. Оформляя запрос Setup, мы резервируем буфер под эти структуры, указав лишь первые два поля, а устройство в ответ заполняет её данными. Если мы укажем длину меньше чем определено в
По смещению(4) здесь видим коды класса/подкласса устройства и используемый им протокол. Например устройство может принадлежать к классу HID, или накопителям Storage. На сайте USB-IF имеется
Интересными являются и поля начиная со-смещения(14). Это т.н. индексы тесктовых строк, которые необходимо будет передать при оформлении запроса "Usb-String-Descriptor". Если в поле(14) хранится значение =1, то передав его в запрос, получим вендора устройства в текстовом виде. Значения этих индексов уникальны и не повторяются ни в одном из других дескрипторов, поскольку являются указателями на конкретную строку в глобальной таблице строк устройства.
3.2. Дескриптор конфигурации
Устройство USB может иметь несколько различных конфигураций, хотя это скорее исключение, чем правило – большинство устройств просты и имеют всего один конфиг. Радует то, что на этот запрос устройство возвращает сразу все остальные дескрипторы в данной конфигурации, и нам не нужно запрашивать каждый из них в отдельности. То-есть потянув один раз за конфиг, мы получим в нагрузку: Interface, Endpoint и String:
3.3. Дескриптор интерфейса
В этом дескрипторе интерес представляет поле по смещению(4), в котором указывается общее кол-во конечных точек ЕР в данном устройстве. Обратите внимание, что это значение не учитывает точку конфигурирования ЕР0, а только число информационных точек In/Out.
3.4. Дескриптор описания конечных точек ЕР1+
Хост-контролёр использует информацию из этого дескриптора для определения требований к пропускной способности шины устройством. Если взведён старший бит(7) байта по смещению(2), то перед нами канал чтения информации Input, иначе это пайп записи Output. В младшей тетраде этого-же байта лежит порядковый номер точки ЕР1..ЕР16.
В байте по смещению(3) хранится режим передачи данных точкой, и для Usb-накопителей он будет Bulk. Далее идёт размер информационного пакета (как-правило 512-байт), и частота передачи в закодированном виде:
3.5. Дескриптор текстовых строк
Когда в пакете "Setup" хост запрашивает дескриптор строк, то передаёт устройству полностью эту структуру. При этом младший байт поля "wValue" пакета Setup содержит индекс интересующей строки (смотри дескрипторы выше). Если индекс равен нулю, устройство вернёт коды используемых языков
На этом с теорией покончим, и в следующей части займёмся программированием интерфейса USB. Что примечательно, в большинстве случаях мы можем не только читать дескрипторы, но и записывать в них информацию. Пока!
Содержание:
1. Топология шины: хост-контролёр, Root-Hub, режимы работы Control-Bulk-Interrupt;
2. Логическая организация: конечные точки Endpoint, протокол шины USB;
3. Типы дескрипторов устройств.
------------------------------------------------------------
1. Топология шины USB
Начнём с того, что USB поддерживает некоммерческая организация под названием USB-IF, или "Implementers Forum". Ребята из этого форума поставили перед собой задачу, найти универсальное решение для подключения к компьютеру гремучей смеси разнообразных устройств. В приоритете было удовлетворить основные требования подсистемы Рlug-and-Рlay (подключи и играй), которая используется для простой настройки вновь обнаруженных устройств.
В результате, не знакомые на тот момент с технологией PnP внешние порты теперь остались в прошлом, в числе которых последовательный RS-232, параллельный LPT, порты PS/2 клавиатуры и мыши, Gameport, и т.д. Весь этот хлам съедал и без того ограниченный пул системных ресурсов, например аппаратные прерывания IRQ, и каналы прямого доступа к памяти DMA. С приходом универсальной последовательной шины USB удалось избавиться от этих проблем + организовать поддержку "горячего" подключения девайсов, без катастрофических для платы последствий. Только ознакомившись с технической стороной данного усовершенствования понимаешь, какой самоотдачи это требовало от инженеров
Ссылка скрыта от гостей
. Но обо всём по порядку..1.1. Хост-контролёр и Root-Hub
Внутри системной архитектуры, USB-интерфейс придерживается топологии "многоуровневая звезда". Как правило, на одной материнской плате имеется сразу несколько шин USB и каждую из них обслуживает свой хост-контролёр, который способен адресовать не более 127 физических устройств. Непосредственно к хосту, сразу-же присавокупляется корневой концентратор Root-Hub, в тушке которого уже парочка Usb-портов.
Таким образом, отсчёт 127-ми устройств на одной шине начинается именно с корневого хаба, и именно он в топологии является центром звезды. Подключив в порт рута внешний концентратор с несколькими портами, получим ещё одну подчинённую звезду и т.д. Подобная схема физ.устройств чем-то напоминает размножение "методом почкования" и у него имеются свои ограничения – общее число концентраторов на одной шине, не должно превышать шести (каскадирование хабов).
В терминологии USB, все подключаемые в порты концентраторов устройства принято называть конечной точкой, что на их манер звучит как Endpoint. В свою очередь шинный контролёр и вся его подчинённая свита собираются под общим названием Usb-Node – физический узел. Вот как эту картину можно представить в графике:
Здесь видно, что Usb-контролёры в качестве магистральной используют параллельную PCI-шину компьютера. Это действительно только для скоростей USB-1 и 2. Если-же в системную архитектуру включён хост USB-3 с поддержкой SuperSpeed, он подключается уже к высокоскоростной последовательной трассе PCI-Express. В остальном, на программном уровне все три контролёра и внутренняя организация их узлов мало чем отличаются – в спецификациях USB-1,2,3 фундаментальных различий нет и они лишь дополняют некоторыми особенностями одна другую.
Выше упоминалось, что в одной системе присутствуют сразу несколько независимых шин USB, и каждой заправляет собственный хост-контролёр. Ознакомиться с топологией конкретной системы позволит как-минимум её диспетчер устройств, если раскрыть в нём соответствующую ветвь. На скрине ниже представлен вариант моей тестовой платы где видно, что данный боард с чипсетом G41 имеет целых 5-шин и столько-же корневых хабов, причём всего один из контролёров поддерживает USB-2:
Здесь может возникнуть резонный вопрос: -"Если на каждую шину USB можно повесить до 127 девайсов, то зачем нужны ещё доп.четыре шины?". Всё верно, ..только в пользу зоопарка имеется весомый аргумент! Дело в том, что число 127 здесь чисто теоретическое и привязано исключительно к 7-битной адресации устройств внутри одного узла Node. В реальной-же ситуации, шина USB является разделяемым ресурсом, что в конечном счёте отрицательно сказывается на её пропускной способности.
К примеру, если USB-2 имеет заявленную скорость 60 Мбайт/с (480 Мбит), то это лишь с расчётом на один девайс. Если к этой-же шине подключить второе устройство, они разделят транспорт "по-братски" и каждое из устройств получит уже по 30 Мбайт/с. Связано это с тем, что USB последовательная шина, шириной всего 1-бит, и соответственно контролёру придётся чередовать информационные пакеты двух девайсов по очереди. Так-что архитектурное решение с несколькими шинами вполне оправдано, чтобы каждая из конечных точек имела возможность работать на макс.скорости.
Другой особенностью интерфейса USB является то, что практически всегда шинные транзакации инициирует исключительно хост-контролёр. Такая тактика была выбрана не случайно, ведь усмотреть сразу за сотней резвящимися на одной шине "разнополыми" пакетами – задача не простая. Конечная точка Endpoint (в лице нашего Usb-брелка или иного девайса) лишь отвечает на запросы, и лишена возможности самостоятельно начинать диалог. Это накладывает ограничение на прямую связь между двумя устройствами – в любом случае трафик проходит через посредника Hub.
Все девайсы томятся в ожидании команды от хоста на передачу данных, однако исключением является ситуация, когда девайс по истечении определённого таймаута был вогнан контролёром в режим низкого энергопотребления. Тогда устройству разрешается подать хосту сигнал "Wakeup" о своём пробуждении. Данный алгоритм является типичным при взаимодействии хоста с бездействующей Usb-клавой или мышью.
1.2. Режимы работы хост-контролёра
Спецификация USB строго гласит, что любой хост-контролёр обязан поддерживать 4 базовых режима передачи данных, которые далее могут комбинироваться:
1. Сontrol Transfers – одинарные пакеты с управляющими сообщениями. Режим используется контролёром для конфигурирования устройств во время их подключения, и для управления ими в процессе работы. Протокол применяется буквально для всех классов USB-устройств, и обеспечивает гарантированную доставку данных.
2. Interrupt – короткие передачи по прерываниям, которые имеют спонтанный характер и должны обслуживаться не замедлительно. Клиентами этого режима являются как-правило клавиатура и мышь. Нажал клавишу – получил прерывание. При случайных ошибках обмен повторяется.
3. Bulk Transfers – передача больших массивов данных. Именно в этот режим переходит контролёр при работе с USB-Flash и другими накопителями класса "Mass Storage Device". Протокол с самым низким приоритетом передач. При загруженной шине транзакации могут приостанавливаться, хотя доставка пакетов гарантированна. Это герой данной статьи, так-что запомним его на будущее!
4. Isochronous – изохронные передачи. Их требуют потоковые устройства типа: видеокамеры, микрофон/колонки, девайсы для записи CD/DVD и т.д. Режим был введён только со-второй версии спецификации USB-2.0, для передачи непрерывных потоков данных на высокой скорости High-Speed. На приёмном узле нету времени на проверку целостности пакетов, поэтому некоторые из них могут теряться.
Программная модель взаимодействия хост-контролёра с подчинёнными устройствами построена так, что именно устройство запрашивает у контролёра требуемые ему ресурсы, а хост обязан их предоставить. В общем случае дела обстоят следующим образом..
При первом обнаружении устройства в системе, контролёр замораживает все текущие транзакации и назначает обнаруженному устройству адрес(0). Этот адрес зарезервирован для служебных целей, и не используется в уже сконфигурированной системе, поэтому адреса всех активных на данный момент устройств начинаются с единицы и до 127. Теперь, в режиме "Control Transfer", устройству с нулевым адресом контролёр посылает серию управляющих запросов Setup, на что обнаруженное устройство возвращает контролёру все свои характеристики в виде дескрипторов: Device, Configuration, Interface, Endpoint, Power и String (позже мы их программно считаем).
На основании этих данных, контролёр выделяет устройству затребованную полосу пропускания шины, определяет режим работы с устройством (Bulk, Int, Isochronous), скорость обмена и потребляемый от шины ток. Только полностью сконфигурировав устройство, контролёр назначает ему свободный адрес в диапазоне 1..127, после чего продолжает приостановленные задачи.
Сканирование устройств осуществляется постоянно, в реальном времени отслеживая изменения физической топологии шины. Например, концентраторы Hub фиксируют и сразу сообщают контролёру о подключение/отключение устройств к своим портам. Тот, в свою очередь определяет, кем/чем является неизвестное устройство – хабом или обычным девайсом, и в зависимости от этого создает определённый канал обмена с ним, Pipe. В момент, когда устройство отключается, хаб прихлопывает соответствующий свой порт и сразу-же докладывает контролёру, который удаляет сведения о данном устройстве из всех служебных структур.
2. Логическая организация шины USB
Мы выяснили, что на одной шине контролёр может обслуживать макс 127 устройств, которые в топологии называют конечными точками Endpoint. Однако на логическом уровне, каждый Endpoint это целый набор независимых буферов, общее число которых может достигать 16. Эти буферы имеют размеры до 64-байт и нумеруются как ЕР0..ЕР16. Другими словами, одна физическая точка Endpoint в виде нашего USB-брелка, на логическом уровне может содержать в себе до 16-ти логических точек 0..16. В программировании интерфейса USB логический уровень играет огромную роль, поэтому разберёмся хотя-бы с основными его понятиями.
2.1. Конечные точки "Endpoint"
Выше упоминалось, что на этапе конфигурирования нового устройства, хост запрашивает все его дескрипторы, куда производитель обязан прошить полный паспорт своего продукта. Иначе контролёр просто не сможет опознать USB-девайс и настроить с ним связь должным образом. Среди полученных данных (а точнее в дескрипторе интерфейса) устройство вернёт и кол-во своих логических точек(ЕР), а контролёр привяжет к каждой из них свой пайп.
Здесь мы рассматриваем устройства класса Mass-Storage (т.е. накопители) поэтому авансом скажу, что у этого класса устройств, логических точек всегда только три: ЕР1 служит каналом чтения данных, ЕР2 канал для записи во флеш, и нулевой канал ЕР0. Вне зависимости от класса, ЕР0 присутствует буквально во всех USB-устройствах, т.к. именно через него контролёр посылает девайсу сервисные запросы при настройке в режиме "Control". Надеюсь схема ниже немного прояснит обстановку дел на этом фронте:
Канал конфигурации ЕР0 – это единственный двунаправленный пайп и активен он всегда, в то время-как информационные каналы In/Out могут быть отключены в какой-либо из конфигураций, или-же при первом обнаружении устройства. Набор конечных точек начиная с ЕР1 напрямую зависит от типа устройства, но точки In/Out всегда ходят парами. К примеру у девайса может быть один обязательный канал ЕР0 и пара инфо-каналов, как у второго устройства с адресом[127] на рисунке выше. Не точно, но 4-канальную схему с ЕР1..ЕР4 вроде используют девайсы USB-3, но это лишь предположение, т.к. проверить сейчас не могу. У всех устройств USB-2 всегда только одна пара In/Out.
Каждая точка ЕРх имеет свой набор характеристик, которые хранятся в дескрипторе "Usb-Endpoint" устройства. Битовая маска описывает: поддерживаемый точкой тип передачи данных (изохронный, Control, Bulk, Int, или все сразу CBI), размер инфо-пакета в байтах 64-512, и требования к частоте обслуживания. В практической части мы попытаемся сдампить на консоль всю эту инфу, а пока достаточно лишь знать об этом.
Устройство может выполнять несколько различных функциональных задач. Например, райдер DVD может исполнять роль плеера и работать как устройство хранения данных. Для решения каждой задачи в устройстве определяется Interface – набор и правила использования точек, для выполнения конкретной задачи. У многофункциональных устройств может быть несколько интерфейсов. Набор одновременно поддерживаемых интерфейсов составляет "конфигурацию" устройства. В свою очередь, устройство может иметь сразу несколько конфигураций, из которых на этапе инициализации хост выбирает одну, делая её активной.
2.2. Протокол шины USB
Прежде чем перейти к программной поддержки интерфейса, не помешает коротко рассмотреть протокол шины, т.е. какого вида информацию гоняет по шине контролёр и чем ему отвечают устройства. Как и принято в этой "науке", здесь всё продумано до мелочей, однако некоторую путаницу вносит зоопарк режимов работы хост-контролёра, таких как Control, Bulk, Interrupt. Поскольку это принципиально разные диалоги, то и протоколы взаимодействия с устройствами у них разные, но некоторая стабильность всё-же просматривается – на ней и остановимся.
Сердцебиение шины USB в непрерывном режиме поддерживают кадры Frame, которые хост выставляет на шину с периодом 1 мс. Каждый фрейм начинается с маркера SOF (Start of Frame) и заканчивается маркером EOF. По большому счёту, фремы играют роль синхроимпульсов для концентраторов Hub. Однако для непосредственно Usb-устройств, такие периоды слишком большие, поэтому каждый кадр делится ещё и на 8 микрокадров длительностью по 125 мкс. Теперь эти микрокадры можно рассматривать как транспорт для передачи упакованных в пакеты данных.
Во время передачи хостом маркеров ЕOF, все устройства на шине должны молчать. Если это не так и какой-либо девайс позволит себе выставить на шину хоть 1-бит, то его называют "болтливым", и к нему применяются жёсткие меры, вплоть до полного отключения соответствующего порта.
Неотъемлемой частью интерфейса USB является способность хоста усыплять неактивные устройства. Это достигается за счёт того, что на время 3 мс его канал Pipe просто запирается для микро-фреймов. В результате девайс автоматически уходит в спячку с энергопотреблением от шины ~0.5 мA, хотя рабочий ток большинства устройств составляет около 500 мA для USB-2, и порядка 800 мA для USB-3.
Один цикл-обмена контролёра с конечной точкой назвали транзакацией. В зависимости от режима обмена данными, транзакции состоят из двух (при изохронных передачах) или трёх пакетов для Bulk и прочих. Трёхпакетная передача подразумевает гарантированную доставку данных, для чего в протокол добавляется третий пакет размером в байт – Handshake (рукопожатие).
В любом случае, транзакации начинаются с пакет-маркера под названием Token, после которого идут уже полезные данные. В рассматриваемом нами режиме Bulk (передача массивов данных) размер пакета Token составляет 24-бита, куда запрессована наиболее ценная информация, например 7-битный адрес получателя на шине (итого 127), и 4-битный номер его конечной точки ЕРх (итого 16-точек). На рисунке ниже представлен формат одной транзакации, и битовая карта её пакета Token.
Контролёр выставляет информацию на шину начиная с младшего бита, и заканчивая старшим (справа-налево). Каждый из трёх пакетов предваряет неуказанная здесь 32-битная последовательность Sync (синхронизация), и 8-битный EOP (End-of-Packet):
По сути, этот запрос от контролёра является широковещательным. Приняв его по шине, все устройства начинают судорожно сравнивать поле "Device Address" в пакете токена со своим, и при его не совпадении обязаны проигнорировать транзакацию. Как видим, в токене сначала идёт тетрада "PID", что подразумевает идентификатор пакета. Следующее поле "Check" является копией PID и введено для надёжности. Далее следуют адрес USB-устройства на шине, номер его конечной точки ЕР и контрольная сумма токена. В тетраде PID закодирована следующая информация:
0001 = транзакация на запись информации в устройство (Out);
1001 = транзакация на чтение (In);
1101 = транзакации конфигурирования устройства в режиме Control (пакет Setup);
0100 = маркер транзакации PING (начиная с USB-2).
3. Дескрипторы USB-устройств
Все USB-устройства имеют иерархию дескрипторов, которые хранятся в постоянной памяти ROM, контролёра самого Usb-устройства. Производитель прошивает эту информацию для хоста, чтобы он мог правильно идентифицировать подключённый девайс. Имеются стандартные и расширенные дескрипторы. Стандартные должны поддерживать буквально все классы USB-устройств, в то время как вторые весьма специфичны и могут отсутствовать в таблице.
Для чтения дескрипторов, в режиме Control хост посылает конечной точке ЕР0 устройства, пакет под названием "Setup", в котором указывается тип дескриптора и его размер. В ответ на этот запрос устройство возвращает хосту требуемую информацию. Общая схема инициализации устройства и типы дескрипторов выглядит примерно так:
Как видим, стандартных дескрипторов всего 5, и они содержат базовые сведения об устройстве.
В следующих таблицах представлена детальная информация, которую несёт в себе каждый из них.
3.1. Дескриптор физического устройства
Все дескрипторы имеют общий формат. Первый байт указывает длину дескриптора, а второй – его тип. Оформляя запрос Setup, мы резервируем буфер под эти структуры, указав лишь первые два поля, а устройство в ответ заполняет её данными. Если мы укажем длину меньше чем определено в
Ссылка скрыта от гостей
, хост посчитает запрос недействительным и отвергнет его. Однако, если размер больше ожидаемого, он проигнорирует лишние байты. Второй байт с типом введён для программного обеспечения, чтобы оно знало, с кем имеет дело. Вот какую информацию мы получим у себя в буфере:По смещению(4) здесь видим коды класса/подкласса устройства и используемый им протокол. Например устройство может принадлежать к классу HID, или накопителям Storage. На сайте USB-IF имеется
Ссылка скрыта от гостей
, с которым имеет смысл ознакомиться.Интересными являются и поля начиная со-смещения(14). Это т.н. индексы тесктовых строк, которые необходимо будет передать при оформлении запроса "Usb-String-Descriptor". Если в поле(14) хранится значение =1, то передав его в запрос, получим вендора устройства в текстовом виде. Значения этих индексов уникальны и не повторяются ни в одном из других дескрипторов, поскольку являются указателями на конкретную строку в глобальной таблице строк устройства.
3.2. Дескриптор конфигурации
Устройство USB может иметь несколько различных конфигураций, хотя это скорее исключение, чем правило – большинство устройств просты и имеют всего один конфиг. Радует то, что на этот запрос устройство возвращает сразу все остальные дескрипторы в данной конфигурации, и нам не нужно запрашивать каждый из них в отдельности. То-есть потянув один раз за конфиг, мы получим в нагрузку: Interface, Endpoint и String:
3.3. Дескриптор интерфейса
В этом дескрипторе интерес представляет поле по смещению(4), в котором указывается общее кол-во конечных точек ЕР в данном устройстве. Обратите внимание, что это значение не учитывает точку конфигурирования ЕР0, а только число информационных точек In/Out.
3.4. Дескриптор описания конечных точек ЕР1+
Хост-контролёр использует информацию из этого дескриптора для определения требований к пропускной способности шины устройством. Если взведён старший бит(7) байта по смещению(2), то перед нами канал чтения информации Input, иначе это пайп записи Output. В младшей тетраде этого-же байта лежит порядковый номер точки ЕР1..ЕР16.
В байте по смещению(3) хранится режим передачи данных точкой, и для Usb-накопителей он будет Bulk. Далее идёт размер информационного пакета (как-правило 512-байт), и частота передачи в закодированном виде:
3.5. Дескриптор текстовых строк
Когда в пакете "Setup" хост запрашивает дескриптор строк, то передаёт устройству полностью эту структуру. При этом младший байт поля "wValue" пакета Setup содержит индекс интересующей строки (смотри дескрипторы выше). Если индекс равен нулю, устройство вернёт коды используемых языков
Ссылка скрыта от гостей
. Код инглиша = 0x0409, и походу это единственный знакомый Win код. Если индекс больше нуля, запрашивается строка с указанным индексом. Кстати строки возвращаются в кодировке Юникод:На этом с теорией покончим, и в следующей части займёмся программированием интерфейса USB. Что примечательно, в большинстве случаях мы можем не только читать дескрипторы, но и записывать в них информацию. Пока!
Последнее редактирование: