# bootloader: Phase 4 - done
This commit is contained in:
parent
acb1fed1f2
commit
6c564f1638
6 changed files with 309 additions and 24 deletions
|
|
@ -8,7 +8,7 @@
|
||||||
| 1 — Скелет (CDC + LED) | ✅ завершена | сборка/HAB/SWD-прошивка/ping-pong/debug — все пункты верификации пройдены на реальной плате, детали ниже |
|
| 1 — Скелет (CDC + LED) | ✅ завершена | сборка/HAB/SWD-прошивка/ping-pong/debug — все пункты верификации пройдены на реальной плате, детали ниже |
|
||||||
| 2 — bootutil (Direct-XIP) | ✅ завершена | host-тесты 5/5, аппаратная верификация — все 5 сценариев пройдены на реальной плате (детали и 3 найденных/исправленных бага — [DEBUG_LOG_PHASE2.md](DEBUG_LOG_PHASE2.md)) |
|
| 2 — bootutil (Direct-XIP) | ✅ завершена | host-тесты 5/5, аппаратная верификация — все 5 сценариев пройдены на реальной плате (детали и 3 найденных/исправленных бага — [DEBUG_LOG_PHASE2.md](DEBUG_LOG_PHASE2.md)) |
|
||||||
| 3 — SD-путь установки | ✅ полностью верифицирована (2026-07-13) | все 5 сценариев + раунд 4 (консолидация детекта на единый PRSSTAT + ранний сэмпл кнопки) + **аппаратный watchdog** (`bsp/wdog`, см. ниже) пройдены на реальной плате. По пути найден и исправлен баг чек-листа сценария 1 (стабы не PIC, см. ниже) — не регресс кода. См. [DEBUG_LOG_PHASE3_SD.md](DEBUG_LOG_PHASE3_SD.md), чек-лист — [test_stub/HARDWARE_VERIFICATION_PHASE3.md](test_stub/HARDWARE_VERIFICATION_PHASE3.md) |
|
| 3 — SD-путь установки | ✅ полностью верифицирована (2026-07-13) | все 5 сценариев + раунд 4 (консолидация детекта на единый PRSSTAT + ранний сэмпл кнопки) + **аппаратный watchdog** (`bsp/wdog`, см. ниже) пройдены на реальной плате. По пути найден и исправлен баг чек-листа сценария 1 (стабы не PIC, см. ниже) — не регресс кода. См. [DEBUG_LOG_PHASE3_SD.md](DEBUG_LOG_PHASE3_SD.md), чек-лист — [test_stub/HARDWARE_VERIFICATION_PHASE3.md](test_stub/HARDWARE_VERIFICATION_PHASE3.md) |
|
||||||
| 4 — SDRAM/W25Q smoke-test + LED-паттерны | не начата | рекомендуется ПОСЛЕ Фазы 6 (см. ниже) |
|
| 4 — SDRAM/W25Q smoke-test + LED-паттерны | ✅ полностью верифицирована (2026-07-20) | `bsp_sdram_configure()` (C-порт DCD) + `qspi_info` (JEDEC → чип/ёмкость) + словарь LED-паттернов. Host-тесты 16/16 (без изменений — вся новая логика аппаратная, host-тестировать нечего). Аппаратная верификация: SDRAM 4-фазный dev-тест (`sdram_test`) все фазы green; сценарии LED 1-5 из чек-листа пройдены (6 сознательно не гонялся — реальная проверка smoke-тестов идёт через service-tui сразу после прошивки, не через синтетический чек-лист). По пути найдено/исправлено 3 бага — детали ниже. Чек-лист — [test_stub/HARDWARE_VERIFICATION_LED_PATTERNS.md](test_stub/HARDWARE_VERIFICATION_LED_PATTERNS.md), словарь для сервисных инженеров — [docs/bootloader/LED_PATTERNS.md](../../docs/bootloader/LED_PATTERNS.md) |
|
||||||
| 5 — HAB Release + service-tui | не начата | |
|
| 5 — HAB Release + service-tui | не начата | |
|
||||||
| 6 — Устойчивость и восстановление (recovery) | ✅ полностью верифицирована (2026-07-14) | 6a (счётчик `SRC_GPR3` + фолбэк) + 6b (recovery-режим, BTN_2, ослабленный gate) + стенд `test_stub` реализованы; host-тесты 16/16, ARM+HAB зелёные. Все 6 сценариев чек-листа пройдены на реальной плате; по пути найдено/исправлено 4 бага (2 в коде, 2 в стенде/чек-листе — детали ниже). 6c (контракт tft_app) — документация под будущий план. Чек-лист — [test_stub/HARDWARE_VERIFICATION_PHASE6.md](test_stub/HARDWARE_VERIFICATION_PHASE6.md) |
|
| 6 — Устойчивость и восстановление (recovery) | ✅ полностью верифицирована (2026-07-14) | 6a (счётчик `SRC_GPR3` + фолбэк) + 6b (recovery-режим, BTN_2, ослабленный gate) + стенд `test_stub` реализованы; host-тесты 16/16, ARM+HAB зелёные. Все 6 сценариев чек-листа пройдены на реальной плате; по пути найдено/исправлено 4 бага (2 в коде, 2 в стенде/чек-листе — детали ниже). 6c (контракт tft_app) — документация под будущий план. Чек-лист — [test_stub/HARDWARE_VERIFICATION_PHASE6.md](test_stub/HARDWARE_VERIFICATION_PHASE6.md) |
|
||||||
|
|
||||||
|
|
@ -699,6 +699,101 @@ while (true)` без выхода по времени, если callback `cardDe
|
||||||
Полноценной аппаратной инъекции неисправностей не делаем (в проекте и для остальных HIL-тестов это не
|
Полноценной аппаратной инъекции неисправностей не делаем (в проекте и для остальных HIL-тестов это не
|
||||||
принято) — ограничиваемся проверкой на реальной плате в штатном состоянии.
|
принято) — ограничиваемся проверкой на реальной плате в штатном состоянии.
|
||||||
|
|
||||||
|
### ✅ Реализовано (2026-07-20): SDRAM/QSPI smoke-test + словарь LED-паттернов
|
||||||
|
|
||||||
|
**`bsp_sdram_configure()` — C-порт DCD.** DCD (`tools/host/dcd/dcd.bin`) — источник истины, 4 года в
|
||||||
|
производстве. Порт побитовый, не пересчитанный из формул SDK: значения регистров SEMC — дословно из
|
||||||
|
DCD, тактовая цепочка — через типизированный CCM API (лосслесс перенос значений), init-последовательность
|
||||||
|
SDRAM — через `SEMC_SendIPCommand()` (командные слова DCD `0xA55A000F/000C/000A` decode ровно в
|
||||||
|
`kSEMC_SDRAMCM_Prechargeall/AutoRefresh/Modeset`, подтверждено). Первый SEMC-блок DCD — мёртвый код
|
||||||
|
(полностью перезаписывается вторым до какого-либо использования) — не портирован. Секция «Clock Init»
|
||||||
|
DCD — наоборот НЕ дублирование: `SKIP_SYSCLK_INIT` (`bsp/CMakeLists.txt`) гасит PLL2+PFD2+SEMC-mux в
|
||||||
|
штатном `BOARD_BootClockRUN()` для всех таргетов, включая bootloader — без DCD и без своего кода эту
|
||||||
|
цепочку не поднимает никто; `bsp_sdram_configure()` обязана делать это сама (SEMC ≈135.77 МГц, PLL2
|
||||||
|
528 → PFD2 `FRAC=35` ≈271.54 → `SEMC_PODF` ÷2). Детали и вся математика — [bsp/sdram/README.md](../../bsp/sdram/README.md).
|
||||||
|
|
||||||
|
**Найденный и исправленный баг: AXI-QoS регистры валили C-код фолтом (не DCD).** Хвост DCD пишет
|
||||||
|
NIC-301 GPV-регистры приоритета шины (`0x41044100+`, `0x41442100+`). Первая гипотеза («такой периферии
|
||||||
|
на этом кристалле нет») оказалась неверной — пользователь предоставил выжимку из RM (гл. 29 "Network
|
||||||
|
Interconnect Bus System"), адреса совпали день-в-день. Настоящая причина —
|
||||||
|
[bsp/generated/board.c](../../bsp/generated/board.c) `board_mpu_init()`: Region 0 (deny-all, errata
|
||||||
|
1013783-B воркэраунд) перехватывает весь диапазон 0x0..0xFFFFFFFF, а Region 10 (периферия) покрывает
|
||||||
|
только 4 МБ от `0x40000000` — GPV-регистры на `0x41xxxxxx` мимо. DCD это переживает (пишет до включения
|
||||||
|
MPU), C-код — нет. Исправлено: Region 11 (`0x41000000`, 8 МБ, Device/AP_FULL) добавлен в
|
||||||
|
`board_mpu_init()` — общий для всех прошивок код, задет один раз, не только для bootloader.
|
||||||
|
|
||||||
|
**Dev-only глубокий тест (`{"type":"cmd","cmd":"sdram_test"}`, только Debug,
|
||||||
|
`BOOTLOADER_DEV_DIAGNOSTICS`).** [dev_sdram_test.c](dev_sdram_test.c) — 4 фазы 1:1 портированы из
|
||||||
|
`firmware_test/src/tests/test_sdram.c` (address_bus/data_bus/sequential/retention), но против нового
|
||||||
|
C-порта, не против DCD напрямую. **Все фазы зелёные на железе** — реальный прогон ~4.2 с (комментарий
|
||||||
|
в оригинале про «~30 с» оказался завышен раза в 6 — запись садится в D-Cache почти мгновенно, реальная
|
||||||
|
задержка SDRAM только на `flush_dcache()` и чтение при верификации; поправлено в коде).
|
||||||
|
|
||||||
|
**`qspi_info` — идентификация чипа QSPI.** `bsp_qspi_decode_chip()` — новая функция в
|
||||||
|
[bsp/qspi_flash](../../bsp/qspi_flash/README.md) (capacity byte JEDEC → `"W25Q64/128/256/512"` + МБ,
|
||||||
|
работает и без успешного `bsp_qspi_init()` — LUT для JEDEC грузится безусловным первым шагом внутри
|
||||||
|
неё). Ёмкость сверяется с минимумом `BOOTLOADER_FLASH_MAP.md` (16 МБ, W25Q128) — не только
|
||||||
|
идентификация, а реальный BOM/QC-гейт: W25Q64 драйвер поддерживает технически, но для карты флеша этой
|
||||||
|
платы это неверная деталь, а не "чуть меньше запас".
|
||||||
|
|
||||||
|
**Smoke-test в `main.c`** (после `bsp_qspi_init()`/`bsp_usb_cdc_init()`, до `attempt_boot()`,
|
||||||
|
неблокирующий): `bsp_sdram_configure()`+`bsp_sdram_init()` и чтение JEDEC. **Найдена и исправлена
|
||||||
|
проблема видимости**: результаты (`smoke_pass`/`smoke_fail`, `qspi_info`) изначально шлются один раз
|
||||||
|
сразу после соответствующего `init()` — хост почти никогда не успевает открыть порт к этому моменту
|
||||||
|
(USB enumeration), `cli_send()` неблокирующий и теряет запись. Решение — тот же паттерн, что уже был у
|
||||||
|
`wdog`: `protocol_set_smoke_result()`/`protocol_set_qspi_info()` кэширует исход,
|
||||||
|
`protocol_send_smoke_status()`/`protocol_send_qspi_info()` переспрашивает в любой момент сессии по
|
||||||
|
командам `"smoke_status"`/`"qspi_info"`.
|
||||||
|
|
||||||
|
**Словарь LED-паттернов.** Новый модуль [led_status.{c,h}](src/led_status.c) — единственный владелец
|
||||||
|
всех паттернов. Состояния: норма (ждём SD, APP выкл) / установка (APP соло 250/250) / железо не в
|
||||||
|
порядке (объединяет `!qspi_ok`, недостаточную ёмкость QSPI и провал SDRAM smoke-test в один паттерн
|
||||||
|
APP соло 100/100 — деталь причины только по CDC) / recovery (оба LED синхронно 100/100, без изменений
|
||||||
|
с Фазы 6) / разовая вспышка 4×(80/80) на отклонённый SD-образ. Полное описание для сервисных
|
||||||
|
инженеров, без ссылок и деталей реализации — [docs/bootloader/LED_PATTERNS.md](../../docs/bootloader/LED_PATTERNS.md).
|
||||||
|
|
||||||
|
Паттерн «установка» рисуется изнутри самой (блокирующей) установки — окно `led_status_install_begin/end()`
|
||||||
|
в `sd_update.c`, `led_status_tick_install()` (no-op вне окна) вызывается и из цикла копирования чанков,
|
||||||
|
и из поблочного стирания слота в `flash_map_backend.c` (общий файл — понадобилась правка
|
||||||
|
`test_stub/CMakeLists.txt`, добавить include-path и сам `led_status.c` в `add_mcuboot_stub()`, иначе
|
||||||
|
`flash_map_backend.c` там не собирался).
|
||||||
|
|
||||||
|
**Найденное и задокументированное (не баг, ограничение архитектуры XIP-safety): APP подвисает во время
|
||||||
|
стирания блока.** Аппаратно проверено — во время ~150 мс стирания каждого 64 КБ блока
|
||||||
|
(`bsp_qspi_erase_block_64k()`) паттерн «установка» не гладкий 250/250, а дёрганый/подвисающий.
|
||||||
|
Причина: `qspi_irq_lock()` держит `__disable_irq()` на всю длительность IP-команды (обязательно —
|
||||||
|
иначе прерывание может зафетчить код из flash прямо во время стирания → HardFault, см.
|
||||||
|
[bsp/qspi_flash/README.md](../../bsp/qspi_flash/README.md), «XIP-безопасность»), а под этим глушится и
|
||||||
|
SysTick, на котором целиком держится `bsp_tick_get_ms()` (`bsp/tick/src/tick.c`) — часы паттерна на
|
||||||
|
это время фактически стоят. Во время копирования (страницы по ~3 мс) то же самое явление на порядок
|
||||||
|
короче — там мигание заметно ровнее. Осознанно не чинится — маскировать IRQ на время IP-команды нужно
|
||||||
|
по конструкции. Задокументировано в `@note` у `led_status_tick_install()` и в чек-листе.
|
||||||
|
|
||||||
|
**Найденное и подтверждённое штатным (не баг): повтор отклонения при вставленной плохой SD-карте.**
|
||||||
|
Раз файл на карте не меняется, каждый периодический пере-скан (`SD_RETRY_PERIOD_MS`, Фаза 3) заново
|
||||||
|
получает `SD_CANDIDATE_INVALID` — LED-вспышка и CDC-ошибка повторяются каждые ~1.5 с, пока карта не
|
||||||
|
вынута. Это взаимодействие с уже существующей с Фазы 3 логикой периодического пере-скана, не регрессия
|
||||||
|
этой фазы — оставлено как есть.
|
||||||
|
|
||||||
|
**Аппаратная верификация** —
|
||||||
|
[test_stub/HARDWARE_VERIFICATION_LED_PATTERNS.md](test_stub/HARDWARE_VERIFICATION_LED_PATTERNS.md):
|
||||||
|
сценарии 1-5 (норма / битый файл / неверная подпись / успешная установка / recovery) пройдены на
|
||||||
|
плате. Сценарий 6 (искусственная неисправность через временный порог в коде) сознательно не прогонялся
|
||||||
|
— реальная проверка smoke-тестов на производстве идёт через service-tui сразу после прошивки
|
||||||
|
загрузчика, не через синтетический чек-лист.
|
||||||
|
|
||||||
|
**Host-тесты**: `just build::test-host` — по-прежнему **16/16** (новых нет: вся логика этой фазы —
|
||||||
|
тонкая аппаратная оркестрация без доступа к железу не протестируешь, толстой host-тестируемой логики
|
||||||
|
здесь не появилось — соответствует принципу проекта, см. низ файла).
|
||||||
|
|
||||||
|
**ARM-сборка**: `just build::build-bootloader-debug`/`hab-bootloader-debug` и
|
||||||
|
`build-bootloader-release`/`hab-bootloader-release` — все зелёные. `m_text`: Debug 96512 Б из 247 КБ
|
||||||
|
(38.16%, было 90576 Б / 35.69% после Фазы 6), Release 47232 Б (18.67%) — запас по-прежнему большой.
|
||||||
|
|
||||||
|
**Не сделано, сознательно отложено**: событие `"booting"` на CDC (упоминалось в исходном плане) — не
|
||||||
|
реализовано; ценность низкая (переход к прыжку — микросекунды, отдельным CDC-событием почти нечего
|
||||||
|
добавить к уже наблюдаемому факту прыжка), добавим по факту реальной необходимости, не заранее.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Фаза 5 — HAB, релизные пресеты, интеграция в service-tui
|
## Фаза 5 — HAB, релизные пресеты, интеграция в service-tui
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,10 @@ watchdog приостановлен, пошаговая отладка сбро
|
||||||
|
|
||||||
## Архитектура
|
## Архитектура
|
||||||
|
|
||||||
Загрузчик работает **без SDRAM** (SEMC поднимает само приложение в своём раннем startup) и без
|
Загрузчик **не зависит от SDRAM** для своей работы (XIP только из W25Q, без DCD) и без дисплея/RTOS:
|
||||||
дисплея/RTOS: инициализация, доступ к QSPI-flash, чтение FatFS с SD, проверка и выбор образа, прыжок.
|
инициализация, доступ к QSPI-flash, чтение FatFS с SD, проверка и выбор образа, прыжок. SEMC/SDRAM
|
||||||
|
трогаются только диагностически (`bsp_sdram_configure()`, boot-time smoke-test) — реально их поднимает
|
||||||
|
для себя уже само приложение в своём раннем startup.
|
||||||
Линкер жёстко ограничивает код бюджетом области загрузчика (256 КБ) с `ASSERT` на границу Slot A —
|
Линкер жёстко ограничивает код бюджетом области загрузчика (256 КБ) с `ASSERT` на границу Slot A —
|
||||||
превышение становится ошибкой сборки, а не тихим заездом в чужую область.
|
превышение становится ошибкой сборки, а не тихим заездом в чужую область.
|
||||||
|
|
||||||
|
|
@ -80,6 +82,8 @@ firmware/bootloader/
|
||||||
│ │ установить в целевой слот с потоковой verify-записью
|
│ │ установить в целевой слот с потоковой verify-записью
|
||||||
│ ├── cli.* — построчный IO + диспетчеризация команд
|
│ ├── cli.* — построчный IO + диспетчеризация команд
|
||||||
│ ├── protocol.* — сериализация исходящих событий
|
│ ├── protocol.* — сериализация исходящих событий
|
||||||
|
│ ├── led_status.* — словарь LED-паттернов (см. ../../docs/bootloader/LED_PATTERNS.md)
|
||||||
|
│ ├── dev_sdram_test.* — [DEV-ONLY, Debug] глубокий тест SDRAM по команде "sdram_test"
|
||||||
│ └── version.h.in — шаблон версии (CMake → generated/version.h)
|
│ └── version.h.in — шаблон версии (CMake → generated/version.h)
|
||||||
│
|
│
|
||||||
├── mcuboot_port/ — интеграция bootutil (MCUboot) поверх bsp_qspi_flash:
|
├── mcuboot_port/ — интеграция bootutil (MCUboot) поверх bsp_qspi_flash:
|
||||||
|
|
@ -96,30 +100,44 @@ firmware/bootloader/
|
||||||
|
|
||||||
## Протокол
|
## Протокол
|
||||||
|
|
||||||
USB CDC ACM, JSON-строки, максимум 128 байт на строку.
|
USB CDC ACM, JSON-строки. Входящая строка — до `CLI_LINE_BUF_SIZE` (128) байт, исходящее сообщение —
|
||||||
|
до `PROTO_BUF_SIZE` (192) байт (несимметрично: `qspi_info`/`sdram_test` длиннее старых сообщений).
|
||||||
|
|
||||||
**Команды хоста:**
|
**Команды хоста:**
|
||||||
|
|
||||||
| Команда | Ответ |
|
| Команда | Ответ |
|
||||||
| ------------------------------------ | ------------------------------------------------------------------------------------- |
|
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| `{"type":"cmd","cmd":"ping"}` | `{"type":"pong"}` |
|
| `{"type":"cmd","cmd":"ping"}` | `{"type":"pong"}` |
|
||||||
| `{"type":"cmd","cmd":"get_version"}` | `{"type":"version_response","fw":"X.Y.Z"}` |
|
| `{"type":"cmd","cmd":"get_version"}` | `{"type":"version_response","fw":"X.Y.Z"}` |
|
||||||
| `{"type":"cmd","cmd":"wdog"}` | `{"type":"wdog","armed":…,"timeout_s":…,"recovered":…,"reset_count":…,"threshold":…}` |
|
| `{"type":"cmd","cmd":"wdog"}` | `{"type":"wdog","armed":…,"timeout_s":…,"recovered":…,"reset_count":…,"threshold":…}` |
|
||||||
|
| `{"type":"cmd","cmd":"smoke_status"}` | `{"type":"status","state":"smoke_pass"}` / `"smoke_fail"` — результат boot-time smoke-теста SDRAM/SEMC (переспрос, см. ниже) |
|
||||||
|
| `{"type":"cmd","cmd":"qspi_info"}` | `{"type":"qspi_info","chip":"W25Q128","mfr":"0xEF","cap_byte":"0x18","size_mb":16,"pass":true}` (переспрос, см. ниже) |
|
||||||
|
| `{"type":"cmd","cmd":"sdram_test"}` <br> **[DEV-ONLY, Debug-сборка]** | серия из 6 `{"type":"sdram_test","phase":"…","pass":…,"duration_ms":…,"fail_addr":"…","expected":"…","got":"…"}` (`configure`/`address_bus`/`data_bus`/`sequential`/`retention`/`summary`) — блокирует главный цикл на ~4 с. Нет в Release/HAB (`BOOTLOADER_DEV_DIAGNOSTICS`) |
|
||||||
|
|
||||||
|
`smoke_status`/`qspi_info` ничего не отвечают, если соответствующий boot-time чек ещё не отработал —
|
||||||
|
в штатной последовательности `main.c` такого не бывает.
|
||||||
|
|
||||||
**Исходящие статусы** `{"type":"status","state":"…"}`:
|
**Исходящие статусы** `{"type":"status","state":"…"}`:
|
||||||
|
|
||||||
| Состояние | Когда |
|
| Состояние | Когда |
|
||||||
| ---------------- | ------------------------------- |
|
| ---------------- | ------------------------------------------ |
|
||||||
| `waiting_for_sd` | нет валидного слота, ждём карту |
|
| `waiting_for_sd` | нет валидного слота, ждём карту |
|
||||||
| `installing` | идёт запись образа в слот |
|
| `installing` | идёт запись образа в слот |
|
||||||
| `update_skipped` | кандидат отклонён по версии |
|
| `update_skipped` | кандидат отклонён по версии |
|
||||||
| `recovery_mode` | плата в режиме восстановления |
|
| `recovery_mode` | плата в режиме восстановления |
|
||||||
|
| `smoke_pass` | boot-time smoke-тест SDRAM/SEMC прошёл |
|
||||||
|
| `smoke_fail` | boot-time smoke-тест SDRAM/SEMC провалился |
|
||||||
|
|
||||||
**Ошибки** `{"ok":false,"error":"…"}`: `SD_CANDIDATE_INVALID`, `SD_INSTALL_WRITE_FAILED`,
|
**Ошибки** `{"ok":false,"error":"…"}`: `SD_CANDIDATE_INVALID`, `SD_INSTALL_WRITE_FAILED`,
|
||||||
`SD_INSTALL_REJECTED`, `SD_DOWNGRADE_ERASE_FAILED`, `PARSE_ERR`, `UNKNOWN_CMD`, `LINE_TOO_LONG`.
|
`SD_INSTALL_REJECTED`, `SD_DOWNGRADE_ERASE_FAILED`, `PARSE_ERR`, `UNKNOWN_CMD`, `LINE_TOO_LONG`.
|
||||||
|
|
||||||
Событие `wdog` эмитится также автоматически один раз на старте, если предыдущий сброс был по
|
**Автоматически на старте, без команды:** `wdog` — если предыдущий сброс был по watchdog
|
||||||
watchdog (`recovered:true`).
|
(`recovered:true`); `qspi_info` и `smoke_pass`/`smoke_fail` — сразу после соответствующей проверки.
|
||||||
|
Все три — best-effort: хост почти никогда не успевает открыть порт к этому моменту (USB enumeration),
|
||||||
|
поэтому у `qspi_info`/`smoke_status` (но не у одноразового boot-time `wdog`) есть команда-переспрос
|
||||||
|
в таблице выше.
|
||||||
|
|
||||||
|
LED-индикация, соответствующая этим состояниям, — [LED_PATTERNS.md](../../docs/bootloader/LED_PATTERNS.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -153,10 +153,13 @@ int flash_area_erase(const struct flash_area *area, uint32_t off, uint32_t len)
|
||||||
* Зависание самого стирания флеша всё равно ловится: refresh — по
|
* Зависание самого стирания флеша всё равно ловится: refresh — по
|
||||||
* ЗАВЕРШЕНИИ блока, а не перед ним. */
|
* ЗАВЕРШЕНИИ блока, а не перед ним. */
|
||||||
bsp_wdog_refresh();
|
bsp_wdog_refresh();
|
||||||
/* Прогресс-хук индикации — no-op вне окна установки (см.
|
/* Прогресс-хук индикации — no-op вне окна установки, ничего не
|
||||||
* led_status.h). Нужен, чтобы APP не замирал на ~5 c стирания слота
|
* рисует на revert/recovery-стирании (та же функция). Вызывается
|
||||||
* при установке; на revert/recovery-стирании (тоже зовут эту
|
* исправно каждый блок, но САМ блок (~150 мс) идёт под
|
||||||
* функцию) ничего не рисует. */
|
* qspi_irq_lock() — глушит SysTick, от которого тикает
|
||||||
|
* bsp_tick_get_ms(); на глаз это видно как подвисание/дёрганое
|
||||||
|
* мигание, не гладкие 250/250 (известное ограничение, см. @note
|
||||||
|
* у led_status_tick_install() в led_status.h). */
|
||||||
led_status_tick_install();
|
led_status_tick_install();
|
||||||
if (bsp_qspi_erase_block_64k(block_addr) != BSP_OK)
|
if (bsp_qspi_erase_block_64k(block_addr) != BSP_OK)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,21 @@ void led_status_install_end(void);
|
||||||
* APP 250/250). No-op вне окна install.
|
* APP 250/250). No-op вне окна install.
|
||||||
*
|
*
|
||||||
* Звать из ВСЕХ блокирующих циклов установки — и поблочного стирания слота
|
* Звать из ВСЕХ блокирующих циклов установки — и поблочного стирания слота
|
||||||
* (flash_area_erase, ~5 c на 2 МБ), и копирования чанков (sd_update), иначе
|
* (flash_area_erase, ~5 c на 2 МБ), и копирования чанков (sd_update).
|
||||||
* APP замирал бы на время стирания.
|
*
|
||||||
|
* @note [ИЗВЕСТНОЕ ОГРАНИЧЕНИЕ, подтверждено на железе] Во время самого
|
||||||
|
* стирания блока (~150 мс на 64 КБ, bsp_qspi_erase_block_64k())
|
||||||
|
* qspi_irq_lock() держит __disable_irq() на всю длительность busy-wait
|
||||||
|
* — это глушит и SysTick, на котором держится bsp_tick_get_ms()
|
||||||
|
* (см. bsp/tick/src/tick.c). Часы, от которых считается фаза мигания,
|
||||||
|
* не идут внутри каждого такого окна — тик вызывается исправно между
|
||||||
|
* блоками, но «сейчас» между вызовами почти не меняется, поэтому
|
||||||
|
* глазом видно подвисание/дёрганое мигание, а не плавные 250/250.
|
||||||
|
* Во время копирования чанков (страницы по ~3 мс) окна намного короче
|
||||||
|
* — там мигание заметно более гладкое. Осознанно не чиним (означало бы
|
||||||
|
* не маскировать IRQ на время IP-команды — прямой путь к HardFault,
|
||||||
|
* см. bsp/qspi_flash/README.md, «XIP-безопасность»); фиксируем как
|
||||||
|
* факт в HARDWARE_VERIFICATION_LED_PATTERNS.md.
|
||||||
*/
|
*/
|
||||||
void led_status_tick_install(void);
|
void led_status_tick_install(void);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,16 @@ function(add_mcuboot_stub)
|
||||||
${BSP_STARTUP_FILE}
|
${BSP_STARTUP_FILE}
|
||||||
${BSP_SYSCALLS_FILE}
|
${BSP_SYSCALLS_FILE}
|
||||||
${CMAKE_SOURCE_DIR}/firmware/bootloader/mcuboot_port/flash_map_backend.c
|
${CMAKE_SOURCE_DIR}/firmware/bootloader/mcuboot_port/flash_map_backend.c
|
||||||
|
${CMAKE_SOURCE_DIR}/firmware/bootloader/src/led_status.c
|
||||||
${MCUBOOT_BOOTUTIL_DIR}/src/bootutil_public.c)
|
${MCUBOOT_BOOTUTIL_DIR}/src/bootutil_public.c)
|
||||||
|
|
||||||
target_include_directories(${ARG_NAME} PRIVATE ${MCUBOOT_BOOTUTIL_INCLUDES})
|
# led_status.h — flash_map_backend.c (общий с bootloader) зовёт
|
||||||
|
# led_status_tick_install() из поблочного erase (см. led_status.h) — не
|
||||||
|
# добавляет стабу нового поведения (окно установки здесь никогда не
|
||||||
|
# открыто, tick — no-op), просто нужен для компиляции общего файла.
|
||||||
|
target_include_directories(
|
||||||
|
${ARG_NAME} PRIVATE ${MCUBOOT_BOOTUTIL_INCLUDES}
|
||||||
|
${CMAKE_SOURCE_DIR}/firmware/bootloader/src)
|
||||||
|
|
||||||
target_compile_definitions(
|
target_compile_definitions(
|
||||||
${ARG_NAME}
|
${ARG_NAME}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
# Аппаратная верификация — словарь LED-паттернов (Фаза 4)
|
||||||
|
|
||||||
|
Чек-лист проверяет `led_status.c` на реальной плате: 5 сценариев из
|
||||||
|
[docs/bootloader/LED_PATTERNS.md](../../docs/bootloader/LED_PATTERNS.md) + опционально «неисправность
|
||||||
|
железа» (требует временной правки кода — раздел 6). Между сценариями 1-3 SWD-перепрошивка/power cycle
|
||||||
|
не нужны — бутлоадер сам пере-сканирует SD раз в ~1.5 с, доставать/менять файл на карте можно на ходу.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Подготовка (один раз)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Собрать и прошить bootloader
|
||||||
|
just build::build-bootloader-debug
|
||||||
|
just host::flash-swd-bootloader-debug
|
||||||
|
# ОБЯЗАТЕЛЕН power cycle платы после прошивки — SWD не ресетит автоматически
|
||||||
|
|
||||||
|
# Собрать подписанные образы-заглушки (test_stub) — понадобится stub_a_v1_confirmed.bin
|
||||||
|
just build::build-mcuboot-stub
|
||||||
|
ls build/Debug/signed/ # ожидаем stub_a_v1_confirmed.bin, stub_b_v2_confirmed.bin, ...
|
||||||
|
|
||||||
|
# Стереть оба слота — чистая плата для сценариев 1-4
|
||||||
|
uv run --directory tools/hil pyocd erase --target mimxrt1050_quadspi --frequency 4000000 \
|
||||||
|
--sector 0x60040000+0x200000
|
||||||
|
uv run --directory tools/hil pyocd erase --target mimxrt1050_quadspi --frequency 4000000 \
|
||||||
|
--sector 0x60240000+0x200000
|
||||||
|
|
||||||
|
# Подготовить 2 тестовых файла для SD-карты (кладём рядом, на карту переносим по одному под именем TFT_APP.BIN)
|
||||||
|
head -c 512 /dev/urandom > /tmp/TFT_APP_garbage.bin
|
||||||
|
|
||||||
|
cp build/Debug/signed/stub_a_v1_confirmed.bin /tmp/TFT_APP_badsig.bin
|
||||||
|
python3 -c "
|
||||||
|
with open('/tmp/TFT_APP_badsig.bin', 'r+b') as f:
|
||||||
|
f.seek(0x1000) # далеко за 32-байтным заголовком — magic/версия целы
|
||||||
|
b = f.read(1)
|
||||||
|
f.seek(0x1000)
|
||||||
|
f.write(bytes([b[0] ^ 0xFF])) # один перевёрнутый байт ломает hash/подпись
|
||||||
|
"
|
||||||
|
|
||||||
|
# Открыть монитор порта — держать открытым все сценарии, смотреть и на CDC, и на LED
|
||||||
|
just host::uart-monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
Power cycle после прошивки → плата должна войти в сценарий 1 сама.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Норма — ждём SD
|
||||||
|
|
||||||
|
**Подготовка:** SD-карта НЕ вставлена (или без файла `TFT_APP.BIN`).
|
||||||
|
|
||||||
|
**Смотреть:** HEARTBEAT мигает 50 мс горит / 450 мс не горит. APP не горит вообще.
|
||||||
|
|
||||||
|
**В мониторе:** каждые ~1.5 с — `{"type":"status","state":"waiting_for_sd"}`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Образ отклонён — битый файл (`SD_CANDIDATE_INVALID`)
|
||||||
|
|
||||||
|
**Подготовка:** на SD-карту, под именем `TFT_APP.BIN`, положить `/tmp/TFT_APP_garbage.bin`. Вставить
|
||||||
|
карту (если уже была вставлена — просто заменить файл, ждать до ~1.5 с).
|
||||||
|
|
||||||
|
**Смотреть:** APP делает ровно 4 быстрых вспышки (80 мс горит / 80 мс не горит), затем гаснет —
|
||||||
|
плата возвращается к паттерну «Норма» (пункт 1). HEARTBEAT всё это время не меняется.
|
||||||
|
|
||||||
|
**В мониторе:** `{"ok":false,"error":"SD_CANDIDATE_INVALID"}`, следом снова `waiting_for_sd`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Образ отклонён — неверная подпись (`SD_INSTALL_REJECTED`)
|
||||||
|
|
||||||
|
**Подготовка:** заменить файл на SD-карте на `/tmp/TFT_APP_badsig.bin` (переименовать в `TFT_APP.BIN`).
|
||||||
|
|
||||||
|
**Смотреть:** сначала на секунду-две APP переходит в паттерн «Установка» (250/250, см. пункт 4 ниже —
|
||||||
|
файл пишется в Slot A, только потом проверка подписи проваливается), затем — те же 4 быстрых вспышки,
|
||||||
|
затем возврат к «Норме».
|
||||||
|
|
||||||
|
**В мониторе:** `{"type":"status","state":"installing"}`, затем `{"ok":false,"error":"SD_INSTALL_REJECTED"}`,
|
||||||
|
следом снова `waiting_for_sd`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Установка — успешная (включая стирание слота)
|
||||||
|
|
||||||
|
**Подготовка:** заменить файл на SD-карте на `build/Debug/signed/stub_a_v1_confirmed.bin` (переименовать
|
||||||
|
в `TFT_APP.BIN`). Это единственный образ, который можно ставить на чистую плату (см. §5.1
|
||||||
|
`PLAN.md` — цель установки всегда Slot A, `stub_a_*` слинкован под её адрес; `stub_b_*` здесь
|
||||||
|
использовать нельзя — прыгнет в нерабочий код).
|
||||||
|
|
||||||
|
**Смотреть:**
|
||||||
|
1. APP переходит в 250 мс вкл / 250 мс выкл.
|
||||||
|
2. Пока идёт стирание слота (~5 с) — мигание подвисает/дёргается, ровным 250/250 не выглядит.
|
||||||
|
**Это ожидаемо, не баг**: `bsp_qspi_erase_block_64k()` держит `__disable_irq()` на все ~150 мс
|
||||||
|
каждого блока — глушится и SysTick, от которого тикает `bsp_tick_get_ms()`, часы паттерна на это
|
||||||
|
время фактически стоят. Известное ограничение, см. `@note` у `led_status_tick_install()`
|
||||||
|
(`led_status.h`) — сознательно не чиним (означало бы не маскировать IRQ на время IP-команды
|
||||||
|
стирания, прямой путь к HardFault на XIP).
|
||||||
|
3. Во время копирования (после стирания, несколько секунд) — мигание заметно более гладкое, чем на
|
||||||
|
шаге 2: там каждая запись страницы держит IRQ всего ~3 мс, а не ~150 мс.
|
||||||
|
4. По завершении — оба LED гаснут (бутлоадер передал управление), затем APP начинает мигать в паттерне
|
||||||
|
самого `test_stub` (500 мс вкл / 250 мс выкл) — это уже не бутлоадер, а другая прошивка.
|
||||||
|
|
||||||
|
**В мониторе:** `{"type":"status","state":"installing"}`, дальше тишина по протоколу бутлоадера (CDC
|
||||||
|
переходит под управление `test_stub`, если он вообще что-то шлёт).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Recovery Mode
|
||||||
|
|
||||||
|
**Подготовка:** удержать `BSP_BUTTON_2` в момент подачи питания (после сценария 4 в Slot A уже валидный
|
||||||
|
образ — это не мешает, `BTN 2` проверяется раньше выбора слота).
|
||||||
|
|
||||||
|
**Смотреть:** оба светодиода горят и гаснут ОДНОВРЕМЕННО: 100 мс горят / 100 мс не горят.
|
||||||
|
|
||||||
|
**В мониторе:** `{"type":"status","state":"recovery_mode"}`.
|
||||||
|
|
||||||
|
**Дополнительно (не обязательно):** если в этот момент вставить SD с валидным образом — на несколько
|
||||||
|
секунд паттерн переключится на «Установка» (250/250, как в пункте 4), затем плата стирает оба слота и
|
||||||
|
прыгает. Это ожидаемо, не баг: пока установка идёт, экран рисует именно она, recovery-паттерн
|
||||||
|
возвращается только если установка не состоялась.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. (Опционально) Неисправность железа
|
||||||
|
|
||||||
|
Настоящую неисправность QSPI/SDRAM на живой плате не устроить без пайки — этот пункт использует
|
||||||
|
временную правку одной строки, чтобы искусственно провалить проверку ёмкости QSPI. **Не забыть
|
||||||
|
откатить после проверки.**
|
||||||
|
|
||||||
|
**Подготовка:**
|
||||||
|
1. В `firmware/bootloader/src/main.c` найти строку:
|
||||||
|
```c
|
||||||
|
const uint32_t QSPI_MIN_FLASH_SIZE_MB = 16U;
|
||||||
|
```
|
||||||
|
Временно поднять порог выше реальной ёмкости чипа на плате (если на плате W25Q128 — 16 МБ,
|
||||||
|
поставить, например, `32U`).
|
||||||
|
2. Пересобрать и прошить: `just build::build-bootloader-debug && just host::flash-swd-bootloader-debug`.
|
||||||
|
3. Power cycle.
|
||||||
|
|
||||||
|
**Смотреть:** HEARTBEAT мигает как обычно (50/450). APP мигает быстро без остановки: 100 мс горит /
|
||||||
|
100 мс не горит — и не останавливается (устойчиво до следующего POR).
|
||||||
|
|
||||||
|
**В мониторе:** `{"type":"qspi_info","chip":"W25Q128","...","pass":false}`.
|
||||||
|
|
||||||
|
**Откатить обязательно:**
|
||||||
|
1. Вернуть строку в `main.c` к `16U`.
|
||||||
|
2. Пересобрать и прошить заново: `just build::build-bootloader-debug && just host::flash-swd-bootloader-debug`.
|
||||||
|
3. Power cycle, убедиться что плата снова показывает «Норма» (пункт 1) — не оставлять плату/дерево
|
||||||
|
с искусственно применённым порогом.
|
||||||
Loading…
Reference in a new issue