Лекция 6 (2012-11-19)
PCIe, USB, FireWire
Сегодня про один из насущных примеров периферии -- USB. Universal Serial Bus. Пример того, как интерфейсы делать не надо. Но, несмотря на сложность, низкую производительность и прочие негативные факторы, заложенные by design, он был достаточно популярен, потому что был прост в использовании и накладывал мало ограничений на устройства -- как на периферийные, так и на хост-контроллеры. Вторая версия вышла в 2000 году, она декларировала пропускную способность до 480 мегабит в секунду, реально тогда достигать такой производительности было дорого. В связи с этим в специфик был сделан ряд допущений, которые позволяли реализовать compliant hardware, которое не удовлетворяло скоростным характеристикам и требования там были попроще.
Начну с того, что напомню про то, как устроен PCIe -- пример того, как периферийные интерфейсы делать возможно надо. В нем достаточно большое количество legacy, но если рассматривать только ту часть, которая касается совместимых устройств, а не legacy -- то там достаточно все аккуратненько.
PCIe
Рут комплекс. У каждого устройства в дереве есть даунстримные порты и один апстримный порт (если это свитч). Из этой древовидной топологии следует адресация и всё такое. Каждому устройству сопоставляются свои диапазоны адресного пространства. Если приходит транзакция сверху и видишь, что она твоя -- направляешь вниз, иначе выбрасываешь.
Адресные пространства указываются в адресных регистрах (BAR).
Транзакции устройствами могут посылаться в любой момент. Остановить устройства в этом начинании может исключительно flow control, который считается по нескольким аспектам. Идея в том, что есть несколько лимитов(буферов для временного хранения транзакций). Устройство складирует доступные лимиты -- отдельно на количество транзакций, отдельно на общий объем. Эти размеры вычисляются независимо по каждому линку. Информация о том, что появилось место, передается либо как часть пакетов, либо отдельным пакетом, если нет встречного трафика. Endpoints могут посылать транзакции сами, даже друг другу, но чаще всего общаются с хостом. Два ендпойнта при общении друг с другом должны оба знать об общении.
GPUDirect от NVIDIA -- либо GPU могут обмениваться друг с другом по PCIe (не через отдельный мост, как в случае с SLI), так могут обмениваться по PCIe с сетевым интерфейсом. Чаще всего рассматривается в связке с Infiniband. Но это должна поддерживать и видеокарточка, и сетевой интерфейс.
Тот факт, что оно так устроено позволяет достигать на любом линке максимальной пропускной способности, немого снижающейся за счет кодирования и выравнивания лейнов. Если не рассматривать 32 лейна, то на оверхеды уходит 3-4%.
Universal Serial Bus
PCIe тоже в некотором смысле последовательный -- в том, что передача битов по каждому лейну идет последовательно. Но в отличие от PCIe, где на один лейн отводятся честные 2 дифпары, чтобы был у нас дуплекс, в USB дифпара одна. Там 4 сигнала -- напряжение, дифпара, земля.
USB хост, к которому подключаются хабы, к которому подключаются function. Одно устройство может предоставлять несколько функций, и чтобы не городить виртуальный свитч было придумано такое устройство. Свитч -- недешёвая штука с точки зрения реализациии. PCIe контроллер это несколько сотен тысяч транзисторов. PCIe свитч на 3-4 линка это миллион транзисторов, и свитчу нужны ещё буфера и так далее. Наличие функций позволяет всего этого безобразия избежать. В случае USB, если у вас compound device, который может иметь несколько функций -- например, клавиатура с трекпойнтом, то вы должны будете реализовать таки да хаб, который есть unremovable функцией и оно реализуется именно так и никак иначе. Это USB 2.0. В USB 3.0 был сделан ряд довольно значимых изменений, но на общую схему он не повлиял.
В USB 3.0 стало только хуже. В теории можно добиться того, что вам USB устройство отдает что-нибудь похожее на 60 мегабайт в секунду. В USB 3.0, при заявленной скорости 5 гигатранзакций в секунду, максимальная теоретически достижимая пропускная способность 400 мегабайт в секунду.
Это строго дерево, дерево можно выстроить по уровням, уровней не больше 7. Всегда есть первый уровень -- хост контроллер и скорее всего у вас будут хабы. Дальше у вас первые два уровня есть, в ограничение уровнями упереться сложно, но то, что два уровня съеты почти всегда, несколько печально.
Самое страшное в USB это то, что это polled шина. То есть, все операции изначально issued by HC. Функция не может что-то послать без запроса хост контроллера. Казалось бы есть устройства, который могут захотеть что-то посылать, как им работать? Про это дальше.
Основных версий стандарта 4:
- 1.0 lowspeed 1996 1.5 Mb
- 1.1 fullspeed 1998 12
- 2.0 highspeed 200? 480
- 3.0 superspeed 201? 5000.
Есть возможность доставать то ли до 10 ампер, то ли 100 Ватт. Везде идет питание 5 вольт. По стандарту первых трех версий там может быть не больше полуампера. Но, поскольку USB 3.0 появлялся примерно одновременно с Thunderbolt, и у него та же заявленная пропускная способность. Потому что Thunderbolt расширение PCIe для внешней периферии.
Содержательно в стандарте USB определяется три уровня.
- client software - function
- driver - endpoint
- hc - bus
Есть прикладное ПО, которое общается с функциями посредством доставляемого неопределенного в стандарте программного интерфейса. Есть некая абстракция, которая определяется на уровне client software. Библиотека которая реализует этот интерфейс взаимодействует с USB драйвером. Client software генерирует IRP. Эти IRP передаются драйверу,а драйвер на основании них генерирует трансфер. Трансфер на физическом уровне.
Определяется несколько видов. Соответственно устройство общается с хостом посредством пайпов. То есть, все взаимодействия которые происходят, они на самом деле происходят посредством пайпов. Это такая вот FIFO абстракция реализованная в железе. В USB 2.0 пайпов 4 вида:
- configuratuinal. У девайса всегда есть хотя бы один конфигурейшн пайп, который считается сконфигурированным изначально -- как с ним работать.
- interrupt
- isochronous
- bulk
При подключении устройства хаб вообще сообщает, что у него какие-то порты и их соcтояние. Когда подключаем устройство, он поднимает соответствующий бит в конфигурационном пространстве данного порта, хост контроллер это замечает и конфигурирует это новое устройство. Если подключили хаб, то все происходит рекурсивно. Обратно ситуация при отключении -- если что-то отключается, то хаб это замечает сам, выставляет нолик соответствующему порту, после чего хост контроллер это замечает и делает необходимое для удаления устройства. В связи с этим важный аспект, то что вообще одной из задачей драйвера хост контроллера является постоянное что-то всех устройств находящихся на шине. Хост контроллер занимается тем, что периодически опрашивает, предоставляет возможность дать всем интерфейсам. Всем ендпойнтам, или наоборот читает, что с них, в случае необходимости энумерации он читает с них, делает запросы всем хабам.
Сама последовательность состоит из нескольких сообщений. Сначала HC посылает запрос по некоему адресу. Устройство видит запрос, смотрит, что совпадает адрес, и если готов принимать данные, то там есть два направления in и out, чудесное свойство и по завершению транзакции посылается acknowledgement. Это вариант взаимодействия в 3 сообщения, бывает ещё в 4. Если у нас что-то, то говорит ура и получает acknowledgement, что все принято, или что все поломалось по дороге, либо если это транзакция что-то, то о ней говорят, что устройство утверждает, что может ее принять, транзакция уходит, устройство ее принимает.
Как я уже сказал при конфигурации появляется набор пайпов. Как эти пайпы работают -- собственно позволяют как-то перекачивать данные, а именно.
Пайп вида configuration и соответствующие транзакции доставляются как только, так сразу. У них есть гарантированная полоса опускания в 10 процентов от фрейма. Все время передачи разделяется на фреймы, в случае 1.0 это 1 миллисекнуда, в случае 2.0 это 125 микросекунд -- так называемые микрофреймы. В рамках этого фрейма происходит локация времени для различных видов транзакций.
Транзакции вида interrupt устроены следующим образом. У них есть некие ограничения на пропускную способность, то есть они "вот опрашивай меня с такой то частотой, я буду тебе посылать сообщения такого то размера". Частота может варьироваться от раза в микрофрейм до 255 милисекунд или до 4 секунд. И тогда хост контроллер с такой периодичностью будет посылать запросы к данной функции на предмет "а есть чего?". Такой вид пайпа обычно используется для устройств ввода -- мыши ли клавиатуры. Обычно, они могут запросить больше пропускной способности, но в обмен на это им не будет предоставлена возможность retransfer если случилась ошибка передачи сообщения. Механизм достаточно высокоскоростной шины, но нифига не отказоустойчивой. Регулируется точно также -- размер транзакции в микрофрейм и частота опроса. Обычно используется во всяком аудио-видео (asynchronous?).
Bulk транзакции -- передачи, которые могут ходить в свободное время. У них низший приоритет. Устройство, если хочет что-нибудь послать, будет когда-нибудь опрошено, но никаких гарантий когда -- нет.
Когда эти пайпы конфигурируются устройство запрашивает пайп определенного размера -- "8 пайпов, каждый из которых пусть меня опрашивает каждую милисекунду а я ему буду давать 512 байт". HC считает, что он может устройству дать, и, если пайпов не хватает, то конфигурация может пройти не очень успешно.
Есть ещё аспект размера буферов. У вас должно хватать буферов на то, что эти транзакции могли идти нужным потоком. Задержки и физические ограничения должны учитываться HC.
Тактировнаие. В USB есть несколько клоков. Одни из них больше пользовательские -- больше для software, тактируется interrupt, лейну когда посылать очередные IRP. Третий клок, которым тактируется сама шина USB на 1.8 килогерца. Этот клок встроен в способ передачи данных, там использует NRZI. Есть два состояния и между ними происходит переключение. За счет переключения на каждом такте добиваетесь того, что этот клок всегда есть.
Можно заметить здесь в отличие от PCIe или firewire нет никакого DMA. Устройства обмениваются транзакциями. Это хорошо с точки зрения безопасности, с другой стороны не очень быстро может оказаться, потому что весь трафик надо обрабатывать ПО, и вы имеет чудесную нагрузку на процессор. В свое время это было еще одним ограничением на 480 мегабит. Это сейчас USB ест 10-15 процентов, а тогда это могло быть гораздо больше.
В USB 3.0 кажется появился DMA, но от этого сильно легче не стало.
Еще расскажу про firewire.
Firewire
Примерно тогда же, в конце 90 - начале 2000 появилась шина firewire IEEE1394. Всего было несколько редакций, прежде чем оно почило в бозе. Довольно интересно -- там были варианты с 6 и 9 контактными разъемами, да, 6 контактов соответственно без питания, 9 с питанием. Скорости были что-то типа 400/800/1600/3200, но в реальности даже 800 вы наверно не видели, если только у вас нет какого-нибудь макбука. Отличие от USB HC .. Сам стандарт в отличие USB стандарт firewire это стандарт IEEE и чтобы заменить его копию надо вступить в консорциум и платить деньги -- небольшие, около 200 баксов в год, но тем не менее. Есть там стандарт firewire, есть OHCI, которая соответствующей рабочей группой утверждено и распространяется и при реализации ориентируются на него. С SATA та же история -- сам стандарт закрыт, а стандарт advanced host controller interface -- имплементация Intel HC SATA, которая в виду открытости победила проприетарные реализации контроллеров. С FW все грустнее, хоть и есть OHCI, но из-за закрытости стандарта ендпойнты к нему не реализуешь и большого распространения он не получил.
Тем не менее он довольно интересный, там все довольно просто.
Есть несколько видов запросов
- async dma recv
- async dma transmit
- isochr dma rv
- isochr dma tr
На шине может быть от 0 до 15 устройств и этих портов опять же может быть много. Топология -- скорее сего можно построить то же дерево но в реальности так никто не делает, все просто их стекают в цепочку. Там нетривиальная схема взаимодействия хоста и pending transactions. Если у вас реализован out of order buffer, вы можете комплитнуть транзакцию сразу -- сказать ок. Она получена и на хост она уйдет потом. Если лень реализовывать буфер можете не комплитить, пока не уйдет на хост.
Записи в рамках эти запросов определено 48-битное адресное пространство которое делится следующим образом.
Старшие 20 бит единицы, остальные -- пространство адресов, за исключением 256 мегабайт эти 4 гигабайт это software control memory, называется она high. High и middle отличаются тем что в middle обработка различных ошибок, они посвящены тому что ошибки уходят в программное обеспечение. В high ошибки обрабатываются стандартно, то в случае middle никаких действий по ошибке не происходит считается что по об этом заботится само. Например, по этим адресам реализуется ip over 1394. Смешно тем, что там реализуется сразу ip, а не Ethernet и ping там не работает.
Если сравнивать с PATA, то при подключении двух устройств деградация будет намного более сильная чем в случае с FW.