# bootloader: SEMC Init, Led dictionary
This commit is contained in:
parent
12bb949834
commit
acb1fed1f2
31 changed files with 1278 additions and 168 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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_
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@
|
|||
* LED_HEARTBEAT — системный, мигает как признак жизни прошивки
|
||||
* LED_APP — прикладной, управляется из firmware по ситуации
|
||||
*
|
||||
* Пины сконфигурированы в generated/pin_mux.h. Этот хедер не знает
|
||||
* ни про GPIO-порты, ни про NXP SDK.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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-операций.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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-строк на них не заводили |
|
||||
|
|
|
|||
|
|
@ -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,17 +65,51 @@
|
|||
|
||||
/* ── 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);
|
||||
|
|
|
|||
|
|
@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* ── Константы ─────────────────────────────────────────────────────────── */
|
||||
/* ── Константы верификации ─────────────────────────────────────────────── */
|
||||
|
||||
/** @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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,6 +228,9 @@ recovery-индикация это показывают), лечится SD-фи
|
|||
|
||||
### Светодиоды
|
||||
|
||||
Полный словарь (установка, «железо не в порядке», ошибка образа, приоритет между ними) —
|
||||
[LED_PATTERNS.md](LED_PATTERNS.md). Кратко:
|
||||
|
||||
| Состояние | Паттерн |
|
||||
| ---------------------- | ------------------------------------------------ |
|
||||
| Загрузчик жив, ждёт SD | Один LED: короткий импульс ~50 мс, пауза ~450 мс |
|
||||
11
docs/bootloader/LED_PATTERNS.md
Normal file
11
docs/bootloader/LED_PATTERNS.md
Normal file
|
|
@ -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) |
|
||||
|
|
@ -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, ограниченным бюджетом
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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; /**< Размер области, байт. */
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <string.h>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
|||
282
firmware/bootloader/src/dev_sdram_test.c
Normal file
282
firmware/bootloader/src/dev_sdram_test.c
Normal file
|
|
@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* ── Константы (те же значения, что в 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);
|
||||
}
|
||||
45
firmware/bootloader/src/dev_sdram_test.h
Normal file
45
firmware/bootloader/src/dev_sdram_test.h
Normal file
|
|
@ -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_ */
|
||||
113
firmware/bootloader/src/led_status.c
Normal file
113
firmware/bootloader/src/led_status.c
Normal file
|
|
@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* ── Периоды паттернов, мс (СИНХРОНИЗИРОВАТЬ с 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);
|
||||
}
|
||||
}
|
||||
63
firmware/bootloader/src/led_status.h
Normal file
63
firmware/bootloader/src/led_status.h
Normal file
|
|
@ -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_ */
|
||||
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,10 +12,33 @@
|
|||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef BOOTLOADER_DEV_DIAGNOSTICS
|
||||
#include <inttypes.h>
|
||||
#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 */
|
||||
|
|
|
|||
|
|
@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/** @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_ */
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue