lift_indicator_suite/docs/mimxrt1052/UPDATE_FLOW.md

11 KiB
Raw Permalink Blame History

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 (сводка)

  1. Параметризованный .ld tft_app под два адреса слота (образец — test_stub MIMXRT1052xxxxx_mcuboot_slot.ld + --defsym=__slot_base__=).
  2. Рецепт сборки+подписи двух бинарей на релиз (по образцу build-mcuboot-stub, но production-ключ).
  3. Расширить sd_update: TFT_APP_<A|B>.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 — адреса слотов, обоснование размеров.
  • ../../firmware/bootloader/PLAN.md — фазы; Фаза 6 (recovery, taxonomy зависаний), Фаза 5 (HAB/production/service-tui).
  • HAB_GUIDE.md — подпись самого bootloader (HAB), не путать с imgtool-подписью образов tft_app.