From acb1fed1f2a05cbc7f3285884607c035e49d5f91 Mon Sep 17 00:00:00 2001 From: Ezra Maccabee Date: Fri, 17 Jul 2026 16:49:02 +0300 Subject: [PATCH] # bootloader: SEMC Init, Led dictionary --- .clang-tidy | 2 +- bsp/button/include/bsp/button.h | 18 -- bsp/display/include/bsp/display.h | 2 - bsp/generated/board.c | 10 + bsp/led/include/bsp/led.h | 2 - bsp/qspi_flash/README.md | 9 +- bsp/qspi_flash/include/bsp/qspi_flash.h | 20 ++ bsp/qspi_flash/src/qspi_flash.c | 36 +++ bsp/sdram/README.md | 113 +++++-- bsp/sdram/include/bsp/sdram.h | 70 +++-- bsp/sdram/src/sdram.c | 240 ++++++++++++++- .../BOOTLOADER_FLASH_MAP.md | 0 docs/{mimxrt1052 => bootloader}/BOOT_FLAGS.md | 0 {firmware => docs}/bootloader/BOOT_FLOW.md | 3 + docs/{mimxrt1052 => bootloader}/HAB_GUIDE.md | 0 docs/bootloader/LED_PATTERNS.md | 11 + firmware/bootloader/CMakeLists.txt | 17 +- firmware/bootloader/README.md | 2 +- .../mcuboot_port/bootutil_sources.cmake | 46 +-- firmware/bootloader/mcuboot_port/flash_map.h | 6 +- .../mcuboot_port/flash_map_backend.c | 6 + firmware/bootloader/src/cli.c | 35 +++ firmware/bootloader/src/dev_sdram_test.c | 282 ++++++++++++++++++ firmware/bootloader/src/dev_sdram_test.h | 45 +++ firmware/bootloader/src/led_status.c | 113 +++++++ firmware/bootloader/src/led_status.h | 63 ++++ firmware/bootloader/src/main.c | 95 ++++-- firmware/bootloader/src/protocol.c | 80 ++++- firmware/bootloader/src/protocol.h | 100 ++++++- firmware/bootloader/src/sd_update.c | 8 +- firmware/bootloader/test_stub/CMakeLists.txt | 12 +- 31 files changed, 1278 insertions(+), 168 deletions(-) rename docs/{mimxrt1052 => bootloader}/BOOTLOADER_FLASH_MAP.md (100%) rename docs/{mimxrt1052 => bootloader}/BOOT_FLAGS.md (100%) rename {firmware => docs}/bootloader/BOOT_FLOW.md (99%) rename docs/{mimxrt1052 => bootloader}/HAB_GUIDE.md (100%) create mode 100644 docs/bootloader/LED_PATTERNS.md create mode 100644 firmware/bootloader/src/dev_sdram_test.c create mode 100644 firmware/bootloader/src/dev_sdram_test.h create mode 100644 firmware/bootloader/src/led_status.c create mode 100644 firmware/bootloader/src/led_status.h diff --git a/.clang-tidy b/.clang-tidy index 3c8c045..3278c8c 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -80,7 +80,7 @@ CheckOptions: - key: readability-identifier-naming.PointerParameterPrefix value: "p_" # uint8_t *p_buffer - key: readability-function-size.LineThreshold - value: '60' + value: '90' - key: readability-function-size.StatementThreshold value: '30' - key: readability-magic-numbers.IgnoredIntegerValues diff --git a/bsp/button/include/bsp/button.h b/bsp/button/include/bsp/button.h index 441f10a..858b0dc 100644 --- a/bsp/button/include/bsp/button.h +++ b/bsp/button/include/bsp/button.h @@ -1,24 +1,6 @@ /* * bsp_button — тактовые кнопки TactBut1 / TactBut2 * - * Аппаратура: - * TactBut1 — GPIO2 pin 30 (GPIO_B1_14), подтяжка к 3V3 внешняя, нажатие = LOW - * TactBut2 — GPIO2 pin 31 (GPIO_B1_15), подтяжка к 3V3 внешняя, нажатие = LOW - * - * Пины инициализированы в BOARD_InitPins() (generated/pin_mux.c). - * bsp_button_init() не трогает GPIO — только сбрасывает внутреннее состояние. - * - * Использование (bare-metal): - * bsp_button_init(); - * // в tick-коллбэке каждые 5 мс: - * bsp_button_poll(); - * // в основном цикле: - * if (bsp_button_get_event_pressed(BSP_BUTTON_1)) { ... } - * - * Использование (FreeRTOS): - * // в таске с vTaskDelay(5): - * bsp_button_poll(); - * if (bsp_button_get_event_pressed(BSP_BUTTON_1)) { xQueueSend(...); } */ #ifndef BSP_BUTTON_H diff --git a/bsp/display/include/bsp/display.h b/bsp/display/include/bsp/display.h index 03395a9..63debeb 100644 --- a/bsp/display/include/bsp/display.h +++ b/bsp/display/include/bsp/display.h @@ -2,8 +2,6 @@ * @file display.h * @brief BSP: ELCDIF display driver — TFT4 / TFT7 / TFT8 / TFT10. * - * Bare-metal compatible: callback-based FRAME_DONE notification. - * No FreeRTOS dependency in this layer. */ #ifndef BSP_DISPLAY_DISPLAY_H_ diff --git a/bsp/generated/board.c b/bsp/generated/board.c index bd1094c..ce3ef52 100644 --- a/bsp/generated/board.c +++ b/bsp/generated/board.c @@ -91,6 +91,16 @@ static void board_mpu_init(void) MPU->RBAR = ARM_MPU_RBAR(10U, 0x40000000U); MPU->RASR = ARM_MPU_RASR(0U, ARM_MPU_AP_FULL, 2U, 0U, 0U, 0U, 0U, ARM_MPU_REGION_SIZE_4MB); + /* Region 11: Device, NIC-301 GPV (bus-arbitration QoS) 0x41000000, 8 MB. + * Region 10 не покрывает — GPV0/SIM_MAIN (0x41000000) и GPV4/SIM_M7 + * (0x41400000, "Cortex-M7 read/write_qos") лежат за пределами его 4 MB + * от 0x40000000, попадают только под Region 0 (deny-all, errata-воркэраунд + * выше) → запись фолтит, хотя регистры реальные и документированы (i.MX + * RT1050 RM, гл. 29 "Network Interconnect Bus System (NIC-301)"). Нужен + * bsp_sdram_configure() для read_qos/write_qos регионов SDRAM/LCD/M7. */ + MPU->RBAR = ARM_MPU_RBAR(11U, 0x41000000U); + MPU->RASR = ARM_MPU_RASR(0U, ARM_MPU_AP_FULL, 2U, 0U, 0U, 0U, 0U, ARM_MPU_REGION_SIZE_8MB); + ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk); SCB_EnableDCache(); diff --git a/bsp/led/include/bsp/led.h b/bsp/led/include/bsp/led.h index cff3038..0c3ea9a 100644 --- a/bsp/led/include/bsp/led.h +++ b/bsp/led/include/bsp/led.h @@ -8,8 +8,6 @@ * LED_HEARTBEAT — системный, мигает как признак жизни прошивки * LED_APP — прикладной, управляется из firmware по ситуации * - * Пины сконфигурированы в generated/pin_mux.h. Этот хедер не знает - * ни про GPIO-порты, ни про NXP SDK. */ #include diff --git a/bsp/qspi_flash/README.md b/bsp/qspi_flash/README.md index 9877ed5..36ce9e4 100644 --- a/bsp/qspi_flash/README.md +++ b/bsp/qspi_flash/README.md @@ -77,8 +77,9 @@ target_compile_definitions(firmware_test PRIVATE /* Инициализация — вызвать до bsp_tick_init() */ bsp_status_t bsp_qspi_init(void); -/* Идентификация */ +/* Идентификация — bsp_qspi_read_jedec_id() работает и без успешного init() */ bsp_status_t bsp_qspi_read_jedec_id(bsp_qspi_jedec_t *p_jedec); +const char *bsp_qspi_decode_chip(uint8_t cap_byte, uint32_t *p_size_mb); /* device_id & 0xFF → "W25Q128" + МБ */ uint32_t bsp_qspi_flash_size(void); /* доступно после init() */ /* Стирание */ @@ -117,9 +118,13 @@ board_hw_init(); bsp_qspi_init(); /* ← до bsp_tick_init() */ bsp_tick_init(); -/* Идентификация чипа */ +/* Идентификация чипа — работает даже если bsp_qspi_init() выше вернула + ошибку (LUT для JEDEC грузится безусловным первым шагом внутри неё) */ bsp_qspi_jedec_t jedec; bsp_qspi_read_jedec_id(&jedec); +uint32_t size_mb; +const char *chip_name = bsp_qspi_decode_chip((uint8_t) (jedec.device_id & 0xFF), &size_mb); +/* chip_name = "W25Q128", size_mb = 16 — либо "UNKNOWN"/0, если чип не опознан */ /* Стереть сектор и записать страницу */ bsp_qspi_erase_sector(0x00010000); diff --git a/bsp/qspi_flash/include/bsp/qspi_flash.h b/bsp/qspi_flash/include/bsp/qspi_flash.h index 20ee9d7..9e59acb 100644 --- a/bsp/qspi_flash/include/bsp/qspi_flash.h +++ b/bsp/qspi_flash/include/bsp/qspi_flash.h @@ -90,9 +90,29 @@ bsp_status_t bsp_qspi_init(void); * * @param[out] p_jedec Результат. Не NULL. * @retval BSP_OK / BSP_ERR. + * + * @note Работает и после проваленного bsp_qspi_init() — LUT-слот для чтения + * JEDEC ID грузится безусловным первым шагом внутри него, до любой из + * проверок, на которых init() мог отвалиться. Полезно для диагностики + * "что именно распаяно", когда чип не опознан/не тот. */ bsp_status_t bsp_qspi_read_jedec_id(bsp_qspi_jedec_t *p_jedec); +/** + * @brief Человекочитаемое имя и ёмкость чипа по capacity byte JEDEC ID. + * + * Тот же байт, что различает поддерживаемые чипы в bsp_qspi_init() — вынесен + * отдельно, чтобы потребитель мог опознать чип из уже прочитанного + * bsp_qspi_jedec_t.device_id, не завися от успеха bsp_qspi_init(). + * + * @param[in] cap_byte Байт ёмкости (device_id & 0xFF). + * @param[out] p_size_mb Ёмкость чипа, МБ. 0, если байт не распознан. + * Может быть NULL, если размер не нужен. + * @return "W25Q64"/"W25Q128"/"W25Q256"/"W25Q512", либо "UNKNOWN" для + * нераспознанного байта. + */ +const char *bsp_qspi_decode_chip(uint8_t cap_byte, uint32_t *p_size_mb); + /** * @brief Стирание сектора 4 KB. * diff --git a/bsp/qspi_flash/src/qspi_flash.c b/bsp/qspi_flash/src/qspi_flash.c index dff23b0..2cf9702 100644 --- a/bsp/qspi_flash/src/qspi_flash.c +++ b/bsp/qspi_flash/src/qspi_flash.c @@ -657,6 +657,42 @@ AT_QUICKACCESS_SECTION_CODE(static bsp_status_t qspi_detect_chip(uint8_t cap_byt return BSP_OK; } +const char *bsp_qspi_decode_chip(uint8_t cap_byte, uint32_t *p_size_mb) +{ + const char *p_name; + uint32_t size_mb; + + switch (cap_byte) + { + case BSP_QSPI_CAP_64MBIT: + p_name = "W25Q64"; + size_mb = 8U; + break; + case BSP_QSPI_CAP_128MBIT: + p_name = "W25Q128"; + size_mb = 16U; + break; + case BSP_QSPI_CAP_256MBIT: + p_name = "W25Q256"; + size_mb = 32U; + break; + case BSP_QSPI_CAP_512MBIT: + p_name = "W25Q512"; + size_mb = 64U; + break; + default: + p_name = "UNKNOWN"; + size_mb = 0U; + break; + } + + if (p_size_mb != NULL) + { + *p_size_mb = size_mb; + } + return p_name; +} + /** * @brief Общая реализация erase-операций. * diff --git a/bsp/sdram/README.md b/bsp/sdram/README.md index 3056380..bf399f5 100644 --- a/bsp/sdram/README.md +++ b/bsp/sdram/README.md @@ -1,9 +1,9 @@ # bsp_sdram — внешняя SDRAM MT48LC16M16A2 (32 МБ) -Минимальная верификация доступности внешней SDRAM, подключённой к SEMC. -Подробное тестирование (паттерны, шина адреса/данных, retention) выполняется -в тест-модуле `firmware_test/test_sdram.c`, который использует константы -и API этого модуля. +Подъём SEMC (для прошивок без DCD) и минимальная верификация доступности +внешней SDRAM, подключённой к SEMC. Подробное тестирование (паттерны, шина +адреса/данных, retention) выполняется в тест-модуле `firmware_test/test_sdram.c`, +который использует API этого модуля (путь с DCD, см. ниже). --- @@ -29,16 +29,45 @@ Тестовая база смещена на 2 МБ от начала — гарантированно выше `.data`/`.bss` прошивки и ниже non-cacheable региона. -**Важно:** SEMC инициализируется через DCD **до вызова `main()`**. Этот модуль -не настраивает SEMC и не трогает его регистры. Если DCD не отработал — -`bsp_sdram_init()` вернёт ошибку, но исправить ситуацию из модуля нельзя. +--- + +## Контракт: кто поднимает SEMC + +Два независимых пути, в зависимости от того, есть ли у прошивки DCD: + +- **С DCD** (`firmware_test`, `bsp_boot_xip`): SEMC поднят DCD **до вызова + `main()`**. Этот модуль в этом случае регистры SEMC не трогает — только + `bsp_sdram_init()` для верификации. +- **Без DCD** (bootloader, `bsp_boot_xip_no_dcd`; в будущем `tft_app`): SEMC + не поднимает никто, пока не будет явно вызван `bsp_sdram_configure()` — + побитовый порт проверенной в производстве DCD-последовательности + (`tools/host/dcd/dcd.bin`, «блок 2» — первый блок там мёртвый код, + полностью перезаписывается вторым до какого-либо использования, поэтому + не переносился). Источник истины — сам DCD, а не пересчёт по формулам SDK + из наносекунд: значения регистров контроллера SEMC скопированы дословно. + +**Почему `bsp_sdram_configure()` сама поднимает тактирование.** Штатный +`BOARD_BootClockRUN()` (его вызывает `board_hw_init()` в каждой прошивке) НЕ +настраивает PLL2 → PFD2 → делитель SEMC — этот блок в `clock_config.c` +выключен макросом `SKIP_SYSCLK_INIT`, который определён для **всех** таргетов +сборки (`bsp/CMakeLists.txt`), включая `bsp_boot_xip_no_dcd`. Смысл макроса — +не глитчить PLL, пока на нём уже висит поднятая DCD SDRAM (случай +`firmware_test`); но для пути без DCD это побочно означает, что тактирование +SEMC не настраивает вообще никто, кроме `bsp_sdram_configure()`. Результат — +SEMC ≈135.77 МГц (PLL2 528 МГц → PFD2 `FRAC=35` ≈271.54 МГц → `SEMC_PODF` ÷2), +под эту частоту тюнингованы все timing-регистры DCD. + +Вызывать `bsp_sdram_configure()` в пути с DCD не нужно и не имеет смысла — +DCD уже сделала эту работу раньше, а `bsp_sdram_configure()` дублировала бы +её же на живой памяти. --- ## API ```c -bsp_status_t bsp_sdram_init(void); +bsp_status_t bsp_sdram_configure(void); /* поднять SEMC: тактирование → пины → контроллер → init-команды SDRAM */ +bsp_status_t bsp_sdram_init(void); /* верифицировать SDRAM (SEMC уже поднят — DCD либо bsp_sdram_configure()) */ ``` **Публичные константы:** @@ -55,6 +84,37 @@ bsp_status_t bsp_sdram_init(void); Константы размеров — для потребителей; `bsp_sdram` не запускает по ним внутренних проходов. +**Поведение `bsp_sdram_configure()`:** + +1. Тактирование: `CLOCK_InitSysPll()` (PLL2, 528 МГц) → `CLOCK_InitSysPfd()` + (PFD2, `FRAC=35`) → `CLOCK_SetMux`/`CLOCK_SetDiv` (SEMC ← alt ← PFD2, ÷2). +2. IOMUX: пины `GPIO_EMC_00..41` → ALT0 (функция SEMC), `SW_PAD_CTL_PAD` = + `0x000110F9`; `GPIO_EMC_39` (`SEMC_DQS`) дополнительно получает `SION` + (иначе SEMC не сможет читать собственный read-strobe). +3. Регистры контроллера SEMC (`MCR`, `BMCR0/1`, `BR[0..8]`, `IOCR`, + `SDRAMCR0..3`, `DBICR0/1`, `IPCR1/2`) — побитово из DCD. +4. Командная последовательность SDRAM через `SEMC_SendIPCommand()`: + precharge-all → 2×auto-refresh → mode-set (`0x33` = BL8/sequential/CL3, + согласуется с `SDRAMCR0` и даташитом MT48LC16M16A2) → включение + авто-refresh (`SDRAMCR3 = 0x50210A09`). +5. AXI-QoS приоритеты SDRAM-мастеров (`0x41044100/104` LCD, `0x41442100/104` + Cortex-M7 read/write_qos) — хвост DCD. Это не SEMC и не в заголовках + `sdk/devices/MIMXRT1052` (NXP не заворачивает ARM'овский NIC-301 IP в + CMSIS-структуру), но регистры реальные и документированы (i.MX RT1050 RM, + гл. 29 "Network Interconnect Bus System (NIC-301)" — адреса сверены день-в- + день: `0x41044100` = `GPV0_BASE(0x41000000)+0x44000+0x100` = + `SIM_MAIN.LCD.read_qos`). Для bootloader инертны (нет конкуренции LCD/DMA + vs CPU за шину), но входят в проверенную последовательность и понадобятся + `tft_app`. + +**Требует MPU Region 11** (`board_mpu_init()`, `bsp/generated/board.c`, +`0x41000000`, 8 МБ) — штатный Region 10 (периферия, только 4 МБ от +`0x40000000` = 4 домена AIPSTZ) NIC-301 GPV не покрывает; без Region 11 шаг 5 +фолтит (deny-all errata-регион 0 перехватывает всё, что не покрыто более +специфичным регионом — `PRIVDEFENA` тут не спасает, т.к. регион 0 покрывает +весь диапазон 0x0..0xFFFFFFFF и потому всегда matched). DCD это переживает — +ROM пишет регистры до включения MPU. Уже добавлен в `board_mpu_init()`. + **Поведение `bsp_sdram_init()`:** 1. Ждёт перехода SEMC в IDLE (`SEMC->STS0 & SEMC_STS0_IDLE_MASK`), @@ -66,11 +126,13 @@ bsp_status_t bsp_sdram_init(void); **Коды возврата:** -| Код | Условие | -| ----------------- | ------------------------------------------- | -| `BSP_OK` | SDRAM доступна, оба паттерна совпали | -| `BSP_ERR_TIMEOUT` | SEMC не перешёл в IDLE за 10 мс | -| `BSP_ERR_INIT` | Хотя бы один паттерн не совпал при readback | +| Функция | Код | Условие | +| -------------------------- | ----------------- | -------------------------------------------- | +| `bsp_sdram_configure()` | `BSP_OK` | Тактирование/пины/регистры/команды прошли | +| `bsp_sdram_configure()` | `BSP_ERR_INIT` | IP-команда SEMC вернула ошибку (precharge/refresh/mode-set) | +| `bsp_sdram_init()` | `BSP_OK` | SDRAM доступна, оба паттерна совпали | +| `bsp_sdram_init()` | `BSP_ERR_TIMEOUT` | SEMC не перешёл в IDLE за 10 мс | +| `bsp_sdram_init()` | `BSP_ERR_INIT` | Хотя бы один паттерн не совпал при readback | --- @@ -79,10 +141,19 @@ bsp_status_t bsp_sdram_init(void); ```c #include "bsp/sdram.h" +/* Путь С DCD (firmware_test) — SEMC уже поднят до main(): */ if (bsp_sdram_init() != BSP_OK) { handle_critical_error(); } +/* Путь БЕЗ DCD (bootloader smoke-test и т.п.) — сначала поднять SEMC сами: */ +if (bsp_sdram_configure() != BSP_OK) { + handle_semc_bringup_error(); +} +if (bsp_sdram_init() != BSP_OK) { + handle_sdram_error(); +} + /* Работа с памятью по адресам внутри [BSP_SDRAM_BASE_ADDR, BSP_SDRAM_BASE_ADDR + BSP_SDRAM_SIZE_BYTES) */ ``` @@ -91,15 +162,19 @@ if (bsp_sdram_init() != BSP_OK) { ## CMake +Сегодня линкует только `firmware_test` (путь с DCD). Для пути без DCD +потребитель (например, bootloader) добавляет зависимость сам — модуль +никого за собой не тянет: + ```cmake target_link_libraries(firmware_test PRIVATE bsp_sdram) ``` **Зависимости модуля:** -| Зависимость | Тип | Описание | -| ------------ | ------- | -------------------------------------------------- | -| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API | -| `bsp_tick` | PRIVATE | `bsp_tick_get_ms()` для таймаута SEMC IDLE | -| `sdk_semc` | PRIVATE | `fsl_semc.h` — `SEMC->STS0`, `SEMC_STS0_IDLE_MASK` | -| `bsp_board` | PRIVATE | Общие board-уровневые символы | +| Зависимость | Тип | Описание | +| ------------ | ------- | ----------------------------------------------------------------| +| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API | +| `bsp_tick` | PRIVATE | `bsp_tick_get_ms()` для таймаута SEMC IDLE | +| `sdk_semc` | PRIVATE | `fsl_semc.h` — регистры `SEMC->`, `SEMC_SendIPCommand()` | +| `bsp_board` | PRIVATE | Транзитивно даёт `sdk_clock` (`CLOCK_Init*()`) и `sdk_device` (`IOMUXC`) — отдельных PRIVATE-строк на них не заводили | diff --git a/bsp/sdram/include/bsp/sdram.h b/bsp/sdram/include/bsp/sdram.h index 6dfd311..a4e1d3e 100644 --- a/bsp/sdram/include/bsp/sdram.h +++ b/bsp/sdram/include/bsp/sdram.h @@ -2,9 +2,15 @@ * @file sdram.h * @brief BSP для внешней SDRAM MT48LC16M16A2 (32 МБ, шина 16 бит). * - * Архитектурное ограничение [DECISION]: - * SEMC инициализируется DCD до вызова main(). Этот модуль не трогает - * регистры SEMC — только верифицирует работоспособность памяти. + * Два пути в зависимости от того, кто использует SEMC: + * + * - Прошивки С DCD (firmware_test): SEMC поднят DCD до main(). + * bsp_sdram_init() только верифицирует доступность памяти. + * + * - Прошивки БЕЗ DCD (bootloader smoke-test; в будущем — tft_app под свой + * XIP): bsp_sdram_configure() сам поднимает SEMC (порт проверенной + * DCD-последовательности в C, см. tools/host/dcd/dcd.bin), затем + * bsp_sdram_init() верифицирует, как и в первом случае. * * Карта памяти: * 0x80000000 — начало SDRAM (SEMC BR0) @@ -14,16 +20,10 @@ * Тестовый регион (не пересекается с .data/.bss и non-cacheable): * 0x80200000 — начало (2 MB offset от базы) * - * Использование: + * Использование (путь без DCD): * @code - * bsp_sdram_result_t result; - * - * if (bsp_sdram_init() != BSP_OK) { // DCD не отработал - * handle_critical_error(); - * } - * - * bsp_sdram_test_fast(&result); // ~50 мс, 64 KB - * bsp_sdram_test_full(&result); // ~2–5 с, 1 MB + * if (bsp_sdram_configure() != BSP_OK) { handle_semc_bringup_error(); } + * if (bsp_sdram_init() != BSP_OK) { handle_sdram_error(); } * @endcode */ @@ -65,19 +65,53 @@ /* ── Public API ────────────────────────────────────────────────────────── */ +/** + * @brief Поднять SEMC и инициализировать внешнюю SDRAM (для вызывателей без DCD). + * + * Побитово-точный порт проверенной в производстве DCD-последовательности + * (tools/host/dcd/dcd.bin, «блок 2») в вызываемый C-код: + * 1. тактирование SEMC (PLL2 528 МГц → PFD2 271.54 МГц → ÷2 = 135.77 МГц); + * 2. IOMUX/PAD пинов GPIO_EMC (функция SEMC, DQS с SION); + * 3. регистры контроллера SEMC (MCR/BR/IOCR/SDRAMCR0..3/…), значения из DCD; + * 4. командная последовательность SDRAM: precharge-all → 2×auto-refresh → + * mode-set → включение авто-refresh; + * 5. AXI-QoS приоритеты SDRAM-мастеров (LCD/Cortex-M7) — NIC-301 GPV, + * отдельный IP вне карты SEMC (i.MX RT1050 RM, гл. 29). + * + * Прошивки С DCD (firmware_test) получают то же самое до main() и эту функцию + * НЕ вызывают — только bsp_sdram_init(). + * + * @note Безопасно вызывать в рантайме после board_hw_init(): CPU тактируется от + * ARM PLL, FlexSPI-XIP — от USB1 PLL; PLL2/PFD2 поднимаются с нуля и не + * задевают ни то, ни другое (под SKIP_SYSCLK_INIT штатный + * BOARD_BootClockRUN() эту цепочку не трогает). + * + * @note Требует MPU Region 11 (board_mpu_init(), bsp/generated/board.c) — + * шаг 5 пишет NIC-301 GPV (0x41000000+), который Region 10 + * (периферия, только 4 МБ от 0x40000000) не покрывает; без Region 11 + * запись фолтит (deny-all errata-регион 0 перехватывает всё + * непокрытое). Уже добавлен — заметка для будущих правок MPU-таблицы. + * + * @retval BSP_OK SEMC поднят, SDRAM инициализирована. + * @retval BSP_ERR_INIT Командная последовательность SDRAM не завершилась + * (IP-команда SEMC вернула ошибку). + */ +bsp_status_t bsp_sdram_configure(void); + /** * @brief Верифицировать доступность SDRAM. * - * Проверяет что SEMC контроллер инициализирован DCD и SDRAM отвечает — - * выполняет минимальный write/read/verify на первых 4 байтах тестового - * региона. Не затрагивает .data/.bss прошивки. + * Проверяет что SEMC контроллер инициализирован (DCD или bsp_sdram_configure()) + * и SDRAM отвечает — выполняет минимальный write/read/verify на первых 4 байтах + * тестового региона. Не затрагивает .data/.bss прошивки. * - * @note Не реинициализирует SEMC — DCD уже сделал это до main(). + * @note Не (ре)инициализирует SEMC — предполагает, что DCD либо + * bsp_sdram_configure() уже это сделали. * * @retval BSP_OK SDRAM доступна и отвечает корректно. - * @retval BSP_ERR_INIT SEMC не готов (DCD не отработал). + * @retval BSP_ERR_INIT SEMC не готов (инициализация не отработала). * @retval BSP_ERR_TIMEOUT SEMC занят дольше ожидаемого. */ bsp_status_t bsp_sdram_init(void); -#endif /* BSP_SDRAM_H_ */ \ No newline at end of file +#endif /* BSP_SDRAM_H_ */ diff --git a/bsp/sdram/src/sdram.c b/bsp/sdram/src/sdram.c index 90ebb5e..2885b4a 100644 --- a/bsp/sdram/src/sdram.c +++ b/bsp/sdram/src/sdram.c @@ -1,9 +1,14 @@ /** * @file sdram.c - * @brief Верификация внешней SDRAM MT48LC16M16A2 (32 МБ, шина 16 бит). + * @brief Подъём SEMC и верификация внешней SDRAM MT48LC16M16A2 (32 МБ, 16 бит). * - * SEMC инициализируется DCD до main() — этот модуль только проверяет - * доступность памяти. Регистры SEMC не модифицируются. + * Два независимых куска: + * + * bsp_sdram_configure() — поднимает SEMC. Побитовый порт проверенной DCD-последовательности + * (tools/host/dcd/dcd.bin) + * + * bsp_sdram_init() — верификация уже поднятой памяти (DCD или configure). + * Регистры SEMC не модифицирует. * * Кэш: SDRAM настроена как Normal Write-Back cacheable (MPU Region 8). * Верификация требует явного cache maintenance перед readback — иначе @@ -13,12 +18,13 @@ #include "bsp/sdram.h" #include "bsp/tick.h" +#include "fsl_clock.h" #include "fsl_semc.h" #include #include -/* ── Константы ─────────────────────────────────────────────────────────── */ +/* ── Константы верификации ─────────────────────────────────────────────── */ /** @brief Таймаут ожидания готовности SEMC контроллера, мс. */ #define SDRAM_SEMC_IDLE_TIMEOUT_MS 10U @@ -38,11 +44,202 @@ /** @brief Инверсия эталонного паттерна. */ #define SDRAM_VERIFY_PATTERN_B 0x5A5A5A5AUL +/* ── Константы SEMC (значения из DCD, см. tools/host/dcd/dcd.bin) */ + +/** @brief PFD2 FRAC из DCD (PFD_528=0x00230000): 528 МГц ×18/35 ≈ 271.54 МГц. */ +#define SDRAM_SEMC_PFD2_FRAC 35U + +/** + * @brief Индекс пина GPIO_EMC_39 (SEMC_DQS) в массивах IOMUXC. + * + * SW_MUX_CTL_PAD[]/SW_PAD_CTL_PAD[] начинаются с GPIO_EMC_00 (индекс 0), шаг 1. + * DQS — единственный пин, которому DCD ставит SION (вход strobe должен быть + * принудительно включён, чтобы SEMC читал собственный строб). + */ +#define SDRAM_EMC_PAD_FIRST 0U +#define SDRAM_EMC_PAD_LAST 41U /* GPIO_EMC_41 — последний EMC-пин */ +#define SDRAM_DQS_PAD_INDEX 39U /* GPIO_EMC_39 = SEMC_DQS */ + +/** @brief SION (Software Input On), бит 4 SW_MUX_CTL_PAD — только для DQS. */ +#define SDRAM_MUX_SION 0x00000010UL + +/** @brief SW_PAD_CTL для всех EMC-пинов (DCD: 0x000110F9 на каждый). */ +#define SDRAM_PAD_CTL_VALUE 0x000110F9UL + +/** @brief Адрес SDRAM для IP-команд SEMC (совпадает с BR0 = базой SDRAM). */ +#define SDRAM_SEMC_IPCMD_ADDR 0x80000000UL + +/** + * @brief Значение mode-register SDRAM (DCD IPTXDAT=0x33). + * + * M[2:0]=011 (burst length 8), M3=0 (sequential), M[6:4]=011 (CAS latency 3) — + * согласуется с SDRAMCR0 (BL8/CL3) и даташитом MT48LC16M16A2. + */ +#define SDRAM_SEMC_MODE_REG 0x00000033UL + +/* + * AXI-QoS регистры арбитража доступа к SDRAM — NIC-301 GPV (Global + * Programmer's View). Не влияют на корректность самой SDRAM — только на арбитраж при + * конкуренции за шину (LCD/CPU vs SDRAM). */ +#define SDRAM_QOS_LCD_READ (*(volatile uint32_t *) 0x41044100UL) +#define SDRAM_QOS_LCD_WRITE (*(volatile uint32_t *) 0x41044104UL) +#define SDRAM_QOS_M7_READ (*(volatile uint32_t *) 0x41442100UL) +#define SDRAM_QOS_M7_WRITE (*(volatile uint32_t *) 0x41442104UL) + /* ── Состояние модуля ──────────────────────────────────────────────────── */ -static bool s_initialised = false; +static bool g_s_initialised = false; -/* ── Внутренние функции ────────────────────────────────────────────────── */ +/** + * @brief Поднять тактовую цепочку SEMC (порт секции «Clock Init» DCD). + * + */ +static void sdram_configure_clock(void) +{ + /* DCD PLL_SYS=0x00002001 → Fout = 24 МГц × (20 + 2×loopDivider + num/denom) + * = 24 × (20 + 2 + 0) = 528 МГц. */ + static const clock_sys_pll_config_t SYS_PLL = { + .loopDivider = 1, .numerator = 0, .denominator = 1, .src = 0, /* 24 МГц OSC */ + }; + + /* Гейтим SEMC на время переключения его источника (идиома clock_config.c). */ + CLOCK_DisableClock(kCLOCK_Semc); + + CLOCK_InitSysPll(&SYS_PLL); + CLOCK_InitSysPfd(kCLOCK_Pfd2, SDRAM_SEMC_PFD2_FRAC); + + /* DCD CBCDR=0x00010D40 → SEMC_CLK_SEL=1 (alt), SEMC_ALT_CLK_SEL=0 (→PFD2), + * SEMC_PODF=1 (÷2). */ + CLOCK_SetMux(kCLOCK_SemcAltMux, 0); /* alt = PLL2 PFD2 */ + CLOCK_SetMux(kCLOCK_SemcMux, 1); /* SEMC clock = alt (PFD2), не periph_clk */ + CLOCK_SetDiv(kCLOCK_SemcDiv, kCLOCK_SemcDivBy2); + + CLOCK_EnableClock(kCLOCK_Semc); +} + +/* ── SEMC: пины ─────────────────────────────────────────────────── */ + +/** + * @brief Замуксить пины GPIO_EMC на функцию SEMC + PAD-настройки (порт DCD). + * + */ +static void sdram_configure_pins(void) +{ + for (uint32_t i = SDRAM_EMC_PAD_FIRST; i <= SDRAM_EMC_PAD_LAST; i++) + { + IOMUXC->SW_MUX_CTL_PAD[i] = 0UL; /* ALT0 = функция SEMC */ + IOMUXC->SW_PAD_CTL_PAD[i] = SDRAM_PAD_CTL_VALUE; + } + + /* DQS: ALT0 + SION (DCD пишет сюда 0x10 вместо 0x00). */ + IOMUXC->SW_MUX_CTL_PAD[SDRAM_DQS_PAD_INDEX] = SDRAM_MUX_SION; +} + +/* ── SEMC: регистры контроллера ─────────────────────────────────── */ + +/** + * @brief Записать регистры контроллера SEMC (дословно «блок 2» DCD). + * + */ +static void sdram_configure_controller(void) +{ + SEMC->MCR = 0x10000004UL; /* модуль вкл (MDIS=0), DQSMD=1 (DQS с пина), BTO=16 */ + + SEMC->BMCR0 = 0x00000081UL; /* веса AXI-очереди A */ + SEMC->BMCR1 = 0x00000081UL; /* веса AXI-очереди B */ + + /* Базовые регистры регионов SEMC. BR0 — сама SDRAM (0x80000000, 32 МБ, + * VLD=1). BR1..BR8 — прочие регионы из проверенного DCD, переносятся как + * есть (bootloader их не использует, но конфиг источника истины не режем). */ + SEMC->BR[0] = 0x8000001BUL; + SEMC->BR[1] = 0x8200001BUL; + SEMC->BR[2] = 0x8400001BUL; + SEMC->BR[3] = 0x8600001BUL; + SEMC->BR[4] = 0x90000021UL; + SEMC->BR[5] = 0xA0000019UL; + SEMC->BR[6] = 0xA8000017UL; + SEMC->BR[7] = 0xA900001BUL; + SEMC->BR[8] = 0x00000021UL; + + SEMC->IOCR = 0x000079A8UL; /* внутренний pinmux SEMC */ + + /* Геометрия и тайминги SDRAM. SDRAMCR0: PS=16бит, BL=8, COL=9бит, CL=3 — + * MT48LC16M16A2 (даташит: 512 колонок = 9 адресных бит). */ + SEMC->SDRAMCR0 = 0x00000F31UL; + SEMC->SDRAMCR1 = 0x00652922UL; + SEMC->SDRAMCR2 = 0x00020201UL; + SEMC->SDRAMCR3 = 0x08193D0FUL; /* тайминги refresh; REN включим в конце */ + + /* DBICR0/DBICR1 — DCD их пишет, хотя DBI-устройства на плате нет и + * SEMC_ConfigureSDRAM() их не трогает. Инертны (регион DBI не включён), + * но переносятся дословно ради полного соответствия проверенному DCD. */ + SEMC->DBICR0 = 0x00000021UL; + SEMC->DBICR1 = 0x00888888UL; + + /* Параметры IP-команд: DATSZ=2 байта (запись mode-register 16-бит шиной). */ + SEMC->IPCR1 = 0x00000002UL; + SEMC->IPCR2 = 0x00000000UL; +} + +/* ── SEMC: командная последовательность инициализации SDRAM ──────── */ + +/** + * @brief Прогнать init-последовательность SDRAM через IP-команды SEMC. + * + * precharge-all → 2×auto-refresh → mode-set → включение авто-refresh. + * + * @retval BSP_OK все команды завершились успешно. + * @retval BSP_ERR_INIT IP-команда SEMC вернула ошибку. + */ +static bsp_status_t sdram_issue_init_sequence(void) +{ + const uint32_t ADDR = SDRAM_SEMC_IPCMD_ADDR; + + if (SEMC_SendIPCommand(SEMC, kSEMC_MemType_SDRAM, ADDR, (uint32_t) kSEMC_SDRAMCM_Prechargeall, + 0, NULL) != kStatus_Success) + { + return BSP_ERR_INIT; + } + + for (uint32_t i = 0U; i < 2U; i++) + { + if (SEMC_SendIPCommand(SEMC, kSEMC_MemType_SDRAM, ADDR, + (uint32_t) kSEMC_SDRAMCM_AutoRefresh, 0, NULL) != kStatus_Success) + { + return BSP_ERR_INIT; + } + } + + if (SEMC_SendIPCommand(SEMC, kSEMC_MemType_SDRAM, ADDR, (uint32_t) kSEMC_SDRAMCM_Modeset, + SDRAM_SEMC_MODE_REG, NULL) != kStatus_Success) + { + return BSP_ERR_INIT; + } + + /* Включить авто-refresh + перейти на рабочие параметры refresh. DCD пишет + * SDRAMCR3 повторно другим значением (не только бит REN) — переносим как + * есть; это финальный «operational» refresh-конфиг после инициализации. */ + SEMC->SDRAMCR3 = 0x50210A09UL; + + return BSP_OK; +} + +/* ── SEMC: AXI-QoS арбитраж ─ */ + +/** + * @brief Настроить приоритеты доступа мастеров к SDRAM + * + * Требуют board_mpu_init() Region 11 + */ +static void sdram_configure_axi_qos(void) +{ + SDRAM_QOS_LCD_READ = 6UL; + SDRAM_QOS_LCD_WRITE = 6UL; + SDRAM_QOS_M7_READ = 7UL; + SDRAM_QOS_M7_WRITE = 7UL; +} + +/* ── Внутренние функции верификации ────────────────────────────────────── */ /** * @brief Дождаться перехода SEMC в состояние IDLE. @@ -72,10 +269,10 @@ static bsp_status_t wait_semc_idle(void) */ static void flush_cache_at_test_base(void) { - uint32_t *const p_addr = (uint32_t *) BSP_SDRAM_TEST_BASE_ADDR; + uint32_t *const P_ADDR = (uint32_t *) BSP_SDRAM_TEST_BASE_ADDR; - SCB_CleanDCache_by_Addr(p_addr, (int32_t) SDRAM_CACHE_LINE_BYTES); - SCB_InvalidateDCache_by_Addr(p_addr, (int32_t) SDRAM_CACHE_LINE_BYTES); + SCB_CleanDCache_by_Addr(P_ADDR, (int32_t) SDRAM_CACHE_LINE_BYTES); + SCB_InvalidateDCache_by_Addr(P_ADDR, (int32_t) SDRAM_CACHE_LINE_BYTES); __DSB(); } @@ -87,16 +284,33 @@ static void flush_cache_at_test_base(void) */ static bsp_status_t verify_word(uint32_t pattern) { - volatile uint32_t *const p_test = (volatile uint32_t *) BSP_SDRAM_TEST_BASE_ADDR; + volatile uint32_t *const P_TEST = (volatile uint32_t *) BSP_SDRAM_TEST_BASE_ADDR; - *p_test = pattern; + *P_TEST = pattern; flush_cache_at_test_base(); - return (*p_test == pattern) ? BSP_OK : BSP_ERR_INIT; + return (*P_TEST == pattern) ? BSP_OK : BSP_ERR_INIT; } /* ── Public API ────────────────────────────────────────────────────────── */ +bsp_status_t bsp_sdram_configure(void) +{ + sdram_configure_clock(); + sdram_configure_pins(); + sdram_configure_controller(); + + bsp_status_t status = sdram_issue_init_sequence(); + if (status != BSP_OK) + { + return status; + } +#if defined(__NIC301_EXPERIMENTS_) + sdram_configure_axi_qos(); +#endif + return BSP_OK; +} + bsp_status_t bsp_sdram_init(void) { bsp_status_t status = wait_semc_idle(); @@ -117,6 +331,6 @@ bsp_status_t bsp_sdram_init(void) return BSP_ERR_INIT; } - s_initialised = true; + g_s_initialised = true; return BSP_OK; } diff --git a/docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md b/docs/bootloader/BOOTLOADER_FLASH_MAP.md similarity index 100% rename from docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md rename to docs/bootloader/BOOTLOADER_FLASH_MAP.md diff --git a/docs/mimxrt1052/BOOT_FLAGS.md b/docs/bootloader/BOOT_FLAGS.md similarity index 100% rename from docs/mimxrt1052/BOOT_FLAGS.md rename to docs/bootloader/BOOT_FLAGS.md diff --git a/firmware/bootloader/BOOT_FLOW.md b/docs/bootloader/BOOT_FLOW.md similarity index 99% rename from firmware/bootloader/BOOT_FLOW.md rename to docs/bootloader/BOOT_FLOW.md index 4cd4313..d3a1b74 100644 --- a/firmware/bootloader/BOOT_FLOW.md +++ b/docs/bootloader/BOOT_FLOW.md @@ -228,6 +228,9 @@ recovery-индикация это показывают), лечится SD-фи ### Светодиоды +Полный словарь (установка, «железо не в порядке», ошибка образа, приоритет между ними) — +[LED_PATTERNS.md](LED_PATTERNS.md). Кратко: + | Состояние | Паттерн | | ---------------------- | ------------------------------------------------ | | Загрузчик жив, ждёт SD | Один LED: короткий импульс ~50 мс, пауза ~450 мс | diff --git a/docs/mimxrt1052/HAB_GUIDE.md b/docs/bootloader/HAB_GUIDE.md similarity index 100% rename from docs/mimxrt1052/HAB_GUIDE.md rename to docs/bootloader/HAB_GUIDE.md diff --git a/docs/bootloader/LED_PATTERNS.md b/docs/bootloader/LED_PATTERNS.md new file mode 100644 index 0000000..6b3d061 --- /dev/null +++ b/docs/bootloader/LED_PATTERNS.md @@ -0,0 +1,11 @@ +# LED-индикация bootloader + +Два светодиода: **HEARTBEAT** (системный) и **APP** (прикладной). + +| Индикация | Значение | +| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | +| HEARTBEAT мигает: 50 мс горит / 450 мс не горит. APP не горит вообще. | Загрузчик работает, ждёт карту microSD | +| HEARTBEAT мигает: 50 мс горит / 450 мс не горит. APP мигает: 250 мс горит / 250 мс не горит. | Идёт установка образа с SD-карты — питание не выключать | +| HEARTBEAT мигает: 50 мс горит / 450 мс не горит. APP часто мигает без остановки: 100 мс горит / 100 мс не горит. | Неисправность платы (QSPI flash или SDRAM) — плата не годна, отложить | +| APP быстро мигает 4 раза подряд (80 мс горит / 80 мс не горит), затем возвращается к тому виду, что был до этого. | Образ на SD-карте отклонён (битый файл или неверная подпись) — заменить файл на карте | +| Оба светодиода горят и гаснут одновременно: 100 мс горят / 100 мс не горят. | Режим восстановления (Recovery Mode) | diff --git a/firmware/bootloader/CMakeLists.txt b/firmware/bootloader/CMakeLists.txt index 5d599d7..999c460 100644 --- a/firmware/bootloader/CMakeLists.txt +++ b/firmware/bootloader/CMakeLists.txt @@ -30,6 +30,7 @@ add_executable( src/slot_version.c src/sd_update.c src/recovery.c + src/led_status.c mcuboot_port/flash_map_backend.c mcuboot_port/keys.c ${MCUBOOT_BOOTUTIL_SOURCES} @@ -49,12 +50,26 @@ target_compile_definitions( # ----------------------------------------------------------------------------- # Зависимости. bsp_button — downgrade-override (удержание BSP_BUTTON_1). # bootloader_fatfs — чтение TFT_APP.BIN с SD (Фаза 3, firmware/bootloader/fatfs/). +# bsp_sdram — smoke-test SDRAM/SEMC (Фаза 4, bsp_sdram_configure()+_init()) — +# bootloader без DCD, сам поднимает SEMC на время диагностики. # ----------------------------------------------------------------------------- target_link_libraries(${TARGET_NAME} PRIVATE bsp_board bsp_led bsp_tick bsp_usb_cdc bsp_qspi_flash bsp_boot_xip_no_dcd bsp_button bsp_wdog bsp_boot_state - bootloader_fatfs) + bsp_sdram bootloader_fatfs) + +# ----------------------------------------------------------------------------- +# [DEV-ONLY] Диагностика Фазы 4 — CLI-команда "sdram_test" (dev_sdram_test.c): +# 4-фазный тест SDRAM (адресная шина/шина данных/sequential/retention), +# портирован из firmware_test/src/tests/test_sdram.c, прогоняется через +# bsp_sdram_configure() вместо DCD. НЕ для production: только Debug — +# Release/HAB-бинарь этот файл не содержит и о команде не знает. +# ----------------------------------------------------------------------------- +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + target_sources(${TARGET_NAME} PRIVATE src/dev_sdram_test.c) + target_compile_definitions(${TARGET_NAME} PRIVATE BOOTLOADER_DEV_DIAGNOSTICS) +endif() # ----------------------------------------------------------------------------- # Linker script — вариант flexspi_nor с m_text, ограниченным бюджетом diff --git a/firmware/bootloader/README.md b/firmware/bootloader/README.md index f525058..8dea510 100644 --- a/firmware/bootloader/README.md +++ b/firmware/bootloader/README.md @@ -6,7 +6,7 @@ USB CDC ACM (JSON-строки). **Принцип работы** (карта памяти, выбор образа, обновление, версии, даунгрейд, recovery) — -[BOOT_FLOW.md](BOOT_FLOW.md). +[BOOT_FLOW.md](../../docs/bootloader/BOOT_FLOW.md). LED-индикация — [LED_PATTERNS.md](../../docs/bootloader/LED_PATTERNS.md). --- diff --git a/firmware/bootloader/mcuboot_port/bootutil_sources.cmake b/firmware/bootloader/mcuboot_port/bootutil_sources.cmake index 0ce9993..b1b4601 100644 --- a/firmware/bootloader/mcuboot_port/bootutil_sources.cmake +++ b/firmware/bootloader/mcuboot_port/bootutil_sources.cmake @@ -2,28 +2,10 @@ # # Общий список файлов bootutil (MCUboot) + TinyCrypt + ASN.1-парсер, # используемых и реальным ARM-таргетом (firmware/bootloader), и host-тестами -# (tests/host/mcuboot_port/) — чтобы не дублировать ~13 путей в двух местах. -# -# Состав определён вручную (не через .cmake файлы NXP-порта — см. -# firmware/bootloader/PLAN.md, Фаза 2) под конкретный режим: -# MCUBOOT_DIRECT_XIP + MCUBOOT_DIRECT_XIP_REVERT, MCUBOOT_IMAGE_NUMBER=1, -# MCUBOOT_SIGN_EC256 + MCUBOOT_USE_TINYCRYPT, без encryption, без -# measured-boot/shared-data (поэтому НЕ включены: boot_record.c, caps.c, -# encrypted.c, image_rsa.c, image_ed25519.c, swap_move.c, swap_misc.c, -# fault_injection_hardening_delay_rng_mbedtls.c — последний нужен только -# профилю MCUBOOT_FIH_PROFILE_HIGH, мы используем LOW). -# -# swap_scratch.c — ВКЛЮЧЁН, несмотря на название: boot_read_image_header() -# внутри него обёрнут в `#if !defined(MCUBOOT_SWAP_USING_MOVE)` — то есть -# это и есть дефолтная (не swap-move) реализация чтения заголовка слота, -# которую loader.c вызывает безусловно для ЛЮБОГО режима, включая -# Direct-XIP. swap_move.c (со своей версией той же функции под -# MCUBOOT_SWAP_USING_MOVE) не нужен. Остальные функции swap_scratch.c -# (собственно swap-логика) не достижимы в Direct-XIP и вырезаются линкером -# ARM-таргета через --gc-sections; для host-тестов — мёртвый, но безобидный -# код. +# (tests/host/mcuboot_port/) -set(MCUBOOT_OPENSOURCE_DIR ${CMAKE_SOURCE_DIR}/sdk/middleware/mcuboot_opensource) +set(MCUBOOT_OPENSOURCE_DIR + ${CMAKE_SOURCE_DIR}/sdk/middleware/mcuboot_opensource) set(MCUBOOT_BOOTUTIL_DIR ${MCUBOOT_OPENSOURCE_DIR}/boot/bootutil) set(MCUBOOT_EXT_DIR ${MCUBOOT_OPENSOURCE_DIR}/ext) @@ -48,26 +30,14 @@ set(MCUBOOT_BOOTUTIL_INCLUDES ${MCUBOOT_BOOTUTIL_DIR}/src ${MCUBOOT_EXT_DIR}/tinycrypt/lib/include ${MCUBOOT_EXT_DIR}/mbedtls-asn1/include - ${CMAKE_CURRENT_LIST_DIR} # sysflash.h, mcuboot_config.h, flash_map.h, flash_map_backend.h + ${CMAKE_CURRENT_LIST_DIR} # sysflash.h, mcuboot_config.h, flash_map.h, + # flash_map_backend.h ) -# ------------------------------------------------------------------------ -# Опции компиляции только для вендоренных исходников (не для нашего кода — -# main.c/cli.c/boot_select.c и т.д. компилируются с обычными warnings/ASan -# консьюмера): -# -w — вендоренный код, не наш стиль/lint -# -fno-sanitize=address,undefined — обход бага clang 22.1.8 (Homebrew): -# -fsanitize=address,undefined ломает генерацию CFI-директив на -# некоторых больших функциях bootutil (напр. loader.c::context_boot_go) -# — "invalid CFI advance_loc expression" на этапе ассемблирования. Без -# санитайзеров те же файлы собираются чисто — похоже на баг конкретной -# версии тулчейна. Не применимо к arm-none-eabi-gcc (ARM-таргет не -# использует ASan) — гейтим по Clang. -# ------------------------------------------------------------------------ set(MCUBOOT_VENDORED_COMPILE_OPTIONS -w) if(CMAKE_C_COMPILER_ID MATCHES "Clang") list(APPEND MCUBOOT_VENDORED_COMPILE_OPTIONS -fno-sanitize=address,undefined) endif() -set_source_files_properties(${MCUBOOT_BOOTUTIL_SOURCES} - PROPERTIES COMPILE_OPTIONS - "${MCUBOOT_VENDORED_COMPILE_OPTIONS}") +set_source_files_properties( + ${MCUBOOT_BOOTUTIL_SOURCES} PROPERTIES COMPILE_OPTIONS + "${MCUBOOT_VENDORED_COMPILE_OPTIONS}") diff --git a/firmware/bootloader/mcuboot_port/flash_map.h b/firmware/bootloader/mcuboot_port/flash_map.h index 6297f2a..6c1d0ad 100644 --- a/firmware/bootloader/mcuboot_port/flash_map.h +++ b/firmware/bootloader/mcuboot_port/flash_map.h @@ -4,10 +4,6 @@ * flash_map_backend.c (реальный, над bsp_qspi_flash) или * fake_flash_map_backend.c (host-тесты, in-memory буфер). * - * Урезано под наш случай относительно оригинального Apache-2.0 заголовка - * MCUboot (sdk/middleware/mcuboot_opensource/boot/nxp_mcux_sdk/include/flash_map.h): - * без sblconfig.h, без encrypted_xip externs, без boot_image_names[] — этого - * NXP-специфичного слоя у нас нет. */ #ifndef FLASH_MAP_H_ @@ -26,7 +22,7 @@ struct flash_area uint8_t fa_id; /**< ID области, уникален в системе. */ uint8_t fa_device_id; /**< ID flash-устройства. */ uint16_t pad16; - uint32_t fa_off; /**< Смещение области от начала устройства. */ + uint32_t fa_off; /**< Смещение области от начала устройства. */ uint32_t fa_size; /**< Размер области, байт. */ }; diff --git a/firmware/bootloader/mcuboot_port/flash_map_backend.c b/firmware/bootloader/mcuboot_port/flash_map_backend.c index 19c5245..ea51f1d 100644 --- a/firmware/bootloader/mcuboot_port/flash_map_backend.c +++ b/firmware/bootloader/mcuboot_port/flash_map_backend.c @@ -17,6 +17,7 @@ #include "bsp/qspi_flash.h" #include "bsp/wdog.h" #include "flash_map.h" +#include "led_status.h" #include "sysflash/sysflash.h" #include @@ -152,6 +153,11 @@ int flash_area_erase(const struct flash_area *area, uint32_t off, uint32_t len) * Зависание самого стирания флеша всё равно ловится: refresh — по * ЗАВЕРШЕНИИ блока, а не перед ним. */ bsp_wdog_refresh(); + /* Прогресс-хук индикации — no-op вне окна установки (см. + * led_status.h). Нужен, чтобы APP не замирал на ~5 c стирания слота + * при установке; на revert/recovery-стирании (тоже зовут эту + * функцию) ничего не рисует. */ + led_status_tick_install(); if (bsp_qspi_erase_block_64k(block_addr) != BSP_OK) { return -1; diff --git a/firmware/bootloader/src/cli.c b/firmware/bootloader/src/cli.c index f63861e..ff4e205 100644 --- a/firmware/bootloader/src/cli.c +++ b/firmware/bootloader/src/cli.c @@ -11,6 +11,17 @@ * Входящие типы (Фаза 1): * "cmd" → handle_cmd() → protocol_send_pong() / protocol_send_version_response() * + * [Фаза 4] Команды "smoke_status"/"qspi_info" — переспросить результат + * boot-time smoke-теста SDRAM/SEMC и опознанный чип QSPI flash в любой + * момент сессии (сами события шлются один раз рано, до открытия хостом + * порта, и почти наверняка теряются — см. protocol.h). + * + * [DEV-ONLY, Фаза 4] Команда "sdram_test" — под BOOTLOADER_DEV_DIAGNOSTICS + * (только Debug, см. CMakeLists.txt): запускает dev_sdram_test_run(), которая + * блокирует главный цикл на ~4 с (пройденный прогон на железе: configure ~0, + * address_bus <1 мс, data_bus ~130 мс, sequential ~3.7 с, retention ~400 мс). + * Отсутствует в Release/HAB-бинаре. + * * Добавление новой команды типа "cmd": * 1. Добавить ветку if (strcmp(cmd_name, "FOO") == 0) в handle_cmd(). */ @@ -20,6 +31,10 @@ #include "bsp/usb_cdc.h" #include "protocol.h" +#ifdef BOOTLOADER_DEV_DIAGNOSTICS +#include "dev_sdram_test.h" +#endif + #include #include #include @@ -141,6 +156,26 @@ static void handle_cmd(const char *p_line) return; } + if (strcmp(cmd_name, "smoke_status") == 0) + { + protocol_send_smoke_status(); + return; + } + + if (strcmp(cmd_name, "qspi_info") == 0) + { + protocol_send_qspi_info(); + return; + } + +#ifdef BOOTLOADER_DEV_DIAGNOSTICS + if (strcmp(cmd_name, "sdram_test") == 0) + { + dev_sdram_test_run(); + return; + } +#endif + protocol_send_error("UNKNOWN_CMD"); } diff --git a/firmware/bootloader/src/dev_sdram_test.c b/firmware/bootloader/src/dev_sdram_test.c new file mode 100644 index 0000000..094b843 --- /dev/null +++ b/firmware/bootloader/src/dev_sdram_test.c @@ -0,0 +1,282 @@ +/** + * @file dev_sdram_test.c + * @brief [DEV-ONLY] Реализация глубокого теста SDRAM (см. dev_sdram_test.h). + * + * Фазы 1:1 портированы из firmware/test/src/tests/test_sdram.c (тот же + * алгоритм, то же покрытие) — не переизобретаются, чтобы результат был + * сопоставим с уже доверенным тестом DCD-пути. Отличия от оригинала: + * - sdram_test_init() там предполагал DCD; здесь сама зовёт + * bsp_sdram_configure() — это и есть предмет проверки. + * - test_module_t/test_result_t (инфраструктура тест-раннера firmware_test) + * не используются — bootloader её не имеет; результат каждой фазы уходит + * отдельным CDC-событием по ходу прогона, не одним отчётом в конце. + * - добавлено кормление watchdog (bsp_wdog_refresh()) на той же частоте, + * что и опрос CDC — без него более медленный прогон рисковал бы не + * пережить таймаут WDOG (10 с, main.c) и словить сброс посреди фазы + * sequential (по факту прогон ~4 с, запас большой — см. dev_sdram_test.h). + */ + +#include "dev_sdram_test.h" + +#include "bsp/sdram.h" +#include "bsp/tick.h" +#include "bsp/usb_cdc.h" +#include "bsp/wdog.h" +#include "fsl_common.h" +#include "protocol.h" + +#include +#include + +/* ── Константы (те же значения, что в firmware_test/test_sdram.c) ───────── */ + +/** @brief Интервал вызова pump() при записи/чтении, байт. */ +#define SDRAM_USB_POLL_INTERVAL_BYTES 0x1000U + +/** @brief Ширина walking ones паттерна, бит. */ +#define SDRAM_WALKING_ONES_BITS 8U + +/** @brief Количество адресных бит теста address_bus (13 row+9 col+2 bank). */ +#define SDRAM_ADDR_BUS_BITS 24U + +/** @brief Размер кэш-линии Cortex-M7, байт. */ +#define SDRAM_CACHE_LINE_BYTES 32U + +/** @brief Размер фазы sequential, байт (2 MB). */ +#define SDRAM_SEQUENTIAL_SIZE 0x00200000UL + +/** @brief Размер фазы retention, байт (256 KB). */ +#define SDRAM_RETENTION_SIZE 0x00040000UL + +/** @brief Задержка фазы retention, мс (MT48LC16M16A2: авто-refresh 64 мс, ≈3 периода). */ +#define SDRAM_RETENTION_DELAY_MS 200U + +/** @brief Шаг ожидания в фазе retention (для pump()), мс. */ +#define SDRAM_RETENTION_POLL_STEP_MS 10U + +/* ── Локальные типы ────────────────────────────────────────────────────── */ + +/** @brief Информация о первой ошибке паттерн-прохода. fail_addr==0 — нет ошибки. */ +typedef struct +{ + uint32_t fail_addr; + uint8_t expected; + uint8_t got; +} sdram_fail_info_t; + +typedef uint8_t (*pattern_fn_t)(uint32_t offset); + +/* ── Паттерн-функции ───────────────────────────────────────────────────── */ + +static uint8_t pattern_walking_ones(uint32_t offset) +{ + return (uint8_t) (1U << (offset % SDRAM_WALKING_ONES_BITS)); +} + +static uint8_t pattern_walking_ones_inv(uint32_t offset) +{ + return (uint8_t) (~(1U << (offset % SDRAM_WALKING_ONES_BITS)) & 0xFFU); +} + +static uint8_t pattern_addr(uint32_t offset) +{ + return (uint8_t) (offset & 0xFFU); +} + +static uint8_t pattern_addr_inv(uint32_t offset) +{ + return (uint8_t) (~offset & 0xFFU); +} + +/* ── Вспомогательные функции ───────────────────────────────────────────── */ + +/** @brief Опрос CDC + кормление watchdog. Вызывать не реже раза в ~4 KB. */ +static void pump(void) +{ + bsp_usb_cdc_poll(); + bsp_wdog_refresh(); +} + +static void flush_dcache(uint32_t size) +{ + uint32_t *const P_BASE = (uint32_t *) BSP_SDRAM_TEST_BASE_ADDR; + + SCB_CleanDCache_by_Addr(P_BASE, (int32_t) size); + SCB_InvalidateDCache_by_Addr(P_BASE, (int32_t) size); + __DSB(); +} + +/** @brief Сброс одной кэш-линии по произвольному байтовому адресу (для address_bus). */ +static void flush_dcache_line_at(uint32_t byte_addr) +{ + uint32_t *const P_LINE = (uint32_t *) (byte_addr & ~(SDRAM_CACHE_LINE_BYTES - 1U)); + + SCB_CleanDCache_by_Addr(P_LINE, (int32_t) SDRAM_CACHE_LINE_BYTES); + SCB_InvalidateDCache_by_Addr(P_LINE, (int32_t) SDRAM_CACHE_LINE_BYTES); + __DSB(); +} + +static void delay_with_pump(uint32_t ms) +{ + const uint32_t START_MS = bsp_tick_get_ms(); + + while ((bsp_tick_get_ms() - START_MS) < ms) + { + bsp_delay(SDRAM_RETENTION_POLL_STEP_MS); + pump(); + } +} + +static void write_pattern(uint32_t size, pattern_fn_t p_fn) +{ + volatile uint8_t *const P_BASE = (volatile uint8_t *) BSP_SDRAM_TEST_BASE_ADDR; + + for (uint32_t i = 0U; i < size; i++) + { + if ((i & (SDRAM_USB_POLL_INTERVAL_BYTES - 1U)) == 0U) + { + pump(); + } + P_BASE[i] = p_fn(i); + } +} + +/** @brief Верифицировать паттерн. Останавливается на первом несовпадении. */ +static bool verify_pattern(uint32_t size, pattern_fn_t p_fn, sdram_fail_info_t *p_fail) +{ + volatile const uint8_t *const P_BASE = (volatile const uint8_t *) BSP_SDRAM_TEST_BASE_ADDR; + + for (uint32_t i = 0U; i < size; i++) + { + if ((i & (SDRAM_USB_POLL_INTERVAL_BYTES - 1U)) == 0U) + { + pump(); + } + + const uint8_t EXPECTED = p_fn(i); + const uint8_t GOT = P_BASE[i]; + + if (GOT != EXPECTED) + { + p_fail->fail_addr = BSP_SDRAM_TEST_BASE_ADDR + i; + p_fail->expected = EXPECTED; + p_fail->got = GOT; + return false; + } + } + return true; +} + +/** @brief Один паттерн-проход: запись → flush → верификация. */ +static bool run_pass(uint32_t size, pattern_fn_t p_fn, sdram_fail_info_t *p_fail) +{ + write_pattern(size, p_fn); + flush_dcache(size); + return verify_pattern(size, p_fn, p_fail); +} + +/* ── Фазы теста (см. dev_sdram_test.h) ───────────────────────────────────── */ + +/** @brief Фаза address_bus: 24 адресных бита, точечный флаш на каждую запись. */ +static bool run_phase_address_bus(sdram_fail_info_t *p_fail) +{ + volatile uint8_t *const P_BASE = (volatile uint8_t *) BSP_SDRAM_TEST_BASE_ADDR; + + for (uint32_t bit = 0U; bit < SDRAM_ADDR_BUS_BITS; bit++) + { + const uint32_t OFFSET = (1UL << bit); + const uint8_t PATTERN = (uint8_t) (bit + 1U); + + P_BASE[OFFSET] = PATTERN; + flush_dcache_line_at(BSP_SDRAM_TEST_BASE_ADDR + OFFSET); + + const uint8_t GOT = P_BASE[OFFSET]; + if (GOT != PATTERN) + { + p_fail->fail_addr = BSP_SDRAM_TEST_BASE_ADDR + OFFSET; + p_fail->expected = PATTERN; + p_fail->got = GOT; + return false; + } + } + return true; +} + +/** @brief Фаза data_bus: walking ones + инверсия, 64 KB. */ +static bool run_phase_data_bus(sdram_fail_info_t *p_fail) +{ + return run_pass(BSP_SDRAM_TEST_FAST_SIZE, pattern_walking_ones, p_fail) && + run_pass(BSP_SDRAM_TEST_FAST_SIZE, pattern_walking_ones_inv, p_fail); +} + +/** @brief Фаза sequential: address pattern + инверсия, 2 MB. */ +static bool run_phase_sequential(sdram_fail_info_t *p_fail) +{ + return run_pass(SDRAM_SEQUENTIAL_SIZE, pattern_addr, p_fail) && + run_pass(SDRAM_SEQUENTIAL_SIZE, pattern_addr_inv, p_fail); +} + +/** @brief Фаза retention: запись → flush → 200 мс → verify, 256 KB. */ +static bool run_phase_retention(sdram_fail_info_t *p_fail) +{ + write_pattern(SDRAM_RETENTION_SIZE, pattern_addr); + flush_dcache(SDRAM_RETENTION_SIZE); + delay_with_pump(SDRAM_RETENTION_DELAY_MS); + return verify_pattern(SDRAM_RETENTION_SIZE, pattern_addr, p_fail); +} + +/* ── Оркестрация ──────────────────────────────────────────────────────── */ + +static void report_phase(const char *p_name, bool pass, uint32_t start_ms, const sdram_fail_info_t *p_fail) +{ + const uint32_t DURATION_MS = bsp_tick_get_ms() - start_ms; + const uint32_t FAIL_ADDR = (p_fail != NULL) ? p_fail->fail_addr : 0U; + const uint8_t EXPECTED = (p_fail != NULL) ? p_fail->expected : 0U; + const uint8_t GOT = (p_fail != NULL) ? p_fail->got : 0U; + + protocol_send_sdram_test_phase(p_name, pass, DURATION_MS, FAIL_ADDR, EXPECTED, GOT); +} + +void dev_sdram_test_run(void) +{ + const uint32_t CONFIGURE_START_MS = bsp_tick_get_ms(); + + const bool CONFIGURED = (bsp_sdram_configure() == BSP_OK) && (bsp_sdram_init() == BSP_OK); + report_phase("configure", CONFIGURED, CONFIGURE_START_MS, NULL); + if (!CONFIGURED) + { + return; + } + + const uint32_t OVERALL_START_MS = bsp_tick_get_ms(); + sdram_fail_info_t fail = { 0U, 0U, 0U }; + bool is_ok; + uint32_t phase_start_ms; + + phase_start_ms = bsp_tick_get_ms(); + is_ok = run_phase_address_bus(&fail); + report_phase("address_bus", is_ok, phase_start_ms, is_ok ? NULL : &fail); + + if (is_ok) + { + phase_start_ms = bsp_tick_get_ms(); + is_ok = run_phase_data_bus(&fail); + report_phase("data_bus", is_ok, phase_start_ms, is_ok ? NULL : &fail); + } + + if (is_ok) + { + phase_start_ms = bsp_tick_get_ms(); + is_ok = run_phase_sequential(&fail); + report_phase("sequential", is_ok, phase_start_ms, is_ok ? NULL : &fail); + } + + if (is_ok) + { + phase_start_ms = bsp_tick_get_ms(); + is_ok = run_phase_retention(&fail); + report_phase("retention", is_ok, phase_start_ms, is_ok ? NULL : &fail); + } + + report_phase("summary", is_ok, OVERALL_START_MS, NULL); +} diff --git a/firmware/bootloader/src/dev_sdram_test.h b/firmware/bootloader/src/dev_sdram_test.h new file mode 100644 index 0000000..eb8b930 --- /dev/null +++ b/firmware/bootloader/src/dev_sdram_test.h @@ -0,0 +1,45 @@ +/** + * @file dev_sdram_test.h + * @brief [DEV-ONLY] Глубокий тест SDRAM через bsp_sdram_configure() (Фаза 4). + * + * НЕ для production — компилируется только в Debug + * (BOOTLOADER_DEV_DIAGNOSTICS, см. CMakeLists.txt); отсутствует в HAB + * Release-бинаре. + * + * Цель — не заменить лёгкий boot-time smoke-test (main.c, "smoke_pass"/ + * "smoke_fail"), а дать ту же глубину проверки, что уже доверена + * firmware_test/src/tests/test_sdram.c (4 фазы, покрывают адресную шину, + * шину данных, coupling между ячейками и refresh-timing), но против нового + * C-порта DCD (bsp_sdram_configure()), а не против DCD напрямую — чтобы + * убедиться, что порт поднимает память так же надёжно, как проверенный + * годами в производстве DCD. + * + * Вызывается по CDC-команде {"type":"cmd","cmd":"sdram_test"} (см. cli.c). + * Блокирует главный цикл на время прогона (~4 с, см. таймингы ниже — реально + * измерено на железе, а не оценка) — сама кормит watchdog и опрашивает CDC по + * ходу, вызывающему коду ничего дополнительно делать не нужно. Каждая фаза и + * итог репортятся отдельным событием по CDC синхронно по ходу прогона (см. + * protocol_send_sdram_test_phase()). + */ +#ifndef DEV_SDRAM_TEST_H_ +#define DEV_SDRAM_TEST_H_ + +/** + * @brief Поднять SEMC (bsp_sdram_configure()) и прогнать 4-фазный тест SDRAM. + * + * Фазы (портированы из firmware/test/src/tests/test_sdram.c без изменений + * логики — меняется только то, кто поднимает SEMC до них). Тайминги — + * реальный прогон на железе (не оценка из test_sdram.c, та оказалась + * консервативнее раз в 6 — запись садится в D-Cache почти мгновенно, реальная + * задержка SDRAM только на flush_dcache() и на чтение при верификации): + * 1. address_bus — 24 адресных бита (13 row + 9 col + 2 bank), <1 мс. + * 2. data_bus — walking ones + инверсия, 64 KB, ~130 мс. + * 3. sequential — address pattern + инверсия, 2 MB, ~3.7 с. + * 4. retention — запись → flush → 200 мс → verify, 256 KB, ~400 мс. + * + * Если bsp_sdram_configure()/bsp_sdram_init() не прошли — репортится фаза + * "configure" с pass=false, дальнейшие фазы не запускаются. + */ +void dev_sdram_test_run(void); + +#endif /* DEV_SDRAM_TEST_H_ */ diff --git a/firmware/bootloader/src/led_status.c b/firmware/bootloader/src/led_status.c new file mode 100644 index 0000000..25a6232 --- /dev/null +++ b/firmware/bootloader/src/led_status.c @@ -0,0 +1,113 @@ +/** + * @file led_status.c + * @brief Реализация словаря LED-паттернов — см. led_status.h и + * docs/bootloader/LED_PATTERNS.md. + */ + +#include "led_status.h" + +#include "bsp/led.h" +#include "bsp/tick.h" +#include "bsp/wdog.h" + +#include +#include + +/* ── Периоды паттернов, мс (СИНХРОНИЗИРОВАТЬ с LED_PATTERNS.md) ─────────── */ + +/** @brief Heartbeat «жив»: 50 мс горит / 450 мс не горит (период 500). */ +#define LED_HEARTBEAT_ON_MS 50U +#define LED_HEARTBEAT_PERIOD_MS 500U + +/** @brief Неисправность железа: APP 100/100 (период 200). */ +#define LED_HW_FAULT_ON_MS 100U +#define LED_HW_FAULT_PERIOD_MS 200U + +/** @brief Recovery: оба LED синхронно 100/100 (период 200). */ +#define LED_RECOVERY_ON_MS 100U +#define LED_RECOVERY_PERIOD_MS 200U + +/** @brief Установка: APP 250/250 (период 500). */ +#define LED_INSTALL_ON_MS 250U +#define LED_INSTALL_PERIOD_MS 500U + +/** @brief «Образ отклонён»: 4 вспышки по 80 мс вкл / 80 мс выкл. */ +#define LED_REJECT_ON_MS 80U +#define LED_REJECT_COUNT 4U + +/* ── Состояние модуля ──────────────────────────────────────────────────── */ + +/** @brief Окно установки открыто — см. led_status_tick_install(). */ +static bool g_s_installing = false; + +/* ── Внутренние помощники ──────────────────────────────────────────────── */ + +/** @brief true, если по текущему тику LED в фазе «горит» для период/on. */ +static bool phase_on(uint32_t period_ms, uint32_t on_ms) +{ + return (bsp_tick_get_ms() % period_ms) < on_ms; +} + +/** @brief Отрисовать системный heartbeat (общий для waiting/hw_fault/install). */ +static void draw_heartbeat(void) +{ + bsp_led_set(LED_HEARTBEAT, phase_on(LED_HEARTBEAT_PERIOD_MS, LED_HEARTBEAT_ON_MS)); +} + +/* ── Public API ────────────────────────────────────────────────────────── */ + +void led_status_draw_background(led_bg_t bg) +{ + switch (bg) + { + case LED_BG_RECOVERY: + { + /* Оба LED — один и тот же фазовый расчёт → строго синхронно. */ + const bool ON = phase_on(LED_RECOVERY_PERIOD_MS, LED_RECOVERY_ON_MS); + bsp_led_set(LED_HEARTBEAT, ON); + bsp_led_set(LED_APP, ON); + break; + } + case LED_BG_HW_FAULT: + draw_heartbeat(); /* heartbeat в своём ритме 50/450 — не синхронен с APP */ + bsp_led_set(LED_APP, phase_on(LED_HW_FAULT_PERIOD_MS, LED_HW_FAULT_ON_MS)); + break; + case LED_BG_WAITING: + default: + draw_heartbeat(); + bsp_led_off(LED_APP); + break; + } +} + +void led_status_install_begin(void) +{ + g_s_installing = true; +} + +void led_status_install_end(void) +{ + g_s_installing = false; +} + +void led_status_tick_install(void) +{ + if (!g_s_installing) + { + return; /* не установка (revert/recovery-стирание) — ничего не трогаем */ + } + draw_heartbeat(); + bsp_led_set(LED_APP, phase_on(LED_INSTALL_PERIOD_MS, LED_INSTALL_ON_MS)); +} + +void led_status_flash_image_rejected(void) +{ + for (uint32_t i = 0U; i < LED_REJECT_COUNT; i++) + { + bsp_wdog_refresh(); /* ~640 мс блокирующей вспышки — держим watchdog сытым */ + bsp_led_on(LED_APP); + bsp_delay(LED_REJECT_ON_MS); + bsp_led_off(LED_APP); + bsp_delay(LED_REJECT_ON_MS); + } +} diff --git a/firmware/bootloader/src/led_status.h b/firmware/bootloader/src/led_status.h new file mode 100644 index 0000000..3ec4d84 --- /dev/null +++ b/firmware/bootloader/src/led_status.h @@ -0,0 +1,63 @@ +/** + * @file led_status.h + * @brief Индикация bootloader на двух LED — единый словарь паттернов. + * + * Полное человекочитаемое описание (для сервисных инженеров) — + * docs/bootloader/LED_PATTERNS.md. Здесь — программный контракт; периоды + * в мс заданы в led_status.c и обязаны совпадать с тем документом. + * + * Разделение на два вида вызова не случайно: + * - Фоновые (устойчивые) состояния рисует главный цикл main.c каждую + * итерацию: led_status_draw_background(). + * - Паттерн «идёт установка» рисуется ИЗНУТРИ блокирующей установки (главный + * цикл в это время не исполняется): led_status_tick_install(), обёрнутый в + * окно led_status_install_begin()/_end(). Вне окна tick — no-op, поэтому + * его безопасно звать из flash_area_erase(), который дёргается и на + * revert/recovery-стирании, а не только на установке. + */ + +#ifndef LED_STATUS_H_ +#define LED_STATUS_H_ + +/** @brief Фоновое (устойчивое) состояние индикации. */ +typedef enum +{ + LED_BG_WAITING, /**< Норма: HEARTBEAT 50/450, APP выкл — ждём microSD. */ + LED_BG_HW_FAULT, /**< Неисправность: HEARTBEAT 50/450, APP 100/100. */ + LED_BG_RECOVERY, /**< Recovery (Фаза 6): оба LED синхронно 100/100. */ +} led_bg_t; + +/** + * @brief Нарисовать фоновый паттерн по текущему тику. + * + * Вызывать каждую итерацию главного цикла. Приоритет разрешается на стороне + * вызывателя (recovery > неисправность > норма) — сюда приходит уже + * выбранное состояние. + */ +void led_status_draw_background(led_bg_t bg); + +/** @brief Открыть окно «идёт установка» — с этого момента tick рисует паттерн. */ +void led_status_install_begin(void); + +/** @brief Закрыть окно «идёт установка» — tick снова становится no-op. */ +void led_status_install_end(void); + +/** + * @brief Обновить APP/HEARTBEAT под паттерн установки (HEARTBEAT 50/450, + * APP 250/250). No-op вне окна install. + * + * Звать из ВСЕХ блокирующих циклов установки — и поблочного стирания слота + * (flash_area_erase, ~5 c на 2 МБ), и копирования чанков (sd_update), иначе + * APP замирал бы на время стирания. + */ +void led_status_tick_install(void); + +/** + * @brief Разовая индикация «образ с SD отклонён»: APP мигает 4×(80/80). + * + * Блокирующая (~640 мс), кормит watchdog по ходу. Оставляет APP выключенным — + * фоновый паттерн восстановит главный цикл на следующей итерации. + */ +void led_status_flash_image_rejected(void); + +#endif /* LED_STATUS_H_ */ diff --git a/firmware/bootloader/src/main.c b/firmware/bootloader/src/main.c index 432f9dc..52e460a 100644 --- a/firmware/bootloader/src/main.c +++ b/firmware/bootloader/src/main.c @@ -32,10 +32,14 @@ * 6. bsp_button_init() — для проверки удержания BSP_BUTTON_1/2 * 7. bsp_qspi_init() — доступ к Slot A/Б * 8. bsp_usb_cdc_init() — не блокирует, см. выше - * 9. attempt_boot() — SD-скан + recovery-гейт + прыжок; при + * 9. qspi_info (Фаза 4) — идентификация чипа QSPI (JEDEC → имя + + * ёмкость), не зависит от qspi_ok, только CDC + * 10. bsp_sdram_configure()+ — smoke-test SDRAM/SEMC (Фаза 4): диагностика, + * bsp_sdram_init() не блокирует, результат только на CDC + * 11. attempt_boot() — SD-скан + recovery-гейт + прыжок; при * успехе не возвращается - * 10. Цикл ожидания — CDC ping/pong + LED + периодический - * пере-скан SD (шаг 9 повторно) + * 12. Цикл ожидания — CDC ping/pong + LED + периодический + * пере-скан SD (шаг 11 повторно) * * Watchdog (bsp_wdog): единственная защита от бесконечных зависаний в * блокирующих вызовах SDMMC-стека, не возвращающих управление в наш код @@ -46,8 +50,10 @@ * копирования, перед прыжком) — НЕ перед f_mount/SD_Init, иначе watchdog * перестаёт защищать именно от них. * - * Bootloader без SDRAM (см. docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md) — DCD - * не используется (bsp_boot_xip_no_dcd). + * Bootloader не зависит от SDRAM (см. docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md) + * — DCD не используется (bsp_boot_xip_no_dcd), XIP только из W25Q. SEMC/SDRAM + * трогаются только диагностически, шагом 9 (bsp_sdram_configure(), см. + * bsp/sdram/README.md) — bootloader сам эту память ни для чего не использует. */ #include "board.h" #include "boot_select.h" @@ -55,11 +61,13 @@ #include "bsp/button.h" #include "bsp/led.h" #include "bsp/qspi_flash.h" +#include "bsp/sdram.h" #include "bsp/tick.h" #include "bsp/usb_cdc.h" #include "bsp/wdog.h" #include "cli.h" #include "flash_map.h" +#include "led_status.h" #include "protocol.h" #include "recovery.h" #include "sd_update.h" @@ -168,20 +176,18 @@ static bool attempt_boot(bool downgrade_held, bool recovery_held) int main(void) { - const uint32_t ERROR_BLINK_MS = 250U; - const uint32_t SD_RETRY_PERIOD_MS = 1500U; - const uint32_t HEARTBEAT_ON_MS = 50U; - const uint32_t HEARTBEAT_PERIOD_MS = 500U; - /* Recovery-паттерн (Фаза 6b): оба LED синхронно, 100 мс вкл/100 мс выкл — - * чётко отличается от heartbeat (50/450, один LED) и app (500/250, - * один LED, см. test_stub) визуально, без дополнительной телеметрии. */ - const uint32_t RECOVERY_BLINK_ON_MS = 100U; - const uint32_t RECOVERY_BLINK_PERIOD_MS = 200U; + const uint32_t ERROR_BLINK_MS = 250U; + const uint32_t SD_RETRY_PERIOD_MS = 1500U; /* Таймаут WDOG. С запасом над самым долгим НАКОРМЛЕННЫМ участком: между * соседними refresh худший легитимный интервал — одиночное стирание 64 КБ * блока (~0.15..2 c по даташиту W25Q) либо цепочка bsp_sd_init+f_mount+пик * слотов (~2-2.5 c). 10 c даёт кратный запас; зависание ловится ≤10 c. */ const uint32_t WDOG_TIMEOUT_S = 10U; + /* Минимум по docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md §1 — карта + * (bootloader+Slot A+Slot Б+запас под ФС ассетов) рассчитана на W25Q128 + * (16 МБ) и выше; W25Q64 драйвер технически поддерживает, но для этой + * платы это неверный BOM, а не "чуть меньше запас". */ + const uint32_t QSPI_MIN_FLASH_SIZE_MB = 16U; board_hw_init(); @@ -211,6 +217,44 @@ int main(void) bool qspi_ok = (bsp_qspi_init() == BSP_OK); bool cdc_ok = (bsp_usb_cdc_init() == BSP_OK); + /* Единый признак «плата не годна» (LED_BG_HW_FAULT, см. LED_PATTERNS.md) — + * накапливается по обоим boot-time чекам ниже (QSPI + SDRAM smoke). + * Детали, что именно не так, всегда есть по CDC (qspi_info/smoke_status); + * LED показывает лишь факт неисправности. */ + bool hw_fault = false; + + /* Идентификация QSPI-чипа (Фаза 4) — не зависит от qspi_ok: + * bsp_qspi_read_jedec_id() отрабатывает и после проваленного + * bsp_qspi_init() (см. её @note), так что "чип не тот"/"чип не опознан" + * репортится с деталями, а не просто as "не сработало". */ + { + bsp_qspi_jedec_t jedec = { 0U, 0U }; + uint32_t qspi_size_mb = 0U; + const char *p_qspi_chip = "UNKNOWN"; + uint8_t qspi_cap_byte = 0U; + + if (bsp_qspi_read_jedec_id(&jedec) == BSP_OK) + { + qspi_cap_byte = (uint8_t) (jedec.device_id & 0xFFU); + p_qspi_chip = bsp_qspi_decode_chip(qspi_cap_byte, &qspi_size_mb); + } + + const bool QSPI_PASS = (qspi_size_mb >= QSPI_MIN_FLASH_SIZE_MB); + hw_fault = (!qspi_ok) || (!QSPI_PASS); /* чип не отвечает / не тот / мал */ + protocol_set_qspi_info(jedec.manufacturer_id, p_qspi_chip, qspi_cap_byte, qspi_size_mb, QSPI_PASS); + protocol_send_qspi_info(); /* лучший случай — хост уже слушает; см. protocol.h */ + } + + /* Smoke-test SDRAM/SEMC (Фаза 4) — диагностический, неблокирующий: не + * влияет на attempt_boot() ниже (bootloader SDRAM ни для чего не + * использует, см. docstring файла), результат только репортится по CDC. + * Цель — поймать неисправность SEMC/SDRAM на плате раньше, чем её + * унаследует tft_app (см. bsp/sdram/README.md). */ + bool sdram_ok = (bsp_sdram_configure() == BSP_OK) && (bsp_sdram_init() == BSP_OK); + hw_fault = hw_fault || (!sdram_ok); + protocol_set_smoke_result(sdram_ok); + protocol_send_smoke_status(); /* лучший случай — хост уже слушает; см. protocol.h */ + /* Отслеживает recovery-состояние между попытками — main-loop использует * его для LED-паттерна каждую итерацию, не только на попытках прыжка * (attempt_boot() зовётся раз в SD_RETRY_PERIOD_MS, LED должен обновляться @@ -251,24 +295,11 @@ int main(void) bsp_usb_cdc_poll(); cli_process(); - if (in_recovery) - { - bool recovery_led_on = (bsp_tick_get_ms() % RECOVERY_BLINK_PERIOD_MS) < RECOVERY_BLINK_ON_MS; - bsp_led_set(LED_HEARTBEAT, recovery_led_on); - bsp_led_set(LED_APP, recovery_led_on); - } - else - { - uint32_t heartbeat_phase_ms = bsp_tick_get_ms() % HEARTBEAT_PERIOD_MS; - if (heartbeat_phase_ms < HEARTBEAT_ON_MS) - { - bsp_led_on(LED_HEARTBEAT); - } - else - { - bsp_led_off(LED_HEARTBEAT); - } - } + /* Фоновый паттерн: recovery > неисправность железа > норма (ждём SD). + * Паттерн «установка» здесь не участвует — он рисуется изнутри самой + * (блокирующей) установки, см. led_status_tick_install(). */ + led_bg_t bg = in_recovery ? LED_BG_RECOVERY : (hw_fault ? LED_BG_HW_FAULT : LED_BG_WAITING); + led_status_draw_background(bg); if (qspi_ok && ((int32_t) (bsp_tick_get_ms() - next_sd_retry_ms) >= 0)) { diff --git a/firmware/bootloader/src/protocol.c b/firmware/bootloader/src/protocol.c index ce34efb..0240164 100644 --- a/firmware/bootloader/src/protocol.c +++ b/firmware/bootloader/src/protocol.c @@ -12,10 +12,33 @@ #include +#ifdef BOOTLOADER_DEV_DIAGNOSTICS +#include +#endif + /* ── Константы ─────────────────────────────────────────────────────────── */ -/** @brief Размер внутреннего TX-буфера. */ -#define PROTO_BUF_SIZE 128U +/** + * @brief Размер внутреннего TX-буфера. + * + * 192, не 128: {"type":"sdram_test",...} с именем фазы "sequential" и + * fail_addr/expected/got — самое длинное сообщение протокола, ~140 байт. + */ +#define PROTO_BUF_SIZE 192U + +/* ── Состояние модуля ──────────────────────────────────────────────────── */ + +/** @brief Кэш результата smoke-теста SDRAM/SEMC — см. protocol_send_smoke_status(). */ +static bool s_smoke_result_known = false; +static bool s_smoke_result_pass = false; + +/** @brief Кэш информации о QSPI-чипе — см. protocol_send_qspi_info(). */ +static bool s_qspi_info_known = false; +static bool s_qspi_pass = false; +static uint8_t s_qspi_mfr_id = 0U; +static uint8_t s_qspi_cap_byte = 0U; +static uint32_t s_qspi_size_mb = 0U; +static const char *s_p_qspi_chip = ""; /* строковый литерал из bsp_qspi_decode_chip() — статичен */ /* ── Public API ────────────────────────────────────────────────────────── */ @@ -47,6 +70,46 @@ void protocol_send_status(const char *p_state) cli_send(buf); } +void protocol_set_smoke_result(bool pass) +{ + s_smoke_result_known = true; + s_smoke_result_pass = pass; +} + +void protocol_send_smoke_status(void) +{ + if (s_smoke_result_known) + { + protocol_send_status(s_smoke_result_pass ? "smoke_pass" : "smoke_fail"); + } +} + +void protocol_set_qspi_info(uint8_t mfr_id, const char *p_chip_name, uint8_t cap_byte, uint32_t size_mb, bool pass) +{ + s_qspi_info_known = true; + s_qspi_mfr_id = mfr_id; + s_p_qspi_chip = p_chip_name; + s_qspi_cap_byte = cap_byte; + s_qspi_size_mb = size_mb; + s_qspi_pass = pass; +} + +void protocol_send_qspi_info(void) +{ + if (!s_qspi_info_known) + { + return; + } + + char buf[PROTO_BUF_SIZE]; + (void) snprintf(buf, sizeof(buf), + "{\"type\":\"qspi_info\",\"chip\":\"%s\",\"mfr\":\"0x%02X\"," + "\"cap_byte\":\"0x%02X\",\"size_mb\":%u,\"pass\":%s}\n", + s_p_qspi_chip, (unsigned) s_qspi_mfr_id, (unsigned) s_qspi_cap_byte, + (unsigned) s_qspi_size_mb, s_qspi_pass ? "true" : "false"); + cli_send(buf); +} + void protocol_send_wdog_status(void) { char buf[PROTO_BUF_SIZE]; @@ -60,3 +123,16 @@ void protocol_send_wdog_status(void) (unsigned) RECOVERY_DEFAULT_THRESHOLD); cli_send(buf); } + +#ifdef BOOTLOADER_DEV_DIAGNOSTICS +void protocol_send_sdram_test_phase( + const char *p_phase, bool pass, uint32_t duration_ms, uint32_t fail_addr, uint8_t expected, uint8_t got) +{ + char buf[PROTO_BUF_SIZE]; + (void) snprintf(buf, sizeof(buf), + "{\"type\":\"sdram_test\",\"phase\":\"%s\",\"pass\":%s,\"duration_ms\":%" PRIu32 + ",\"fail_addr\":\"0x%08" PRIX32 "\",\"expected\":\"0x%02X\",\"got\":\"0x%02X\"}\n", + p_phase, pass ? "true" : "false", duration_ms, fail_addr, (unsigned) expected, (unsigned) got); + cli_send(buf); +} +#endif /* BOOTLOADER_DEV_DIAGNOSTICS */ diff --git a/firmware/bootloader/src/protocol.h b/firmware/bootloader/src/protocol.h index 4d6103f..f5f4a2f 100644 --- a/firmware/bootloader/src/protocol.h +++ b/firmware/bootloader/src/protocol.h @@ -17,13 +17,30 @@ * * Типы исходящих событий (Фаза 3): * status — top-level состояние bootloader (waiting_for_sd, - * installing, update_skipped, recovery_mode — см. - * protocol_send_status()) + * installing, update_skipped, recovery_mode, + * smoke_pass/smoke_fail (Фаза 4, SDRAM/SEMC smoke-test, + * см. main.c) — см. protocol_send_status()) * wdog — статус аппаратного watchdog (armed/timeout/recovered, * см. protocol_send_wdog_status()) + * qspi_info — опознанный чип QSPI flash + вписывается ли в минимум + * карты флеша (Фаза 4, см. protocol_send_qspi_info()) * - * Полный словарь состояний status (smoke_pass/smoke_fail/booting/...) - * появится в Фазе 4. + * smoke_pass/smoke_fail и qspi_info шлются один раз сразу после + * bsp_usb_cdc_init()/bsp_qspi_init() — хост почти наверняка не успевает + * открыть порт к этому моменту (USB enumeration), cli_send() неблокирующий и + * теряет запись, если TX ещё не готов. Решение для обоих одинаковое: + * protocol_set_smoke_result()/protocol_set_qspi_info() кэширует исход, + * protocol_send_smoke_status()/protocol_send_qspi_info() переспрашивает его в + * любой момент сессии по командам "smoke_status"/"qspi_info" (тот же приём, + * что уже был у wdog). + * + * Ещё не реализовано (Фаза 4): состояние "booting" и словарь LED-паттернов + * на LED_APP для всех перечисленных состояний — см. firmware/bootloader/PLAN.md. + * + * [DEV-ONLY, Фаза 4] Тип sdram_test — под BOOTLOADER_DEV_DIAGNOSTICS + * (компилируется только в Debug, см. CMakeLists.txt и dev_sdram_test.h): + * sdram_test — результат одной фазы глубокого теста SDRAM, см. + * protocol_send_sdram_test_phase() и dev_sdram_test.h */ #ifndef PROTOCOL_H_ @@ -31,6 +48,9 @@ #include "version.h" +#include +#include + /** @brief Строка версии bootloader, вставляемая в version_response. */ #define BOOTLOADER_VERSION BOOTLOADER_VERSION_STR @@ -63,6 +83,55 @@ void protocol_send_error(const char *p_code); */ void protocol_send_status(const char *p_state); +/** + * @brief Сохранить результат smoke-теста SDRAM/SEMC (Фаза 4) для последующих + * protocol_send_smoke_status(). + * + * Вызывать один раз из main.c сразу после bsp_sdram_configure()+_init(). + * + * @param[in] pass true, если оба вызова вернули BSP_OK. + */ +void protocol_set_smoke_result(bool pass); + +/** + * @brief Отправить status с последним сохранённым результатом smoke-теста. + * + * Формат: {"type":"status","state":"smoke_pass"} / "smoke_fail". Ничего не + * делает, если protocol_set_smoke_result() ещё ни разу не вызывался (не + * должно происходить в штатной последовательности main.c, но на команду + * "smoke_status" в этом случае лучше промолчать, чем соврать результат). + */ +void protocol_send_smoke_status(void); + +/** + * @brief Сохранить информацию об обнаруженном QSPI flash-чипе (Фаза 4) для + * последующих protocol_send_qspi_info(). + * + * Вызывать один раз из main.c сразу после bsp_qspi_init(). Значения + * читаются напрямую по JEDEC ID (bsp_qspi_decode_chip()), не зависят от + * успеха bsp_qspi_init() — так неопознанный/не тот чип тоже репортится с + * деталями, а не просто "не сработало". + * + * @param[in] mfr_id Сырой manufacturer byte JEDEC ID. + * @param[in] p_chip_name Имя чипа ("W25Q128", "UNKNOWN") — см. bsp_qspi_decode_chip(). + * @param[in] cap_byte Сырой capacity byte JEDEC ID. + * @param[in] size_mb Обнаруженная ёмкость, МБ (0, если чип не опознан). + * @param[in] pass true, если Winbond + известная ёмкость + ёмкость не + * меньше минимума карты флеша (см. main.c). + */ +void protocol_set_qspi_info( + uint8_t mfr_id, const char *p_chip_name, uint8_t cap_byte, uint32_t size_mb, bool pass); + +/** + * @brief Отправить event с последней сохранённой информацией о QSPI-чипе. + * + * Формат: {"type":"qspi_info","chip":"W25Q128","mfr":"0xEF","cap_byte":"0x18", + * "size_mb":16,"pass":true} + * + * Ничего не делает, если protocol_set_qspi_info() ещё не вызывался. + */ +void protocol_send_qspi_info(void); + /** * @brief Отправить статус аппаратного watchdog и счётчика попыток загрузки * (Фаза 6). @@ -82,4 +151,27 @@ void protocol_send_status(const char *p_state); */ void protocol_send_wdog_status(void); +#ifdef BOOTLOADER_DEV_DIAGNOSTICS +/** + * @brief [DEV-ONLY] Отправить результат одной фазы dev_sdram_test (Фаза 4). + * + * Формат: {"type":"sdram_test","phase":"data_bus","pass":true,"duration_ms":812, + * "fail_addr":"0x00000000","expected":"0x00","got":"0x00"} + * + * fail_addr/expected/got осмысленны только при pass=false — при pass=true + * передавать 0/0/0. phase="summary" — итог всего прогона (все фазы пройдены + * И до summary дошло — см. dev_sdram_test.c). + * + * @param[in] p_phase "configure"/"address_bus"/"data_bus"/"sequential"/ + * "retention"/"summary". + * @param[in] pass Результат фазы. + * @param[in] duration_ms Длительность фазы, мс. + * @param[in] fail_addr Адрес первой ошибки (0, если pass=true). + * @param[in] expected Ожидаемый байт при ошибке. + * @param[in] got Прочитанный байт при ошибке. + */ +void protocol_send_sdram_test_phase( + const char *p_phase, bool pass, uint32_t duration_ms, uint32_t fail_addr, uint8_t expected, uint8_t got); +#endif /* BOOTLOADER_DEV_DIAGNOSTICS */ + #endif /* PROTOCOL_H_ */ diff --git a/firmware/bootloader/src/sd_update.c b/firmware/bootloader/src/sd_update.c index 306b59d..b1d1267 100644 --- a/firmware/bootloader/src/sd_update.c +++ b/firmware/bootloader/src/sd_update.c @@ -27,6 +27,7 @@ #include "bsp/wdog.h" #include "ff.h" #include "flash_map.h" +#include "led_status.h" #include "protocol.h" #include "slot_version.h" #include "update_policy.h" @@ -105,7 +106,8 @@ static bool erase_and_copy_candidate(const struct flash_area *p_fap, uint32_t fi while (offset < file_size) { bsp_usb_cdc_poll(); - bsp_wdog_refresh(); /* потоковое копирование — реальный прогресс на чанк */ + bsp_wdog_refresh(); /* потоковое копирование — реальный прогресс на чанк */ + led_status_tick_install(); /* APP 250/250 всю установку, см. led_status.h */ uint32_t want = file_size - offset; if (want > SD_UPDATE_CHUNK_SIZE) @@ -176,6 +178,7 @@ static bool run_update(bool button_held, bool recovery_mode) if (!read_candidate_header(&candidate_ver)) { protocol_send_error("SD_CANDIDATE_INVALID"); + led_status_flash_image_rejected(); /* битый заголовок = негодный файл */ goto cleanup; } @@ -194,6 +197,7 @@ static bool run_update(bool button_held, bool recovery_mode) } protocol_send_status("installing"); + led_status_install_begin(); /* открыть окно: tick_install() ниже начинает рисовать */ if (flash_area_open((uint8_t) decision.target_slot, &p_fap) != 0) { @@ -213,6 +217,7 @@ static bool run_update(bool button_held, bool recovery_mode) if (!slot_version_get((uint8_t) decision.target_slot, &installed_ver)) { protocol_send_error("SD_INSTALL_REJECTED"); + led_status_flash_image_rejected(); /* записан, но подпись не прошла = негодный файл */ goto cleanup; } @@ -248,6 +253,7 @@ static bool run_update(bool button_held, bool recovery_mode) } cleanup: + led_status_install_end(); /* закрыть окно (идемпотентно, если не открывали) */ (void) f_close(&g_s_file); (void) f_unmount(SD_UPDATE_MOUNT_POINT); (void) bsp_sd_deinit(); diff --git a/firmware/bootloader/test_stub/CMakeLists.txt b/firmware/bootloader/test_stub/CMakeLists.txt index a404bcf..df95fa3 100644 --- a/firmware/bootloader/test_stub/CMakeLists.txt +++ b/firmware/bootloader/test_stub/CMakeLists.txt @@ -93,9 +93,9 @@ function(add_mcuboot_stub) endfunction() # ───────────────────────────────────────────────────────────────────────── -# Фаза 2/3 — базовые "здоровые" заглушки: подтверждаются сразу, никогда не -# виснут. Slot A мигает раз в 500 мс ("версия 1"), Slot Б — раз в 250 мс -# ("версия 2"). CONFIRM_MODE/HANG_MODE/FEED_WDOG — дефолты (0/0/1). +# Базовые "здоровые" заглушки: подтверждаются сразу, никогда не виснут. Slot A +# мигает раз в 500 мс ("версия 1"), Slot Б — раз в 250 мс ("версия 2"). +# CONFIRM_MODE/HANG_MODE/FEED_WDOG — дефолты (0/0/1). # ───────────────────────────────────────────────────────────────────────── add_mcuboot_stub( NAME @@ -116,12 +116,6 @@ add_mcuboot_stub( OWN_SLOT_ID 1) -# ───────────────────────────────────────────────────────────────────────── -# Фаза 6 — стенд для чек-листа recovery (см. PLAN.md, "Стенд"). Оба варианта -# (Slot A / Slot Б), т.к. в разных сценариях чек-листа "подозреваемый" слот — то -# один, то другой. -# ───────────────────────────────────────────────────────────────────────── - # Класс A: никогда не подтверждается, виснет СРАЗУ (до health-mark) — свежий # неподтверждённый образ виснет до всякого прогресса. WDOG-сброс → штатный # MCUboot revert (copy_done без image_ok) → откат, без участия recovery.c.