Поддержка USB в KolibriOS: что внутри? Часть 5: уровень логического устройства

от автора

Обработка подключения устройства, начатая на уровне поддержки хост-контроллеров, остановилась, подготовив нулевую конечную точку устройства к работе. Уровень поддержки каналов предоставил методы работы с каналами. Самое время их применить для продолжения инициализации устройства: включается функция 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 для дескриптора устройства повторяется, теперь запрашивая данные полностью.

Дескриптор устройства

На рисунке показаны шестнадцатеричные дампы дескрипторов двух различных устройств: сверху — виртуального хаба 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;
  • число конфигураций.

Конфигурации — взаимоисключающие режимы работы устройства, между которыми нет ничего общего. В теории USB-устройство может иметь несколько разных конфигураций, между которыми выбирать должен софт. Спецификация USB в качестве несколько надуманного примера приводит модем, который может предоставлять либо один канал на 128 Кбит/с, либо два независимых канала на 64 Кбит/с каждый. На практике подавляющее большинство устройств имеет ровно одну конфигурацию и использует другие методы выбора режима работы. Впрочем, эту одну конфигурацию всё равно нужно явным образом включить, до этого устройство останется нефункциональным и способным только отдавать дескрипторы.

KolibriOS всегда выбирает первую конфигурацию устройства. Функция usb_get_descr_callback, вызываемая после чтения дескриптора устройства, отправляет команду GET_DESCRIPTOR для первого дескриптора конфигурации — тип 2, номер 0.

Вместе с дескриптором конфигурации устройство возвращает много других дескрипторов, задающих детали конфигурации. Общий объём данных, ассоциированных с дескриптором конфигурации, заранее неизвестен, но содержится в самом дескрипторе конфигурации. Поэтому запрос протекает в два этапа — на первом этапе функция usb_get_descr_callback запрашивает 8 байт, на втором этапе функция usb_know_length_callback вытаскивает из прочитанных байт общий размер и запрашивает уже его.

Дескриптор конфигурации

Одна из самых простых конфигураций — у виртуального хаба 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: уровень логического устройства

ссылка на оригинал статьи http://habrahabr.ru/company/kolibrios/blog/200172/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *