В этом посте я рассмотрю как настроить, скомпилировать и прошить в плату загрузчики, которые необходимы для запуска Линукса на ней.
В моем случае, перед запуском Линукса должен быть предварительно загружен и выполнен код двух загрузчиков, которые подготовят для него необходимую периферию. Дело в том, что изначально, сразу после подачи питания, микроконтроллер может запускать лишь тот код, который лежит в его внутренней flash-памяти и лишь начиная с адреса 0x0. Поэтому, первый загрузчик - загрузчик первого уровня AT91Bootstrap, занимается инициализацией необходимой периферии - тактовой подсистемы, контроллера SDRAM и NandFlash и MMU, чтобы можно было работать с внешней памятью и внешним же флэшем. Затем он загружает во внешнюю SRAM/DRAM память код загрузчика второго уровня и передает исполнение ему.
В принципе, загрузчик первого уровня может сразу загрузить в память из внешней флешки ядро Линукса и передать исполнение ему. Но загрузчик второго уровня - U-Boot, имеет больше возможностей, например он может грузить Линукс и образ корневой файловой системы по tftp. Поэтому мы грузим его вместо Линукса сразу после AT91Bootstrap'а.
Теперь, пара слов о том, где располагаются вышеперечисленные загрузчики в рассматриваемой отладочной плате. Загрузчик первого уровня лежит во внутренней флеш-памяти микроконтроллера и начинает оттуда исполняться, сразу после подачи питания. Загрузчик второго уровня лежит во внешней флеш-памяти начиная с адреса 0x20000, и запускается из внешней RAM-памяти, будучи туда предварительно скопированным.
http://dmilvdv.narod.ru/AT91SAM9260/nandflash_map.png |
AT91Bootstrap
Данный загрузчик проще всего взять с сайта атмела или отсюда.
Еще, могут быть полезны следующие ссылки: http://www.at91.com/linux4sam/bin/view/Linux4SAM/AT91Bootstrap и http://dmilvdv.narod.ru/AT91SAM9260/index.html?bootstrap.html.
Перед компиляцией необходимо залезть в файл board/at91sam9260ek/nandflash/at91sam9260ek.h и проверить следующие константы:
- PLLA_SETTINGS - настройки схемы, которая управляет частотой тактового сигнала, используемого в вычислительном ядре микроконтроллера. Как я выяснил путем копания в исходниках AT91Bootstrap, принципиальной схеме платы и даташите, входным сигналом для этой схемы является сигнал с кварца на 12 МГц, подключенного к пинам XIN/XOUT микроконтроллера. Внутри, этот сигнал сначала умножается, а затем делится на определенные коэффициенты, которые задаются данной константой, и лишь потом подается куда надо.
Значения полей константы проще всего посмотреть в даташите, в секции Power Management Controller (PMC) User Interface (искомый регистр - CKGR_PLLAR)
Значение константы для 12 МГц внешнего кварца - 0x2031bf03, частота тактового сигнала на выходе - 196 МГц; - PLLB_SETTINGS - настройки для схемы, подобной рассмотренной выше, но тактовый сигнал подается уже на периферию и, через еще один делитель, к USB-контроллеру. Значение константы для 12 МГц кварца - 0x10077f01, частота тактового сигнала на выходе схемы - 96 МГц, частота тактового сигнала, идущего к USB-контроллеру - 48 МГц (частота этого тактового сигнала должна быть равной 48/96/192 МГц для корректной работы USB);
- MASTER_CLOCK - судя по всему это частота тактового сигнала, идущего на периферию микроконтроллера, наличие которой диктуется спецификацией на семейство микроконтроллеров подобного типа (не та периферия, которая добавлена в чип производителем). Хотя я могу и ошибаться...
Значение константы - (200000000/2); - IMG_ADDRESS - это адрес, по-которому загрузчик второго уровня записан во внешнюю NandFlash-память. Значение константы - 0x20000;
- IMG_SIZE - максимально допустимый размер загрузчика второго уровня. Значение - 0x40000. AT91Bootstrap просто считывает IMG_SIZE байт из NandFlash-памяти по адресу IMG_ADDRESS и записывает их во внешнюю память. Если размер загрузчика второго уровня будет несколько больше, то очевидно, что не весь его код будет загружен в во внешнюю память, отчего будут, как минимум, сбои в работе;
- JUMP_ADDR - адрес, на который прыгнет AT91Bootstrap по завершению своей работы. Ожидается, что по этому адресу будет лежать какой-то исполняемый код, в нашем случае - код загрузчика второго уровня U-Boot.
Значение - 0x23f00000.
Для компиляции загрузчика используются следующие команды:
cd board/at91sam9260ek/nandflash/
make CROSS_COMPILE=arm-elf-
Как видно, для компиляции AT91Bootstrap'а мы используем предварительно собранный нами кросскомпилятор для ARM-ов. Естественно, путь к кросскомпилятору должен быть прописан в $PATH.
Файл для прошивки в плату - nandflash_at91sam9260ek.bin. Прошивать следует при помощи скрипта SendBootFile через утилиту SAM-BA.
Если все сделано верно, то ... вы об этом не узнаете, поскольку AT91Bootstrap по умолчанию не выводит ничего в последовательный канал. На функции вывода (не говоря уже о вводе-выводе) просто не хватало места и их использование было отключено через #undef CFG_DEBUG.
Говорят, что старые версии компилятора GCC (<= 3.3) могли скомпилировать AT91Bootstrap вместе с функциями вывода так, чтобы он не вылезал за ограничения по размеру (что-то около 4 Кб, точно увы не помню...).
Файл для прошивки в плату - nandflash_at91sam9260ek.bin. Прошивать следует при помощи скрипта SendBootFile через утилиту SAM-BA.
Если все сделано верно, то ... вы об этом не узнаете, поскольку AT91Bootstrap по умолчанию не выводит ничего в последовательный канал. На функции вывода (не говоря уже о вводе-выводе) просто не хватало места и их использование было отключено через #undef CFG_DEBUG.
Говорят, что старые версии компилятора GCC (<= 3.3) могли скомпилировать AT91Bootstrap вместе с функциями вывода так, чтобы он не вылезал за ограничения по размеру (что-то около 4 Кб, точно увы не помню...).
Das U-Boot
Для U-Boot'а я использовал исходники версии 1.3.4 отсюда и патч для соответствующей версии с сайта Linux4SAM.
Перед компиляцией, как и в случае с AT91Bootstrap, тоже следует кое-что проверить и может быть даже подправить в исходниках:
- Первым делом нужно проверить - совпадает ли стартовый адрес u-boot'а с адресом JUMP_ADDR из настроек AT91Bootstrap? Стартовый адрес записан в файле board/at91sam9260ek/config.mk и в нашем случае он должен быть равен 0x23f00000;
- После, стоит залезть в файл ./include/configs/at91sam9260.h и проверить все содержащиеся там константы на соответствие реальности. В моем случае, практически ничего не пришлось править.
Теперь, можно приступать к компиляции. Тут все относительно просто - вначале объясняем системе сборки, что у нас плата AT91SAM9260 с загрузкой из NandFlash'а:
Загрузчик достаточно мощный, особенно по сравнению с загрузчиком первого уровня. У него есть свой командный интерпретатор, в который можно попасть, нажав любую клавишу в течение нескольких секунд перед загрузкой ядра - он сам вам предложит это сделать (hit any key to stop autoboot). В этот же интерпретатор вы и попадете в первый раз, если у вас U-Boot'у еще нечего грузить.
Что и как загружать определяется переменными окружения, которые, кстати, можно сохранять в NandFlash'е командой saveenv. А командой printenv можно соответственно посмотреть их. Кстати, чтобы посмотреть справку по командам достаточно ввести help, а чтобы узнать что-то об определенной команде - help command.
Из переменных окружения я выделю лишь пару, важных нам на данном этапе - это bootargs и bootcmd. Первая переменная - список аргументов, которые передаются ядру линукса при загрузке. Установить ее, как и любую другую переменную, можно командой:
Отмечу, что вместо nand read нужно использовать команду cp.b, если загружаемый код лежит в DataFlash'е, и у них вроде как есть некие различия в принимаемых аргументах...
Еще отмечу, что команда bootm не просто передает управление по такому-то адресу, как это делает AT91Bootstrap в конце своей работы. Дело в том, что бинарник с, например, ядром линукса, должен быть предварительно обработан утилитой mkimage перед тем, как его сможет грузить U-Boot. Эта утилита собирается вместе с U-Boot'ом; и занимается она тем, что добавляет к файлу заголовок с информацией о нем - сжатое или несжатое ядро, какой ОС оно принадлежит и т.п... Понятно, что путь к данной утилите должен быть прописан в $PATH (лежит она где-то в ./tools/).
Сначала bootm проверяет информацию в заголовке на соответствие истине, при этом она ищет заголовок по адресу, переданному ей первым аргументом. Если все в порядке, то она копирует ядро по адресу "load address", который берется из заголовка, "откусывая" при этом сам заголовок и прыгает по адресу "entry point", который тоже берется из заголовка и который в нашем случае должен быть равен "load address". Важно следить, чтобы область памяти, в которую было первоначально скопировано ядро и область памяти [load_address, load_address+kernel_size] не перекрывались, иначе ничего не будет работать! Совсем.
Лично я, всегда просто устанавливал оба этих адреса в 0x20008000 и ничего не перекрывалось при размере ядра в 0x27b2c4 байт.
На этом все. В заключение, я выложу бинарники обеих загрузчиков, которые я использовал на своей плате:
make at91sam9260ek_nandflash_configА потом, компилируем:
make ARCH=arm CROSS_COMPILE=arm-elf-В результате, в верхнем каталоге дерева исходных кодов у нас будет лежать файл u-boot.bin, который мы и будет зашивать в NandFlash платы по адресу 0x20000 при помощи самбы.
Как видно, не все было так гладко, как описывается... |
Что и как загружать определяется переменными окружения, которые, кстати, можно сохранять в NandFlash'е командой saveenv. А командой printenv можно соответственно посмотреть их. Кстати, чтобы посмотреть справку по командам достаточно ввести help, а чтобы узнать что-то об определенной команде - help command.
Из переменных окружения я выделю лишь пару, важных нам на данном этапе - это bootargs и bootcmd. Первая переменная - список аргументов, которые передаются ядру линукса при загрузке. Установить ее, как и любую другую переменную, можно командой:
setenv bootargs console=ttyS0,115200 root=/dev/sda1 rootwaitВторая же переменная окружения - bootcmd - задает список команд, которые будут выполнены на этапе загрузки u-boot'ом какой-нибудь ОС или программы (когда истечет время для "hit any key to stop autoboot"). Что немаловажно, это могут быть не только такие команды, которые приведут к загрузке операционной системы, но и любые другие команды. Установить эту переменную можно так:
setenv bootcmd "nand read 0x22000000 0x200000 0x4fffff; bootm 0x22000000"Здесь выполнятся сразу две команды. Первая загрузит во внешнюю оперативную память 0x4fffff байтов начиная с адреса 0x22000000. Причем, эти байты будут читаться из NandFlash с адреса 0x200000. Вторая я же команда передаст управление по адресу 0x22000000 во внешней RAM.
Отмечу, что вместо nand read нужно использовать команду cp.b, если загружаемый код лежит в DataFlash'е, и у них вроде как есть некие различия в принимаемых аргументах...
Еще отмечу, что команда bootm не просто передает управление по такому-то адресу, как это делает AT91Bootstrap в конце своей работы. Дело в том, что бинарник с, например, ядром линукса, должен быть предварительно обработан утилитой mkimage перед тем, как его сможет грузить U-Boot. Эта утилита собирается вместе с U-Boot'ом; и занимается она тем, что добавляет к файлу заголовок с информацией о нем - сжатое или несжатое ядро, какой ОС оно принадлежит и т.п... Понятно, что путь к данной утилите должен быть прописан в $PATH (лежит она где-то в ./tools/).
Сначала bootm проверяет информацию в заголовке на соответствие истине, при этом она ищет заголовок по адресу, переданному ей первым аргументом. Если все в порядке, то она копирует ядро по адресу "load address", который берется из заголовка, "откусывая" при этом сам заголовок и прыгает по адресу "entry point", который тоже берется из заголовка и который в нашем случае должен быть равен "load address". Важно следить, чтобы область памяти, в которую было первоначально скопировано ядро и область памяти [load_address, load_address+kernel_size] не перекрывались, иначе ничего не будет работать! Совсем.
Лично я, всегда просто устанавливал оба этих адреса в 0x20008000 и ничего не перекрывалось при размере ядра в 0x27b2c4 байт.
На этом все. В заключение, я выложу бинарники обеих загрузчиков, которые я использовал на своей плате: