|
|
  |
Bootloader или загрузчик, расскажите по подробней... |
|
|
|
Jun 18 2009, 05:47
|
Участник

Группа: Новичок
Сообщений: 64
Регистрация: 7-11-08
Пользователь №: 41 437

|
Цитата(zltigo @ Jun 18 2009, 00:22)  Зачем? Чем '0x12345678' привлекательнее 'buffer' Ух ты!!!! Вроде я уследил за ходом выших мыслей... Я создаю массив buffer[4096], забиваю его данными, а потом в команде 51 просто указываю имя массива buffer(имя является адресом первого элемента). правильно ли я понял????
|
|
|
|
|
Jun 23 2009, 12:51
|
Участник

Группа: Новичок
Сообщений: 64
Регистрация: 7-11-08
Пользователь №: 41 437

|
Ложу я прошивку с помощью бутлоадера(сам он находиться по 0-му адресу) по адресу 0х00004000 проверяю целостность и прочее - все Ок. передаю управление на прошивку вот так:
typedef void (*pt2Function)(void);
pt2Function BootCall = (pt2Function)0x00004000;
в итоге - ничего. он все равно крутится в бутлоадере.
может я не правельно передаю управление???
|
|
|
|
|
Jun 24 2009, 06:19
|
Участник

Группа: Новичок
Сообщений: 64
Регистрация: 7-11-08
Пользователь №: 41 437

|
Цитата(Anatrulij @ Jun 23 2009, 15:51)  может я не правельно передаю управление??? Утро вечера мудренее  . Разобрался - надо было перед вызовом вышеописанного перехода сбросить все альтернативные функции портов. Но я не знаю почему данная процедура помогла.
|
|
|
|
|
Jun 24 2009, 07:29
|
Гуру
     
Группа: Админы
Сообщений: 2 736
Регистрация: 17-06-04
Из: Киев
Пользователь №: 48

|
Вот только перейдете вы по этому адресу в user-mode процессора и перенастроить стеки для других режимов уже не сможете. Поэтому лучше вызывать вот такую функцию (для KEIL): Код /* Функция передачи управления пользовательской программе по адресу 0x2000 */ void __asm user(void) { ARM mov r0, #0x00002000 swi 0 } в проект добавить файл swi.s Код PRESERVE8 AREA SWI_Area, CODE, READONLY ARM
EXPORT SWI_Handler SWI_Handler BX R0 END и конечно в startup.s сделать импорт SWI_Handler Код IMPORT SWI_Handler Reset_Addr DCD Reset_Handler Undef_Addr DCD Undef_Handler SWI_Addr DCD SWI_Handler PAbt_Addr DCD PAbt_Handler DAbt_Addr DCD DAbt_Handler DCD 0 ; Reserved Address IRQ_Addr DCD IRQ_Handler FIQ_Addr DCD FIQ_Handler
Undef_Handler B Undef_Handler ;SWI_Handler B SWI_Handler PAbt_Handler B PAbt_Handler DAbt_Handler B DAbt_Handler IRQ_Handler B IRQ_Handler FIQ_Handler B FIQ_Handler
--------------------
Вам помочь или не мешать?
|
|
|
|
|
Jun 24 2009, 07:53
|
Участник

Группа: Новичок
Сообщений: 64
Регистрация: 7-11-08
Пользователь №: 41 437

|
Цитата(Nixon @ Jun 24 2009, 10:29)  Вот только перейдете вы по этому адресу в user-mode процессора и перенастроить стеки для других режимов уже не сможете. Поэтому лучше вызывать вот такую функцию (для KEIL): Спасибо за совет. А можно более подробно прокоментировать код?
|
|
|
|
|
Jun 24 2009, 10:12
|
Участник

Группа: Новичок
Сообщений: 64
Регистрация: 7-11-08
Пользователь №: 41 437

|
Цитата(Nixon @ Jun 24 2009, 12:13)  Что именно тут комментировать? Вы вызываете SWI прерывание, при вызове которого переключается режим cpu на supervisor и на всякий случай в ARM. И уже в этом режиме вы переходите на адрес пользовательской программы (адрес передается в r0). а зачем делать импорт SWI_Handler. многого я еще не знаю и что бы не задавать глупых вопросов, ткните где я могу найти описания того как правельно инициализировать контроллер, вектора и прочее.
|
|
|
|
|
Jun 30 2010, 14:32
|
Частый гость
 
Группа: Свой
Сообщений: 187
Регистрация: 22-06-05
Из: Минск, Беларусь
Пользователь №: 6 213

|
Для организации загрузки программы написал несколько классов, которые должны использоваться в бутлоадере (AES шифрование, подсчет CRC и т.д.). Написал собственно загрузчик, но теперь осталось правильно все это скомпоновать.
Как сделать, чтобы определенные объекты классов компилировались с 0-го сектора (загрузчик), а остальные скажем со 2-го сектора (пользовательская программа)?
Вычитал, что это решается с помощью scatter, нашел где он настраивается в Keil, но там вроде можно всю программу сместить, а как сделать частями, ума не приложу. Была идея сделать 2 разных проекта, один загрузчик и разместить его с 0-го сектора (и использовать далее во всех остальных проектах его без изменения). А второй проект это пользовательская программа, которая всегда начинается со 2-го сектора. Есть еще плюсы, у этого метода, но главные минусы в том, что: - всегда надо четко и, самое главное, в ручную оговаривать точки входа в пользовательскую программу (включая вектора прерываний) и загрузчик, потому что при компиляции они не будут знать о существовании друг друга; - хотелось быть часть однотипного кода по работе с интерфейсами, USB, CAN и UART разместить в загрузчике, чтобы не дублировать их в двух программах. Но тут встает та же проблема, ручного указания адресов методов и самое главное ручное резервирование памяти, которое используют объекты классов интерфейсов, чтобы компилятор пользовательской программы туда ничего не назначил.
|
|
|
|
|
Jun 30 2010, 15:52
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Цитата(Yaumen @ Jun 30 2010, 17:32)  Была идея сделать 2 разных проекта, один загрузчик и разместить его с 0-го сектора (и использовать далее во всех остальных проектах его без изменения). А второй проект это пользовательская программа, которая всегда начинается со 2-го сектора. Правильное решение. Программы независимы, нет возможности развивая программу случайно сделать ее несовместимой с уже выпущенными и разлетевшимися по стране загрузчиками. Цитата(Yaumen @ Jun 30 2010, 17:32)  - всегда надо четко и, самое главное, в ручную оговаривать точки входа в пользовательскую программу (включая вектора прерываний) и загрузчик, потому что при компиляции они не будут знать о существовании друг друга; Вход в загрузчик - по адресу 0. В приложение (после ремапа) - тоже по адресу 0. Адрес фиксирован, не меняется. Цитата(Yaumen @ Jun 30 2010, 17:32)  - хотелось быть часть однотипного кода по работе с интерфейсами, USB, CAN и UART разместить в загрузчике, чтобы не дублировать их в двух программах. Но тут встает та же проблема, ручного указания адресов методов сделайте таблицу переходов, расположите ее раз и навсегда в фиксированном месте. Цитата(Yaumen @ Jun 30 2010, 17:32)  и самое главное ручное резервирование памяти, которое используют объекты классов интерфейсов, чтобы компилятор пользовательской программы туда ничего не назначил. Расположите их в начале ОЗУ, сразу за отремапленой таблицей векторов. А приложению уменьшите количество ОЗУ на этот размер. Но это тоже не лучшее решение - а найдете вы потом (через год) багу в этом коде, которая не мешает загрузчику, но не дает работать приложению. И что - отзывать все выпущенные приборы на перешивку загрузчика? Или писать очередное обновление, которое перепишет загрузчик с ненулевой вероятностью сбоя питания в этот момент и все - устройство надо возвращать. Пусть лучше дублируются, но загрузчик и приложение будут совершенно автономны.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Jul 1 2010, 03:21
|

Участник

Группа: Участник
Сообщений: 67
Регистрация: 8-05-09
Из: Томск
Пользователь №: 48 809

|
Цитата(Сергей Борщ @ Jun 30 2010, 22:52)  сделайте таблицу переходов, расположите ее раз и навсегда в фиксированном месте. Можно с этого места поподробнее?  Т.е. саму идею понял, но как это все должно выглядеть - не совсем
|
|
|
|
|
Jul 1 2010, 05:30
|
Частый гость
 
Группа: Свой
Сообщений: 187
Регистрация: 22-06-05
Из: Минск, Беларусь
Пользователь №: 6 213

|
Цитата(Сергей Борщ @ Jun 30 2010, 18:52)  Вход в загрузчик - по адресу 0. В приложение (после ремапа) - тоже по адресу 0. Адрес фиксирован, не меняется. Вот тут немного поподробнее. С загрузчиком все понятно, у него свой main(), который может располагаться где угодно в пределах первых 2-х секторов, а переход на main() действительно будет по 0-му адресу. Теперь о пользовательской программе. У него также main может располагаться в любом месте оставшейся памяти и об адресе этого main() загрузчик естественно ничего не знает. Значит ему надо это указать, опять же в пользовательской области зафиксировать переход на main(), по этому адресу и будет переходить загрузчик в случае, если с прошивкой все в норме. Возможно есть еще какой-то способ, но похоже я до него еще в голове не дошел. Есть еще один минус у двух проектов. Я планирую использовать AES шифрование, которое жестко будет зашито в загрузчик, а значит все пользовательские программы необходимо шифровать одним и тем же ключем, это же касается и сигнатуры. Мне кажется это неправильно!!! Теперь что касается общей части для обоих проектов. Смысл идеи состоит в том, чтобы дать возможность обновлять прошивку удаленно. В моем случае это происходит через штатно установленный отдельный модуль связи, который общается с другими модулями по CAN. Для работы на этой шине, разработан протокол обмена, позволяющий передавать файлы больших размеров, склеивать их на приемном устройстве, вести одновременный прием от нескольких устройств, ну и т.д. Это достаточно большой кусок куда, который не хотелось бы дублировать, чтобы не тратить лишнего, так как я и так уже вышел на использование 75% имеющейся памяти. А в добавок, этот фрагмент кода очень емкий по использованию RAM. Мне самому идея удаленного программирования не нравится, однако начальство настаивает на его присутствии, так как у нас уже были случаи, когда приходилось объезжать множество объектов для перепрошивки, а когда эти объекты находятся в разных городах или даже в странах это еще более утомительно.
|
|
|
|
|
Jul 1 2010, 09:40
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Цитата(alvy @ Jul 1 2010, 06:21)  Можно с этого места поподробнее?  Т.е. саму идею понял, но как это все должно выглядеть - не совсем для gcc примерно так: Код boot_app_cross_table.h: struct boot_app_cross_table { uint32_t (*Serial_no)(); void (*AES_init)(uint32_t *init_vector); void (*AES_decode)(uint32_t *block); bool (*TCP_send)(void const * from, size_t size); }extern Bootloader;
//========= bootloader.cpp:
extern uint32_t Serial_no(); void AES_init(uint32_t * init_vector) { AES.init(init_vector); }
void AES_decode(uint32_t * block) { AES.decode(block); } extern void TCP_send(void const * from, size_t size);
__attribute__((section, ".boot_app_cross")) Bootloader = { Serial_no, AES_init, AES_decode, TCP_send, };
//========= bootloader.ld: MEMORY { ..... CROSS (rx) : ORIGIN = 0x00100040, LENGTH = 4 * 4 .... }
SECTIONS { ..... .cross : { KEEP(*(.boot_app_cross*)) } > CROSS
//========= application.cpp: #include "boot_app_cross_table" void Test() { printf("Serial number: %d", Bootloader.Serial_no()); }
//========= makefile: LDFLAGS += -Wl,--defsym,Bootloader=0x00100040 Цитата(Yaumen @ Jul 1 2010, 08:30)  Вот тут немного поподробнее. С загрузчиком все понятно, у него свой main(), который может располагаться где угодно в пределах первых 2-х секторов, а переход на main() действительно будет по 0-му адресу. До main() еще дойти надо. До вызова main() исполняется startup-код, который готовит переменные, настраивает стеки и кое-какую периферию, вызывает конструкторы глобальных объектов и уже после всего этого вызывает main(). А сам стартап-код вызывается из вектора исключения RESET. Цитата(Yaumen @ Jul 1 2010, 08:30)  Теперь о пользовательской программе. У него также main может располагаться в любом месте оставшейся памяти и об адресе этого main() загрузчик естественно ничего не знает. У приложения точно так-же есть своя таблица векторов. В которой вектор RESET указывает на стартап-код. А стартап-код вызывает main(). Загрузчик копирует эту таблицу в начало ОЗУ и делает remap. Таблица оказывается отражена на адрес 0. По адресу 0 оказывается команда перехода на стартап-код приложения. Осталось перейти на адрес 0. Цитата(Yaumen @ Jul 1 2010, 08:30)  Есть еще один минус у двух проектов. Я планирую использовать AES шифрование, которое жестко будет зашито в загрузчик, а значит все пользовательские программы необходимо шифровать одним и тем же ключем, это же касается и сигнатуры. Мне кажется это неправильно!!! Почему? А если все программы будут с разными ключами - как вы их загрузите в одно устройство? Цитата(Yaumen @ Jul 1 2010, 08:30)  разработан протокол обмена, позволяющий передавать файлы больших размеров, склеивать их на приемном устройстве, вести одновременный прием от нескольких устройств, ну и т.д. Это достаточно большой кусок куда, который не хотелось бы дублировать Нужна ли вся эта функциональность загрузчику? Может ему достаточно сильно урезанной версии? Или может у приложения есть возможность сохранить образ прошивки в какой-то внешней памяти, а загрузчик тогда будет брать шифрованную прошивку из этой памяти? А как аварийный вариант и для первоначальной загрузки предусмотреть что-нибудь типа uart?
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Jul 1 2010, 10:56
|
Частый гость
 
Группа: Свой
Сообщений: 187
Регистрация: 22-06-05
Из: Минск, Беларусь
Пользователь №: 6 213

|
Цитата(Сергей Борщ @ Jul 1 2010, 12:40)  Почему? А если все программы будут с разными ключами - как вы их загрузите в одно устройство? Имелось в виду, что создавая загрузчик как отдельный проект я его буду зашивать во все свои устройства, а это могут быть совершенно разные устройства и модули Цитата Нужна ли вся эта функциональность загрузчику? Может ему достаточно сильно урезанной версии? Или может у приложения есть возможность сохранить образ прошивки в какой-то внешней памяти, а загрузчик тогда будет брать шифрованную прошивку из этой памяти? А как аварийный вариант и для первоначальной загрузки предусмотреть что-нибудь типа uart? Если бы в микроконтроллерах RAMа было бы больше ROMa, то можно было бы использовать RAM как временный буфер, но увы это не так. А случись что во время программирования, то и вообще этот механизм накрывается медным тазом.
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|