Предотвращение случайной перезаписи прошивки

Сначала немного предыстории. Когда прошивка по какой-либо причине дает сбой (например, переполнение стека, поврежденный указатель функции...), может случиться так, что она куда-то прыгнет и начнет выполнять какой-то код. Это рано или поздно приведет к сбросу сторожевого таймера. MCU перезагрузится, и мы вернемся на правильный путь. Пока не...

Что если у нас есть код, который записывает данные во флэш-память (например, загрузчик)? Теперь может случиться так, что мы случайно перескочим прямо в код записи во флэш-память — пропустив все проверки. Прежде чем сторожевой таймер начнет лаять, вы получите поврежденную прошивку. Это именно то, что происходило со мной.

Теперь кто-то может сказать — исправьте основную ошибку, из-за которой мы даже перешли к написанию кода. Ну, когда вы разрабатываете, вы постоянно меняете код. Даже если сейчас такой ошибки нет, завтра она может появиться. Кроме того, ни один код не свободен от ошибок — по крайней мере, не мой.

Так что теперь я делаю своего рода перекрестную проверку. У меня есть переменная с именем «wen», для которой я установил значение 0xa5 перед обычными проверками (например, проверкой, чтобы убедиться, что пункт назначения действителен). Затем, непосредственно перед выполнением фактического стирания или записи, я проверяю, действительно ли «wen» установлен на 0xa5. В противном случае это означает, что мы как-то случайно перескочили на написание кода. После успешной записи «wen» очищается. Я сделал это на C, и это сработало хорошо. Но все же есть небольшая теоретическая вероятность повреждения, потому что есть несколько инструкций от этой последней проверки «wen» до записи в регистр SPMCR.

Теперь я хочу улучшить это, поместив эту проверку в сборку между инструкциями записи в SPMCR и spm.

__asm__ __volatile__
(   
    "lds __zero_reg__, %0\n\t"
    "out %1, %2\n\t"
    "ldi r25, %3\n\t"
    "add __zero_reg__, r25\n\t"
    "brne spm_fail\n\t"
    "spm\n\t"
    "rjmp spm_done\n\t"
    "spm_fail: clr __zero_reg__\n\t"
    "call __assert\n\t"
    "spm_done:"
    :
    : "i" ((uint16_t)(&wen)),
      "I" (_SFR_IO_ADDR(__SPM_REG)),
      "r" ((uint8_t)(__BOOT_PAGE_ERASE)),
      "M" ((uint8_t)(-ACK)),
      "z" ((uint16_t)(adr))
   : "r25"
);

Код еще не пробовал, завтра сделаю. Вы видите проблемы? Как бы вы решили/решили бы такую ​​проблему?


person Stefan    schedule 16.02.2012    source источник


Ответы (2)


Один метод, который я видел, заключается в том, чтобы убедиться, что байты непосредственно перед вашими процедурами записи во флэш-память вызовут какой-то тайм-аут сторожевого таймера или перезагрузят процессор. Таким образом, невозможно выполнить случайные данные, ведущие к функции записи во флэш-память, и просто «попасть» в функцию.

Перед сбросом может потребоваться несколько NOP, чтобы убедиться, что инструкции интерпретируются правильно.

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

person tomlogic    schedule 17.02.2012
comment
Да, жир очищается после успешной записи. Этот вызов __assert фактически запускает сброс сторожевого таймера (плюс регистрируется некоторая информация о том, что его вызвало). Рад слышать, что люди действительно используют такие подходы :) - person Stefan; 17.02.2012

Я не уверен, почему вам нужна возможность записи во флэш-память в вашем загрузчике. Наш загрузчик работает, потому что он может обновлять прикладную программу через последовательный порт. Таким образом, мы устраняем возможность непреднамеренной записи, гарантируя, что загрузчик не содержит кода, записываемого во флэш-память. Этот загружаемый код является заголовком в том же пакете, который содержит записываемое изображение. Во встроенном образе хранится контрольная сумма алгоритма программирования, и он проверяет ее перед запуском.

Если вы пишете вещи, которые генерируются внутри, я бы посмотрел на аппаратные блокировки. Разрешить запись только в том случае, если вы ранее установили определенный дискретный выходной контакт в положение ON. Чтобы ответить на проблему «что, если IP перескочит через проверки»? можно в 2 части. Сначала установите некоторые критические переменные для алгоритма. (адрес для записи, например, сохраните его инициализированным в недействительной памяти и установите его правильно только в отдельном вызове, сделанном перед записью. Затем попросите функцию записи проверить вашу аппаратную блокировку. Выполните один из шагов разрешения в прерывании, или в ответ на таймер, что-то, что вряд ли будет выполнено в правильной последовательности, если у вас мошеннический IP-адрес.

Если ваш IP-адрес действительно может прыгать куда угодно, может быть невозможно предотвратить непреднамеренную запись. Лучшее, на что вы можете надеяться, это убедиться, что единственный путь к цели также устанавливает все остальное, необходимое для успешного написания.

person AShelly    schedule 17.02.2012
comment
Я делаю то же самое (обновление через UART). В приложении и в загрузчике есть код записи. Так что они могут перекрестно обновлять друг друга, и я также храню некоторую конфигурацию во флэш-памяти. Я не знаю, какой у вас uC, но тот, что у меня, не может выполнять код из ОЗУ, поэтому заливать код для записи не вариант. На самом деле я использую аналогичную процедуру, описанную вами, с блокировкой HW. Это то, что я пытался объяснить, когда задавал вопрос... похоже, я плохо поработал :) - person Stefan; 17.02.2012
comment
@Stefan: будьте осторожны с обновлением загрузчика из приложения. Что происходит при потере питания между стиранием и записью? К сожалению, ваш загрузчик исчез, и вы заблокировали свое устройство. В одном из проектов Freescale HCS08 загрузчик фактически устанавливает регистры ЦП, чтобы защитить его от перезаписи, и приложение не может выполнять запись на эти страницы во флэш-памяти. Быть безопасным. - person tomlogic; 17.02.2012
comment
@tomlogic: у меня есть 2 кода записи - в приложении и в загрузчике - и они могут перекрестно обновлять друг друга. uC по умолчанию просыпается в коде приложения. Если мне не удастся обновить загрузчик, я все равно проснусь в приложении и смогу повторить попытку. А загрузчик использует хитрость. Он записывает код приложения от верхней страницы к самой нижней. Прежде чем записать первую (которая на самом деле самая высокая) страницу, он пишет «перейти к загрузчику» на странице 0. Теперь единственная забота состоит в том, чтобы не ошибиться при записи самой последней страницы (страницы 0). Таким образом, шансы получить невосстанавливаемую флэш-память крайне малы. - person Stefan; 18.02.2012
comment
@tomlogic: ... Причина, по которой мне нужно писать во флэш-память из кода приложения, на самом деле заключается не в том, чтобы обновить загрузчик, а в том, что у меня есть некоторая конфигурация, хранящаяся во флэш-памяти, которую я хочу обновить из приложения (мне было намного проще использовать флэш-память чем ЭСППЗУ). Таким образом, часть кода приложения, которая записывает данные во флэш-память, должна находиться в загрузочной части флэш-памяти. Теперь, если я заблокирую загрузчик, это фактически означает, что я заблокировал часть приложения. Я вижу, у вас действительно есть опыт в таких вещах, поэтому я был бы рад услышать ваше мнение, так как мне не с кем обсуждать такие вещи. - person Stefan; 18.02.2012