From 39409479633c29a36a9242ea51644dc634796bc4 Mon Sep 17 00:00:00 2001 From: Ezra Maccabee Date: Tue, 10 Mar 2026 18:16:57 +0300 Subject: [PATCH] # 6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Кастомный скрипт в tools/host для прошивки bare-metal платы --- bsp/CMakeLists.txt | 5 +- docs/HAB_GUIDE.md | 223 +++++++++++++++++ firmware/test/CMakeLists.txt | 1 - firmware/test/main.c | 1 - tools/README.md | 253 +++++++++++++------- tools/host/dcd/dcd.bin | Bin 1088 -> 1208 bytes tools/host/dcd/dcd_compiler.py | 216 ----------------- tools/host/dcd/dcd_mt48lc16m16a2.cfg | 232 ------------------ tools/host/dcd/ivt_flashloader.bin | Bin 0 -> 87104 bytes tools/host/fdcb/fcb.bin | Bin 512 -> 0 bytes tools/host/fdcb/fcb_w25q128_quad.yaml | 152 ------------ tools/host/fdcb/fcb_w25q64_quad.yaml | 152 ------------ tools/host/flash_usb.py | 209 ++++++++++++++++ tools/host/hab/hab_app.yaml | 18 +- tools/host/hab/hab_bootloader.yaml | 19 +- tools/host/hab/hab_firmware_test.yaml | 23 +- tools/host/image/bootimg_app.yaml | 26 -- tools/host/image/bootimg_bootloader.yaml | 26 -- tools/host/image/bootimg_firmware_test.yaml | 36 --- 19 files changed, 640 insertions(+), 952 deletions(-) create mode 100644 docs/HAB_GUIDE.md delete mode 100644 tools/host/dcd/dcd_compiler.py delete mode 100644 tools/host/dcd/dcd_mt48lc16m16a2.cfg create mode 100644 tools/host/dcd/ivt_flashloader.bin delete mode 100644 tools/host/fdcb/fcb.bin delete mode 100644 tools/host/fdcb/fcb_w25q128_quad.yaml delete mode 100644 tools/host/fdcb/fcb_w25q64_quad.yaml create mode 100644 tools/host/flash_usb.py delete mode 100644 tools/host/image/bootimg_app.yaml delete mode 100644 tools/host/image/bootimg_bootloader.yaml delete mode 100644 tools/host/image/bootimg_firmware_test.yaml diff --git a/bsp/CMakeLists.txt b/bsp/CMakeLists.txt index a200399..d8bc15f 100644 --- a/bsp/CMakeLists.txt +++ b/bsp/CMakeLists.txt @@ -16,8 +16,9 @@ set_source_files_properties(board/board.c board/pin_mux.c PROPERTIES COMPILE_OPTIONS "-w") # Дефайны PUBLIC — транзитивно подтянутся во все firmware таргеты TODO: для # каждого приложения свои дефайны -target_compile_definitions(bsp_board PUBLIC CPU_MIMXRT1052CVJ5B - SKIP_SYSCLK_INIT) +target_compile_definitions( + bsp_board PUBLIC CPU_MIMXRT1052CVJ5B SKIP_SYSCLK_INIT XIP_EXTERNAL_FLASH=1 + XIP_BOOT_HEADER_ENABLE=1 XIP_BOOT_HEADER_DCD_ENABLE=1) # Сценарий A: XIP — код исполняется прямо из Flash add_library(bsp_boot_xip INTERFACE) diff --git a/docs/HAB_GUIDE.md b/docs/HAB_GUIDE.md new file mode 100644 index 0000000..291aadb --- /dev/null +++ b/docs/HAB_GUIDE.md @@ -0,0 +1,223 @@ +# High Assurance Boot (HAB) — Принцип работы и применение на MIMXRT1052 + +## 1. Что такое HAB + +**High Assurance Boot (HAB)** — механизм безопасной загрузки, встроенный в BootROM микроконтроллеров семейства NXP i.MX/RT. Его цель — криптографически гарантировать, что на чипе запустится только авторизованный код. + +HAB работает на уровне кремния: BootROM прошит на заводе и не может быть изменён. Вся цепочка доверия строится от него. + +--- + +## 2. Угрозы без HAB + +Без механизма защищённой загрузки любой, имеющий физический доступ к устройству, может: + +- подключить программатор и залить произвольную прошивку +- загрузить вредоносный код через USB в режиме Serial Download Protocol (SDP) +- модифицировать прошивку во Flash между производством и эксплуатацией + +HAB устраняет эти угрозы на аппаратном уровне. + +--- + +## 3. Структура HAB-образа + +HAB-образ — это не просто бинарник прошивки, а обёртка с метаданными вокруг него. BootрOM ищет обязательные структуры по фиксированным смещениям. + +### Карта памяти Flash (FlexSPI NOR, XIP) + +```bash +0x60000000 ┌──────────────────────────────────┐ + │ FCB — Flash Configuration Block │ 512 байт + │ (конфигурация FlexSPI контроллера│ + │ записывается Flashloader'ом) │ +0x60001000 ├──────────────────────────────────┤ + │ IVT — Image Vector Table │ 32 байта +0x60001020 ├──────────────────────────────────┤ + │ BDT — Boot Data Table │ 12 байт +0x60001030 ├──────────────────────────────────┤ + │ DCD — Device Config Data │ опционально + │ (инициализация SDRAM и др.) │ + ├──────────────────────────────────┤ +0x60003000 │ Код прошивки (.text, .data, ...) │ + │ │ + ├──────────────────────────────────┤ + │ CSF — Command Sequence File │ только в signed-образе + │ (подпись, ключи, хэши) │ + └──────────────────────────────────┘ +``` + +### 3.1 IVT — Image Vector Table + +Главная точка входа для BootROM. Структура фиксирована, 32 байта, тег `0xD1`: + +```c +typedef struct { + uint32_t header; // тег 0xD1, длина, версия + uint32_t entry; // адрес Reset_Handler прошивки + uint32_t reserved1; + uint32_t dcd; // адрес DCD (0 если не нужен) + uint32_t boot_data; // адрес BDT + uint32_t self; // адрес самого IVT + uint32_t csf; // адрес CSF (0 если unsigned) + uint32_t reserved2; +} ivt_t; +``` + +BootROM находит IVT по фиксированному смещению от начала Flash (`ivtOffset = 0x1000` для FlexSPI NOR), читает его и получает адреса всех остальных структур. + +### 3.2 BDT — Boot Data Table + +Сообщает BootROM, где начинается образ и каков его полный размер: + +```c +typedef struct { + uint32_t start; // 0x60000000 — начало образа во Flash + uint32_t length; // полный размер в байтах + uint32_t plugin; // 0 для обычного образа +} boot_data_t; +``` + +### 3.3 DCD — Device Configuration Data + +Опциональная секция с командами инициализации периферии, которые BootROM выполняет **до** передачи управления прошивке. Типичное применение — инициализация SDRAM (SEMC), чтобы к моменту старта первой инструкции прошивки память уже была доступна. + +DCD содержит последовательность команд двух типов: + +| Команда | Тег | Назначение | +|---|---|---| +| `Write Data` | `0xCC` | Записать значение по адресу | +| `Check Bits Set` | `0xCF` | Ждать пока бит станет `1` (poll) | + +Формат бинарника: `Tag(1) | Length(2 BE) | Parameter(1) | данные`. + +### 3.4 CSF — Command Sequence File + +Криптографическая секция. Присутствует только в подписанных образах. Содержит: + +- таблицу публичных ключей (SRK table) +- хэши защищённых регионов образа (IVT, BDT, код) +- подпись RSA 2048/4096 или ECDSA + +--- + +## 4. Режимы HAB + +### 4.1 По типу образа + +| Режим | `flags` | CSF | Применение | +|---|---|---|---| +| Unsigned | `0x00` | отсутствует | разработка, отладка | +| Signed | `0x08` | RSA/ECDSA подпись | производство | +| Signed + Encrypted | `0x0C` | подпись + шифрование кода | максимальная защита | + +### 4.2 По состоянию чипа (OTP fuse) + +Независимо от типа образа, чип может находиться в одном из двух состояний: + +**HAB Open** (fuse `SEC_CONFIG = 0`, заводское состояние): +BootROM проверяет подпись, но **игнорирует ошибки** — запуск происходит в любом случае. Это позволяет работать с unsigned образами на этапе разработки. + +**HAB Closed** (fuse `SEC_CONFIG = 1`): +При ошибке проверки подписи BootROM **отказывает в запуске** безвозвратно. Запись fuse необратима — чип нельзя вернуть в Open состояние. + +--- + +## 5. Цепочка доверия (Trust Chain) + +```bash +Производитель устройства + │ + ├── Генерирует пару ключей RSA 4096 + │ private.pem → хранится в защищённом месте (HSM / vault) + │ public.pem → включается в образ (SRK table) + │ + ├── SHA256(public.pem) записывается в OTP fuses (SRK_HASH) + │ ↑ необратимо, выполняется один раз при производстве + │ + └── При сборке каждого релиза: + nxpimage hab export (flags=0x08) + → хэшируются IVT + BDT + код + → подписывается private.pem + → CSF встраивается в образ + +При загрузке на чипе: + BootROM + → читает IVT → находит CSF + → извлекает public key из CSF + → SHA256(public key) == SRK_HASH из fuses? → ДА + → проверяет подпись хэшей регионов → ОК + → прыгает на entry point +``` + +--- + +## 6. Почему Unsigned-образ требует HAB-контейнер + +Распространённое заблуждение: "раз образ без подписи, зачем вообще HAB?". Ответ прост: + +**BootROM всегда ищет IVT по фиксированному смещению.** Это не опциональная безопасность — это формат загрузки. Без IVT и BDT BootROM не знает: + +- куда прыгать (entry point) +- где находится DCD для инициализации SDRAM +- каков размер образа + +`nxpimage hab export` для unsigned образа выполняет именно эту работу — добавляет IVT, BDT, встраивает DCD — без какой-либо криптографии. CSF при этом равен нулю. + +--- + +## 7. Процесс прошивки MIMXRT1052 (USB, без JTAG) + +BootROM в режиме SDP умеет только писать в RAM и прыгать. Для записи во Flash необходим **Flashloader** — специальная программа от NXP. + +``` +Плата в SDP режиме (BOOT_MOD_1 = 3V3) + │ + │ sdphost -u 0x1FC9,0x0130 + ├─ write-file 0x20001C00 ivt_flashloader.bin + └─ jump-address 0x20001C00 + │ + │ (ожидание ~3с инициализации) + │ + Flashloader запущен + │ blhost -u 0x15A2,0x0073 + ├─ fill-memory 0x2000 4 0xC0000007 ← option word для FlexSPI NOR + ├─ configure-memory 9 0x2000 ← Flashloader пишет FCB в Flash + ├─ flash-erase-region 0x60000000 … + ├─ write-memory 0x60002000 firmware_hab.bin ← HAB образ + └─ reset +``` + +--- + +## 8. Жизненный цикл для проекта TFT + +| Стадия | Режим HAB | Подпись | Fuse | +|---|---|---|---| +| Разработка | Open | Unsigned (`0x00`) | не трогаем | +| Входной контроль | Open | Unsigned (`0x00`) | не трогаем | +| Предсерийные образцы | Open | Signed (`0x08`) | не трогаем | +| Серийное производство | **Closed** | Signed (`0x08`) | `SEC_CONFIG = 1` | + +> ⚠️ Запись `SEC_CONFIG = 1` необратима. Перед закрытием HAB необходимо убедиться, что подписанный образ успешно проходит верификацию на реальном железе. + +--- + +## 9. Инструменты + +| Инструмент | Назначение | +|---|---| +| `nxpimage hab export` | Сборка HAB-образа (IVT + BDT + DCD + опционально CSF) | +| `nxpimage hab parse` | Разбор готового образа для проверки | +| `sdphost` | Связь с BootROM в SDP режиме (запись в RAM, прыжок) | +| `blhost` | Связь с Flashloader (запись во Flash, конфигурация памяти) | +| `nxpdevscan` | Обнаружение подключённых NXP устройств | + +--- + +## 10. Литература + +- **NXP AN12263** — "HAB Code Signing for i.MX RT" — пошаговая инструкция по подписи образов +- **MIMXRT1052 Reference Manual** — Chapter 9: System Boot (структуры IVT, BDT, DCD, CSF) +- [**SPSDK Documentation**](https://spsdk.readthedocs.io) — документация по nxpimage, blhost, sdphost +- **NXP Application Note AN4581** — "Secure Boot on i.MX50, i.MX53, and i.MX 6 Series" diff --git a/firmware/test/CMakeLists.txt b/firmware/test/CMakeLists.txt index cb8e78c..757555f 100644 --- a/firmware/test/CMakeLists.txt +++ b/firmware/test/CMakeLists.txt @@ -13,7 +13,6 @@ target_sources(firmware_test PRIVATE main.c target_link_libraries( firmware_test PRIVATE bsp_board - bsp_boot_xip # SDK драйверы периферии — добавляй по мере написания тестов sdk_lpuart # UART тест sdk_flexcan # CAN тест diff --git a/firmware/test/main.c b/firmware/test/main.c index 642ecdc..889c3da 100644 --- a/firmware/test/main.c +++ b/firmware/test/main.c @@ -11,7 +11,6 @@ #include "fsl_gpio.h" #include "pin_mux.h" - /******************************************************************************* * Definitions ******************************************************************************/ diff --git a/tools/README.md b/tools/README.md index b7ac870..49a260b 100644 --- a/tools/README.md +++ b/tools/README.md @@ -1,164 +1,257 @@ -# Host Tools — окружение для прошивки +# tools/host — Окружение прошивки MIMXRT1052CVJ5B -Изолированное Python-окружение на базе [uv](https://docs.astral.sh/uv/). -Устанавливается на хост-машине разработчика — **не внутри devcontainer**. +Изолированное Python-окружение на базе [uv](https://docs.astral.sh/uv/) для сборки +HAB-образов и прошивки платы через USB. Запускается на хост-машине разработчика — +**не внутри devcontainer**. + +--- ## Структура ```bash tools/host/ ├── dcd/ -│ ├── dcd_mt48lc16m16a2.cfg ← источник истины для DCD (коммитить) -│ ├── dcd_compiler.py ← скрипт: .cfg → .bin -│ └── dcd.bin ← артефакт (пересобирается из .cfg) -│ -├── fdcb/ -│ ├── fcb_w25q64_quad.yaml ← конфиг FCB: W25Q64, Quad I/O, 133 MHz -│ └── fcb.bin ← артефакт (пересобирается из .yaml) -│ +│ ├── dcd.bin ← DCD бинарник (инициализация SDRAM, коммитить как есть) +│ └── ivt_flashloader.bin ← NXP Flashloader (коммитить) ├── hab/ │ ├── hab_firmware_test.yaml ← HAB: Debug + DCD (входной контроль) │ ├── hab_bootloader.yaml ← HAB: Release, без DCD │ └── hab_app.yaml ← HAB: Release + DCD (боевая прошивка) -│ -├── image/ -│ ├── bootimg_firmware_test.yaml -│ ├── bootimg_bootloader.yaml -│ └── bootimg_app.yaml -│ -├── pyproject.toml ← зависимости (spsdk, версия зафиксирована) +├── flash_usb.py ← скрипт прошивки через USB +├── pyproject.toml ← зависимости (spsdk==3.7.x) ├── uv.lock ← lockfile (коммитить) -├── .python-version └── README.md ``` -## Установка uv (один раз на машину) +## Предварительные требования + +### 1. uv — один раз на машину ```bash -# Linux/macOS +# macOS / Linux curl -LsSf https://astral.sh/uv/install.sh | sh # Windows powershell -c "irm https://astral.sh/uv/install.ps1 | iex" ``` -## Первый запуск (один раз на репозиторий) +### 2. Зависимости проекта — один раз на репозиторий ```bash cd tools/host uv sync ``` -### udev правило для USB SDP — только Linux, один раз на машину +### 3. udev правила — только Linux, один раз на машину -Без этого правила `blhost` требует `sudo`: +Без этого для работы с USB нужен `sudo`: ```bash -echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1fc9", ATTR{idProduct}=="0135", MODE="0666", GROUP="plugdev"' \ - | sudo tee /etc/udev/rules.d/99-nxp-sdp.rules +sudo tee /etc/udev/rules.d/99-nxp-mimxrt.rules << 'EOF' +# NXP BootROM — SDP режим (BOOT_MOD_1 = 3V3) +SUBSYSTEM=="usb", ATTR{idVendor}=="1fc9", ATTR{idProduct}=="0130", MODE="0666", GROUP="plugdev" +# NXP Flashloader — после jump-address +SUBSYSTEM=="usb", ATTR{idVendor}=="15a2", ATTR{idProduct}=="0073", MODE="0666", GROUP="plugdev" +EOF + sudo udevadm control --reload-rules && sudo udevadm trigger sudo usermod -a -G plugdev $USER -# Перелогиниться после usermod! +# После usermod — перелогиниться! ``` -## Пайплайн сборки загрузочного образа +--- -### Шаг 1 — DCD (пересобирать при изменении dcd_mt48lc16m16a2.cfg) +## Карта Flash (W25Q64FVSSIG, 8 MB) ```bash -cd tools/host/dcd -uv run python3 dcd_compiler.py dcd_mt48lc16m16a2.cfg dcd.bin +0x60000000 ┌─────────────────────────────┐ + │ FCB — Flash Config Block │ 512 байт + │ (пишет Flashloader, │ + │ не входит в HAB образ) │ +0x60001000 ├─────────────────────────────┤ + │ IVT — Image Vector Table │ ← начало HAB образа + │ BDT — Boot Data Table │ +0x60001040 ├─────────────────────────────┤ + │ DCD — SDRAM init │ ~1088 байт (для fw_test и app) +0x60003000 ├─────────────────────────────┤ + │ Код прошивки │ + │ (.text, .data, ...) │ + └─────────────────────────────┘ ``` -### Шаг 2 — FCB (пересобирать при изменении fcb_w25q64_quad.yaml) +--- + +## Пайплайн: сборка HAB образа ```bash -cd tools/host/fdcb -uv run nxpimage bootable-image fcb export -c fcb_w25q64_quad.yaml -o fcb.bin -``` +cd tools/host/hab -### Шаг 3 — Загрузочный образ - -```bash -cd tools/host/image - -# firmware_test (Debug, входной контроль) -uv run nxpimage bootable-image export -c bootimg_firmware_test.yaml +# firmware_test (Debug) — входной контроль платы +uv run nxpimage hab export --force \ + -c hab_firmware_test.yaml \ + -o ../../../../build/Debug/firmware_test_hab.bin # bootloader (Release) -uv run nxpimage bootable-image export -c bootimg_bootloader.yaml +uv run nxpimage hab export --force \ + -c hab_bootloader.yaml \ + -o ../../../../build/Release/bootloader_hab.bin -# app (Release, боевая прошивка) -uv run nxpimage bootable-image export -c bootimg_app.yaml +# app (Release) — боевая прошивка +uv run nxpimage hab export --force \ + -c hab_app.yaml \ + -o ../../../../build/Release/app_hab.bin ``` -Артефакты попадают в `build/Debug/` и `build/Release/` соответственно. - -### Проверка образа +### Проверка образа после сборки ```bash -cd tools/host/image -uv run nxpimage bootable-image parse \ - -b ../../../../build/Debug/firmware_test_bootable.bin \ - -f mimxrt1050 +uv run nxpimage hab parse -b ../../../../build/Debug/firmware_test_hab.bin ``` -В выводе должны присутствовать секции `FCB`, `IVT`, `BDT`, `DCD`, `App`. +Ожидаемый результат: IVT с корректным `entry`, DCD с тегом `0xD2`, +`csf = 0x00000000` (unsigned). -## Прошивка через USB (BootROM SDP режим) +--- -Перевести плату в SDP режим: `BOOT_MOD_1 = 1` (подтянуть к 3V3), затем подать питание или нажать Reset. +## Пайплайн: прошивка через USB + +### Подготовка платы + +Перевести плату в режим SDP (Serial Download Protocol): + +1. Подтянуть `BOOT_MOD_1` к `3V3` +2. Reset +3. Подключить USB к ПК + +В этом режиме плата определяется как `VID:PID = 1FC9:0130`. + +### Запуск скрипта ```bash cd tools/host -# Проверить что устройство определилось (VID:PID 1FC9:0135) -export VID_PID=0x1fc9:0x0130 -uv run nxpdevscan +# firmware_test +uv run python3 flash_usb.py --firmware firmware_test --build-type Debug -# Версия BootROM на таргете -uv run blhost -u $VID_PID get-property 1 -# Доступные BootROM области памяти -uv run blhost -u $VID_PID list-memory +# app +uv run python3 flash_usb.py --firmware app --build-type Release -# Прошить firmware_test -uv run blhost -u $VID_PID flash-erase-all -uv run blhost -u $VID_PID write-memory 0x60000000 \ - ../../build/Debug/firmware_test_bootable.bin -uv run blhost -u $VID_PID reset - -# Загрузить в RAM без записи во Flash (для быстрой отладки) -uv run blhost -u $VID_PID load-image ../../build/Debug/firmware_test_bootable.bin +# bootloader +uv run python3 flash_usb.py --firmware bootloader --build-type Release ``` -После прошивки вернуть `BOOT_MOD_1 = 0` (подтянуть к GND) для нормального старта из Flash. +После прошивки: вернуть `BOOT_MOD_1` к `GND`, Reset — плата стартует из Flash. -## Удалённая прошивка (сервер) +### Последовательность команд внутри скрипта ```bash -# Из devcontainer или локально — копируем образ на сервер и прошиваем -./tools/host/flash_remote.sh firmware_test Debug +Плата в SDP режиме (1FC9:0130) + │ + ├─ sdphost write-file 0x20001C00 ivt_flashloader.bin + └─ sdphost jump-address 0x20001C00 + │ + │ (ожидание до 10с пока Flashloader поднимется на 15A2:0073) + │ + Flashloader (15A2:0073) + │ + │ Шаг 1 — инициализация FlexSPI NOR контроллера + ├─ blhost fill-memory 0x2000 4 0xC0000007 word + ├─ blhost configure-memory 9 0x2000 + │ + │ Шаг 2 — стирание Flash + ├─ blhost flash-erase-region 0x60000000 0 (memoryId=0, XIP) + │ + │ Шаг 3 — запись FCB в 0x60000000 + │ (ПОСЛЕ стирания! Flashloader генерирует FCB из параметров шага 1) + ├─ blhost fill-memory 0x2000 4 0xF000000F word + ├─ blhost configure-memory 9 0x2000 + │ + │ Шаг 4 — запись HAB образа начиная с 0x60001000 + ├─ blhost write-memory 0x60001000 *_hab.bin 0 (memoryId=0, XIP) + └─ blhost reset ``` +> **Почему FCB не в образе?** +> Flashloader генерирует FCB автоматически из параметров FlexSPI (option word +> `0xC0000007`). Запись FCB через `0xF000000F` — это отдельная команда, +> выполняемая строго после `flash-erase-region`, иначе FCB будет затёрт следующим +> стиранием. + +--- + +## DCD — инициализация SDRAM + +`dcd/dcd.bin` — бинарный блоб команд, который BootROM выполняет до передачи +управления прошивке. Инициализирует PLL, CCM clock gates, SEMC контроллер +и микросхему SDRAM (MT48LCxxM4). + +Файл хранится в репозитории в бинарном виде и **не требует пересборки** — он +меняется только при изменении схемотехники. + +```bash +Цель: MT48LC16M16A2P-6A, 32 MB, шина 16 бит, CS0 + SEMC BR0: base=0x80000000, size=32MB, VLD=1 + SEMC BR1–BR3: VLD=0 (один чип, один CS) +``` + +Использование DCD по прошивкам: + +| Прошивка | DCD | Причина | +|---|---|---| +| `firmware_test` | ✓ | тесты работают с SDRAM | +| `app` | ✓ | FreeRTOS heap и буферы LCDIF размещены в SDRAM | +| `bootloader` | ✗ | загрузчик не использует SDRAM, инициализация в app | + +--- + +## Flashloader + +`dcd/ivt_flashloader.bin` — NXP-программа, загружаемая в RAM через SDP. +Предоставляет `blhost` доступ к Flash, которого нет через BootROM напрямую. + +```bash +Entry point: 0x20002401 +Загрузка по: 0x20001C00 +VID:PID после старта: 15A2:0073 +Источник: MCUXpresso Secure Provisioning Tool 25.12 +``` + +--- + ## Диагностика ```bash -# Список поддерживаемых семейств (проверка установки) -uv run nxpimage bootable-image get-families +# Найти подключённые NXP устройства +uv run nxpdevscan -# Список доступных шаблонов для mimxrt1050 -uv run nxpimage bootable-image list-templates -f mimxrt1050 +# Проверить связь с BootROM через SDP +sdphost -u 0x1FC9,0x0130 -- error-status -# Версия SPSDK +# Проверить что Flashloader отвечает (после jump-address) +blhost -u 0x15A2,0x0073 -- get-property 1 0 + +# Версии инструментов uv run nxpimage --version uv run blhost --version ``` +### Типичные ошибки + +| Симптом | Причина | Решение | +|---|---|---| +| `USB HID device not found: 1FC9:0130` | Плата не в SDP режиме | Проверить `BOOT_MOD_1` → 3V3 и Reset | +| Flashloader timeout после jump | `ivt_flashloader.bin` повреждён или не тот | Взять из SPT 25.12 | +| Плата не стартует после прошивки | `BOOT_MOD_1` не переключён обратно | Вернуть `BOOT_MOD_1` → GND, Reset | +| Плата зависает сразу после старта | DCD завис (неверный `dcd.bin`) | Использовать только верифицированный `dcd.bin` | +| Плата стартует, периферия не работает | CCM clock gates в DCD отключают нужные клоки | Проверить `dcd.bin` — только верифицированный вариант | + +--- + ## Обновление зависимостей ```bash cd tools/host uv add "spsdk==X.Y.Z" uv sync -git add uv.lock +git add uv.lock pyproject.toml ``` diff --git a/tools/host/dcd/dcd.bin b/tools/host/dcd/dcd.bin index d8fff956e0374709b6fabc7c762f3c47f036cd28..ccdf38733735bb87ecd83152ad49085f269edaf0 100644 GIT binary patch delta 182 zcmX@Wv4hj&63Y(9Gb}PJ4*Ul){sRG!&VkYeP`U(4S3v0+DBS?2c^eEE7!(*cMr>nZ zdcZJQfLTUn0t17hgFXWb0|NsGkal2T;8q9HE(`*U43is}jVE(6s|ZR1MI>4n7(ld4 k8<3vt!2C@~g~gG@5vY%W4M>9wU~_a)1o7E{G>Fd*0DC1nmH+?% delta 134 zcmdnNd4MD05{rZ583q>?2mS*Y3=9nXKspCVGXUuV0|;FLqzr)k3Lwn`q-%gQGmvfo z(hLr~4M1fIjOQ76WI$|?;2Gu|mW>|Um?q~jD+)3==rc&PfK=);$g~0J$qSgjaq+MK Kxjq4kT$}(5BOJK^ diff --git a/tools/host/dcd/dcd_compiler.py b/tools/host/dcd/dcd_compiler.py deleted file mode 100644 index 866ba8b..0000000 --- a/tools/host/dcd/dcd_compiler.py +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/env python3 -""" -dcd_cfg_to_bin.py — конвертация DCD .cfg (NXP Config Tools) в .bin (BootROM формат) - -Формат DCD бинарника (IMX RT RM, Chapter 9.7): - [Header] - 0xD2 — DCD tag - length[1:0] — общая длина в байтах (big-endian, 2 байта) - 0x41 — version - - [Commands] — последовательно: - Write Data (0xCC): - 0xCC tag, length BE16, parameter (ширина: 4=word) - [addr BE32, value BE32] × N — группируются подряд идущие записи - - Check Bits Set (0xCF) — wait_until (expr & mask) != 0: - 0xCF tag, 0x000C length, parameter (4=word) - addr BE32, mask BE32, count BE32 (0 = бесконечно) - -Использование: - python3 dcd_cfg_to_bin.py dcd.cfg dcd.bin - python3 dcd_cfg_to_bin.py dcd.cfg # выведет dcd.bin рядом с cfg -""" - -import re -import struct -import sys -from pathlib import Path -from dataclasses import dataclass, field -from typing import Union - -# ─── Константы DCD ──────────────────────────────────────────────────────────── -DCD_TAG = 0xD2 -DCD_VERSION = 0x41 -CMD_WRITE = 0xCC -CMD_CHECK_SET = 0xCF # wait_until (reg & mask) != 0 -CMD_CHECK_CLEAR = 0xD0 # wait_until (reg & mask) == 0 -WIDTH_WORD = 0x04 # 32-bit - - -# ─── Типы команд ────────────────────────────────────────────────────────────── -@dataclass -class WriteEntry: - addr: int - value: int - - -@dataclass -class WriteCmd: - entries: list[WriteEntry] = field(default_factory=list) - - def to_bytes(self) -> bytes: - length = 4 + len(self.entries) * 8 - # NXP RM format: Tag(1) | Length(2 BE) | Parameter(1) - hdr = struct.pack(">BHB", CMD_WRITE, length, WIDTH_WORD) - data = b"".join( - struct.pack(">II", e.addr, e.value) for e in self.entries - ) - return hdr + data - - -@dataclass -class CheckCmd: - addr: int - mask: int - check_set: bool = True # True = wait != 0, False = wait == 0 - - def to_bytes(self) -> bytes: - tag = CMD_CHECK_SET if self.check_set else CMD_CHECK_CLEAR - # NXP RM format: Tag(1) | Length(2 BE=12) | Parameter(1) | Addr(4) | Mask(4) - # Parameter: bits[2:0]=width(4=32bit), bit[3]=check_set → 0x1C / check_clear → 0x14 - # No count field — poll indefinitely is the default (length=12, not 16) - param = 0x1C if self.check_set else 0x14 - length = 12 # 4 header + 4 addr + 4 mask - return struct.pack(">BHBII", tag, length, param, self.addr, self.mask) - - -# ─── Парсер .cfg ───────────────────────────────────────────────────────────── -def parse_hex(s: str) -> int: - return int(s, 16) if s.startswith("0x") or s.startswith("0X") else int(s) - - -def parse_cfg(text: str) -> list[Union[WriteCmd, CheckCmd]]: - """ - Парсит .cfg и возвращает список команд DCD. - Соседние write-записи группируются в одну WriteCmd. - """ - # Убираем блочные комментарии /* ... */ - text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL) - # Убираем строчные комментарии // ... - text = re.sub(r"//[^\n]*", "", text) - - commands: list[Union[WriteCmd, CheckCmd]] = [] - current_write: WriteCmd | None = None - - # Паттерн: *(uint32_t*)0xADDR = 0xVALUE; - re_write = re.compile( - r"\*\s*\(\s*uint32_t\s*\*\s*\)\s*(0x[0-9A-Fa-f]+)\s*=\s*(0x[0-9A-Fa-f]+)\s*;" - ) - - # Паттерн: (*(uint32_t*)0xADDR & 0xMASK) != 0; - re_check_set = re.compile( - r"\(\s*\*\s*\(\s*uint32_t\s*\*\s*\)\s*(0x[0-9A-Fa-f]+)\s*&\s*(0x[0-9A-Fa-f]+)\s*\)\s*!=\s*0\s*;" - ) - - # Паттерн: (*(uint32_t*)0xADDR & 0xMASK) == 0; - re_check_clear = re.compile( - r"\(\s*\*\s*\(\s*uint32_t\s*\*\s*\)\s*(0x[0-9A-Fa-f]+)\s*&\s*(0x[0-9A-Fa-f]+)\s*\)\s*==\s*0\s*;" - ) - - for line in text.splitlines(): - line = line.strip() - if not line: - continue - - m = re_write.search(line) - if m: - addr = parse_hex(m.group(1)) - value = parse_hex(m.group(2)) - if current_write is None: - current_write = WriteCmd() - commands.append(current_write) - current_write.entries.append(WriteEntry(addr, value)) - continue - - m = re_check_set.search(line) - if m: - if current_write is not None: - current_write = None # разрываем группу write - addr = parse_hex(m.group(1)) - mask = parse_hex(m.group(2)) - commands.append(CheckCmd(addr, mask, check_set=True)) - continue - - m = re_check_clear.search(line) - if m: - if current_write is not None: - current_write = None - addr = parse_hex(m.group(1)) - mask = parse_hex(m.group(2)) - commands.append(CheckCmd(addr, mask, check_set=False)) - continue - - # Не write и не check — разрываем текущую write-группу - # (на случай если между записями есть другой синтаксис) - if current_write is not None: - current_write = None - - return commands - - -# ─── Сборка бинарника ───────────────────────────────────────────────────────── -def build_dcd_binary(commands: list[Union[WriteCmd, CheckCmd]]) -> bytes: - body = b"".join(cmd.to_bytes() for cmd in commands) - total_length = 4 + len(body) # 4 байта header - # NXP RM format: Tag(1) | Length(2 BE) | Version(1) - header = struct.pack(">BHB", DCD_TAG, total_length, DCD_VERSION) - return header + body - - -# ─── Отчёт ──────────────────────────────────────────────────────────────────── -def print_report(commands: list[Union[WriteCmd, CheckCmd]], out_size: int) -> None: - writes = sum(1 for c in commands if isinstance(c, WriteCmd)) - checks = sum(1 for c in commands if isinstance(c, CheckCmd)) - entries = sum(len(c.entries) for c in commands if isinstance(c, WriteCmd)) - - print(f"\n{'─'*52}") - print(f" DCD конвертация завершена") - print(f"{'─'*52}") - print(f" Write команд (групп) : {writes}") - print(f" Write записей всего : {entries}") - print(f" Check команд : {checks}") - print(f" Размер бинарника : {out_size} байт (0x{out_size:04X})") - print(f"{'─'*52}") - - print("\n Команды:") - for i, cmd in enumerate(commands): - if isinstance(cmd, WriteCmd): - print(f" [{i:02d}] WRITE × {len(cmd.entries):3d} entries" - f" (0x{cmd.entries[0].addr:08X} … 0x{cmd.entries[-1].addr:08X})") - else: - kind = "CHECK_SET (!= 0)" if cmd.check_set else "CHECK_CLR (== 0)" - print(f" [{i:02d}] {kind} addr=0x{cmd.addr:08X} mask=0x{cmd.mask:08X}") - print() - - -# ─── main ───────────────────────────────────────────────────────────────────── -def main(): - if len(sys.argv) < 2: - print(__doc__) - sys.exit(1) - - cfg_path = Path(sys.argv[1]) - if not cfg_path.exists(): - print(f"[ERROR] Файл не найден: {cfg_path}") - sys.exit(1) - - out_path = Path(sys.argv[2]) if len(sys.argv) >= 3 else cfg_path.with_suffix(".bin") - - text = cfg_path.read_text(encoding="utf-8", errors="replace") - commands = parse_cfg(text) - - if not commands: - print("[ERROR] Ни одной команды не найдено. Проверь формат .cfg файла.") - sys.exit(1) - - binary = build_dcd_binary(commands) - out_path.write_bytes(binary) - - print(f"[OK] Входной файл : {cfg_path}") - print(f"[OK] Выходной файл: {out_path}") - print_report(commands, len(binary)) - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/tools/host/dcd/dcd_mt48lc16m16a2.cfg b/tools/host/dcd/dcd_mt48lc16m16a2.cfg deleted file mode 100644 index aae92f9..0000000 --- a/tools/host/dcd/dcd_mt48lc16m16a2.cfg +++ /dev/null @@ -1,232 +0,0 @@ -[DCD Descriptor] - -// ============================================================================= -// Clock Init -// MT48LC16M16A2 SDRAM on MIMXRT1052CVJ5B -// -// Включаем только необходимые тактовые домены (fix 6): -// CCM_CCGR0: биты [1:0]=11 (aips_tz1), биты [3:2]=11 (aips_tz2) — доступ к периферии -// CCM_CCGR2: биты [29:28]=11 (iomuxc) — конфигурация пинов -// CCM_CCGR3: биты [5:4]=11 (semc) — контроллер SDRAM -// CCM_CCGR4: биты [3:2]=11 (iomuxc_gpr) — GPR регистры -// CCM_CCGR5: биты [1:0]=11 (rom) — BootROM -// -// ВАЖНО: если после изменения плата не стартует — проверь по RM Table 18-5 -// (CCM Clock Gating Register) и вернись к 0xFFFFFFFF для отладки. -// ============================================================================= - -*(uint32_t*)0x400FC068 = 0x0000000F; // CCM_CCGR0: aips_tz1 + aips_tz2 -*(uint32_t*)0x400FC06C = 0x00000000; // CCM_CCGR1: не нужен для DCD -*(uint32_t*)0x400FC070 = 0x30000000; // CCM_CCGR2: iomuxc (CG14, bits[29:28]) -*(uint32_t*)0x400FC074 = 0x00000030; // CCM_CCGR3: semc (CG2, bits[5:4]) -*(uint32_t*)0x400FC078 = 0x0000000C; // CCM_CCGR4: iomuxc_gpr (CG1, bits[3:2]) -*(uint32_t*)0x400FC07C = 0x00000003; // CCM_CCGR5: rom (CG0, bits[1:0]) -*(uint32_t*)0x400FC080 = 0x00000000; // CCM_CCGR6: не нужен для DCD - -// PLL_SYS: 528 MHz (24 MHz * 22), DIV_SELECT=1 → Fvco=528MHz -*(uint32_t*)0x400D8030 = 0x00002001; - -// Ждём lock PLL_SYS (bit 31 = LOCK) перед переключением источника тактирования (fix 3) -(*(uint32_t*)0x400D8030 & 0x80000000) != 0; - -// PFD2 = 528 * 18 / 0x23(35) = ~271 MHz... нет. -// PFD528[PFD2_FRAC] = 0x23 = 35 → Fout = 528 * 18 / 35 = ~271? -// Фактически 0x00230000 → PFD2_FRAC = 0x23 = 35 → F = 528*18/35 ≈ 271 МГц -// Но для SEMC @ 133 МГц нужен делитель CCM_CBCDR[SEMC_PODF]. -// Проверь соответствие с clock_config.c! -*(uint32_t*)0x400D8100 = 0x00230000; // CCM_ANALOG_PFD_528: PFD2 = 133 MHz - -// CCM_CBCDR: SEMC_CLK_SEL=1 (PLL2_PFD2), SEMC_ALT_CLK_SEL=0, SEMC_PODF=1 (делитель /2) -// → SEMC clock = 133 / 2 = 66.5 МГц -// Если нужно 133 МГц — SEMC_PODF=0 (делитель /1), но убедись что SDRAM выдержит -*(uint32_t*)0x400FC014 = 0x00010D40; // CCM_CBCDR - -// ============================================================================= -// SDRAM Init — Configure IOMUXC for SEMC pins -// MT48LC16M16A2: 16Mx16bit, 4 Banks, LVTTL -// ============================================================================= - -// IOMUXC — MUX mode: все пины SEMC в режиме ALT0 (SEMC функция) -*(uint32_t*)0x401F8014 = 0x00000000; -*(uint32_t*)0x401F8018 = 0x00000000; -*(uint32_t*)0x401F801C = 0x00000000; -*(uint32_t*)0x401F8020 = 0x00000000; -*(uint32_t*)0x401F8024 = 0x00000000; -*(uint32_t*)0x401F8028 = 0x00000000; -*(uint32_t*)0x401F802C = 0x00000000; -*(uint32_t*)0x401F8030 = 0x00000000; -*(uint32_t*)0x401F8034 = 0x00000000; -*(uint32_t*)0x401F8038 = 0x00000000; -*(uint32_t*)0x401F803C = 0x00000000; -*(uint32_t*)0x401F8040 = 0x00000000; -*(uint32_t*)0x401F8044 = 0x00000000; -*(uint32_t*)0x401F8048 = 0x00000000; -*(uint32_t*)0x401F804C = 0x00000000; -*(uint32_t*)0x401F8050 = 0x00000000; -*(uint32_t*)0x401F8054 = 0x00000000; -*(uint32_t*)0x401F8058 = 0x00000000; -*(uint32_t*)0x401F805C = 0x00000000; -*(uint32_t*)0x401F8060 = 0x00000000; -*(uint32_t*)0x401F8064 = 0x00000000; -*(uint32_t*)0x401F8068 = 0x00000000; -*(uint32_t*)0x401F806C = 0x00000000; -*(uint32_t*)0x401F8070 = 0x00000000; -*(uint32_t*)0x401F8074 = 0x00000000; -*(uint32_t*)0x401F8078 = 0x00000000; -*(uint32_t*)0x401F807C = 0x00000000; -*(uint32_t*)0x401F8080 = 0x00000000; -*(uint32_t*)0x401F8084 = 0x00000000; -*(uint32_t*)0x401F8088 = 0x00000000; -*(uint32_t*)0x401F808C = 0x00000000; -*(uint32_t*)0x401F8090 = 0x00000000; -*(uint32_t*)0x401F8094 = 0x00000000; -*(uint32_t*)0x401F8098 = 0x00000000; -*(uint32_t*)0x401F809C = 0x00000000; -*(uint32_t*)0x401F80A0 = 0x00000000; -*(uint32_t*)0x401F80A4 = 0x00000000; -*(uint32_t*)0x401F80A8 = 0x00000000; -*(uint32_t*)0x401F80AC = 0x00000000; -*(uint32_t*)0x401F80B0 = 0x00000010; // SEMC_DATA15: SION=1 (software input on) -*(uint32_t*)0x401F80B4 = 0x00000000; -*(uint32_t*)0x401F80B8 = 0x00000000; - -// IOMUXC — PAD ctrl: 0x000110F9 -// SRE=1 (Fast Slew Rate) -// DSE=7 (Drive Strength: R0/7 — максимальный) -// SPEED=3 (200 MHz) -// ODE=0 (Open Drain disabled) -// PKE=1 (Pull/Keeper enabled) -// PUE=1 (Pull — не Keeper) -// PUS=0 (100K pull-down) -// HYS=0 (Hysteresis disabled) -*(uint32_t*)0x401F8204 = 0x000110F9; -*(uint32_t*)0x401F8208 = 0x000110F9; -*(uint32_t*)0x401F820C = 0x000110F9; -*(uint32_t*)0x401F8210 = 0x000110F9; -*(uint32_t*)0x401F8214 = 0x000110F9; -*(uint32_t*)0x401F8218 = 0x000110F9; -*(uint32_t*)0x401F821C = 0x000110F9; -*(uint32_t*)0x401F8220 = 0x000110F9; -*(uint32_t*)0x401F8224 = 0x000110F9; -*(uint32_t*)0x401F8228 = 0x000110F9; -*(uint32_t*)0x401F822C = 0x000110F9; -*(uint32_t*)0x401F8230 = 0x000110F9; -*(uint32_t*)0x401F8234 = 0x000110F9; -*(uint32_t*)0x401F8238 = 0x000110F9; -*(uint32_t*)0x401F823C = 0x000110F9; -*(uint32_t*)0x401F8240 = 0x000110F9; -*(uint32_t*)0x401F8244 = 0x000110F9; -*(uint32_t*)0x401F8248 = 0x000110F9; -*(uint32_t*)0x401F824C = 0x000110F9; -*(uint32_t*)0x401F8250 = 0x000110F9; -*(uint32_t*)0x401F8254 = 0x000110F9; -*(uint32_t*)0x401F8258 = 0x000110F9; -*(uint32_t*)0x401F825C = 0x000110F9; -*(uint32_t*)0x401F8260 = 0x000110F9; -*(uint32_t*)0x401F8264 = 0x000110F9; -*(uint32_t*)0x401F8268 = 0x000110F9; -*(uint32_t*)0x401F826C = 0x000110F9; -*(uint32_t*)0x401F8270 = 0x000110F9; -*(uint32_t*)0x401F8274 = 0x000110F9; -*(uint32_t*)0x401F8278 = 0x000110F9; -*(uint32_t*)0x401F827C = 0x000110F9; -*(uint32_t*)0x401F8280 = 0x000110F9; -*(uint32_t*)0x401F8284 = 0x000110F9; -*(uint32_t*)0x401F8288 = 0x000110F9; -*(uint32_t*)0x401F828C = 0x000110F9; -*(uint32_t*)0x401F8290 = 0x000110F9; -*(uint32_t*)0x401F8294 = 0x000110F9; -*(uint32_t*)0x401F8298 = 0x000110F9; -*(uint32_t*)0x401F829C = 0x000110F9; -*(uint32_t*)0x401F82A0 = 0x000110F9; -*(uint32_t*)0x401F82A4 = 0x000110F9; -*(uint32_t*)0x401F82A8 = 0x000110F9; - -// ============================================================================= -// SEMC Controller — конфигурация для MT48LC16M16A2 -// Базовый адрес SDRAM: 0x80000000 -// Размер: 32 MB (MT48LC16M16A2: 16M × 16bit = 32 MB) -// ============================================================================= - -// SEMC_MCR: MDIS=0, BTO=0x1F (timeout), DQSMD=1 (DQS internal loop) -*(uint32_t*)0x402F0000 = 0x10000004; - -// SEMC_BMCR0 & BMCR1: burst весовые коэффициенты -// 0x00000081: WQOS=1, RQOS=0, AGE=0, BSTLEN=0x10 (16 burst) -*(uint32_t*)0x402F0008 = 0x00000081; // SEMC_BMCR0 -*(uint32_t*)0x402F000C = 0x00000081; // SEMC_BMCR1 - -// SEMC_BR0..BR7 — Base Register для каждого region -// BR0: SDRAM @ 0x80000000, размер 32MB (MR=0x1B → 32MB), VLD=1 -*(uint32_t*)0x402F0010 = 0x8000001B; // BR0: SDRAM CS0, 0x80000000, 32MB -*(uint32_t*)0x402F0014 = 0x82000000; // BR1: SDRAM CS1 (если есть) -*(uint32_t*)0x402F0018 = 0x84000000; // BR2 -*(uint32_t*)0x402F001C = 0x86000000; // BR3 -*(uint32_t*)0x402F0020 = 0x90000021; // BR4: NAND -*(uint32_t*)0x402F0024 = 0xA0000019; // BR5: NOR/SRAM -*(uint32_t*)0x402F0028 = 0xA8000017; // BR6 -*(uint32_t*)0x402F002C = 0xA900001B; // BR7 -*(uint32_t*)0x402F0030 = 0x00000021; // BR8 - -// SEMC_INTEN: прерывания отключены -*(uint32_t*)0x402F0004 = 0x000079A8; - -// SEMC_SDRAMCR0: тайминги MT48LC16M16A2 @ 133MHz -// PS=1 (16-bit data bus), BL=3 (Burst Length 8), COL=2 (9-bit col addr), -// CL=3 (CAS Latency 3) -*(uint32_t*)0x402F0040 = 0x00000F31; - -// SEMC_SDRAMCR1 -// PRE2ACT=2, ACT2RW=2, RFRC=9 (tRFC), WRC=2, CKEOFF=2, ACT2PRE=6 -*(uint32_t*)0x402F0044 = 0x00652922; - -// SEMC_SDRAMCR2: Refresh rate -// MT48LC16M16A2: 4096 refresh cycles / 64ms → ~15.6 мкс @ 133MHz -// SRRC=0x02 (самообновление), REF2REF=0x02, ACT2ACT=0x01 -*(uint32_t*)0x402F0048 = 0x00020201; - -// SEMC_SDRAMCR3 -// REBL=1, PRESCALE=0x21, RT=0x3D, UT=0x0F -*(uint32_t*)0x402F004C = 0x08193D0F; - -// SEMC_IOCR -*(uint32_t*)0x402F0080 = 0x00000021; -*(uint32_t*)0x402F0084 = 0x00888888; - -// SEMC_IPTXDAT / IPRXDAT placeholder -*(uint32_t*)0x402F0094 = 0x00000002; -*(uint32_t*)0x402F0098 = 0x00000000; - -// ============================================================================= -// SDRAM Init Sequence (JEDEC: Precharge All → Auto-Refresh × 2 → Mode Register Set) -// ============================================================================= - -// SEMC_IPCR0: IP command target адрес = начало SDRAM -*(uint32_t*)0x402F0090 = 0x80000000; - -// IPCMD: Precharge All (команда 0xF) -*(uint32_t*)0x402F009C = 0xA55A000F; -// Ждём завершения: SEMC_INTR[IPCMDDONE] (bit 0) -(*(uint32_t*)0x402F003C & 0x01) != 0; - -// IPCMD: Auto-Refresh #1 (команда 0xC) -*(uint32_t*)0x402F0090 = 0x80000000; -*(uint32_t*)0x402F009C = 0xA55A000C; -(*(uint32_t*)0x402F003C & 0x01) != 0; - -// IPCMD: Auto-Refresh #2 -*(uint32_t*)0x402F0090 = 0x80000000; -*(uint32_t*)0x402F009C = 0xA55A000C; -(*(uint32_t*)0x402F003C & 0x01) != 0; - -// Mode Register Set: -// MRS = 0x33: Burst Length=8 (BL=3), Burst Type=Sequential, CAS Latency=3 -*(uint32_t*)0x402F00A0 = 0x00000033; -*(uint32_t*)0x402F0090 = 0x80000000; -// IPCMD: Mode Register Set (команда 0xA) -*(uint32_t*)0x402F009C = 0xA55A000A; -(*(uint32_t*)0x402F003C & 0x01) != 0; - -// SEMC_SDRAMCR3: финальное значение Refresh Rate после инит-последовательности -// Переключаемся с медленного (init) refresh на рабочий -*(uint32_t*)0x402F004C = 0x50210A09; \ No newline at end of file diff --git a/tools/host/dcd/ivt_flashloader.bin b/tools/host/dcd/ivt_flashloader.bin new file mode 100644 index 0000000000000000000000000000000000000000..cab033085b3a074b9a0cb9eecf07c34ad781ddaa GIT binary patch literal 87104 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%0AN;b$zD1Y-(ELFQx5Pzr*V>{2{eW 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 z zu%iXN7jq0#60?9%Ir65(gi_?Tb1-iq--bD>UzfdDFLK`f6y}LRH1owN0rMRS|D5PN z+#n@JBQFzW8si&e31P`jnr#RPBMM;UeH0<)M4E~)*9-%e4w|i1G?EUDF%MDFj7C>( z1aakUgb|$4NI0GoIT#GDTQeSnj$3OEA_f5#d6JV3Sv&P+(0X z@*?5nfa03V)z11&<_MQ@wO4B7KN|g{7OQn<;9~zq&Wug*8V-k~MBPIUJ zQL$lhvEdRvBPBL!j!B%(j!T>o6&5N{F`-P4sc3+DLXwh&6dU{0RC}8gAvuX;`Uhr( zh2+?zq}nGDAvswfN%pBs zD#eE3jK|8D43lMbSlz+&VEMuN2Ddl(eg@y~;NLU&_rjox=S7YeE#y?G@$z&V;yz@k zg}J$fAPTlJfpt|&U2g#%SCaRl>3Mb5P)Y!4op|2C_Xo}~1c?V^7oi-9u_mI7DWOaT zx%qnd)<^zMAI-SOca5mH;XM*^r+NutbG;Y2^dKU(zo<@JkQ;*Z05$ zytDmhozVXp#i}a-8h|uxv0;o2P0tEwz!_M~2DCm&3RDTu0HilFid+L2C=aUPI^IRG z&XQ+v)O)OgeuA|I^EJK#5Dz>YxfG9`Rb>frZ8+ABlz`xl*1n4M$v__P4Djd^9)xjU zB6x~Hyt8OSx}^d7ydfu%|K?&ea>SvJf0NZEgn~z(Ji~^rdVn`G?aU_<6lO$lXfwOg96gVnOaifJK$c2H!q+;-r9|t-L za!f-wuE~;y>13=B0~vhNz-Q_xlu#0h(pW%(B=M}Yrx1qwU{ZBc5`B2VGLsc0!C(LW=2Li21XVJ1}2aIk${nf9Z6*a5}PpdHH!onn-Pl|OeHG| ziwcYlqS=6MK?Vvy^~iif7DfeRz7dNA2a6K22zA*BXzoFGFLiAr!Zb#p8UZ5I;ZOnq DaM1;p diff --git a/tools/host/fdcb/fcb_w25q128_quad.yaml b/tools/host/fdcb/fcb_w25q128_quad.yaml deleted file mode 100644 index 53ac54f..0000000 --- a/tools/host/fdcb/fcb_w25q128_quad.yaml +++ /dev/null @@ -1,152 +0,0 @@ -# ============================================================================= -# FCB — Flash Configuration Block -# Чип: Winbond W25Q128FVSSIG (64 Mbit = 8 MB), Quad SPI -# Режим: Fast Read Quad I/O (0xEB), 133 MHz, 4-line -# Цель: MIMXRT1052CVJ5B (family: mimxrt1050 в SPSDK 3.7) -# -# LUT-последовательности: -# Seq 0: Fast Read Quad I/O (0xEB) — основное XIP-чтение -# Seq 1: Read Status Reg 1 (0x05) — опрос WIP бита -# Seq 2: Write Enable (0x06) — перед любой записью/стиранием -# Seq 3: Erase Sector 4KB (0x20) — стирание сектора -# Seq 4: Write SR2 (0x31) — установка QE бита (вызов при старте) -# Seq 5: Page Program Quad (0x32) — запись страницы в Quad режиме -# Seq 11: Chip Erase (0x60) — полное стирание чипа -# Seq 12: Read Status Reg 1 (0x05) — дублирует seq1, для blhost -# ============================================================================= - -family: mimxrt1050 -revision: latest -type: flexspi_nor - -fcb_settings: - tag: '0x42464346' # 'FCFB' — magic, фиксировано - version: '0x56010400' # Version 1.4 (актуально для SPSDK 3.7) - - readSampleClkSrc: '0x01' # loopback from DQS pad - csHoldTime: '0x03' - csSetupTime: '0x03' - columnAddressWidth: '0x03' - - # --------------------------------------------------------------------------- - # QE bit enable — W25Q128 Status Register 2, bit 1 - # BootROM выполнит Write Enable (seq 2) + Write SR2 (seq 4) при старте. - # deviceModeArg = 0x02 → SR2 = 0b00000010 (QE=1) - # waitTimeCfgCommands = 30 × 100us = 3ms (достаточно для записи SR) - # --------------------------------------------------------------------------- - deviceModeCfgEnable: '0x01' - deviceModeType: '0x01' - waitTimeCfgCommands: '0x001E' - deviceModeSeq: - seqNum: 1 - seqId: 4 # → Seq 4: Write SR2 (0x31) - deviceModeArg: '0x00000002' # SR2 = 0x02 → QE=1 - - deviceType: '0x01' # Serial NOR - sflashPadType: '0x04' # Quad — 4 линии данных (IO0..IO3) - serialClkFreq: '0x07' # 133 MHz - sflashA1Size: '0x01000000' # 16 MB — W25Q128FVSSIG (128 Mbit / 8) - - pageSize: '0x00000100' # 256 байт — стандарт для W25Q128 - sectorSize: '0x00001000' # 4 KB — минимальная единица стирания - - # --------------------------------------------------------------------------- - # LUT — Look-Up Table - # Каждая последовательность: 8 инструкций (instr0..instr7) - # Неиспользуемые инструкции = STOP (значение по умолчанию, можно не писать) - # --------------------------------------------------------------------------- - - # ── Seq 0: Fast Read Quad I/O (0xEB) ─────────────────────────────────────── - # CMD(×1) 0xEB → RADDR(×4) 24bit → DUMMY(×4) 6cycles → READ(×4) - lookupTable_seq0_instr0: - OPERAND: 0xEB - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - lookupTable_seq0_instr1: - OPERAND: 0x18 # 24-bit address - NUM_PADS: 4PAD - OPERATION_CODE: RADDR_SDR - lookupTable_seq0_instr2: - OPERAND: 0x06 # 6 dummy cycles @ 133MHz (4 mode + 2 dummy) - NUM_PADS: 4PAD - OPERATION_CODE: DUMMY_SDR - lookupTable_seq0_instr3: - OPERAND: 0x04 - NUM_PADS: 4PAD - OPERATION_CODE: READ_SDR - - # ── Seq 1: Read Status Register 1 (0x05) ─────────────────────────────────── - # CMD(×1) 0x05 → READ(×1) 1byte - lookupTable_seq1_instr0: - OPERAND: 0x05 - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - lookupTable_seq1_instr1: - OPERAND: 0x04 - NUM_PADS: 1PAD - OPERATION_CODE: READ_SDR - - # ── Seq 2: Write Enable (0x06) ────────────────────────────────────────────── - # CMD(×1) 0x06 - lookupTable_seq2_instr0: - OPERAND: 0x06 - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - - # ── Seq 3: Erase Sector 4KB (0x20) ───────────────────────────────────────── - # CMD(×1) 0x20 → RADDR(×4) 24bit - lookupTable_seq3_instr0: - OPERAND: 0x20 - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - lookupTable_seq3_instr1: - OPERAND: 0x18 - NUM_PADS: 4PAD - OPERATION_CODE: RADDR_SDR - - # ── Seq 4: Write Status Register 2 (0x31) — установка QE бита ────────────── - # CMD(×1) 0x31 → WRITE(×1) 1byte - # Вызывается через deviceModeSeq при инициализации BootROM. - # deviceModeArg (0x02) записывается как аргумент. - lookupTable_seq4_instr0: - OPERAND: 0x31 - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - lookupTable_seq4_instr1: - OPERAND: 0x01 # записать 1 байт - NUM_PADS: 1PAD - OPERATION_CODE: WRITE_SDR - - # ── Seq 5: Page Program Quad Input (0x32) ────────────────────────────────── - # CMD(×1) 0x32 → RADDR(×1) 24bit → WRITE(×4) - lookupTable_seq5_instr0: - OPERAND: 0x32 - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - lookupTable_seq5_instr1: - OPERAND: 0x18 - NUM_PADS: 1PAD - OPERATION_CODE: RADDR_SDR - lookupTable_seq5_instr2: - OPERAND: 0x04 - NUM_PADS: 4PAD - OPERATION_CODE: WRITE_SDR - - # Seq 6..10: не используются (STOP по умолчанию) - - # ── Seq 11: Chip Erase (0x60) ─────────────────────────────────────────────── - # CMD(×1) 0x60 - lookupTable_seq11_instr0: - OPERAND: 0x60 - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - - # ── Seq 12: Read Status Register 1 (дубль для blhost) ────────────────────── - lookupTable_seq12_instr0: - OPERAND: 0x05 - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - lookupTable_seq12_instr1: - OPERAND: 0x04 - NUM_PADS: 1PAD - OPERATION_CODE: READ_SDR \ No newline at end of file diff --git a/tools/host/fdcb/fcb_w25q64_quad.yaml b/tools/host/fdcb/fcb_w25q64_quad.yaml deleted file mode 100644 index f34731e..0000000 --- a/tools/host/fdcb/fcb_w25q64_quad.yaml +++ /dev/null @@ -1,152 +0,0 @@ -# ============================================================================= -# FCB — Flash Configuration Block -# Чип: Winbond W25Q64FVSSIG (64 Mbit = 8 MB), Quad SPI -# Режим: Fast Read Quad I/O (0xEB), 133 MHz, 4-line -# Цель: MIMXRT1052CVJ5B (family: mimxrt1050 в SPSDK 3.7) -# -# LUT-последовательности: -# Seq 0: Fast Read Quad I/O (0xEB) — основное XIP-чтение -# Seq 1: Read Status Reg 1 (0x05) — опрос WIP бита -# Seq 2: Write Enable (0x06) — перед любой записью/стиранием -# Seq 3: Erase Sector 4KB (0x20) — стирание сектора -# Seq 4: Write SR2 (0x31) — установка QE бита (вызов при старте) -# Seq 5: Page Program Quad (0x32) — запись страницы в Quad режиме -# Seq 11: Chip Erase (0x60) — полное стирание чипа -# Seq 12: Read Status Reg 1 (0x05) — дублирует seq1, для blhost -# ============================================================================= - -family: mimxrt1050 -revision: latest -type: flexspi_nor - -fcb_settings: - tag: '0x42464346' # 'FCFB' — magic, фиксировано - version: '0x56010400' # Version 1.4 (актуально для SPSDK 3.7) - - readSampleClkSrc: '0x01' # loopback from DQS pad - csHoldTime: '0x03' - csSetupTime: '0x03' - columnAddressWidth: '0x03' - - # --------------------------------------------------------------------------- - # QE bit enable — W25Q64 Status Register 2, bit 1 - # BootROM выполнит Write Enable (seq 2) + Write SR2 (seq 4) при старте. - # deviceModeArg = 0x02 → SR2 = 0b00000010 (QE=1) - # waitTimeCfgCommands = 30 × 100us = 3ms (достаточно для записи SR) - # --------------------------------------------------------------------------- - deviceModeCfgEnable: '0x01' - deviceModeType: '0x01' - waitTimeCfgCommands: '0x001E' - deviceModeSeq: - seqNum: 1 - seqId: 4 # → Seq 4: Write SR2 (0x31) - deviceModeArg: '0x00000002' # SR2 = 0x02 → QE=1 - - deviceType: '0x01' # Serial NOR - sflashPadType: '0x04' # Quad — 4 линии данных (IO0..IO3) - serialClkFreq: '0x07' # 133 MHz - sflashA1Size: '0x00800000' # 8 MB — W25Q64FVSSIG (64 Mbit / 8) - - pageSize: '0x00000100' # 256 байт — стандарт для W25Q64 - sectorSize: '0x00001000' # 4 KB — минимальная единица стирания - - # --------------------------------------------------------------------------- - # LUT — Look-Up Table - # Каждая последовательность: 8 инструкций (instr0..instr7) - # Неиспользуемые инструкции = STOP (значение по умолчанию, можно не писать) - # --------------------------------------------------------------------------- - - # ── Seq 0: Fast Read Quad I/O (0xEB) ─────────────────────────────────────── - # CMD(×1) 0xEB → RADDR(×4) 24bit → DUMMY(×4) 6cycles → READ(×4) - lookupTable_seq0_instr0: - OPERAND: 0xEB - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - lookupTable_seq0_instr1: - OPERAND: 0x18 # 24-bit address - NUM_PADS: 4PAD - OPERATION_CODE: RADDR_SDR - lookupTable_seq0_instr2: - OPERAND: 0x06 # 6 dummy cycles @ 133MHz (4 mode + 2 dummy) - NUM_PADS: 4PAD - OPERATION_CODE: DUMMY_SDR - lookupTable_seq0_instr3: - OPERAND: 0x04 - NUM_PADS: 4PAD - OPERATION_CODE: READ_SDR - - # ── Seq 1: Read Status Register 1 (0x05) ─────────────────────────────────── - # CMD(×1) 0x05 → READ(×1) 1byte - lookupTable_seq1_instr0: - OPERAND: 0x05 - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - lookupTable_seq1_instr1: - OPERAND: 0x04 - NUM_PADS: 1PAD - OPERATION_CODE: READ_SDR - - # ── Seq 2: Write Enable (0x06) ────────────────────────────────────────────── - # CMD(×1) 0x06 - lookupTable_seq2_instr0: - OPERAND: 0x06 - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - - # ── Seq 3: Erase Sector 4KB (0x20) ───────────────────────────────────────── - # CMD(×1) 0x20 → RADDR(×4) 24bit - lookupTable_seq3_instr0: - OPERAND: 0x20 - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - lookupTable_seq3_instr1: - OPERAND: 0x18 - NUM_PADS: 4PAD - OPERATION_CODE: RADDR_SDR - - # ── Seq 4: Write Status Register 2 (0x31) — установка QE бита ────────────── - # CMD(×1) 0x31 → WRITE(×1) 1byte - # Вызывается через deviceModeSeq при инициализации BootROM. - # deviceModeArg (0x02) записывается как аргумент. - lookupTable_seq4_instr0: - OPERAND: 0x31 - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - lookupTable_seq4_instr1: - OPERAND: 0x01 # записать 1 байт - NUM_PADS: 1PAD - OPERATION_CODE: WRITE_SDR - - # ── Seq 5: Page Program Quad Input (0x32) ────────────────────────────────── - # CMD(×1) 0x32 → RADDR(×1) 24bit → WRITE(×4) - lookupTable_seq5_instr0: - OPERAND: 0x32 - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - lookupTable_seq5_instr1: - OPERAND: 0x18 - NUM_PADS: 1PAD - OPERATION_CODE: RADDR_SDR - lookupTable_seq5_instr2: - OPERAND: 0x04 - NUM_PADS: 4PAD - OPERATION_CODE: WRITE_SDR - - # Seq 6..10: не используются (STOP по умолчанию) - - # ── Seq 11: Chip Erase (0x60) ─────────────────────────────────────────────── - # CMD(×1) 0x60 - lookupTable_seq11_instr0: - OPERAND: 0x60 - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - - # ── Seq 12: Read Status Register 1 (дубль для blhost) ────────────────────── - lookupTable_seq12_instr0: - OPERAND: 0x05 - NUM_PADS: 1PAD - OPERATION_CODE: CMD_SDR - lookupTable_seq12_instr1: - OPERAND: 0x04 - NUM_PADS: 1PAD - OPERATION_CODE: READ_SDR \ No newline at end of file diff --git a/tools/host/flash_usb.py b/tools/host/flash_usb.py new file mode 100644 index 0000000..8564e95 --- /dev/null +++ b/tools/host/flash_usb.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +""" +flash_usb.py — прошивка MIMXRT1052 через USB (BootROM SDP → Flashloader → blhost) + +Использование: + python3 flash_usb.py --firmware firmware_test --build-type Debug + python3 flash_usb.py --firmware app --build-type Release + python3 flash_usb.py --firmware bootloader --build-type Release + python3 flash_usb.py --firmware firmware_test --build-type Debug --ram-only + +Шаги: + 1. Устройство в SDP режиме (VID:PID 1FC9:0130) + → sdphost загружает ivt_flashloader.bin в RAM + → sdphost прыгает на flashloader + 2. Flashloader запущен (VID:PID 15A2:0073) + → blhost конфигурирует FlexSPI NOR (пишет FCB) + → blhost стирает нужный регион Flash + → blhost пишет HAB образ начиная с 0x60002000 + → blhost reset +""" + +import argparse +import subprocess +import sys +import time +from pathlib import Path + +# ─── Константы ──────────────────────────────────────────────────────────────── +SCRIPT_DIR = Path(__file__).parent.resolve() +REPO_ROOT = SCRIPT_DIR.parent.parent +BUILD_DIR = REPO_ROOT / "build" +FLASHLOADER = SCRIPT_DIR / "dcd" / "ivt_flashloader.bin" + +# USB VID:PID +SDP_USB = "0x1FC9,0x0130" # BootROM Serial Download Protocol +BLHOST_USB = "0x15A2,0x0073" # Flashloader запущен + +# FlexSPI NOR config option word: 0xC0000007 +# bits[31:28]=0xC — tag (QuadSPI NOR) +# bits[3:0]=0x7 — option size +FLEXSPI_OPTION_ADDR = "0x2000" +FLEXSPI_OPTION_VALUE = "0xC0000007" +FLEXSPI_MEMORY_ID = "9" # FlexSPI NOR memory interface ID + +# Flash layout +FLASH_BASE = 0x60000000 +HAB_OFFSET = 0x1000 # IVT offset: HAB binary starts with IVT at byte 0 + # write address = FLASH_BASE + ivtOffset = 0x60001000 + + +def run(cmd: list[str], check: bool = True) -> subprocess.CompletedProcess: + print(f" $ {' '.join(cmd)}") + result = subprocess.run(cmd, capture_output=False) + if check and result.returncode != 0: + print(f"\n[ERROR] Команда завершилась с кодом {result.returncode}") + sys.exit(1) + return result + + +def step(msg: str) -> None: + print(f"\n{'─'*60}") + print(f" {msg}") + print(f"{'─'*60}") + + +def wait_for_flashloader(timeout: int = 10) -> bool: + """Ждём пока Flashloader поднимется (VID:PID 15A2:0073)""" + print(f"\n Ожидание Flashloader (до {timeout}с)...", end="", flush=True) + for i in range(timeout): + time.sleep(1) + result = subprocess.run( + ["blhost", "-u", BLHOST_USB, "-j", "--", "get-property", "1", "0"], + capture_output=True + ) + if result.returncode == 0: + print(f" OK ({i+1}с)") + return True + print(".", end="", flush=True) + print(" TIMEOUT") + return False + + +def load_flashloader() -> None: + """Загружает Flashloader через SDP если ещё не запущен""" + # Проверяем — вдруг уже запущен + result = subprocess.run( + ["blhost", "-u", BLHOST_USB, "-j", "--", "get-property", "1", "0"], + capture_output=True + ) + if result.returncode == 0: + print(" Flashloader уже запущен — пропускаем загрузку") + return + + if not FLASHLOADER.exists(): + print(f"[ERROR] Не найден: {FLASHLOADER}") + print(" Скачай ivt_flashloader.bin из MCUXpresso Secure Provisioning Tool") + print(" и положи в tools/host/dcd/ivt_flashloader.bin") + sys.exit(1) + + step("Загрузка Flashloader через SDP (1FC9:0130)") + run(["sdphost", "-u", SDP_USB, "-j", "--", + "write-file", "0x20001C00", str(FLASHLOADER)]) + run(["sdphost", "-u", SDP_USB, "-j", "--", + "jump-address", "0x20001C00"]) + + if not wait_for_flashloader(): + print("[ERROR] Flashloader не ответил. Проверь BOOT_MOD пины и подключение.") + sys.exit(1) + + +# Option word для записи FCB в Flash[0x60000000] +# 0xF000000F: tag=0xF → Write FCB command (Flashloader пишет готовый FCB в начало Flash) +FLEXSPI_FCB_VALUE = "0xF000000F" + + +def configure_flexspi() -> None: + """Инициализирует FlexSPI NOR контроллер через Flashloader""" + step("Конфигурация FlexSPI NOR (инициализация контроллера)") + run(["blhost", "-u", BLHOST_USB, "--", + "fill-memory", FLEXSPI_OPTION_ADDR, "4", FLEXSPI_OPTION_VALUE, "word"]) + run(["blhost", "-u", BLHOST_USB, "--", + "configure-memory", FLEXSPI_MEMORY_ID, FLEXSPI_OPTION_ADDR]) + + +def write_fcb() -> None: + """Записывает Flash Configuration Block в 0x60000000 + + Отдельный шаг после erase! Flashloader генерирует FCB из параметров FlexSPI + и пишет его по адресу 0x60000000. Без FCB BootROM не знает как читать Flash. + Option word 0xF000000F: tag=0xF → Write FCB command. + """ + step("Запись FCB в Flash[0x60000000]") + run(["blhost", "-u", BLHOST_USB, "--", + "fill-memory", FLEXSPI_OPTION_ADDR, "4", FLEXSPI_FCB_VALUE, "word"]) + run(["blhost", "-u", BLHOST_USB, "--", + "configure-memory", FLEXSPI_MEMORY_ID, FLEXSPI_OPTION_ADDR]) + + +def flash(hab_bin: Path, ram_only: bool = False) -> None: + if not hab_bin.exists(): + print(f"[ERROR] Файл не найден: {hab_bin}") + print(" Сначала собери образ: uv run nxpimage hab export -c ...") + sys.exit(1) + + if ram_only: + step(f"Загрузка в RAM (без записи во Flash): {hab_bin.name}") + # RAM-only: грузим в RAM прямо через SDP без flashloader + run(["sdphost", "-u", SDP_USB, "-j", "--", + "write-file", f"0x{FLASH_BASE + HAB_OFFSET:08X}", str(hab_bin)]) + run(["sdphost", "-u", SDP_USB, "-j", "--", + "jump-address", f"0x{FLASH_BASE + HAB_OFFSET:08X}"]) + return + + load_flashloader() + configure_flexspi() # 0xC0000007 — инициализация FlexSPI контроллера + + write_addr = f"0x{FLASH_BASE + HAB_OFFSET:08X}" + # Размер стирания: HAB_OFFSET (0x1000) + размер образа, выровнено вверх до 4KB + erase_size = ((HAB_OFFSET + hab_bin.stat().st_size + 0xFFF) // 0x1000) * 0x1000 + + step(f"Прошивка Flash: {hab_bin.name}") + print(f" Образ: {hab_bin}") + print(f" Размер: {hab_bin.stat().st_size} байт") + print(f" Адрес: {write_addr}") + print(f" Стирание: 0x{FLASH_BASE:08X} .. +{erase_size} байт") + + # memoryId=0 — mapped (XIP) адресное пространство + run(["blhost", "-u", BLHOST_USB, "--", + "flash-erase-region", f"0x{FLASH_BASE:08X}", + str(erase_size), "0"]) + + write_fcb() # 0xF000000F — запись FCB в 0x60000000 (после erase!) + + # memoryId=0 — mapped (XIP) адресное пространство + run(["blhost", "-u", BLHOST_USB, "--", + "write-memory", write_addr, str(hab_bin), "0"]) + + step("Reset") + run(["blhost", "-u", BLHOST_USB, "--", "reset"]) + print("\n ✓ Прошивка завершена успешно") + + +def main() -> None: + parser = argparse.ArgumentParser(description="Прошивка MIMXRT1052 через USB") + parser.add_argument("--firmware", required=True, + choices=["firmware_test", "bootloader", "app"], + help="Имя прошивки") + parser.add_argument("--build-type", required=True, + choices=["Debug", "Release"], + help="Тип сборки") + parser.add_argument("--ram-only", action="store_true", + help="Загрузить в RAM без записи во Flash") + args = parser.parse_args() + + # HAB образ собирается командой: nxpimage hab export -c hab_.yaml + # Выходной файл: build//_hab.bin + hab_bin = BUILD_DIR / args.build_type / f"{args.firmware}_hab.bin" + + print(f"\n{'═'*60}") + print(f" MIMXRT1052 Flash Tool") + print(f" Прошивка: {args.firmware} [{args.build_type}]") + print(f" Образ: {hab_bin}") + print(f"{'═'*60}") + + flash(hab_bin, ram_only=args.ram_only) + + +if __name__ == "__main__": + main() diff --git a/tools/host/hab/hab_app.yaml b/tools/host/hab/hab_app.yaml index 438fccf..a07f7d6 100644 --- a/tools/host/hab/hab_app.yaml +++ b/tools/host/hab/hab_app.yaml @@ -1,18 +1,20 @@ # ============================================================================= # HAB Container — firmware/app (боевая прошивка) +# Расположение: tools/host/hab/hab_app.yaml # -# flags: 0x0 — образ НЕ подписан -# DCD: обязателен — линкер размещает FreeRTOS heap и буферы LCDIF -# в SDRAM (0x80000000), которая должна быть инициализирована -# до первой инструкции прошивки. +# Сборка: +# cd tools/host/hab +# uv run nxpimage hab export --force -c hab_app.yaml \ +# -o ../../../build/Release/app_hab.bin # ============================================================================= options: - flags: 0x0 + flags: 0x00 + startAddress: 0x60000000 + ivtOffset: 0x1000 + initialLoadSize: 0x2000 family: mimxrt1050 - bootDevice: flexspi_nor - startAddress: 0x60000000 # FlexSPI NOR base address (XIP) - DCDFilePath: ../dcd/dcd.bin + DCDFilePath: ../dcd/dcd.bin # обязателен — heap FreeRTOS и LCDIF в SDRAM inputImageFile: "../../../build/Release/app.bin" diff --git a/tools/host/hab/hab_bootloader.yaml b/tools/host/hab/hab_bootloader.yaml index 971576a..f5e0786 100644 --- a/tools/host/hab/hab_bootloader.yaml +++ b/tools/host/hab/hab_bootloader.yaml @@ -1,18 +1,21 @@ # ============================================================================= # HAB Container — firmware/bootloader +# Расположение: tools/host/hab/hab_bootloader.yaml # -# flags: 0x0 — образ НЕ подписан -# DCD: отсутствует намеренно — загрузчик не использует SDRAM, -# минимальная зависимость от железа = максимальная надёжность. -# SDRAM инициализируется позже, уже внутри app через её DCD. +# Сборка: +# cd tools/host/hab +# uv run nxpimage hab export --force -c hab_bootloader.yaml \ +# -o ../../../build/Release/bootloader_hab.bin # ============================================================================= options: - flags: 0x0 + flags: 0x00 + startAddress: 0x60000000 + ivtOffset: 0x1000 + initialLoadSize: 0x2000 family: mimxrt1050 - bootDevice: flexspi_nor - startAddress: 0x60000000 # FlexSPI NOR base address (XIP) - + # DCD намеренно отсутствует — загрузчик не использует SDRAM + inputImageFile: "../../../build/Release/bootloader.bin" sections: [] \ No newline at end of file diff --git a/tools/host/hab/hab_firmware_test.yaml b/tools/host/hab/hab_firmware_test.yaml index 3ca10ec..e367f36 100644 --- a/tools/host/hab/hab_firmware_test.yaml +++ b/tools/host/hab/hab_firmware_test.yaml @@ -1,23 +1,22 @@ # ============================================================================= # HAB Container — firmware/test (входной контроль производства) +# Расположение: tools/host/hab/hab_firmware_test.yaml # -# flags: 0x0 — образ НЕ подписан (HAB в open/unsigned режиме) -# DCD: присутствует — инициализация SDRAM обязательна для тестов -# -# Важно: sections пустой — для unsigned образа CSF не нужен. -# BootROM примет образ без проверки подписи пока HAB не залочен (fuses). +# Сборка: +# cd tools/host/hab +# uv run nxpimage hab export --force -c hab_firmware_test.yaml \ +# -o ../../../build/Debug/firmware_test.bin # ============================================================================= options: - flags: 0x0 # unsigned — без подписи + flags: 0x00 # unsigned — без подписи + startAddress: 0x60000000 # FlexSPI NOR base (XIP) + ivtOffset: 0x1000 # IVT offset for FlexSPI NOR boot + initialLoadSize: 0x2000 # размер области IVT+BDT+DCD до кода family: mimxrt1050 - bootDevice: flexspi_nor # ivtOffset=0x1000, initialLoadSize=0x2000 — автоматически - startAddress: 0x60000000 # FlexSPI NOR base address (XIP) - - # DCD — инициализация SDRAM до передачи управления прошивке. - # Сгенерирован из dcd.cfg скриптом tools/dcd_cfg_to_bin.py - DCDFilePath: ../dcd/dcd.bin + DCDFilePath: ../dcd/dcd.bin # инициализация SDRAM +# .bin или .srec — оба принимаются inputImageFile: "../../../build/Debug/firmware_test.bin" sections: [] \ No newline at end of file diff --git a/tools/host/image/bootimg_app.yaml b/tools/host/image/bootimg_app.yaml deleted file mode 100644 index 8278d41..0000000 --- a/tools/host/image/bootimg_app.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# ============================================================================= -# Bootable Image — firmware/app (боевая прошивка) -# Расположение: tools/host/image/bootimg_app.yaml -# -# Пайплайн сборки: -# -# 1. FCB (один раз, из tools/host/fdcb/): -# nxpimage bootable-image fcb export -c fcb_w25q64_quad.yaml -o fcb.bin -# -# 2. Образ (из tools/host/image/): -# nxpimage bootable-image export -c bootimg_app.yaml -# -# 3. Проверка: -# nxpimage bootable-image parse -b ../../../../build/Release/app_bootable.bin -f mimxrt1050 -# ============================================================================= - -family: mimxrt1050 -revision: latest -memory_type: flexspi_nor - -output: "../../../build/Release/app_bootable.bin" -output_format: bin - -fcb: ../fdcb/fcb.bin - -hab_container: ../hab/hab_app.yaml \ No newline at end of file diff --git a/tools/host/image/bootimg_bootloader.yaml b/tools/host/image/bootimg_bootloader.yaml deleted file mode 100644 index 8c6d41b..0000000 --- a/tools/host/image/bootimg_bootloader.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# ============================================================================= -# Bootable Image — firmware/bootloader -# Расположение: tools/host/image/bootimg_bootloader.yaml -# -# Пайплайн сборки: -# -# 1. FCB (один раз, из tools/host/fdcb/): -# nxpimage bootable-image fcb export -c fcb_w25q64_quad.yaml -o fcb.bin -# -# 2. Образ (из tools/host/image/): -# nxpimage bootable-image export -c bootimg_bootloader.yaml -# -# 3. Проверка: -# nxpimage bootable-image parse -b ../../../../build/Release/bootloader_bootable.bin -f mimxrt1050 -# ============================================================================= - -family: mimxrt1050 -revision: latest -memory_type: flexspi_nor - -output: "../../../build/Release/bootloader_bootable.bin" -output_format: bin - -fcb: ../fdcb/fcb.bin - -hab_container: ../hab/hab_bootloader.yaml \ No newline at end of file diff --git a/tools/host/image/bootimg_firmware_test.yaml b/tools/host/image/bootimg_firmware_test.yaml deleted file mode 100644 index 4307b27..0000000 --- a/tools/host/image/bootimg_firmware_test.yaml +++ /dev/null @@ -1,36 +0,0 @@ -# ============================================================================= -# Bootable Image — firmware/test (входной контроль производства) -# Расположение: tools/host/image/bootimg_firmware_test.yaml -# -# Пайплайн сборки: -# -# 1. FCB (один раз, из tools/host/fdcb/): -# nxpimage bootable-image fcb export -c fcb_w25q64_quad.yaml -o fcb.bin -# -# 2. Образ (из tools/host/image/): -# nxpimage bootable-image export -c bootimg_firmware_test.yaml -# -# 3. Проверка: -# nxpimage bootable-image parse -b ../../../../build/Debug/firmware_test_bootable.bin -f mimxrt1050 -# -# Структура образа во Flash (0x60000000): -# 0x000 FCB (512 байт) -# 0x400 зарезервировано -# 0x1000 IVT ] -# 0x1020 BDT ] HAB container -# 0x1030 DCD ] -# 0x3000 код ] -# ============================================================================= - -family: mimxrt1050 -revision: latest -memory_type: flexspi_nor - -output: "../../../build/Debug/firmware_test_bootable.bin" -output_format: bin - -# FCB — собран из tools/host/fdcb/fcb_w25q64_quad.yaml -fcb: ../fdcb/fcb.bin - -# HAB container — YAML напрямую, export соберёт его сам -hab_container: ../hab/hab_firmware_test.yaml \ No newline at end of file