USB Gadget API for Linux — The Linux Kernel documentation

USB Gadget API for Linux — The Linux Kernel  documentation Гаджет

Cdc — виртуальный com порт

Наверное взять и сразу наваять композитное устройство целиком не выйдет — слишком много нюансов и подводных камней. Я думаю лучше будет сначала отладить каждый интерфейс в отдельности, а потом переходить к композитному устройству. Начну с CDC, т.к. он не требует никаких зависимостей.

Я недавно переехал на STM32 Cube — пакет низкоуровневых драйверов для STM32. В нем есть код по управлению USB с реализацией отдельных классов USB устройств. Возьмем шаблонные реализации USB Core и CDC и начнем пилить под себя. Заготовки лежат в директории MiddlewaresSTSTM32_USB_Device_Library.

Шаблонная реализация библиотеки подразумевает написание собственного кода в файлах с названием template. Без понимания всей библиотеки и принципов работы USB это сделать достаточно сложно. Но мы пойдем проще — сгенерируем эти файлы с помощью графического конфигуратора CubeMX.

Реализация предоставленная CubeMX готова к работе прямо из коробки. Аж даже немного обидно, что не пришлось писать никакого кода. Придется изучать CDC на примере полностью готовой реализации. Давайте взглянем на самые интересные места в сгенерированном коде.

Для начала заглянем в дескрипторы, которые находятся в файлах usbd_desc.c (дескриптор устройства) и usbd_cdc.c (дескрипторы конфигурации, интерфейсов, конечных точек). В статье usb in a nutshell (на русском) есть очень детальное описание всех дескрипторов. Не буду описывать каждое поле в отдельности, остановлюсь лишь на самых важных и интересных полях.

Дескриптор устройства
#define USBD_VID 1155#define USBD_LANGID_STRING 1033#define USBD_MANUFACTURER_STRING "STMicroelectronics"#define USBD_PID_FS 22336#define USBD_PRODUCT_STRING_FS "STM32 Virtual ComPort"#define USBD_SERIALNUMBER_STRING_FS "00000000001A"#define USBD_CONFIGURATION_STRING_FS "CDC Config"#define USBD_INTERFACE_STRING_FS "CDC Interface"#define USBD_MAX_NUM_CONFIGURATION 1 __ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = { 0x12,  USB_DESC_TYPE_DEVICE, 0x00, 0x02, 0x02, 0x02, 0x00,  USB_MAX_EP0_SIZE,  LOBYTE(USBD_VID),  HIBYTE(USBD_VID),  LOBYTE(USBD_PID_FS),  HIBYTE(USBD_PID_FS), 0x00, 0x02, USBD_IDX_MFC_STR,  USBD_IDX_PRODUCT_STR,  USBD_IDX_SERIAL_STR,  USBD_MAX_NUM_CONFIGURATION  } ; 

Тут нас интересуют такие поля:

  • bDeviceClass, bDeviceSubClass и bDeviceProtocol — описывают хосту что же это у нас за устройство такое, что оно умеет и какие драйвера нужно грузить. В данном случае тут сказано, что устройство у нас реализует Communication Device Class, а значит хосту нужно сделать виртуальный COM порт и связать его с этим устройством
  • PID (Product ID) и VID (Vendor ID) — по этим полям хост различает разные устройства, подключенные к системе. Устройства при этом реализовывать одинаковый класс. Говорят для устройств продаваемых на рынке очень важно иметь уникальные VID/PID, но я не узнавал кто и где выдает эти ID-шники. Для домашнего устройства в единственном экземпляре достаточно значений по умолчанию

Обратите внимание, что строковые константы (название устройства, серийный номер) не прописаны в самом дескрипторе. Строки описываются отдельными дескрипторами, а все остальные только указывают индекс строки. Строковый дескриптор в случае библиотеки от ST генерируется на лету (грррррр), поэтому приводить я его не буду.

Дескритор конфигурации
__ALIGN_BEGIN constuint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = { 0x09,  USB_DESC_TYPE_CONFIGURATION,  USB_CDC_CONFIG_DESC_SIZ, 0x00, 0x02, 0x01, 0x00, 0xC0, 0x32, 

Тут нам интересно следующее:

  • wTotalLength — размер всего пакета дескрипторов для этой конфигурации — чтобы хост знал где заканчивается эта конфигурация и начинается следующая. Нам его нужно будет поправить, когда мы будем делать композитное устройство. Напомню, что все интерфейсы для этой конфигурации должны располагаться сплошным блоком, а значение wTotalLength определяет длину этого блока.
  • bNumInterfaces: класс Communication Device реализуется с помощью двух интерфейсов. Один для управления, другой для собственно пересылаемых данных
  • bmAttributes и MaxPower указывает, что наше устройство имеет собственный источник питания, но при этом хочет потреблять до 100 мА от USB порта. С этими параметрами явно придется поиграться в будущем.

Дальше идет дескриптор первого из интерфейсов CDC. Этот класс устройств может реализовывать несколько разных моделей общения (телефон, прямое соединение, многостороннее соединение), но в нашем случае это будет Abstract Control Model.

Дескриптор интерфейса управления CDC
0x09,  USB_DESC_TYPE_INTERFACE, 0x00, 0x00, 0x01, 0x02, 0x02, 0x01, 0x00, 

В этом интерфейсе живет только одна конечная точка (bNumEndpoints). Но прежде идет серия функциональных дескрипторов — настроек специфичных для данного класса устройств.

Функциональный дескрипторы
0x05, 0x24, 0x00, 0x10, 0x01, 0x05, 0x24, 0x01, 0x00, 0x01, 0x04, 0x24, 0x02, 0x02, 0x05, 0x24, 0x06, 0x00, 0x01, 

Тут сказано, что наше устройство не знает о понятии “звонок” (в смысле звонок по телефону), но при этом понимает команды параметров линии (скорость, стоп биты, DTR/CTS биты). Последний дескриптор описывает какой из двух интерфейсов CDC является управляющим, а где бегают данные. В общем, тут нам ничего не интересно и менять мы ничего не будем.

Наконец, дескриптор конечной точки для управляющего интерфейса
0x07,  USB_DESC_TYPE_ENDPOINT,  CDC_CMD_EP, 0x03,  LOBYTE(CDC_CMD_PACKET_SIZE),  HIBYTE(CDC_CMD_PACKET_SIZE), 0x10, 

Тут сказано, что эта конечная точка используется для прерываний. Хост будет опрашивать устройство раз в 0x10 (16) мс с вопросом а не требует ли устройство внимания. Также через эту конечную точку будут ходить управляющие команды.

Описание второго интерфейса (там где данные бегают) будет попроще

Интерфейс данных CDC и его конечные точки
0x09,  USB_DESC_TYPE_INTERFACE, 0x01, 0x00, 0x02, 0x0A, 0x00, 0x00, 0x00, 0x07,  USB_DESC_TYPE_ENDPOINT,  CDC_OUT_EP, 0x02,  LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),  HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), 0x00, 0x07,  USB_DESC_TYPE_ENDPOINT,  CDC_IN_EP, 0x02,  LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),  HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), 0x00

В интерфейсе живут 2 конечные точки типа bulk — одна на прием, вторая на передачу. На самом деле в терминологии USB это одна конечная точка, просто двухсторонняя.

Как это все работает объяснять не буду, хотя бы потому что сам до конца не понимаю (например как хост узнает сколько данных нужно забирать со стороны устройства). Самое главное, что библиотека все реализует за нас. Давайте лучше посмотрим на архитектуру.

Библиотека USB от ST весьма слоиста. Я бы выделил такие архитектурные уровни

  • Class Driver (в случае CDC это файлы usbd_cdc и usbd_cdc_if): реализуют логику конкретного класса устройств — CDC для виртуального COM порта, MSC для устройств хранения данных, HID для клавиатур/мышек и всяких специфических устройств с пользовательским интерфейсом.
  • USB Core (usbd_core.c, usbd_ctlreq.c, usbd_ioreq.c): реализует общую логику работы всех классов USB устройств, умеет отдавать хосту запрашиваемые дескрипторы, обрабатывает запросы от хоста и настраивает USB устройство в целом. Также перенаправляет потоки данных из уровня драйвера класса в нижележащие уровни и наоборот.
  • USB HW Driver (usbd_conf.c): Вышележащие слои платформенно независимые и работают одинаковым образом для нескольких серий микроконтроллеров. В коде нет низкоуровневых вызовов функций конкретного микроконтроллера. Файл usbd_conf.c реализует прослойку между USB Core и HAL — библиотеке низкоуровневых драйверов для выбранного микроконтроллера. В основном тут живут простые врапперы, которые перенаправляют вызовы сверху вниз и коллбеки снизу вверх.
  • HAL (stm32f1xx_hal_pcd.c, stm32f1xx_ll_usb.c): занимаются общением с железом микроконтроллера, оперирует регистрами и отвечает на прерывания.

На этом этапе нас будут интересовать только самый верхний слой и одна функция из usbd_conf.c. Начнем с последней:

Функция USBD_LL_Init()
USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev){  hpcd_USB_FS.pData = pdev; pdev->pData = &hpcd_USB_FS; hpcd_USB_FS.Instance = USB; hpcd_USB_FS.Init.dev_endpoints = 8; hpcd_USB_FS.Init.speed = PCD_SPEED_FULL; hpcd_USB_FS.Init.ep0_mps = DEP0CTL_MPS_8; hpcd_USB_FS.Init.low_power_enable = DISABLE; hpcd_USB_FS.Init.lpm_enable = DISABLE; hpcd_USB_FS.Init.battery_charging_enable = DISABLE; if (HAL_PCD_Init(&hpcd_USB_FS) != HAL_OK) { Error_Handler(); } HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x18); HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x58); HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x81 , PCD_SNG_BUF, 0xC0); HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x01 , PCD_SNG_BUF, 0x110); HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x82 , PCD_SNG_BUF, 0x100); return USBD_OK; }

Эта функция инициализирует USB периферию микроконтроллера. Интереснее всего тут серия вызовов функции HAL_PCDEx_PMAConfig(). Дело в том, что на борту микроконтроллера находится цельных 512 байт памяти отведенных специально под буферы USB (эта память называется PMA — Packet Memory Area).

Но вот что странно, объявляли только 2 конечные точки, а вызовов 5. Откуда взялись лишние? На самом деле лишних тут нет. Дело в том, что у каждого USB устройства обязательно должна быть одна двусторонняя конечная точка, через которую устройство инициализируется, а потом управляется.

Эта конечная точка всегда имеет номер 0. Этой функции инициализируются не конечные точки, а буфера. Для нулевой конечной точки создаются 2 буфера — 0x00 на прием и 0x80 на передачу (старший бит указывает направление передачи, младшие — номер конечной точки).

Последний параметр в каждом вызове указывает смещение буфера конечной точки в общем буфере. На форумах видел вопросы «а что это за магическая константа 0x18 (начальный адрес первого буфера)?». Я детально рассмотрю этот вопрос позже. Сейчас лишь скажу, что первые 0x18 байт PMA памяти занимает таблица распределения буферов.

Но это все кишки и другие внутренности. А что снаружи?

Пользовательский код оперирует функциями приема и передачи, которые находятся в файле usbd_cdc_if.c. Чтобы устройство могло отправлять данные в виртуальный COM порт в сторону хоста нам предоставили функцию CDC_Transmit_FS()

Функция CDC_Transmit_FS()
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len) { uint8_t result = USBD_OK; USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData; if (hcdc->TxState != ){ return USBD_BUSY; } USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len); result = USBD_CDC_TransmitPacket(&hUsbDeviceFS); return result; }

Функция CDC_Receive_FS()

static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len){ uint16_t len = *Len; CDCDataReceivedCallback(Buf, len); // Prepare for next reception USBD_CDC_ReceivePacket(&hUsbDeviceFS); return (USBD_OK); }

Обращаю внимание, что эти функции работают с массивами байт без какой либо структуры. В моем случае мне нужно было отправлять строки. Чтобы это было делать удобно я написал аналог функции printf, которая форматировала строку и отправляла ее в порт. Чтобы повысить скорость я также озадачился двойной буферизацией. Подробнее

в разделах “USB с двойной буферизацией” и “printf”.

Еще в этом же файле находятся функции инициализации/деинициализации виртуального COM порта, а также функция изменения параметров порта (скорость, четность, стоп биты и прочее). Реализация по умолчанию не ограничивает себя в скорости и это меня устраивает. Инициализация так же хороша. Оставим все как есть.

Финальный штрих — код, который это все запускает

код, который это все запускает
 USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS); USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC); USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS); USBD_Start(&hUsbDeviceFS); 

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

Чтобы это все заработало нужен драйвер со стороны операционной системы. Как правило это стандартный драйвер и система может подхватить устройство без особой процедуры инсталляции. Насколько я понимаю у меня в системе уже был установлен Virtual COM Port драйвер от STM (поставился с ST Flash Utility) и мое устройство подхватилось самостоятельно. На линуксе также все завелось с полпинка.

Cdc msc composite device

А теперь со всей этой фигней мы попробуем взлететь (С) анекдот

Итак, мы уже знаем как строить USB устройства, которые могут реализовывать либо CDC либо MSC. Попробуем сделать композитное устройство, которое реализует оба интерфейса одновременно. Я посмотрел несколько других проектов, которые реализовывали композитное USB устройство и, как мне кажется, их подход имеет смысл. А именно: реализовать собственный драйвер класса, который будет реализовывать и ту и ту функциональность.

Заготовку для класса возьмем из пакета STM32 Cube (MiddlewaresSTSTM32_USB_Device_LibraryClassTemplate). Начинкой будет творчески преработаный код отсюда .

Структура USB устройства будет такая:

  • У нас будет всего одна конфигурация, а в ней 3 интерфейса
  • Один интерфейс реализует MSC
    • У него одна двунаправленная конечная точка — для передачи и приема
  • CDC реализуется двумя интерфейсами.
    • Первый для управления. У него одна однонаправленная конечная точка для управления интерфейсом
    • Второй интерфейс CDC для данных. У него двунаправленная конечная точка — для передачи и приема
  • Еще одна конечная точка нужна для управления устройство в целом (реализуется ядром USB библиотеки)

image
Красивая картинка, которая описывает пример описания композитного устройства. Взято из спецификации IAD

Для удобства использования объявим в коде номера конечных точек и интерфейсов.

Номера интерфейсов и конечных точек
#define MSC_INTERFACE_IDX 0x0 // Index of MSC interface#define CDC_INTERFACE_IDX 0x1 // Index of CDC interface// endpoints numbers// endpoints numbers#define MSC_EP_IDX 0x01#define CDC_CMD_EP_IDX 0x02#define CDC_EP_IDX 0x03#define IN_EP_DIR 0x80 // Adds a direction bit#define MSC_OUT_EP MSC_EP_IDX #define MSC_IN_EP MSC_EP_IDX | IN_EP_DIR #define CDC_CMD_EP CDC_CMD_EP_IDX| IN_EP_DIR #define CDC_OUT_EP CDC_EP_IDX #define CDC_IN_EP CDC_EP_IDX | IN_EP_DIR 

Нумерация конечных точек повторяет нумерацию интерфейсов. Будем использовать №1 для MSC, №2 для управления CDC, №3 для передачи данных через CDC. Есть еще нулевая конечная точка для общего управления устройством, но она обрабатывается в недрах ядра USB и объявлять эти номера не обязательно.

Интерфейс USB библиотеки от ST оставляет желать лучшего. В некоторых случаях номера конечных точек используются с флагом направления передачи — установленный старший бит означает направление IN — в сторону хоста (я для этого завел константу IN_EP_DIR).

При этом другие функции используют просто номер конечной точки. В отличии от оригинального дизайна я предпочел разделить эти все номера и использовать правильные константы в нужных местах. Там где используются константы с суффиксом EP_IDX флаг направления передачи не используется.

ВАЖНО! Хоть по спецификации USB номера конечных точек могут быть какими угодно, все же лучше расположить их последовательно и в том же порядке, в котором они объявляются в дескрипторах. Мне это знание далось неделей жесткого дебага, когда виндовый USB драйвер упорно ломился не в ту конечную точку и ничего не работало.

Начнем как обычно с дескрипторов. Большая часть дескрипторов будут жить в нашей реализации класса (usbd_msc_cdc.c), но дескриптор устройства и кое какие глобальные штуки определены в ядре USB в файле usbd_desc.c

Сначала чуток констант
#define USBD_VID 0x0483#define USBD_PID 0x5741#define USBD_LANGID_STRING 0x409#define USBD_MANUFACTURER_STRING "STMicroelectronics"#define USBD_PRODUCT_FS_STRING "Composite MSC CDC"#define USBD_SERIALNUMBER_FS_STRING "00000000055C"#define USBD_CONFIGURATION_FS_STRING "Config Name"#define USBD_INTERFACE_FS_STRING "Interface Name"

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

__ALIGN_BEGIN constuint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = { 0x12,  USB_DESC_TYPE_DEVICE, 0x00, 0x02, 0xEF, 0x02, 0x01,  USB_MAX_EP0_SIZE,  LOBYTE(USBD_VID),  HIBYTE(USBD_VID),  LOBYTE(USBD_PID),  HIBYTE(USBD_PID), 0x00, 0x02, USBD_IDX_MFC_STR,  USBD_IDX_PRODUCT_STR,  USBD_IDX_SERIAL_STR,  USBD_MAX_NUM_CONFIGURATION  };

В целом тут все тоже самое, отличаются только поля, которые определяют класс устройства (bDeviceClass). Теперь эти поля указывают, что это композитное устройство. Хосту нужно будет потрудится, разобраться во всех остальных дескрипторах и подгрузить правильные драйвера для каждого из компонентов.

Дескриптор конфигурации примерно такой же как и раньше, разница только в количестве интерфейсов. Теперь у нас их 3

Дескриптор конфигурации
#define USB_MSC_CDC_CONFIG_DESC_SIZ 98staticconstuint8_t USBD_MSC_CDC_CfgDesc[USB_MSC_CDC_CONFIG_DESC_SIZ] = { 0x09,  USB_DESC_TYPE_CONFIGURATION,  USB_MSC_CDC_CONFIG_DESC_SIZ, 0x00, 0x03, 0x01, 0x02, 0xC0, 0x32, 

Далее идет объявление интерфейса и конечных точек для MSC. Не знаю почему именно в таком порядке (сначала MSC потом CDC). Так было в одном из примеров, которые я нашел, оттуда и скопировал. По идее порядок интерфейсов не имеет значения. Главное, чтобы они возили все свои дополнительные дескрипторы рядом. Ну и приколы с нумерацией конечных точек также имеют значение.

Дескрипторы MSC
0x09, 0x04,  MSC_INTERFACE_IDX, 0x00, 0x02, 0x08, 0x06, 0x50,  USBD_IDX_INTERFACE_STR, 0x07, 0x05,  MSC_IN_EP, 0x02,  LOBYTE(USB_MAX_PACKET_SIZE), HIBYTE(USB_MAX_PACKET_SIZE), 0x00, 0x07, 0x05,  MSC_OUT_EP, 0x02,  LOBYTE(USB_MAX_PACKET_SIZE), HIBYTE(USB_MAX_PACKET_SIZE), 0x00, 

Дескрипторы MSC ничем не отличаются от тех, что были в предыдущем разделе.

А вот дальше идет новый тип дескриптора — IAD (Interface Association Descriptor) – дескриптор ассоциации интерфейсов. Ассоциация тут не в смысле организации, а в смысле какой интерфейс с какой функцией ассоциировать.

Дескриптор ассоциации интерфейсов
0x08, 0x0B,  CDC_INTERFACE_IDX, 0x02, 0x02, 0x02, 0x01, 0x00, 

Этот хитрый дескриптор говорит хосту что описание предыдущей функции USB устройства (MSC) закончилось и сейчас будет совсем другая функция. Причем тут же указано какая именно — CDC. Также указано количество связанных с ней интерфейсов и индекс первого из них.

IAD дескриптор не нужен для MSC, т.к. там всего один интерфейс. Но IAD нужен для CDC чтобы сгруппировать 2 интерфейса в одну функцию. Об этом сказано в спецификации этого дескриптора

Наконец дескрипторы CDC. Они полностью соответствуют дескрипторам для одиночной CDC функции с точностью до номеров интерфейсов и конечных точек

Дескрипторы CDC
0x09,  USB_DESC_TYPE_INTERFACE,  CDC_INTERFACE_IDX, 0x00, 0x01, 0x02, 0x02, 0x01, 0x01, 0x05, 0x24, 0x00, 0x10, 0x01, 0x05, 0x24, 0x01, 0x00,  CDC_INTERFACE_IDX   1, 0x04, 0x24, 0x02, 0x02, 0x05, 0x24, 0x06,  CDC_INTERFACE_IDX,  CDC_INTERFACE_IDX   1, 0x07,  USB_DESC_TYPE_ENDPOINT,  CDC_CMD_EP, 0x03,  LOBYTE(CDC_CMD_PACKET_SIZE),  HIBYTE(CDC_CMD_PACKET_SIZE), 0x10, 0x09,  USB_DESC_TYPE_INTERFACE,  CDC_INTERFACE_IDX   1, 0x00, 0x02, 0x0A, 0x00, 0x00, 0x00, 0x07,  USB_DESC_TYPE_ENDPOINT,  CDC_OUT_EP, 0x02,  LOBYTE(CDC_DATA_PACKET_SIZE),  HIBYTE(CDC_DATA_PACKET_SIZE), 0x00, 0x07,  USB_DESC_TYPE_ENDPOINT,  CDC_IN_EP, 0x02,  LOBYTE(CDC_DATA_PACKET_SIZE),  HIBYTE(CDC_DATA_PACKET_SIZE), 0x00, 

Когда все дескрипторы готовы можно посчитать суммарный размер конфигурации.

#define USB_CDC_CONFIG_DESC_SIZ 98

Перейдем к написанию кода. Ядро USB общается с драйверами классов используя вот такой интерфейс

Интерфейс драйвера класса
typedefstruct _Device_cb { uint8_t (*Init) (struct _USBD_HandleTypeDef *pdev , uint8_t cfgidx); uint8_t (*DeInit) (struct _USBD_HandleTypeDef *pdev , uint8_t cfgidx); uint8_t (*Setup) (struct _USBD_HandleTypeDef *pdev , USBD_SetupReqTypedef *req); uint8_t (*EP0_TxSent) (struct _USBD_HandleTypeDef *pdev ); uint8_t (*EP0_RxReady) (struct _USBD_HandleTypeDef *pdev ); uint8_t (*DataIn) (struct _USBD_HandleTypeDef *pdev , uint8_t epnum); uint8_t (*DataOut) (struct _USBD_HandleTypeDef *pdev , uint8_t epnum); uint8_t (*SOF) (struct _USBD_HandleTypeDef *pdev); uint8_t (*IsoINIncomplete) (struct _USBD_HandleTypeDef *pdev , uint8_t epnum); uint8_t (*IsoOUTIncomplete) (struct _USBD_HandleTypeDef *pdev , uint8_t epnum); constuint8_t *(*GetHSConfigDescriptor)(uint16_t *length); constuint8_t *(*GetFSConfigDescriptor)(uint16_t *length); constuint8_t *(*GetOtherSpeedConfigDescriptor)(uint16_t *length); constuint8_t *(*GetDeviceQualifierDescriptor)(uint16_t *length); #if (USBD_SUPPORT_USER_STRING == 1)uint8_t *(*GetUsrStrDescriptor)(struct _USBD_HandleTypeDef *pdev ,uint8_t index, uint16_t *length); #endif } USBD_ClassTypeDef;

Гаджет:  Анимация для души: лучшие работы в жанре релаксационного аниме — Аниме на DTF

В зависимости от состояния или события на шине USB ядро вызывает соответствующую функцию.

Любую архитектурную проблему можно решить введением дополнительного абстрактного слоя… (С) еще один анекдот

Разумеется мы не будем реализовывать весь функционал целиком — за реализацию классов CDC и MSC будет отвечать существующий код. Мы лишь напишем прослойку, которая будет перенаправлять вызовы либо в одну, либо в другую реализацию.

Инициализация и деинициализация
static uint8_t USBD_MSC_CDC_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx){ uint8_t ret = USBD_MSC_Init (pdev, cfgidx); if(ret != USBD_OK) return ret;  ret = USBD_CDC_Init (pdev, cfgidx); if(ret != USBD_OK) return ret; return USBD_OK; } static uint8_t USBD_MSC_CDC_DeInit(USBD_HandleTypeDef *pdev, uint8_t cfgidx){  USBD_MSC_DeInit(pdev, cfgidx);  USBD_CDC_DeInit(pdev, cfgidx); return USBD_OK; }

Тут все просто: инициализируем (деинициализируем) оба класса. Вызываемые функции сами займутся созданием/удалением своих конечных точек.

Пожалуй самой сложной функцией будет Setup.

Обработчик Setup
static uint8_t USBD_MSC_CDC_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req){ // Route requests to MSC interface or its endpoints to MSC class implementaionif(((req->bmRequest & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_INTERFACE && req->wIndex == MSC_INTERFACE_IDX) || ((req->bmRequest & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_ENDPOINT && ((req->wIndex & 0x7F) == MSC_EP_IDX))) { return USBD_MSC_Setup(pdev, req); } return USBD_CDC_Setup(pdev, req); }

Это коллбек на один из стандартных запросов по шине USB, но этот запрос очень многогранный. Это может быть как получение данных (get), так и установка (Set). Это может быть запрос к устройству в целом, к одному из его интерфейсов или конечных точек. Также тут может приплыть как стандартный запрос, определенный базовой спецификацией USB, так и специфичный для определенного устройства или класса. Подробнее

(Раздел “Пакет Setup”).

Из-за обилия разных случаев структура обработчика пакета Setup весьма сложна. Тут не получается написать один if или switch. В коде ядра USB обработка размазана по 3-4 большим функциям и в определенных случаях передается отдельному специализированному обработчику (коих там еще с десяток). Радует только то, что на уровень драйвера класса передается только незначительная часть запросов.

Я подсмотрел какие пакеты ходят через эту функцию и, похоже, можно ориентироваться по получателю. Если получатель пакета интерфейс — в поле wIndex будет номер интерфейса, если конечная точка, то в wIndex будет номер конечной точки. Исходя из этого перенаправляем запросы в соответствующий обработчик.

Кстати, чтобы это работало нужно не забыть поменять дефайн, определяющий количество интерфейсов, а то запрос просто не дойдет и срежется внутри ядра USB

#define USBD_MAX_NUM_INTERFACES 3

Коллбеками DataIn и DataOut все проще. Там есть номер конечной точки — по ней и определим куда запрос перенаправлять

DataIn() и DataOut()
static uint8_t USBD_MSC_CDC_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum){ if(epnum == MSC_EP_IDX) return USBD_MSC_DataIn(pdev, epnum); return USBD_CDC_DataIn(pdev, epnum); } static uint8_t USBD_MSC_CDC_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum){ if(epnum == MSC_EP_IDX) return USBD_MSC_DataOut(pdev, epnum); return USBD_CDC_DataOut(pdev, epnum); } 

Обратите внимание, что флаг направления передачи в номере конечной точки не используется. Т.е. даже если некоторые функции используют MSC_IN_EP (0x81), то в этой функции нужно использовать MSC_EP_IDX (0x01).

Иногда данные приходят в нулевую конечную точку и для этого есть специальный коллбек. Я не знаю что бы я делал, если бы оба класса (и CDC и MSC) имели обработчики на этот случай – в таком запросе не указан интерфейс или номер конечной точки. Было бы невозможно понять кому адресован запрос. Благо такой запрос умеет обрабатывать только класс CDC – вот ему и отправим

Обработчик EP0_RxReady
static uint8_t USBD_MSC_CDC_EP0_RxReady(USBD_HandleTypeDef *pdev){ return USBD_CDC_EP0_RxReady(pdev); }

Больше у нас не будет нетривиальных обработчиков. Есть еще парочка геттеров для дескрипторов, но их код стандартный и не представляет интереса. Заполним «таблицу виртуальных функций»

Таблица указателей на функции
USBD_ClassTypeDef USBD_MSC_CDC_ClassDriver = { USBD_MSC_CDC_Init, USBD_MSC_CDC_DeInit, USBD_MSC_CDC_Setup, NULL, //USBD_MSC_CDC_EP0_TxReady, USBD_MSC_CDC_EP0_RxReady, USBD_MSC_CDC_DataIn, USBD_MSC_CDC_DataOut, NULL, //USBD_MSC_CDC_SOF,NULL, //USBD_MSC_CDC_IsoINIncomplete,NULL, //USBD_MSC_CDC_IsoOutIncomplete, USBD_MSC_CDC_GetCfgDesc, USBD_MSC_CDC_GetCfgDesc, USBD_MSC_CDC_GetCfgDesc, USBD_MSC_CDC_GetDeviceQualifierDesc, };

Теперь код инициализации

Код инициализации
USBD_Init(&hUsbDeviceFS, &FS_Desc, ); USBD_RegisterClass(&hUsbDeviceFS, &USBD_MSC_CDC_ClassDriver); USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS); USBD_MSC_RegisterStorage(&hUsbDeviceFS, &SdMscDriver); USBD_Start(&hUsbDeviceFS);

Инициализируем USB ядро, устанавливаем ему наш драйвер класса и настраиваем вторичные интерфейсы. Все? Нет не все. В таком виде оно не запустится.

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

Проблема в том, что каждый класс считает эти поля своей собственностью и цепляет туда свою структуру.

Решать это можно несколькими способами. Товарищи отсюда вообще затолкали в свою реализацию класса весь код из обоих драйверов (CDC и MSC) чтобы на ходу разбираться что к чему. Другой подход в том, что в эти поля класть структуры, в которых есть место для данных обоих классов.

Мы, пожалуй, пойдем путем попроще. Если драйверы классов хотят эксклюзивных полей – дадим им эти поля

Kernel mode gadget api¶

Gadget drivers declare themselves through a struct
usb_gadget_driver, which is responsible for most parts of enumeration
for a struct usb_gadget.

The response to a set_configuration usually
involves enabling one or more of the struct usb_ep objects exposed by
the gadget, and submitting one or more struct usb_request buffers to
transfer data. Understand those four data types, and their operations,
and you will understand how this API works.

The core API does not expose every possible hardware feature, only the
most widely available ones. There are significant hardware features,
such as device-to-device DMA (without temporary storage in a memory
buffer) that would be added using hardware-specific APIs.

This API allows drivers to use conditional compilation to handle
endpoint capabilities of different hardware, but doesn’t require that.
Hardware tends to have arbitrary restrictions, relating to transfer
types, addressing, packet sizes, buffering, and availability.

As a rule,
such differences only matter for “endpoint zero” logic that handles
device configuration and management. The API supports limited run-time
detection of capabilities, through naming conventions for endpoints.
Many drivers will be able to at least partially autoconfigure
themselves.

In particular, driver init sections will often have endpoint
autoconfiguration logic that scans the hardware’s list of endpoints to
find ones matching the driver requirements (relying on those
conventions), to eliminate some of the most common reasons for
conditional compilation.

Like the Linux-USB host side API, this API exposes the “chunky” nature
of USB messages: I/O requests are in terms of one or more “packets”, and
packet boundaries are visible to drivers. Compared to RS-232 serial
protocols, USB resembles synchronous protocols like HDLC (N bytes per
frame, multipoint addressing, host as the primary station and devices as
secondary stations) more than asynchronous ones (tty style:

8 data bits
per frame, no parity, one stop bit). So for example the controller
drivers won’t buffer two single byte writes into a single two-byte USB
IN packet, although gadget drivers may do so when they implement
protocols where packet boundaries (and “short packets”) are not
significant.

Driver Life Cycle

Gadget drivers make endpoint I/O requests to hardware without needing to
know many details of the hardware, but driver setup/configuration code
needs to handle some differences. Use the API like this:

  1. Register a driver for the particular device side usb controller
    hardware, such as the net2280 on PCI (USB 2.0), sa11x0 or pxa25x as
    found in Linux PDAs, and so on. At this point the device is logically
    in the USB ch9 initial state (attached), drawing no power and not
    usable (since it does not yet support enumeration). Any host should
    not see the device, since it’s not activated the data line pullup
    used by the host to detect a device, even if VBUS power is available.
  2. Register a gadget driver that implements some higher level device
    function. That will then bind() to a usb_gadget, which activates
    the data line pullup sometime after detecting VBUS.
  3. The hardware driver can now start enumerating. The steps it handles
    are to accept USB power and set_address requests. Other steps are
    handled by the gadget driver. If the gadget driver module is unloaded
    before the host starts to enumerate, steps before step 7 are skipped.
  4. The gadget driver’s setup() call returns usb descriptors, based both
    on what the bus interface hardware provides and on the functionality
    being implemented. That can involve alternate settings or
    configurations, unless the hardware prevents such operation. For OTG
    devices, each configuration descriptor includes an OTG descriptor.
  5. The gadget driver handles the last step of enumeration, when the USB
    host issues a set_configuration call. It enables all endpoints used
    in that configuration, with all interfaces in their default settings.
    That involves using a list of the hardware’s endpoints, enabling each
    endpoint according to its descriptor. It may also involve using
    usb_gadget_vbus_draw to let more power be drawn from VBUS, as
    allowed by that configuration. For OTG devices, setting a
    configuration may also involve reporting HNP capabilities through a
    user interface.
  6. Do real work and perform data transfers, possibly involving changes
    to interface settings or switching to new configurations, until the
    device is disconnect()ed from the host. Queue any number of transfer
    requests to each endpoint. It may be suspended and resumed several
    times before being disconnected. On disconnect, the drivers go back
    to step 3 (above).
  7. When the gadget driver module is being unloaded, the driver unbind()
    callback is issued. That lets the controller driver be unloaded.

Drivers will normally be arranged so that just loading the gadget driver
module (or statically linking it into a Linux kernel) allows the
peripheral device to be enumerated, but some drivers will defer
enumeration until some higher level component (like a user mode daemon)
enables it. Note that at this lowest level there are no policies about
how ep0 configuration logic is implemented, except that it should obey
USB specifications. Such issues are in the domain of gadget drivers,
including knowing about implementation constraints imposed by some USB
controllers or understanding that composite devices might happen to be
built by integrating reusable components.

Note that the lifecycle above can be slightly different for OTG devices.
Other than providing an additional OTG descriptor in each configuration,
only the HNP-related differences are particularly visible to driver
code. They involve reporting requirements during the SET_CONFIGURATION
request, and the option to invoke HNP during some suspend callbacks.
Also, SRP changes the semantics of usb_gadget_wakeup slightly.

Core Objects and Methods

These are declared in <linux/usb/gadget.h>, and are used by gadget
drivers to interact with USB peripheral controller drivers.

struct usb_request

describes one i/o request

Definition

Members

buf
Buffer used for data. Always provide this; some controllers
only use PIO, or don’t use DMA for some endpoints.
length
Length of that data
dma
DMA address corresponding to ‘buf’. If you don’t set this
field, and the usb controller needs one, it is responsible
for mapping and unmapping the buffer.
sg
a scatterlist for SG-capable controllers.
num_sgs
number of SG entries
num_mapped_sgs
number of SG entries mapped to DMA (internal)
stream_id
The stream id, when USB3.0 bulk streams are being used
no_interrupt
If true, hints that no completion irq is needed.
Helpful sometimes with deep request queues that are handled
directly by DMA controllers.
zero
If true, when writing data, makes the last packet be “short”
by adding a zero length packet as needed;
short_not_ok
When reading data, makes short packets be
treated as errors (queue stops advancing till cleanup).
dma_mapped
Indicates if request has been mapped to DMA (internal)
complete
Function called when request completes, so this request and
its buffer may be re-used. The function will always be called with
interrupts disabled, and it must not sleep.
Reads terminate with a short packet, or when the buffer fills,
whichever comes first. When writes terminate, some data bytes
will usually still be in flight (often in a hardware fifo).
Errors (for reads or writes) stop the queue from advancing
until the completion function returns, so that any transfers
invalidated by the error may first be dequeued.
context
For use by the completion callback
list
For use by the gadget driver.
status
Reports completion code, zero or a negative errno.
Normally, faults block the transfer queue from advancing until
the completion callback returns.
Code “-ESHUTDOWN” indicates completion caused by device disconnect,
or when the driver disabled the endpoint.
actual
Reports bytes transferred to/from the buffer. For reads (OUT
transfers) this may be less than the requested length. If the
short_not_ok flag is set, short reads are treated as errors
even when status otherwise indicates successful completion.
Note that for writes (IN transfers) some data bytes may still
reside in a device-side FIFO when the request is reported as
complete.

Description

These are allocated/freed through the endpoint they’re used with. The
hardware’s driver can add extra per-request data to the memory it returns,
which often avoids separate memory allocations (potential failures),
later when the request is queued.

Request flags affect request handling, such as whether a zero length
packet is written (the “zero” flag), whether a short read should be
treated as an error (blocking request queue advance, the “short_not_ok”
flag), or hinting that an interrupt is not required (the “no_interrupt”
flag, for use with deep request queues).

Bulk endpoints can use any size buffers, and can also be used for interrupt
transfers. interrupt-only endpoints can be much less functional.

NOTE

this is analogous to ‘struct urb’ on the host side, except that
it’s thinner and promotes more pre-allocation.

struct usb_ep_caps

endpoint capabilities description

Definition

Members

type_control
Endpoint supports control type (reserved for ep0).
type_iso
Endpoint supports isochronous transfers.
type_bulk
Endpoint supports bulk transfers.
type_int
Endpoint supports interrupt transfers.
dir_in
Endpoint supports IN direction.
dir_out
Endpoint supports OUT direction.
struct usb_ep

device side representation of USB endpoint

Definition

Members

driver_data
for use by the gadget driver.
name
identifier for the endpoint, such as “ep-a” or “ep9in-bulk”
ops
Function pointers used to access hardware-specific operations.
ep_list
the gadget’s ep_list holds all of its endpoints
caps
The structure describing types and directions supported by endoint.
claimed
True if this endpoint is claimed by a function.
enabled
The current endpoint enabled/disabled state.
maxpacket
The maximum packet size used on this endpoint. The initial
value can sometimes be reduced (hardware allowing), according to
the endpoint descriptor used to configure the endpoint.
maxpacket_limit
The maximum packet size value which can be handled by this
endpoint. It’s set once by UDC driver when endpoint is initialized, and
should not be changed. Should not be confused with maxpacket.
max_streams
The maximum number of streams supported
by this EP (0 — 16, actual number is 2^n)
mult
multiplier, ‘mult’ value for SS Isoc EPs
maxburst
the maximum number of bursts supported by this EP (for usb3)
address
used to identify the endpoint when finding descriptor that
matches connection speed
desc
endpoint descriptor. This pointer is set before the endpoint is
enabled and remains valid until the endpoint is disabled.
comp_desc
In case of SuperSpeed support, this is the endpoint companion
descriptor that is used to configure the endpoint

Description

the bus controller driver lists all the general purpose endpoints in
gadget->ep_list. the control endpoint (gadget->ep0) is not in that list,
and is accessed only in response to a driver setup() callback.

struct usb_gadget

represents a usb slave device

Definition

Members

work
(internal use) Workqueue to be used for sysfs_notify()
udc
struct usb_udc pointer for this gadget
ops
Function pointers used to access hardware-specific operations.
ep0
Endpoint zero, used when reading or writing responses to
driver setup() requests
ep_list
List of other endpoints supported by the device.
speed
Speed of current connection to USB host.
max_speed
Maximal speed the UDC can handle. UDC must support this
and all slower speeds.
state
the state we are now (attached, suspended, configured, etc)
name
Identifies the controller hardware type. Used in diagnostics
and sometimes configuration.
dev
Driver model state for this abstract device.
isoch_delay
value from Set Isoch Delay request. Only valid on SS/SSP
out_epnum
last used out ep number
in_epnum
last used in ep number
mA
last set mA value
otg_caps
OTG capabilities of this gadget.
sg_supported
true if we can handle scatter-gather
is_otg
True if the USB device port uses a Mini-AB jack, so that the
gadget driver must provide a USB OTG descriptor.
is_a_peripheral
False unless is_otg, the “A” end of a USB cable
is in the Mini-AB jack, and HNP has been used to switch roles
so that the “A” device currently acts as A-Peripheral, not A-Host.
b_hnp_enable
OTG device feature flag, indicating that the A-Host
enabled HNP support.
a_hnp_support
OTG device feature flag, indicating that the A-Host
supports HNP at this port.
a_alt_hnp_support
OTG device feature flag, indicating that the A-Host
only supports HNP on a different root port.
hnp_polling_support
OTG device feature flag, indicating if the OTG device
in peripheral mode can support HNP polling.
host_request_flag
OTG device feature flag, indicating if A-Peripheral
or B-Peripheral wants to take host role.
quirk_ep_out_aligned_size
epout requires buffer size to be aligned to
MaxPacketSize.
quirk_altset_not_supp
UDC controller doesn’t support alt settings.
quirk_stall_not_supp
UDC controller doesn’t support stalling.
quirk_zlp_not_supp
UDC controller doesn’t support ZLP.
quirk_avoids_skb_reserve
udc/platform wants to avoid skb_reserve() in
u_ether.c to improve performance.
is_selfpowered
if the gadget is self-powered.
deactivated
True if gadget is deactivated — in deactivated state it cannot
be connected.
connected
True if gadget is connected.
lpm_capable
If the gadget max_speed is FULL or HIGH, this flag
indicates that it supports LPM as per the LPM ECN & errata.
Гаджет:  Пропали приложения на Android 😭. Как вернуть?

Description

Gadgets have a mostly-portable “gadget driver” implementing device
functions, handling all usb configurations and interfaces. Gadget
drivers talk to hardware-specific code indirectly, through ops vectors.
That insulates the gadget driver from hardware details, and packages
the hardware endpoints through generic i/o queues. The “usb_gadget”
and “usb_ep” interfaces provide that insulation from the hardware.

Except for the driver data, all fields in this structure are
read-only to the gadget driver. That driver data is part of the
“driver model” infrastructure in 2.6 (and later) kernels, and for
earlier systems is grouped in a similar structure that’s not known
to the rest of the kernel.

Values of the three OTG device feature flags are updated before the
setup() call corresponding to USB_REQ_SET_CONFIGURATION, and before
driver suspend() calls. They are valid only when is_otg, and when the
device is acting as a B-Peripheral (so is_a_peripheral is false).

size_t usb_ep_align(struct usb_ep * ep, size_t len)

returns len aligned to ep’s maxpacketsize.

Parameters

structusb_ep*ep
the endpoint whose maxpacketsize is used to align len
size_tlen
buffer size’s length to align to ep‘s maxpacketsize

Description

This helper is used to align buffer’s size to an ep’s maxpacketsize.

size_t usb_ep_align_maybe(struct usb_gadget * g, struct usb_ep * ep, size_t len)

returns len aligned to ep’s maxpacketsize if gadget requires quirk_ep_out_aligned_size, otherwise returns len.

Parameters

structusb_gadget*g
controller to check for quirk
structusb_ep*ep
the endpoint whose maxpacketsize is used to align len
size_tlen
buffer size’s length to align to ep‘s maxpacketsize

Description

This helper is used in case it’s required for any reason to check and maybe
align buffer’s size to an ep’s maxpacketsize.

int gadget_is_altset_supported(struct usb_gadget * g)

return true iff the hardware supports altsettings

Parameters

structusb_gadget*g
controller to check for quirk
int gadget_is_stall_supported(struct usb_gadget * g)

return true iff the hardware supports stalling

Parameters

structusb_gadget*g
controller to check for quirk
int gadget_is_zlp_supported(struct usb_gadget * g)

return true iff the hardware supports zlp

Parameters

structusb_gadget*g
controller to check for quirk
int gadget_avoids_skb_reserve(struct usb_gadget * g)

return true iff the hardware would like to avoid skb_reserve to improve performance.

Parameters

structusb_gadget*g
controller to check for quirk
int gadget_is_dualspeed(struct usb_gadget * g)

return true iff the hardware handles high speed

Parameters

structusb_gadget*g
controller that might support both high and full speeds
int gadget_is_superspeed(struct usb_gadget * g)

return true if the hardware handles superspeed

Parameters

structusb_gadget*g
controller that might support superspeed
int gadget_is_superspeed_plus(struct usb_gadget * g)

return true if the hardware handles superspeed plus

Parameters

structusb_gadget*g
controller that might support superspeed plus
int gadget_is_otg(struct usb_gadget * g)

return true iff the hardware is OTG-ready

Parameters

structusb_gadget*g
controller that might have a Mini-AB connector

Description

This is a runtime test, since kernels with a USB-OTG stack sometimes
run on boards which only have a Mini-B (or Mini-A) connector.

struct usb_gadget_driver

driver for usb ‘slave’ devices

Definition

Members

function
String describing the gadget’s function
max_speed
Highest speed the driver handles.
bind
the driver’s bind callback
unbind
Invoked when the driver is unbound from a gadget,
usually from rmmod (after a disconnect is reported).
Called in a context that permits sleeping.
setup
Invoked for ep0 control requests that aren’t handled by
the hardware level driver. Most calls must be handled by
the gadget driver, including descriptor and configuration
management. The 16 bit members of the setup data are in
USB byte order. Called in_interrupt; this may not sleep. Driver
queues a response to ep0, or returns negative to stall.
disconnect
Invoked after all transfers have been stopped,
when the host is disconnected. May be called in_interrupt; this
may not sleep. Some devices can’t detect disconnect, so this might
not be called except as part of controller shutdown.
suspend
Invoked on USB suspend. May be called in_interrupt.
resume
Invoked on USB resume. May be called in_interrupt.
reset
Invoked on USB bus reset. It is mandatory for all gadget drivers
and should be called in_interrupt.
driver
Driver model state for this driver.
udc_name
A name of UDC this driver should be bound to. If udc_name is NULL,
this driver will be bound to any available UDC.
pending
UDC core private data used for deferred probe of this driver.
match_existing_only
If udc is not found, return an error and don’t add this
gadget driver to list of pending driver

Description

Devices are disabled till a gadget driver successfully bind()`s,whichmeansthedriverwillhandle:c:func:`setup() requests needed to enumerate (and
meet “chapter 9” requirements) then do some useful work.

If gadget->is_otg is true, the gadget driver must provide an OTG
descriptor during enumeration, or else fail the bind() call. In such
cases, no USB traffic may flow until both bind() returns without
having called usb_gadget_disconnect(), and the USB host stack has
initialized.

Drivers use hardware-specific knowledge to configure the usb hardware.
endpoint addressing is only one of several hardware characteristics that
are in descriptors the ep0 implementation returns from setup() calls.

Except for ep0 implementation, most driver code shouldn’t need change to
run on top of different usb controllers. It’ll use endpoints set up by
that ep0 implementation.

The usb controller driver handles a few standard usb requests. Those
include set_address, and feature flags for devices, interfaces, and
endpoints (the get_status, set_feature, and clear_feature requests).

Accordingly, the driver’s setup() callback must always implement all
get_descriptor requests, returning at least a device descriptor and
a configuration descriptor. Drivers must make sure the endpoint
descriptors match any hardware constraints. Some hardware also constrains
other descriptors. (The pxa250 allows only configurations 1, 2, or 3).

The driver’s setup() callback must also implement set_configuration,
and should also implement set_interface, get_configuration, and
get_interface. Setting a configuration (or interface) is where
endpoints should be activated or (config 0) shut down.

(Note that only the default control endpoint is supported. Neither
hosts nor devices generally support control traffic except to ep0.)

Most devices will ignore USB suspend/resume operations, and so will
not provide those callbacks. However, some may need to change modes
when the host is not longer directing those activities. For example,
local controls (buttons, dials, etc) may need to be re-enabled since
the (remote) host can’t do that any longer; or an error state might
be cleared, to make the device behave identically whether or not
power is maintained.

int usb_gadget_probe_driver(struct usb_gadget_driver * driver)

probe a gadget driver

Parameters

structusb_gadget_driver*driver
the driver being registered

Context

can sleep

Description

Call this in your gadget driver’s module initialization function,
to tell the underlying usb controller driver about your driver.
The bind() function will be called to bind it to a gadget before this
registration call returns. It’s expected that the bind() function will
be in init sections.

int usb_gadget_unregister_driver(struct usb_gadget_driver * driver)

unregister a gadget driver

Parameters

structusb_gadget_driver*driver
the driver being unregistered

Context

can sleep

Description

Call this in your gadget driver’s module cleanup function,
to tell the underlying usb controller that your driver is
going away. If the controller is connected to a USB host,
it will first disconnect(). The driver is also requested
to unbind() and clean up any device state, before this procedure
finally returns. It’s expected that the unbind() functions
will in in exit sections, so may not be linked in some kernels.

struct usb_string

wraps a C string and its USB id

Definition

Members

id
the (nonzero) ID for this string
s
the string, in UTF-8 encoding

Description

If you’re using usb_gadget_get_string(), use this to wrap a string
together with its ID.

struct usb_gadget_strings

a set of USB strings in a given language

Definition

Members

language
identifies the strings’ language (0x0409 for en-us)
strings
array of strings with their ids

Description

If you’re using usb_gadget_get_string(), use this to wrap all the
strings for a given language.

void usb_free_descriptors(struct usb_descriptor_header ** v)

free descriptors returned by usb_copy_descriptors()

Parameters

structusb_descriptor_header**v
vector of descriptors

Optional Utilities

The core API is sufficient for writing a USB Gadget Driver, but some
optional utilities are provided to simplify common tasks. These
utilities include endpoint autoconfiguration.

int usb_gadget_get_string(struct usb_gadget_strings * table, int id, u8 * buf)

fill out a string descriptor

Parameters

structusb_gadget_strings*table
of c strings encoded using UTF-8
intid
string id, from low byte of wValue in get string descriptor
u8*buf
at least 256 bytes, must be 16-bit aligned

Description

Finds the UTF-8 string matching the ID, and converts it into a
string descriptor in utf16-le.
Returns length of descriptor (always even) or negative errno

If your driver needs stings in multiple languages, you’ll probably
“switch (wIndex) { … }” in your ep0 string descriptor logic,
using this routine after choosing which set of UTF-8 strings to use.
Note that US-ASCII is a strict subset of UTF-8; any string bytes with
the eighth bit set will be multibyte UTF-8 characters, not ISO-8859/1
characters (which are also widely used in C strings).

int usb_descriptor_fillbuf(void * buf, unsigned buflen, const struct usb_descriptor_header ** src)

fill buffer with descriptors

Parameters

void*buf
Buffer to be filled
unsignedbuflen
Size of buf
conststructusb_descriptor_header**src
Array of descriptor pointers, terminated by null pointer.

Description

Copies descriptors into the buffer, returning the length or a
negative error code if they can’t all be copied. Useful when
assembling descriptors for an associated set of interfaces used
as part of configuring a composite device; or in other cases where
sets of descriptors need to be marshaled.

int usb_gadget_config_buf(const struct usb_config_descriptor * config, void * buf, unsigned length, const struct usb_descriptor_header ** desc)

builts a complete configuration descriptor

Parameters

conststructusb_config_descriptor*config
Header for the descriptor, including characteristics such
as power requirements and number of interfaces.
void*buf
Buffer for the resulting configuration descriptor.
unsignedlength
Length of buffer. If this is not big enough to hold the
entire configuration descriptor, an error code will be returned.
conststructusb_descriptor_header**desc
Null-terminated vector of pointers to the descriptors (interface,
endpoint, etc) defining all functions in this device configuration.

Description

This copies descriptors into the response buffer, building a descriptor
for that configuration. It returns the buffer length or a negative
status code. The config.wTotalLength field is set to match the length
of the result, but other descriptor fields (including power usage and
interface count) must be set by the caller.

Gadget drivers could use this when constructing a config descriptor
in response to USB_REQ_GET_DESCRIPTOR. They will need to patch the
resulting bDescriptorType value if USB_DT_OTHER_SPEED_CONFIG is needed.

struct usb_descriptor_header ** usb_copy_descriptors(struct usb_descriptor_header ** src)

copy a vector of USB descriptors

Parameters

structusb_descriptor_header**src
null-terminated vector to copy

Context

initialization code, which may sleep

Description

This makes a copy of a vector of USB descriptors. Its primary use
is to support usb_function objects which can have multiple copies,
each needing different descriptors. Functions may have static
tables of descriptors, which are used as templates and customized
with identifiers (for interfaces, strings, endpoints, and more)
as needed by a given function instance.

Composite Device Framework

The core API is sufficient for writing drivers for composite USB devices
(with more than one function in a given configuration), and also
multi-configuration devices (also more than one function, but not
necessarily sharing a given configuration). There is however an optional
framework which makes it easier to reuse and combine functions.

Devices using this framework provide a struct usb_composite_driver,
which in turn provides one or more struct usb_configuration
instances. Each such configuration includes at least one struct
usb_function, which packages a user visible role such as “network
link” or “mass storage device”. Management functions may also exist,
such as “Device Firmware Upgrade”.

struct usb_os_desc_ext_prop

describes one “Extended Property”

Definition

Members

entry
used to keep a list of extended properties
type
Extended Property type
name_len
Extended Property unicode name length, including terminating ‘0’
name
Extended Property name
data_len
Length of Extended Property blob (for unicode store double len)
data
Extended Property blob
item
Represents this Extended Property in configfs
struct usb_os_desc

describes OS descriptors associated with one interface

Definition

Members

ext_compat_id
16 bytes of “Compatible ID” and “Subcompatible ID”
ext_prop
Extended Properties list
ext_prop_len
Total length of Extended Properties blobs
ext_prop_count
Number of Extended Properties
opts_mutex
Optional mutex protecting config data of a usb_function_instance
group
Represents OS descriptors associated with an interface in configfs
owner
Module associated with this OS descriptor
struct usb_os_desc_table

describes OS descriptors associated with one interface of a usb_function

Definition

Members

if_id
Interface id
os_desc
“Extended Compatibility ID” and “Extended Properties” of the
interface

Description

Each interface can have at most one “Extended Compatibility ID” and a
number of “Extended Properties”.

struct usb_function

describes one function of a configuration

Definition

struct usb_function {
  const char                      *name;
  struct usb_gadget_strings       **strings;
  struct usb_descriptor_header    **fs_descriptors;
  struct usb_descriptor_header    **hs_descriptors;
  struct usb_descriptor_header    **ss_descriptors;
  struct usb_descriptor_header    **ssp_descriptors;
  struct usb_configuration        *config;
  struct usb_os_desc_table        *os_desc_table;
  unsigned os_desc_n;
  int (*bind)(struct usb_configuration *, struct usb_function *);
  void (*unbind)(struct usb_configuration *, struct usb_function *);
  void (*free_func)(struct usb_function *f);
  struct module           *mod;
  int (*set_alt)(struct usb_function *, unsigned interface, unsigned alt);
  int (*get_alt)(struct usb_function *, unsigned interface);
  void (*disable)(struct usb_function *);
  int (*setup)(struct usb_function *, const struct usb_ctrlrequest *);
  bool (*req_match)(struct usb_function *,const struct usb_ctrlrequest *, bool config0);
  void (*suspend)(struct usb_function *);
  void (*resume)(struct usb_function *);
  int (*get_status)(struct usb_function *);
  int (*func_suspend)(struct usb_function *, u8 suspend_opt);
};

Members

name
For diagnostics, identifies the function.
strings
tables of strings, keyed by identifiers assigned during bind()
and by language IDs provided in control requests
fs_descriptors
Table of full (or low) speed descriptors, using interface and
string identifiers assigned during bind(). If this pointer is null,
the function will not be available at full speed (or at low speed).
hs_descriptors
Table of high speed descriptors, using interface and
string identifiers assigned during bind(). If this pointer is null,
the function will not be available at high speed.
ss_descriptors
Table of super speed descriptors, using interface and
string identifiers assigned during bind(). If this
pointer is null after initiation, the function will not
be available at super speed.
ssp_descriptors
Table of super speed plus descriptors, using
interface and string identifiers assigned during bind(). If
this pointer is null after initiation, the function will not
be available at super speed plus.
config
assigned when usb_add_function() is called; this is the
configuration with which this function is associated.
os_desc_table
Table of (interface id, os descriptors) pairs. The function
can expose more than one interface. If an interface is a member of
an IAD, only the first interface of IAD has its entry in the table.
os_desc_n
Number of entries in os_desc_table
bind
Before the gadget can register, all of its functions bind() to the
available resources including string and interface identifiers used
in interface or class descriptors; endpoints; I/O buffers; and so on.
unbind
Reverses bind; called as a side effect of unregistering the
driver which added this function.
free_func
free the struct usb_function.
mod
(internal) points to the module that created this structure.
set_alt
(REQUIRED) Reconfigures altsettings; function drivers may
initialize usb_ep.driver data at this time (when it is used).
Note that setting an interface to its current altsetting resets
interface state, and that all interfaces have a disabled state.
get_alt
Returns the active altsetting. If this is not provided,
then only altsetting zero is supported.
disable
(REQUIRED) Indicates the function should be disabled. Reasons
include host resetting or reconfiguring the gadget, and disconnection.
setup
Used for interface-specific control requests.
req_match
Tests if a given class request can be handled by this function.
suspend
Notifies functions when the host stops sending USB traffic.
resume
Notifies functions when the host restarts USB traffic.
get_status
Returns function status as a reply to
GetStatus() request when the recipient is Interface.
func_suspend
callback to be called when
SetFeature(FUNCTION_SUSPEND) is reseived
Гаджет:  Penny | Inspector Gadget Wiki | Fandom

Description

A single USB function uses one or more interfaces, and should in most
cases support operation at both full and high speeds. Each function is
associated by usb_add_function() with a one configuration; that function
causes bind() to be called so resources can be allocated as part of
setting up a gadget driver. Those resources include endpoints, which
should be allocated using usb_ep_autoconfig().

To support dual speed operation, a function driver provides descriptors
for both high and full speed operation. Except in rare cases that don’t
involve bulk endpoints, each speed needs different endpoint descriptors.

Function drivers choose their own strategies for managing instance data.
The simplest strategy just declares it “static’, which means the function
can only be activated once. If the function needs to be exposed in more
than one configuration at a given speed, it needs to support multiple
usb_function structures (one for each configuration).

A more complex strategy might encapsulate a usb_function structure inside
a driver-specific instance structure to allows multiple activations. An
example of multiple activations might be a CDC ACM function that supports
two or more distinct instances within the same configuration, providing
several independent logical data links to a USB host.

struct usb_configuration

represents one gadget configuration

Definition

Members

label
For diagnostics, describes the configuration.
strings
Tables of strings, keyed by identifiers assigned during bind()
and by language IDs provided in control requests.
descriptors
Table of descriptors preceding all function descriptors.
Examples include OTG and vendor-specific descriptors.
unbind
Reverses bind; called as a side effect of unregistering the
driver which added this configuration.
setup
Used to delegate control requests that aren’t handled by standard
device infrastructure or directed at a specific interface.
bConfigurationValue
Copied into configuration descriptor.
iConfiguration
Copied into configuration descriptor.
bmAttributes
Copied into configuration descriptor.
MaxPower
Power consumtion in mA. Used to compute bMaxPower in the
configuration descriptor after considering the bus speed.
cdev
assigned by usb_add_config() before calling bind(); this is
the device associated with this configuration.

Description

Configurations are building blocks for gadget drivers structured around
function drivers. Simple USB gadgets require only one function and one
configuration, and handle dual-speed hardware by always providing the same
functionality. Slightly more complex gadgets may have more than one
single-function configuration at a given speed; or have configurations
that only work at one speed.

Composite devices are, by definition, ones with configurations which
include more than one function.

The lifecycle of a usb_configuration includes allocation, initialization
of the fields described above, and calling usb_add_config() to set up
internal data and bind it to a specific device. The configuration’s
bind() method is then used to initialize all the functions and then
call usb_add_function() for them.

Those functions would normally be independent of each other, but that’s
not mandatory. CDC WMC devices are an example where functions often
depend on other functions, with some functions subsidiary to others.
Such interdependency may be managed in any way, so long as all of the
descriptors complete by the time the composite driver returns from
its bind() routine.

struct usb_composite_driver

groups configurations into a gadget

Definition

Members

name
For diagnostics, identifies the driver.
dev
Template descriptor for the device, including default device
identifiers.
strings
tables of strings, keyed by identifiers assigned during bind
and language IDs provided in control requests. Note: The first entries
are predefined. The first entry that may be used is
USB_GADGET_FIRST_AVAIL_IDX
max_speed
Highest speed the driver supports.
needs_serial
set to 1 if the gadget needs userspace to provide
a serial number. If one is not provided, warning will be printed.
bind
(REQUIRED) Used to allocate resources that are shared across the
whole device, such as string IDs, and add its configurations using
usb_add_config(). This may fail by returning a negative errno
value; it should return zero on successful initialization.
unbind
Reverses bind; called as a side effect of unregistering
this driver.
disconnect
optional driver disconnect method
suspend
Notifies when the host stops sending USB traffic,
after function notifications
resume
Notifies configuration when the host restarts USB traffic,
before function notifications
gadget_driver
Gadget driver controlling this driver

Description

Devices default to reporting self powered operation. Devices which rely
on bus powered operation should report this in their bind method.

Before returning from bind, various fields in the template descriptor
may be overridden. These include the idVendor/idProduct/bcdDevice values
normally to bind the appropriate host side driver, and the three strings
(iManufacturer, iProduct, iSerialNumber) normally used to provide user
meaningful device identifiers. (The strings will not be defined unless
they are defined in dev and strings.) The correct ep0 maxpacket size
is also reported, as defined by the underlying controller driver.

module_usb_composite_driver(__usb_composite_driver)

Helper macro for registering a USB gadget composite driver

Parameters

__usb_composite_driver
usb_composite_driver struct

Description

Helper macro for USB gadget composite drivers which do not do anything
special in module init/exit. This eliminates a lot of boilerplate. Each
module may only use this macro once, and calling it replaces module_init()
and module_exit()

struct usb_composite_dev

represents one composite usb gadget

Definition

Members

gadget
read-only, abstracts the gadget’s usb peripheral controller
req
used for control responses; buffer is pre-allocated
os_desc_req
used for OS descriptors responses; buffer is pre-allocated
config
the currently active configuration
qw_sign
qwSignature part of the OS string
b_vendor_code
bMS_VendorCode part of the OS string
os_desc_config
the configuration to be used with OS descriptors
use_os_string
false by default, interested gadgets set it
setup_pending
true when setup request is queued but not completed
os_desc_pending
true when os_desc request is queued but not completed

Description

One of these devices is allocated and initialized before the
associated device driver’s bind() is called.

OPEN ISSUE: it appears that some WUSB devices will need to be
built by combining a normal (wired) gadget with a wireless one.
This revision of the gadget framework should probably try to make
sure doing that won’t hurt too much.

One notion for how to handle Wireless USB devices involves:

  1. a second gadget here, discovery mechanism TBD, but likely
    needing separate “register/unregister WUSB gadget” calls;
  2. updates to usb_gadget to include flags “is it wireless”,
    “is it wired”, plus (presumably in a wrapper structure)
    bandgroup and PHY info;
  3. presumably a wireless_ep wrapping a usb_ep, and reporting
    wireless-specific parameters like maxburst and maxsequence;
  4. configurations that are specific to wireless links;
  5. function drivers that understand wireless configs and will
    support wireless for (additional) function instances;
  6. a function to support association setup (like CBAF), not
    necessarily requiring a wireless adapter;
  7. composite device setup that can create one or more wireless
    configs, including appropriate association setup support;
  8. more, TBD.
int config_ep_by_speed(struct usb_gadget * g, struct usb_function * f, struct usb_ep * _ep)

configures the given endpoint according to gadget speed.

Parameters

structusb_gadget*g
pointer to the gadget
structusb_function*f
usb function
structusb_ep*_ep
the endpoint to configure

Return

error code, 0 on success

This function chooses the right descriptors for a given
endpoint according to gadget speed and saves it in the
endpoint desc field. If the endpoint already has a descriptor
assigned to it — overwrites it with currently corresponding
descriptor. The endpoint maxpacket field is updated according
to the chosen descriptor.

Note

the supplied function should hold all the descriptors
for supported speeds

int usb_add_function(struct usb_configuration * config, struct usb_function * function)

add a function to a configuration

Parameters

structusb_configuration*config
the configuration
structusb_function*function
the function being added

Context

single threaded during gadget setup

Description

After initialization, each configuration must have one or more
functions added to it. Adding a function involves calling its bind()
method to allocate resources such as interface and string identifiers
and endpoints.

This function returns the value of the function’s bind(), which is
zero for success else a negative errno value.

int usb_function_deactivate(struct usb_function * function)

prevent function and gadget enumeration

Parameters

structusb_function*function
the function that isn’t yet ready to respond

Description

Blocks response of the gadget driver to host enumeration by
preventing the data line pullup from being activated. This is
normally called during bind() processing to change from the
initial “ready to respond” state, or when a required resource
becomes available.

For example, drivers that serve as a passthrough to a userspace
daemon can block enumeration unless that daemon (such as an OBEX,
MTP, or print server) is ready to handle host requests.

Not all systems support software control of their USB peripheral
data pullups.

Returns zero on success, else negative errno.

int usb_function_activate(struct usb_function * function)

allow function and gadget enumeration

Parameters

structusb_function*function
function on which usb_function_activate() was called

Description

Reverses effect of usb_function_deactivate(). If no more functions
are delaying their activation, the gadget driver will respond to
host enumeration procedures.

Returns zero on success, else negative errno.

int usb_interface_id(struct usb_configuration * config, struct usb_function * function)

allocate an unused interface ID

Parameters

structusb_configuration*config
configuration associated with the interface
structusb_function*function
function handling the interface

Context

single threaded during gadget setup

Description

usb_interface_id() is called from usb_function.:c:func:bind() callbacks to
allocate new interface IDs. The function driver will then store that
ID in interface, association, CDC union, and other descriptors. It
will also handle any control requests targeted at that interface,
particularly changing its altsetting via set_alt(). There may
also be class-specific or vendor-specific requests to handle.

All interface identifier should be allocated using this routine, to
ensure that for example different functions don’t wrongly assign
different meanings to the same identifier. Note that since interface
identifiers are configuration-specific, functions used in more than
one configuration (or more than once in a given configuration) need
multiple versions of the relevant descriptors.

Returns the interface ID which was allocated; or -ENODEV if no
more interface IDs can be allocated.

int usb_add_config(struct usb_composite_dev * cdev, struct usb_configuration * config, int (*bind) (struct usb_configuration *)

add a configuration to a device.

Parameters

structusb_composite_dev*cdev
wraps the USB gadget
structusb_configuration*config
the configuration, with bConfigurationValue assigned
int(*)(structusb_configuration*)bind
the configuration’s bind function

Context

single threaded during gadget setup

Description

One of the main tasks of a composite bind() routine is to
add each of the configurations it supports, using this routine.

This function returns the value of the configuration’s bind(), which
is zero for success else a negative errno value. Binding configurations
assigns global resources including string IDs, and per-configuration
resources such as interface IDs and endpoints.

int usb_string_id(struct usb_composite_dev * cdev)

allocate an unused string ID

Parameters

structusb_composite_dev*cdev
the device whose string descriptor IDs are being allocated

Context

single threaded during gadget setup

Description

usb_string_id() is called from bind() callbacks to allocate
string IDs. Drivers for functions, configurations, or gadgets will
then store that ID in the appropriate descriptors and string table.

All string identifier should be allocated using this,
usb_string_ids_tab() or usb_string_ids_n() routine, to ensure
that for example different functions don’t wrongly assign different
meanings to the same identifier.

int usb_string_ids_tab(struct usb_composite_dev * cdev, struct usb_string * str)

allocate unused string IDs in batch

Parameters

structusb_composite_dev*cdev
the device whose string descriptor IDs are being allocated
structusb_string*str
an array of usb_string objects to assign numbers to

Context

single threaded during gadget setup

Description

usb_string_ids() is called from bind() callbacks to allocate
string IDs. Drivers for functions, configurations, or gadgets will
then copy IDs from the string table to the appropriate descriptors
and string table for other languages.

All string identifier should be allocated using this,
usb_string_id() or usb_string_ids_n() routine, to ensure that for
example different functions don’t wrongly assign different meanings
to the same identifier.

struct usb_string * usb_gstrings_attach(struct usb_composite_dev * cdev, struct usb_gadget_strings ** sp, unsigned n_strings)

attach gadget strings to a cdev and assign ids

Parameters

structusb_composite_dev*cdev
the device whose string descriptor IDs are being allocated
and attached.
structusb_gadget_strings**sp
an array of usb_gadget_strings to attach.
unsignedn_strings
number of entries in each usb_strings array (sp[]->strings)

Description

This function will create a deep copy of usb_gadget_strings and usb_string
and attach it to the cdev. The actual string (usb_string.s) will not be
copied but only a referenced will be made. The struct usb_gadget_strings
array may contain multiple languages and should be NULL terminated.
The ->language pointer of each struct usb_gadget_strings has to contain the
same amount of entries.
For instance: sp[0] is en-US, sp[1] is es-ES. It is expected that the first
usb_string entry of es-ES contains the translation of the first usb_string
entry of en-US. Therefore both entries become the same id assign.

int usb_string_ids_n(struct usb_composite_dev * c, unsigned n)

allocate unused string IDs in batch

Parameters

structusb_composite_dev*c
the device whose string descriptor IDs are being allocated
unsignedn
number of string IDs to allocate

Context

single threaded during gadget setup

Description

Returns the first requested ID. This ID and next n-1 IDs are now
valid IDs. At least provided that n is non-zero because if it
is, returns last requested ID which is now very useful information.

usb_string_ids_n() is called from bind() callbacks to allocate
string IDs. Drivers for functions, configurations, or gadgets will
then store that ID in the appropriate descriptors and string table.

All string identifier should be allocated using this,
usb_string_id() or usb_string_ids_n() routine, to ensure that for
example different functions don’t wrongly assign different meanings
to the same identifier.

int usb_composite_probe(struct usb_composite_driver * driver)

register a composite driver

Parameters

structusb_composite_driver*driver
the driver to register

Context

single threaded during gadget setup

Description

This function is used to register drivers using the composite driver
framework. The return value is zero, or a negative errno value.
Those values normally come from the driver’s bind method, which does
all the work of setting up the driver to match the hardware.

On successful return, the gadget is ready to respond to requests from
the host, unless one of its components invokes usb_gadget_disconnect()
while it was binding. That would usually be done in order to wait for
some userspace participation.

void usb_composite_unregister(struct usb_composite_driver * driver)

unregister a composite driver

Parameters

structusb_composite_driver*driver
the driver to unregister

Description

This function is used to unregister drivers using the composite
driver framework.

void usb_composite_setup_continue(struct usb_composite_dev * cdev)

Continue with the control transfer

Parameters

structusb_composite_dev*cdev
the composite device who’s control transfer was kept waiting

Description

This function must be called by the USB function driver to continue
with the control transfer’s data/status stage in case it had requested to
delay the data/status stages. A USB function’s setup handler (e.g. set_alt())
can request the composite framework to delay the setup request’s data/status
stages by returning USB_GADGET_DELAYED_STATUS.

Оцените статью
GadgetManiac
Добавить комментарий