11 KiB
UPDATE_FLOW.md — производственная заливка и полевое обновление tft_app
Разбор «что и куда заливать» для связки bootloader → tft_app (MCUboot Direct-XIP, два слота A/Б). Дополняет BOOTLOADER_FLASH_MAP.md (адреса) и ../../firmware/bootloader/PLAN.md (фазы, recovery).
TL;DR (главное, что вызывает непонимание)
Direct-XIP: образ исполняется прямо из адреса своего слота, код обычно НЕ позиционно-независим →
под каждый слот нужна СВОЯ линковка. Обновление, которое встанет в Slot Б, должно быть слинковано
под адрес Slot Б (0x60240000). Поэтому:
Релиз одной версии tft_app = ДВА подписанных бинаря (линковка под A + линковка под Б), из ОДНОГО исходника, параметризованным линкер-скриптом — ровно как уже устроен
test_stub(--defsym=__slot_base__=…). Версия (-v X.Y.Z) одинаковая в обоих.
Да, «собрать новую версию со своим линкер-скриптом под слот Б» — так и есть. Но не вручную по одному:
это один параметризованный .ld и один рецепт сборки, дающий оба бинаря.
1. Почему так — это свойство Direct-XIP, не наша прихоть
- XIP = eXecute In Place: CPU фетчит инструкции напрямую из флеша по адресу слота, образ никуда не копируется (в отличие от swap/scratch-режимов MCUboot, которые мы сознательно НЕ используем).
- Абсолютные адреса — vector table, указатели на функции, литеральные пулы, адрес инициализации
.data— фиксируются на этапе линковки под конкретный базовый адрес. Образ, слинкованный под Slot A (0x60040000), в Slot Б (0x60240000) поедет по чужим адресам и не запустится корректно. - Это прямо зафиксированный «открытый вопрос §4» в BOOTLOADER_FLASH_MAP.md:
«код обычно не позиционно-независим — вероятно потребуется два варианта линковки под Slot A и
Slot Б, либо PIC».
test_stub(Фаза 2) уже подтвердил two-slot-two-linkage на реальном железе.
⚠️ Почему на
test_stub«одинаковый» бинарь как будто работал в обоих слотах. Заглушка крошечная (только мигание LED): почти весь её код PC-relative,.dataминимальна, а vector table релоцируется иboot_select(ставитVTOR), и самим образом в старте — поэтому она позиционно терпима по случайности. Реальный tft_app (большой, с.data, абсолютными указателями, шрифтами-C-массивами, framebuffer) терпимым не будет — ему нужны обе линковки по-настоящему.
2. Карта слотов (напоминание)
| Слот | База | Размер | ORIGIN образа (после imgtool-заголовка 0x200) |
|---|---|---|---|
| Slot A | 0x60040000 |
2 МБ | 0x60040200 |
| Slot Б | 0x60240000 |
2 МБ | 0x60240200 |
Линкер tft_app: ORIGIN = __slot_base__ + 0x200 (образец — cmake/linker/MIMXRT1052xxxxx_mcuboot_slot.ld
у test_stub).
3. Релизные артефакты (на каждую версию)
Из одного исходника tft_app — два подписанных бинаря production-ключом:
| Файл | Линковка (__slot_base__) |
Назначение |
|---|---|---|
tft_app_slotA_vX.Y.Z.signed.bin |
0x60040000 |
ставится в Slot A |
tft_app_slotB_vX.Y.Z.signed.bin |
0x60240000 |
ставится в Slot Б |
- Оба — одна версия
-v X.Y.Z(version-gate сравнивает версию образа, а не его линковку). - Оба подписаны production-ключом (не тестовым sample-ключом MCUboot; см. HAB SRK-церемонию как аналог для bootloader).
- Рецепт — по образцу
just build::build-mcuboot-stub(собирает оба слота + подписывает imgtool'ом), но с production-ключом и реальным tft_app вместо заглушки.
4. Кейсы
| # | Ситуация | Куда/что | Как |
|---|---|---|---|
| 1 | Начальная production-заливка | A-линковка → Slot A; Slot Б пуст | SWD / USB ROM (blhost), just host::flash-production-путь |
| 2 | Первое полевое обновление v1→v2 | A активен → target = Slot Б → ставится Б-линковка v2 | SD, авто по version-gate; после — A=v1 (fallback), Б=v2 (active) |
| 3 | Следующее обновление v2→v3 | Б активен → target = Slot A → ставится A-линковка v3 | SD; после — A=v3 (active), Б=v2 (fallback) |
| 4 | Даунгрейд (BTN_1 при старте) | старая версия в inactive + стирание прежнего активного | SD + удержание BTN_1 (см. Фаза 3) |
| 5 | Recovery (BTN_2 / class C) | оба слота стёрты → чистый борт → A-линковка в Slot A | SD, ослабленный version-gate (см. Фаза 6) |
| 6 | Образ завис после обновления | авто-откат (class A) или счётчик+фолбэк (class B) | см. Фаза 6, taxonomy A/B |
Ключевой инвариант alternation A/Б: обновление всегда идёт в НЕактивный слот, прежний рабочий слот
остаётся нетронутым как fallback (см. update_policy — «целевой слот всегда НЕ активный»). Поэтому в
поле почти всегда есть куда откатиться, если новая версия окажется плохой.
5. Как обновление выбирает нужную линковку — ТЕКУЩИЙ GAP
Сейчас (sd_update.c): ищется ОДИН файл
2:/TFT_APP.BIN и ставится в неактивный слот (decision.target_slot). Для позиционно-терпимого
test_stub это прошло все 5 сценариев Фазы 3 — но для реального tft_app сломается: если активен
Slot A, обновление идёт в Slot Б, а TFT_APP.BIN мог быть слинкован под A → в Slot Б не запустится.
Нужное расширение (часть включения реального tft_app):
- SD несёт оба линкованных бинаря по соглашению имён, напр.
TFT_APP_A.BIN/TFT_APP_B.BIN. sd_updateоткрывает файл по целевому слоту:TFT_APP_A.BIN, если target = Slot A, иначеTFT_APP_B.BIN.- Оператор кладёт на SD оба и не думает, какой слот сейчас активен — bootloader сам берёт нужный под инактивный слот.
- Version-gate: bootloader читает версию из выбранного файла (в обоих одна) — сравнение корректно.
Это простое изменение (одна развилка имени файла по target_slot), но его надо сделать до первого
реального полевого обновления tft_app. Пока стоит test_stub — не мешает.
6. Альтернатива — PIC (почему не сейчас)
Позиционно-независимый образ (один бинарь на оба слота) — теоретически убирает дублирование линковки.
Но на bare-metal Cortex-M это ROPI/RWPI: флаги компилятора, PI-совместимый startup, r9 как
static base для RW-данных, и все библиотеки (FreeRTOS, SDK-драйверы, шрифты) собранные в PI-режиме,
плюс runtime-оверхед. Объём работы и риск большие; для Direct-XIP индустрия стандартно выбирает
dual-link. Оставляем PIC на «если поддержка двух сборок станет реальной обузой» — не сейчас.
7. Конкретные TODO для включения реального tft_app (сводка)
- Параметризованный
.ldtft_app под два адреса слота (образец —test_stubMIMXRT1052xxxxx_mcuboot_slot.ld+--defsym=__slot_base__=). - Рецепт сборки+подписи двух бинарей на релиз (по образцу
build-mcuboot-stub, но production-ключ). - Расширить
sd_update:TFT_APP_<A|B>.BINпоdecision.target_slot(§5). - Контракт tft_app (Фаза 6, 6c): обслуживание WDOG (alive-flag), отложенный
boot_set_confirmed(),bsp_boot_health_mark(). - Решить: production заливать только Slot A или A+Б? Достаточно A — первое обновление наполнит Б и даст fallback другой версии (A+Б одной версией от class-B не спасает — тот же баг в обоих).
Связанные документы
- BOOTLOADER_FLASH_MAP.md — адреса слотов, обоснование размеров.
- ../../firmware/bootloader/PLAN.md — фазы; Фаза 6 (recovery, taxonomy зависаний), Фаза 5 (HAB/production/service-tui).
- HAB_GUIDE.md — подпись самого bootloader (HAB), не путать с imgtool-подписью образов tft_app.