понедельник, 5 апреля 2010 г.

Embedded-faq

Последнее время стали появляться редкие анонсы разных железок типа ноутов и нетобуков на не-x86ой платформе. Кроме того, иногда я замечаю наивных людей, привыкших к уютному миру плаг-н-плея и стандартизированности x86 и считающих, что arm - это тот же x86, но другой.



Самое распространенное заблуждение таких людей заключается в следующем: "Я на ебее (лоре, слешдоте, сайте квалькома) видел лот (новость, анонс) о ноутбуке на процессоре ARM (нетбуке на MIPS). Я читал в инторнетах, что debian (редхат, арчлинупс, гента, винда) имеет порт на arm (mips). Значит на этот ноут можно поставить дебиан".



Эта заметка, а возможно и цикл заметок, покажет кусочек той бездны различий между X86 и теми архитектурами, которые часто называют словом "embedded".



Продолжение под катом






Биос и загрузка


X86


Зачем в современных десктопах и ноутах нужен BIOS? Не синий (черный) экранчик настройки, вылезающий по нажатию кнопочки Del (или F2, F12, F10), который называется BIOS Setup, а сама "базовая система ввода-вывода"?. Запрос в гугл "зачем нужен bios" выдает кучу юмористической информации, например "BIOS - это набор программ, которые переводят понятные пользователю команды Windows на язык, понятный компьютеру.



Во времена страшного черного консольного DOS, виснущей 9x винды, стращных досовских шрифтов, маленьких разрешений экрана, синих панелек и стеклянных монтиров, биос активно использовался этим самым досом и виндусом для работы с железом. Вот копипаст из статьи Вывод текста с использованием поддержки DOS и BIOS:


Пример 5.6. Вывод строки с использованием функции BIOS OEh

lea si, commun ; указываем адрес начала строки
mov ex, 48 ; задаем количество символов в строке
lp: lodsb ; читаем в al очередной символ строки
mov ah, OEh ; код запрашиваемой функции
int 10h ; вывод очередного символа
loop lp ; управление циклом



В настоящий момент, нормальные операционные системы такой ерундой не занимаются и биос нужен только на стадии загрузки системы, чтобы прогнать POST, найти ту ерунду, с которой в этот сезон модно грузиться (pata, sata, usb, pxe) и передать управление на ее "первый сектор". Далее, эта ерунда использует функции биоса для чтения остатков себя из загрузочной области, потом, опять же, используя биос, грузит с накопителя ядро системы и передает управление ему. Ядро уже имеет собственные полноценные драйвера для доступа к накопителю (если он вообще ему нужен).



ARM


Во встроенных системах биоса нет. Нет этих ваших досовских int 10h. Как же происходит загрузка? А как угодно - это личные проблемы каждого производителя процессора, производителя платы на этом процессоре и писателей софта к этому куску кремния



Обычно, загрузка начинается с исполнения инструкции, которая живет по адресу 0x0. Туда должен быть замаплен какой-то постоянный накопитель с произвольным доступом, например NOR память или какой-то ROM. Даже на этом этапе есть масса фокусов, например очень распространенный - по адресу 0x0 может быть два разных устройства, в зависимости от какого-то внешнего фактора. Копипаста из статьи Последовательность запуска, описывающей процесс загрузки телефона Motorola на процессоре Neptune



  • Если на выводе MOD высокий логический уровень, по адресу 0x00000000 находится irom, а по адресу 0x10000000 находится микросхема внешней памяти, идентифицируемая по сигналу выбора CS0 (активному на низкий уровень).


  • Если на выводе MOD низкий логический уровень, по адресу 0x00000000 находится микросхема внешней памяти, подключенная к CS0, а по адресу 0x10000000 - irom.





Дальнейшая процедура загрузки - целиком на совести инженеров, определивших состояние точки MOD и индусов, пишущих бутлоадер:


  • в neptune lte, загрузчик irom 0200 живет в неперезаписываемой памяти, при запуске проверяет (криво) цифровую подпись прошивки на NOR-флеше, после чего запускает вторичный загрузчик, который уже запускает прошивку

  • в neptune lte2, загрузчик irom 0300 живет в той же перезаписываемой памяти, проверяет цифровую подпись вторичного загрузчкика (не всей прошивки!), запускает вторичный загрузчик, который проверяет подпись оставшейся части прошивки и запускает ее

  • в линукосвых моторолах на pxa270 нет загрузочной ROM и по адресу 0x0 замаплена флеш-память (NOR), начинающаяся с джампа на следующий загрузчик (MBM), который запускает следующий за ним (blob-lubbock), который уже запускает ядро. Все три адреса (mbm, blob и ядра) диктуются исключительно размером сектора флеша и фантазией инженеров, проектировавших линейку телефонов: в разных версиях аппаратной платформы (gen1 и gen2) эти адреса немного различаются. Кроме того, энтузиасты из проекта openezx, ставят вместо ядра еще один загрузчик, а ядро помещают на карту памяти (mmc).

  • В телефоне fic-gat01 загрузчик живет во флеше NAND, а в телефоне fic-gta02 (neo frerunner) - два загрузчика: один в NAND, и резервный в NOR. В зависимости от последовательности нажатия кнопок AUR и POWER, грузится один или другой.



Память



Во время загрузки ядра линукса на системе семейства x86, оно определяет объем системной памяти (RAM) автоматически (видимо, через функции того же биоса). Кто-то имевший близкое знакомство с ядром, может знать о флаге mem, который позволяет вручную указать ее объем.



Во встраиваемых системах нет автоматичкского определения объема оперативной памяти (RAM). Строка запуска ядра может выглядеть так: mem=32M@0xa0000000 mem=16M@0xac000000, указывая ядру, что данная система имеет два банка памяти, а также объем и физические адреса обоих.



Если вы учили информатику в школе, то можете помнить, что значит аббревиатура "RAM" : Random Access Memory. В отличии от русскоязычного термина "оперативная память", английское обозначение указывает на то, что доступ к содержимому памяти может производиться в любом порядке, в отличии от дисковых накопителей, которые имеют задержки при позиционировании считывающих головок.



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



Во встраиваемых системах такие накопители обычно не используют из-за стоимости, размера, скорости доступа и необходимости копирования данных в оперативную память. Вместо этого, используется постоянная память или флеш-память NOR. В отличии от жестких дисков, доступ на чтение к содержимому NOR и ROM, подключенных напрямую к процессору, без промежуточных устройств производитя точно так же, как и к рам, то есть без использования драйверов. Зная физический адрес накопителя и адрес данных внутри накопителя, можно обращаться к данным напрямую (например сразу загрузить в регистр на ассемблере, обратиться по указателю в C). Если же в ром или флеш-памяти записан код, то его можно выполнить без копирования в RAM, которая будет нужна только для хранения стека, переменных и различных данных.




Простота доступа к накопителям - одна из причин, по которым во встраимваемых системах не нужен биос и загрузчику достачно знать адрес, по которому лежит ядро системы.



Еще одна особенность флеш-памяти заключается в процедуре ее записи, которая существенно отличается от записи в ram или hdd и больше похожа на работу с CD-RW. Полностью стертая флеш-память типа NOR имеет все биты выставленными в логическую единицу. Любой из битов может быть сброшен из еденицы в логический нуль, но не обратно. Единственный спрособ поменять нуль на еденицу - стереть весь сектор флеша, размер которого равен, например 128 килобайтам. Это вносит существенные изменения в хранение данных на таких накопителях, поэтому в линуксе для них существую специальные файловые системы (JFFS2, YAFFS) и специальных слой абстракции (MTD). Кроме того, во встраиваемых системах часто используются read-only файловые системы, в которых не нужно задумываться о таких тонкостях.



На usb-флешках, распространенных среди пользователей x86-систем, используются те же самые nor и nand, но, в отличии от arm и mipc, между микросхемами и процессором два контроллера (pic и usb) и слой абстракции в самой флешке, которые позволяют устаревшим файловым системам, типа винды, работать с ними, как с hdd и использовать те же самые файловые системы, сокращая срок службы накопителей. Логично было бы использовать специальные фс (см выше), но это невозможно из-за слоя абстракции в контроллере самой флешки.



Работа с устройствами и драйверами



Что делали доблестные пользователи древних систем в ISA шиной? Дергали перемычки, вбивали номера прерываний в биосе, чтобы не вылезли конфликты и ручками вписывали адреса для работы с этими железками. Потом весь этот ужас исчез, появился PCI и достачно втыкнуть железку в слот и она заработает. Ну еще пользователи этих ваших виндусов будут страдть два года и искать в яндексе бесплатные драйвера, но это уже проблемы индейцев. Потом появился usb и стало можно все то же самое, но без открывания корпуса и перезагрузок (пользователей виндов не учитываем, опять же).




Перемычки и irq? Это детский лепет, по сравнению с тем, что творится на арме. Во время загрузки на армовой системе ядро не знает вообще ничего. Чтобы заработал хоть какой-то минимальный плуг-н-плей для usb, нужно проинициализировать хостовый контроллер этого usb. Ядро не может знать, есть ли на в процессоре usb-контроллер, не может знать, какой ему нужен драйвер, не может знать, где его регистры, как включить его CLOCK и какие его порты нужно включать. Usb-контроллер может иметь несколько портов, из которых нулевой выведен наружу как клиентский, второй припаян внутри, как хостовый, а третий вообще не разведен.



Usb - это самый простой случай, потомучто нужно только проинициализировать хост-контроллер, узнать базовый адрес его регистров и натравить на него драйвер ohci-хоста. Все usb-устройства буду автоопределяться и их драйвера буду все так же подхватываться автоматически.



Интересней случай какого-нибудь mmc-контроллера, который подключен к процессоры по шине i2c. В данном случае, ядро должно иметь драйвер i2c-контроллера встроенного в процссор. Если нам повезло, то процессор интеловский, спецификация доступна и драйвер уже есть в ванильном ядре. Если нам не повезло и процессор, например фришкалевский и спецификации нет, то драйвера тоже нет - его нужно писать, вооружившись инструментами реверс-инженеринга. Далее, нам нужно знать адрес нашего mmc-контроллера на i2c шине, а потом протокол работы драйвера с ним. Далее уже протокол mmc, но он стандартный и тут ядро само разберется, если правильно написать драйвер устройства.



Еще интересней случай какого-нибудь ASIC (аппликейшн-специфик интегрейтед циркут). Он тоже висит на шине, которая не умеет pnp, тоже имеет непонятный протокол работы, но кроме всего прочего, внутри нет никакого стандартного протокола, типа MMC, а живет десять регистров и 20 прерываний. И этот ASIC управляет каким-то простым, но очень нужным процессом. Например включением питания, описанного выше MMC, подсветкой экрана и громкостью звукового усилка



Но это я отвлекся на трудности портирования и реверс-инженеринга. Проблема в том, что ядро не знает, какие устройства и шины у него есть, как их включить и какие драйвера для них использовать (если они есть), точно так же, как не знает объем RAM. Если у устройства есть субустройства (шина i2c и радиоприемник на ней), то ядро о нем тоже не знает, если шина не умеет pnp. Даже если устройство встроено в процессор, ядро все равно о нем не знает, потомучто не знает, на каком процессоре запущено.



Это решается очень просто: для каждой платы, на которой умеет запускаться линукс (около двух тысяч, судя по номерам machid), выделен уникальный номер (machid). Каждому номеру соответствует функция инициализации в которой захардкодены все устройства, параметры работы с ними, их драйвера и прочее. Там описано все от размера памяти и адреса регистров usb, до количества этих самых usb портов и функций включения питания на них. Живет это все в arch/архитекрута/mach-машина/плата.c (например arch/arm/mach-pxa/ezx.c). Для пяти телефонов на платформе motorola ezx, там записано пять разных функций, в совокупности - две тысячи строк кода.



Установка системы


В словаре матерых пользователей x86 есть слово "установка" (переустановка, инсталляция и проч.). Один из вариантов процесса установки дистрибутива Debian на ноутбук: записываем образ на usb-флеш, перезагружаем машину, меняем настройки биоса, перезагружаем еще раз, чтобы машина загрузилась с usb-флешки с дистрибутивом. Далее запускается специальная версия того же самого дебиана, которая спрашивает глупые вопросы и копирует себя на выбранный жесткий диск.



В случае встраиваемых систем, все намного проще и интересней. В болшинстве случаев, система не имеет никакой возможности запустить что-то со сменного носителя и вообще его не имеет. Процесс установки системы сводится к записи готового образа системы прямо в системный флеш.



Записью образа занимется один из системных загрузчиков и тут опять существует куча варинтов развития событий:



  • загрузчики в neptune lte не умеют сами писать образ. они умеют только принимать код лоадера по usb, копировать в ram и запускать его. При этом, они проверяют электронную подпись полученного кода, что не позволяет шить что попало (другую систему или "мод" существующей) без взлома загрузчика. Для процесса прошивки необходима проприетарная сервисная утилита, работающая только под виндой


  • на платформе motorola ezx ситуация та же самая, но нет проверки подписи


  • на телефоне neo frerunner можно прошить телефон напрямую через загрузчик по usb (без лоадера) или запустить ядро с sd-карты. Проверок подписи нет, протокол прошивки не такой, как у моторолы, но сервисная утилита доступна в исходниках


  • на множестве отладочных плат прошивка делается через последовательный кабель (он же rs232, он же COM) через свои собственные протоколы


  • в домашних роутерах и точках доступа прошивка происходит по сети, через веб-интерфейс, по сети через tftp или через тот последовательный кабель





У многих вариантво есть одно общее: при прошивке аппарата возможно его убить настолько, что восстановление будет возможно только с помощью специального оборудования или невозможно вообще. Достаточно стереть нулевой байт во флеше на телефоне ezx и можно спокойно его выкидывать или нести в очень хороший сервисный центр, где либо заменят флеш-память, либо прошьют через JTAG (последнее маловероятно). В нептуне lte и фрираннере, есть защита: в нептуне загрузчик зашит в кристалл процессора и не стирается, а во фрираннере два загрузчика.



На этом, пока все. Далее будут дополнения про версии arm, форматы бинарников, работу с дробям и разные порядки байт.


суббота, 3 апреля 2010 г.

Нептун, иром, usb

Залез в потроха L2, пишу себе рамлоадер. Тело в бланке, иром 0200 и у него есть замечательнейшая бага

:


In [100]: dev.jump(0x3fd0000)
Out[100]: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'


Вместо любых данных иром шлет нули. Мой рамлоадер тоже, а вот ramldr - нет.



Отправка данных делается простейшим образом, пробажить негде: запихнуть в хардварный буфер данные и дернуть полтора бита в регистрах. Тем не менее, тупейший баг там зарылся и растет из костылей: для совместимости со старыми нептунами, usb имеет две карты памяти с разыными адресами буферов ввода-вывода. Нужная карта памяти выбирается через нулевой бит регистра 0x24852014 и по дефолту там как раз старая. А иром пишет по адресу из новой.



Зачатки бута на гитхабе тыц