Загрузчик
Как устройство входит в загрузчик производителя, протокол записи и перезапуск приложения.
Прошивка записывается через штатный загрузчик производителя. Он занимает flash 0x08000000..0x08003FFF, в образ приложения не входит и обновлением не затрагивается. Запись идёт только в область приложения 0x08004000..0x0801B8EF, то есть 96 496 байт.
Прошивальщик находится в src-tauri/src/firmware.rs приложения.
Вход в загрузчик
Программные команды делают одно и то же. Через ROM-функции flash переписывается служебный сектор 0x0807E000: слова 0 и 2 сохраняются, слова 1 и 3 инвертируются, по адресу 0x0807E010 записывается маркер 0x21436587. После этого выполняется сброс через регистр 0x40002000.
Загрузчик остаётся в режиме прошивки, если выполнено любое из условий:
- замкнут контакт (
0x4001D000, бит 0); - по
0x0807E010лежит маркер0x21436587; - в RAM по адресу
0x20028000лежат слова0x32021688и0x19714568, которые загрузчик стирает при старте; - вектор сброса приложения равен
0xFFFFFFFF.
Протокол загрузчика
Загрузчик работает как HID EEEF:2929. Отчёты по 64 байта без Report ID. На каждую команду загрузчик отвечает 64 байтами и повторяет код команды в байте 3.
После успешной D3 загрузчик записывает в сектор 0x0807E000 значение CRC, его инверсию, число слов и его инверсию, затем стирает маркер.
Перезапуск
По команде AA загрузчик сразу переходит к вектору сброса приложения, без сброса чипа.
- Вход контактами делается при включении питания, поэтому приложение стартует само.
- Вход программной командой требует переподключения USB. Ни эта прошивка, ни штатная не стартуют после прыжка без него.
Тёплый старт
Watchdog по адресу 0x40005000 переживает программный сброс. Загрузчик его не включает, но кормит. Поэтому прошивка кормит watchdog с первой инструкции Reset_Handler и во время настройки тактирования.
Служебная область flash 0x082000xx читается только после включения питания. После тёплого сброса её чтение приводит к отказу. platform_init читает адреса 0x082000A4 и 0x082000C8 через пробу: обработчик отказа пропускает инструкцию, а значения берутся из кэша в секции .noinit, которая переживает тёплый сброс, либо из запасных констант.