Розбираємо і збираємо назад стек USB

    Ілюстрована проекція моделі мережевої взаємодії OSI на універсальну послідовну шину
 
 

Три «чудових» рівня стека USB

Мене не влаштував вид стека USB, який можна зустріти найчастіше на просторах мережі:
 
 Не сильно корисний стек USB
Рівень шини, логічний, функціональний… Це, звичайно, чудові абстракції, але вони скоріше для тих, хто збирається робити драйвер або прикладної софт для хоста. На стороні ж мікроконтролера я очікую шаблонний кінцевий автомат, в вузли якого ми зазвичай вбудовуємо свій корисний код, і він спершу буде за всіма законами жанру глючити. Або ж глючити буде софт на хості. Або драйвер. У будь-якому випадку хтось буде глючити. У бібліотеках МК теж з наскоку не розібратися. І ось я дивлюся на трафік по шині USB аналізатором, де відбуваються події незнайомою мовою з трьома чудовими рівнями взагалі не в'яжуться. Цікаво, це у мене від грипозної лихоманки в голові такий дисонанс?
 
Якщо у читача бували подібні відчуття, пропоную альтернативне, котре з'явилося мені несподівано ясно в перегрітому мозку бачення стека USB, за мотивами улюбленої 7-рівневої моделі OSI. Я обмежився п'ятьма рівнями:
 
 
 
Я не хочу сказати, що весь софт і бібліотеки вже зроблені або повинні проектуватися, виходячи з цієї моделі. З інженерних міркувань код c рівнями буде сильно перемішаний. Але я хочу допомогти тим, хто починає своє знайомство з шиною USB, хто хоче зрозуміти протоколи обміну пристроїв і термінологію предметної області, підібратися ближче до готових прикладів, бібліотекам і краще орієнтуватися в них. Ця модель не для завантаження в МК, але у ваші блискучі уми, дорогі друзі. А ваші золоті руки потім все самі зроблять, я не сумніваюся :)
 
Отже, поїхали, поправляйте, якщо побачите косяки. Це draft-версія, і якщо вже таке десь було намальовано, прошу пробачити, я не знайшов і тому скрутив сам. Думаю, картинка нікуди не втече, а я поки поясню поважній публіці, навіщо взагалі взявся за цю публікацію.
 
 Черговий флешбек з дев'яностих Свій перший баг з чужого коду я витрусив наприкінці дев'яностих, будучи студентом на підробітках. Це був pppd під FreeBSD, який ми тоді прикрутили на модемний пул. Мотороловскіе модеми залипали в відбої, додзвонитися ніхто не міг, лінія пропадала зазря, і єдиний залишився спосіб через PPP keep-alive чомусь глючил. Ось тоді я і з'ясував, що pppd навіщось чекає шість відповідних байтів LCP замість належних чотирьох. Відчув я себе тоді таким собі хвацьким жукотрясом з дев'яностих:-) При чому тут PPP? Просто він на USB схожий: пакетний і двоточковий. Правда, на відміну від USB 2.0, повнодуплексний.
Хочемо ми цього чи ні, але еволюція мікроконтролерів на місці стояти явно не збирається. Ні, ні, та й промайне в публікаціях (http://habrahabr.ru/post/208026/ , http://habrahabr.ru/post/233391/ ) «важка периферія» — вмонтовані в МК реалізації шини USB, з розборами прикладів, використанням HID і т.п. Треба віддати належне автору RaJa : з восьми прикладів, наведених в стандартній бібліотеці STSW-STM32121 (UM0424) і абияк документованих , він вибрав самий корисний (Custom HID), портував його в безкоштовну середу Em :: Blocks, виклав зрозумілою мовою, трохи прикрасив, браво! Це заощадило мені силу-силенну часу.
 
 

Як пройти в бібліотеку?

Отримавши на GitHub люб'язно викладений автором проект RHIDDemo для Em :: Blocks, я почав портировать його в Keil (мій відладчик CoLink на базі FTDI; хто-небудь, підкажіть плагін від Coocox для Em :: Blocks). Але ніяк не міг зрозуміти: де, чорт візьми, автор роздобув SPL 3.6.1 випуску 2012р, якщо на сайті викладений 3.5.0 від 2011р? Я пройшов досить нудний квест, який на мій подив привів… прямо на готовий проект Custom HID для Keil в складі бібліотеки USB FS 4.0.0. Лежить у всіх на виду, як миша під віником. Ну і ладно. Зате я розкурив, нарешті, релізи STMicroelectronics, знайшов опис бібліотеки USB FS STSW-STM32121 (UM0424) і присік спроби розробника звести мене з розуму. От скажіть, це нормально підкладати вінтажний CMSIS 1.30 зразка 2009р в набір SPL 3.5.0 випуску 2011р, новий SPL 3.6.1 релізу 2012р ховати в USB-FS 4.0.0 релізу 2013р (підклавши туди ж і CMSIS 3.0.1 від 2012р), при тому, що у них же викладена актуальна версія CMSIS 3.30 релізу 2014р? До речі, в SPL 3.6.x для STM32F10X виправили пару багів з USART, що стосуються сигналів про переповнення буфера. Спасибі, хоч release notes залишили…
 
 

HID vs SNMP

Отже, взявшись за STM32F103C8T6, я теж вирішив злегка засунуть по темі USB HID, аж надто добре абстракція USB HID вкладається в концепцію усіляких датчиків, сенсорів та інших шим-керованих драйверів харчування. Чимось нагадало мені SNMP, тільки в сильно спрощеному вигляді: дескриптори HID грають роль SNMP MIB. Коли пристрій инициализируется хостом: «Привіт, хост! Я кавоварка. У мене є кнопка [старт], регулятори [вершки], [цукор], датчики [залишок кави], [залишок води], [залишок цукру], [залишок вершків]. Підтягуй драйвера, дави на кнопку, кофейку поп'ємо ». Нічого не нагадує? Приклад діалогу по SNMP: «Ну, привіт, керуюча станція з софтом за $ 100 000. А я шасі комутатора за $ 200 000, і на мені сидять ще 4 модуля по $ 100 000 за штуку; в кожному ще по 16 портів з непристойною швидкістю, і всіх функцій тут просто не перерахувати… питай окремо по кожному пункту; ах, да завантаження процесора така-то, пам'яті стільки-то… ». І ще на дюжину сторінок в такому ж дусі.
 
Сподобалася мені ідея HID. Але варто було вийти з Windows за рамки навчальних завдань мигання світлодіодами (вперед до реальних оточенням UNIX!), Як початок протягати з усіх незакритих щілин , і я відчув себе якимось безпорадним ламером. Налагоджуючи проект, я інстинктивно схопився за якусь подобу tcpdump (так і називається: usbdump (8) , або usbmon ), але побачив лише повідомлення незнайомою мовою.
 
Стало очевидно: не вистачає фундаментальних знань про шину USB. Якщо модель OSI і стек TCP / IP будь тертий айтишник усвідомлює десь на рівні спинного мозку просто в силу необхідності, то з USB ситуація інша. Воно й зрозуміло: там можна (потрібно) підглянути трафік через той же tcpdump да налаштувати залізо з софтом, а тут повний plug and play, і виправити щось можна, оновивши драйвер або прошивку (або перевстановити ОС). Але ж ми тут з вами зібралися якраз за тим, щоб робити хороші прошивки, чи не так? Почитавши деякі описи USB в мережі, я був здивований, наскільки заплутаною може бути документація. У мене навіть виникло відчуття, що нас спеціально хочуть збити з шляху істинного, напустити туману і позбувшись від конкуренції в зародку. Я не згоден з таким станом речей!
 
 

Ще одна чудова схема

На просторах мережі зустрів ще таку ілюстрацію (лежало в форматі BMP, без жартів):
 
 
Спершу виглядає оптимістично. Нарешті, стек в розібраному вигляді. Кадри, правда, позначені невдало: я б намалював їх вертикальними пунктирними лініями, а EOF — це просто пауза, реально дані не передаються. Але починаємо читати контекст і втрачаємо розуміємо істинний задум автора (заплутати нас):
 
 
Хост-контролер інтерфейсу шини USB формує кадри ;
 Кадри передаються послідовної передачею біт за методом NRZI.
І ось ще:
 
кожен кадр складається з найбільш пріоритетних посилок , склад яких формує драйвер хоста;
кожна передача складається з однієї або декількох транзакцій;
кожна транзакція складається з пакетів ;
кожен пакет складається з ідентифікатора пакету, даних (якщо вони є) і контрольної суми.
Начебто і намальовано все правильно, але в міру прочитання питань стає все більше. Мінімальна передана структура даних по шині — це кадр або пакет? Взагалі, це зверху вниз треба дивитися чи навпаки? І що кодується за методом NRZI — кадри, пакети або просто весь потік бітів по шині? З транзакцій складається посилка, передача, або, може бути, цінна бандероль яка?
Чому не можна просто: хост групує пакети в транзакції і розподіляє їх по тимчасовим квантам, що має назву кадрами, щоб давати пріоритет критичним за часом даними (відео, аудіо) виходячи з поточної пропускної здатності шини? Так, в USB є нюанси з плануванням передачі пакетів, я їх поки не зачіпаю.
 
 

Моє бачення стека USB

Доброю документацією вважаю згадуваний тут на Хабре USB in a NutShell (ура, переклад ), а також USB Made Simple . За ним я і зібрав свою версію стека USB, намалюю її ще раз.
 
 
 
 
Фізичний рівень
На фізичному рівні використовується набір електричних режимів диференціальної пари провідників для позначення станів, за допомогою яких кодується бітовий потік по методу NRZI зі вставкою біт (bit stuffing). Т.е. передаються нулики, одинички і всякі керуючі символи.
 
 
Пакетний рівень
На пакетному рівні між хостом і пристроєм передаються безадресні пакети (пара пристроїв на полудуплексной лінії може обійтися і без адресації). Пакет складається з маркера SYNC для синхронізації тактів приймача, послідовності байт і символу EOP. Довжина пакета змінна, але обмовляється через верхні рівні стека. Перший байт називається Packet Identifier (PID), має простий надлишковий формат для завадостійкості і придатний для згодовування автомату наступного рівня (для збірки транзакцій з пакетів). Пакети з начинкою (довший одного байта PID) забезпечуються контрольної сумою (короткої CRC5 або довгою CRC16, залежно від типу пакета). Аналізатор протоколів повинен, як мінімум, показувати нам пакети.
 
 
Рівень транзакцій
На наступному рівні з пакетів збираються транзакції . Транзакція — це малий набір пакетів (1, 2 або 3), наступних строго один за одним, якими (в напівдуплексному режимі) хост обмінюється з крайовою крапкою (endpoint), і тільки з однією. Дуже важливо, що транзакцію відкриває тільки хост, це специфіка USB (нам в прошивці МК менше мороки). На рівні транзакцій можна говорити про каналі (pipe) між хостом і однієї з кінцевих точок пристрою, але я навмисно уникаю терміна «канальний рівень» (Data Link) з моделі OSI. Аналізатор протоколів повинен хоча б декодувати транзакції.
 
 
Рівень передач
Поверх транзакцій розташуємо рівень передач (transfers). Їх в USB використовується чотири типи: контрольні із крайовою точкою №0 (control transfers), передачі з перериваннями (interrupt transfers), ізохронні (isochronous transfers) і великоблочні передачі (bulk transfers). Останні три є варіантами потокових каналів (stream pipe), про які я ще скажу кілька слів. Цей рівень теж повинен відобразити хороший аналізатор протоколів.
 
 
Прикладний рівень
Вінчає стек, як звичайно, прикладний рівень. Тут відбуваються: установка адреси влаштуванню хостом, розповідь пристрою про себе на мові дескрипторів, команди хоста на вибір конфігурації (контрольні передачі), обмін даними з HID-пристроями (в прикладах поки знайшов передачу з перериваннями, хочу спробувати контрольну), друк на принтері і сканування, доступ до накопичувача USB (великоблочні), спілкування через гарнітури і веб-камери (ізохронні) і багато інших чудові речі.
 
 
Останній штрих
Насправді це майже все, що я хотів разсказать. Втікши секунду вниз по рівнях, можна додати, що хост періодично вкидає по шині ті самі пакети Start of Frame (SOF), розбиваючи час на рівні інтервали, але так, щоб не розбити при цьому самі транзакції. Тому пакети SOF можна вважати самостійними транзакціями. Не слід плутати кадр (фрейм) USB з омонімом канального рівня моделі OSI. Краще вже згадати кадри (фрейми) аудіо CD, це просто квант часу: хост «цокає» в шину пакетами SOF, щоб підключені пристрої заздалегідь планували участь у т.зв. ізохронних передачах , що ганяють потоки даних в реальному часі. Ну або ось так: групи транзакцій плануються хостом по тимчасових інтервалах, що має назву кадрами. Кадр становить 1мс на Full Speed ​​і 125мкс на High Speed ​​USB.
 
 

Дивимося на трафік по USB

Хороша підбірка ілюстрацій є у згаданій книжці USB Made Simple, глава 5: www.usbmadesimple.co.uk/ums_5.htm
 Ось одна з них
Отже, транзакція завжди ініціюється хостом стосовно однієї вибраної оконечной точки на пристрої (крім спеціальної точки з номером 0, їх може бути ще до 15 штук на одному пристрої, наприклад, комбінована клавіатура з мишею, термометром, флешкою, кавоваркою і кнопкою виклику сантехніка замовлення піци).
У разі прийому хостом даних з пристрою останнє не може саме відкрити транзакцію, але може тільки дочекатися потрібного моменту і прийняти участь в ній. Хост відкриває транзакцію влаштуванню пакетом з PID = IN (група Token) і гарантує на потрібний час свободу шини, пристрій вкидає пакет з групи Data, в залежності від типу транзакції хост може підтвердити успіх третім пакетом з групи Handshake (ACK, NAK, STALL, NYET ), транзакція закрита.
При відправці даних на пристрій (PID = OUT, група Token) хост відкриває транзакцію, відправляє пакет з даними (Data), також залежно від режиму може прийняти пакет Handshake з підтвердженням успішності транзакції.
По закінченні транзакції все повернеться на круги своя, пристрій знову чекатиме керуючих пакетів від хоста.
 
 

Режими передачі USB в прикладах STM32 USB FS

Щоб по одній парі проводів можна було гнати копіювання з диска одночасно з аудіо-відео потоком, жестами мишею і сигналом швидкісного осцилографа, існують різні типи повідомлень і передач.
Трохи вище я щойно описав простий потоковий канал (Stream Pipe) між хостом і крайовою точкою, де пакети з начинкою (групи Data) не несуть ніякої спеціальної або керуючої інформацією самої підсистемі USB. Повна свобода листування, бібліотека контролера повинні надавати примітиви для закачування буфера довільного розміру з пам'яті МК хосту або назад. Нарізкою на пакети, пересиланням та «дефрагментацією» нехай займаються бібліотека МК на пару з драйвером хоста. В STM32 це USB_SIL_Write () і USB_SIL_Read (), описані в UM0424. Вони і є той самий логічний рівень абстракції. На стороні хоста см. Опис відповідного драйвера (наприклад, під FreeBSD це ugen (4) ).
Однак використовувати важку периферію кшталт USB для організації простого потокового каналу я вважаю блюзнірством (питається: чим USART не догодив?). Але ситуації, звичайно, бувають всякі.
У будь-якому випадку, щоб підсистема USB взагалі ожила і пристрій визначилося, потрібно обмін контрольними транзакціями.
 
 

DISCLAIMER

Далі будуть згадуватися приклади з тієї самої бібліотеки UM0424 для роботи з Full Speed ​​USB від STMicroelectronics, але вони розраховані під їхні рідні демоплати. Беріть приклад з автора Raja , проявляйте інженерну кмітливість в адаптації проектів під свою демоплату.
 
По софту все зрозуміло: це приклади не для промислового використання, там можуть бути баги, деякі частини (типу таблиці посилань в прикладі Mass storage) захищені патентом, і ви не маєте прав їх використовувати в комерційному проекті. Але це ще нічого, китайці примудряються потім продавати на ринку USB-вироби, у яких навіть бібліотечні VID і PID не спромоглися поміняти.
 
По залізу, як я зрозумів, треба починати з кварцу. У мене челябінський PinBoard II з кварцом 12МГц (всі бібліотеки заточені під 8МГц), я міняв помножувач ФАПЧ з 9 на 6 (посилання з роз'ясненнями), інакше МК розженеться до 108МГц замість 72МГц, а USB на 72МГц замість належних 48МГц взагалі не поїде. Можна ще зменшити оберти МК до 48МГц, помінявши дільник шини USB з півтора до одиниці. Використовувати внутрішній генератор МК HSI специ не люблять : частота може злегка поплисти від нагріву, наслідки для USB передбачити важко. Ну і не забуваємо про периферію, звичайно. Без флеш-пам'яті SPI / SDIO із прикладу Mass storage можна зробити хіба що аналог / dev / null, але його адже хрін отформатіруешь:-)
 
 
Контрольні передачі і канали повідомлень
Думаючи про USB, згадую добрий старий протокол PPP з його LCP , IPCP , CCP і ще хзCP . Обмін хоста з крайовою точкою №0 повідомленнями особливого виду і є місцевий еквівалент xзCP.
Через контрольні передачі пристрій инициализируется, отримує адресу, розповідає про себе хосту на мові дескрипторів (щоб той підшукав і активував потрібний драйвер). Без контрольних операцій не «поїдуть» і прості потокові передачі, якщо пристрій не відповість за формою, хост скоріше заглушить порт: протокол треба дотримуватися.
В принципі, протокол не забороняє повісити на контрольну точку №0 і обмін даними, аналогічно режиму з перериваннями. Заодно задумайтеся: як будете оновлювати прошивку МК, так сказати, в польових умовах? Программатор напоготові тримати? Є й інше рішення.
Приклад: Device firmware upgrade
 
 
Передачі з перериваннями
Цей різновид (interrupt transfer ) призначена для обміну невеликими транзакціями, подібними з контрольними. Ні, пристрій не може переривати хоста, воно чекає опитування, їх частота і розміри пакетів обговорюються заздалегідь в дескрипторі пристрою. Добре підходять для всіляких пультів, датчиків, сенсорів, мишок, світлодіодів та інших HID-кавоварок. Канал з перериваннями кожної точки односпрямований.
Приклади: Custom HID , Joystick mouse , Virtual COM port

Передачі ізохронні
Χρόνος по-грецьки означає «час». Ізохронна передача (isochronous transfer ) — місцевий хайтек, що дозволяє управляти потоками даних в реальному часі. Відрізняється гарантованої (але необов'язково широкої) смугою пропускання і відсутністю підтверджуючих транзакцій, майже як UDP з QoS. Битий пакет? Це бог Хронос штовхнув МК по нозі. Не треба намагатися відправити пакет заново, інакше бог засмутиться. Контрольні суми, проте, перевіряємо нишком від Хроноса. Ізохронні передачі гарні для аудіо-відео і вимірювальних систем реального часу, а також інших іграшок подвійного призначення . Хоча на деякі з них м.б. цікавіше повісити який-небудь AVR, зв'язавши його з нашим ARM по USART або SPI. Ізохронні операції беруть участь у фреймовій сигналізації (згадаємо про цокання пакетом SOF).
Приклад: USB voice speaker

Передачі великоблочні
Ні, мішки з цементом тягати не будемо. Я думаю, всі дізналися режим роботи всіляких накопичувачів USB. Передачі bulk transfer мають мету відправити даних якомога більше і швидше, обов'язково з пересилкою битих пакетів, але без гарантій по смузі пропускання, поступаючись її ізохронним передачам при необхідності (як в TCP без QoS). Про внутрішній устрій USB флешок я якось вже розповідав , тепер можна завантажити і запустити діючий прототип. Я сам його не пробував, але таблиця команд SCSI в описі прикладу (як-би, між іншим) вельми символізує. Ознак алгоритму управління зносом для NAND-пам'яті я не знайшов:-)
УВАГА: місцями діє патентний захист STM.
Приклад: Mass storage

Що залишилося нерозкритим

Я не маю на меті зробити ще один підручник по USB, їх і без мене вистачає, і там добре описані: електрична частина, подробиці протоколів, робота з концентраторами, дескрипторна мову і рівень абстракції HID, проблеми з унікальністю VID / PID, USB 3.0 і багато інші чудові можливості шини USB, як корисні нам, так і не дуже. Айтішникам особливо рекомендую екскурсію на темну сторону з оглядом ворожих девайсів (флешка з замаскованої HID-клавіатурою, яка робитиме страшні речі).

Посилання

Адаптація прикладу Custom HID до безкоштовної середовищі Em :: Blocks та бюджетної демо-платі STM32F103C8T6 виробництва LC-Tech : habrahabr.ru/post/208026/
Битва за ДБЖ: habrahabr.ru/post/233391/ ще одна битва за ІБП: habrahabr.ru/post/233391/#comment_7944489
Екскурсія на темну сторону (шпигунський девайс з AVR): habrahabr.ru/post/153571/
Інструкції з аналізу USB в Wireshark для Windows і Linux: wiki.wireshark.org/CaptureSetup/USB
Книжка USB in a NutShell: www.beyondlogic.org/usbnutshell/usb1.shtml
Переклад USB in a NutShell: microsin.ru/content/view/1107/44/
Книжка USB Made Simple (дійсно спростили): www.usbmadesimple.co.uk
STSW-STM32121, бібліотека STMicroelectronics USB full speed device library і всі згадані приклади (UM0424) www.st.com/web/en/catalog/tools/PF258157

P.S.
Читаючи публікації на Хабре, присвячені в тій чи іншій мірі мікроелектроніці, я розгледів дві інженерних касти, назвемо їх умовно: Промелектронщікі і Айтішники . Це свого роду інженерний Інь і Ян, в кожному з нас є частка того й іншого.

Промелектронщікі мають блискучі знання і навички по залізу, паяють радіодеталі товщиною з волосся лівою рукою із заплющеними очима (причому потім це працює). Поглянувши на електронну схему, майже фізично починають відчувати всі її струми з потенціалами, працюють також і з силовими схемами, і з (великими, швидкими, небезпечними) промисловими виробами. Підхід до програмування МК відповідний: він просто повинен видати потрібні логічні рівні на потрібні ніжки в потрібний час, не настільки важливо яким способом. Консервативні в технологіях (Не вилазь — працює), важку периферію МК не особливо шанують. При обговоренні об'єктно-орієнтованого програмування, інформаційної безпеки, гігантських проектів в мільйон рядків коду і всяких наворочених графічних інтерфейсів скучнеют. Замість пакетно-орієнтованої шини USB воліють потоковий режим USART, посилений або звичним RS-232, або більш брутальним RS-485 (послідовна шина для промислових застосувань, до 10Мбіт / с на 15м, до 100кБіт / с на 1200м, до 32 пристроїв).

Айтішники виховані на розумінні операційних систем, мережевої інфраструктури і складних взаємодій, еліта добре підкована в інформаційній безпеці і розбирається у всяких незримих способах проникнення в чужу систему. Деякі при цьому дуже люблять котиків (ну як їх можна не любити? Я, правда, не тримаю, чи не розводжу і не готую:-). Багато хто любить свободу інформації, лаяти корпорації / уряду і перемагати сили природи зусиллям думки. Паталогічна ліниві, але обожнюють нові технології та закручені інженерні ребуси з дорогими іграшками (бажано розв'язувані на рівні софта або, в крайньому випадку, перемичок). Відносини з паяльником насторожені: не питайте в айтішника, чи любить він паяльник, може неправильно зрозуміти; краще запитайте, чи любить він паяти електронні схеми.

До чого я? Ми просто бачимо цей світ по-різному… Адже ядро ​​Linux кроїли такі ж хлопці, з модулів на С і ассемблерних вставок для конкретних платформ, і без холіваров кшталт обійшлися. По-справжньому серйозний проект я бачу як многоядерную систему, що поєднує сучасний МК із важкій периферією, але не виключаю зв'язки з класичними моделями типу AVR: ними можна обважити які-небудь критичні бистровращающіеся вістря технічного прогресу. Якщо код перевірений роками, то чому ні?

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.