# UPDATE_FLOW.md — производственная заливка и полевое обновление tft_app Разбор «что и куда заливать» для связки **bootloader → tft_app** (MCUboot Direct-XIP, два слота A/Б). Дополняет [BOOTLOADER_FLASH_MAP.md](BOOTLOADER_FLASH_MAP.md) (адреса) и [../../firmware/bootloader/PLAN.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](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](../../firmware/bootloader/src/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 (сводка) 1. **Параметризованный `.ld`** tft_app под два адреса слота (образец — `test_stub` `MIMXRT1052xxxxx_mcuboot_slot.ld` + `--defsym=__slot_base__=`). 2. **Рецепт сборки+подписи** двух бинарей на релиз (по образцу `build-mcuboot-stub`, но production-ключ). 3. **Расширить `sd_update`**: `TFT_APP_.BIN` по `decision.target_slot` (§5). 4. **Контракт tft_app** (Фаза 6, 6c): обслуживание WDOG (alive-flag), отложенный `boot_set_confirmed()`, `bsp_boot_health_mark()`. 5. **Решить**: production заливать только Slot A или A+Б? Достаточно A — первое обновление наполнит Б и даст fallback *другой* версии (A+Б одной версией от class-B не спасает — тот же баг в обоих). --- ## Связанные документы - [BOOTLOADER_FLASH_MAP.md](BOOTLOADER_FLASH_MAP.md) — адреса слотов, обоснование размеров. - [../../firmware/bootloader/PLAN.md](../../firmware/bootloader/PLAN.md) — фазы; Фаза 6 (recovery, taxonomy зависаний), Фаза 5 (HAB/production/service-tui). - [HAB_GUIDE.md](HAB_GUIDE.md) — подпись самого bootloader (HAB), не путать с imgtool-подписью образов tft_app.