# bootloader: phase 0-1 + docs
This commit is contained in:
parent
49ae714955
commit
4644f21f07
14 changed files with 1308 additions and 9 deletions
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
|
@ -38,7 +38,7 @@
|
|||
"interface": "swd",
|
||||
"loadFiles": [],
|
||||
"runToEntryPoint": "main",
|
||||
"preLaunchTask": "build-and-rtt:firmware-test-debug",
|
||||
"preLaunchTask": "build:bootloader-debug",
|
||||
},
|
||||
// =============================================================
|
||||
// firmware/tft_app — FreeRTOS task view
|
||||
|
|
|
|||
|
|
@ -46,8 +46,7 @@ add_subdirectory(lib)
|
|||
# -----------------------------------------------------------------------------
|
||||
if(NOT BUILD_TESTS_HOST)
|
||||
add_subdirectory(firmware/test)
|
||||
# add_subdirectory(firmware/bootloader) - add_subdirectory(firmware/app) #
|
||||
# Загрузчик + Основное приложение
|
||||
add_subdirectory(firmware/bootloader)
|
||||
endif()
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -66,3 +66,12 @@ target_compile_definitions(
|
|||
|
||||
add_library(bsp_boot_ram INTERFACE)
|
||||
target_compile_definitions(bsp_boot_ram INTERFACE SKIP_SYSCLK_INIT)
|
||||
|
||||
# bsp_boot_xip без DCD — для firmware/bootloader: XIP из Flash, но без
|
||||
# инициализации SDRAM (bootloader SDRAM не использует, см.
|
||||
# docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md). В отличие от bsp_boot_xip не
|
||||
# определяет XIP_BOOT_HEADER_DCD_ENABLE.
|
||||
add_library(bsp_boot_xip_no_dcd INTERFACE)
|
||||
target_compile_definitions(
|
||||
bsp_boot_xip_no_dcd INTERFACE XIP_EXTERNAL_FLASH=1 XIP_BOOT_HEADER_ENABLE=1
|
||||
SKIP_SYSCLK_INIT)
|
||||
|
|
|
|||
271
cmake/linker/MIMXRT1052xxxxx_bootloader_flexspi_nor.ld
Normal file
271
cmake/linker/MIMXRT1052xxxxx_bootloader_flexspi_nor.ld
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
** ###################################################################
|
||||
** Processors: MIMXRT1052CVJ5B
|
||||
** MIMXRT1052CVL5B
|
||||
** MIMXRT1052DVJ6B
|
||||
** MIMXRT1052DVL6B
|
||||
**
|
||||
** Compiler: GNU C Compiler
|
||||
** Reference manual: IMXRT1050RM Rev.5, 07/2021 | IMXRT1050SRM Rev.2
|
||||
**
|
||||
** Abstract:
|
||||
** Linker file for firmware/bootloader.
|
||||
**
|
||||
** Вариант MIMXRT1052xxxxx_flexspi_nor.ld с m_text, ограниченным
|
||||
** бюджетом bootloader из docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md —
|
||||
** 256 KB от 0x60000000 (0x60000000..0x60040000). Slot A (tft_app)
|
||||
** начинается на 0x60040000 сразу за границей m_text. Превышение
|
||||
** бюджета — ошибка линковки (ASSERT ниже), а не тихий выход
|
||||
** кода bootloader за пределы своей области.
|
||||
**
|
||||
** Copyright 2016 Freescale Semiconductor, Inc.
|
||||
** Copyright 2016-2024 NXP
|
||||
** SPDX-License-Identifier: BSD-3-Clause
|
||||
** ###################################################################
|
||||
*/
|
||||
|
||||
/* Entry Point */
|
||||
ENTRY(Reset_Handler)
|
||||
|
||||
HEAP_SIZE = DEFINED(__heap_size__) ? __heap_size__ : 0x2000;
|
||||
STACK_SIZE = DEFINED(__stack_size__) ? __stack_size__ : 0x1000;
|
||||
VECTOR_RAM_SIZE = DEFINED(__ram_vector_table__) ? 0x00000400 : 0;
|
||||
|
||||
/* Specify the memory areas */
|
||||
MEMORY
|
||||
{
|
||||
m_flash_config (RX) : ORIGIN = 0x60000000, LENGTH = 0x00001000
|
||||
m_ivt (RX) : ORIGIN = 0x60001000, LENGTH = 0x00001000
|
||||
m_interrupts (RX) : ORIGIN = 0x60002000, LENGTH = 0x00000400
|
||||
m_text (RX) : ORIGIN = 0x60002400, LENGTH = 0x0003DC00 /* до 0x60040000 — граница Slot A */
|
||||
m_qacode (RX) : ORIGIN = 0x00000000, LENGTH = 0x00020000 /* SRAM_ITC 128KB */
|
||||
m_data (RW) : ORIGIN = 0x20000000, LENGTH = 0x00020000 /* SRAM_DTC 128KB */
|
||||
m_data2 (RW) : ORIGIN = 0x20200000, LENGTH = 0x00040000 /* SRAM_OC 256KB */
|
||||
}
|
||||
|
||||
/* Define output sections */
|
||||
SECTIONS
|
||||
{
|
||||
__NCACHE_REGION_START = ORIGIN(m_data2);
|
||||
__NCACHE_REGION_SIZE = 0x2000; /* 8 KB non-cacheable for USB DMA */
|
||||
|
||||
.flash_config :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
__FLASH_BASE = .;
|
||||
KEEP(* (.boot_hdr.conf)) /* flash config section */
|
||||
. = ALIGN(4);
|
||||
} > m_flash_config
|
||||
|
||||
ivt_begin = ORIGIN(m_flash_config) + LENGTH(m_flash_config);
|
||||
|
||||
.ivt : AT(ivt_begin)
|
||||
{
|
||||
. = ALIGN(4);
|
||||
KEEP(* (.boot_hdr.ivt)) /* ivt section */
|
||||
KEEP(* (.boot_hdr.boot_data)) /* boot section */
|
||||
KEEP(* (.boot_hdr.dcd_data)) /* dcd section (не используется bootloader — без DCD) */
|
||||
. = ALIGN(4);
|
||||
} > m_ivt
|
||||
|
||||
/* The startup code goes first into internal RAM */
|
||||
.interrupts :
|
||||
{
|
||||
__VECTOR_TABLE = .;
|
||||
__Vectors = .;
|
||||
. = ALIGN(4);
|
||||
KEEP(*(.isr_vector)) /* Startup code */
|
||||
. = ALIGN(4);
|
||||
} > m_interrupts
|
||||
|
||||
/* The program code and other data goes into internal RAM */
|
||||
.text :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
*(.text) /* .text sections (code) */
|
||||
*(.text*) /* .text* sections (code) */
|
||||
*(.rodata) /* .rodata sections (constants, strings, etc.) */
|
||||
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
|
||||
*(.glue_7) /* glue arm to thumb code */
|
||||
*(.glue_7t) /* glue thumb to arm code */
|
||||
*(.eh_frame)
|
||||
KEEP (*(.init))
|
||||
KEEP (*(.fini))
|
||||
. = ALIGN(4);
|
||||
} > m_text
|
||||
|
||||
.ARM.extab :
|
||||
{
|
||||
*(.ARM.extab* .gnu.linkonce.armextab.*)
|
||||
} > m_text
|
||||
|
||||
.ARM :
|
||||
{
|
||||
__exidx_start = .;
|
||||
*(.ARM.exidx*)
|
||||
__exidx_end = .;
|
||||
} > m_text
|
||||
|
||||
.ctors :
|
||||
{
|
||||
__CTOR_LIST__ = .;
|
||||
/* gcc uses crtbegin.o to find the start of
|
||||
the constructors, so we make sure it is
|
||||
first. Because this is a wildcard, it
|
||||
doesn't matter if the user does not
|
||||
actually link against crtbegin.o; the
|
||||
linker won't look for a file to match a
|
||||
wildcard. The wildcard also means that it
|
||||
doesn't matter which directory crtbegin.o
|
||||
is in. */
|
||||
KEEP (*crtbegin.o(.ctors))
|
||||
KEEP (*crtbegin?.o(.ctors))
|
||||
/* We don't want to include the .ctor section from
|
||||
from the crtend.o file until after the sorted ctors.
|
||||
The .ctor section from the crtend file contains the
|
||||
end of ctors marker and it must be last */
|
||||
KEEP (*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors))
|
||||
KEEP (*(SORT(.ctors.*)))
|
||||
KEEP (*(.ctors))
|
||||
__CTOR_END__ = .;
|
||||
} > m_text
|
||||
|
||||
.dtors :
|
||||
{
|
||||
__DTOR_LIST__ = .;
|
||||
KEEP (*crtbegin.o(.dtors))
|
||||
KEEP (*crtbegin?.o(.dtors))
|
||||
KEEP (*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors))
|
||||
KEEP (*(SORT(.dtors.*)))
|
||||
KEEP (*(.dtors))
|
||||
__DTOR_END__ = .;
|
||||
} > m_text
|
||||
|
||||
.preinit_array :
|
||||
{
|
||||
PROVIDE_HIDDEN (__preinit_array_start = .);
|
||||
KEEP (*(.preinit_array*))
|
||||
PROVIDE_HIDDEN (__preinit_array_end = .);
|
||||
} > m_text
|
||||
|
||||
.init_array :
|
||||
{
|
||||
PROVIDE_HIDDEN (__init_array_start = .);
|
||||
KEEP (*(SORT(.init_array.*)))
|
||||
KEEP (*(.init_array*))
|
||||
PROVIDE_HIDDEN (__init_array_end = .);
|
||||
} > m_text
|
||||
|
||||
.fini_array :
|
||||
{
|
||||
PROVIDE_HIDDEN (__fini_array_start = .);
|
||||
KEEP (*(SORT(.fini_array.*)))
|
||||
KEEP (*(.fini_array*))
|
||||
PROVIDE_HIDDEN (__fini_array_end = .);
|
||||
} > m_text
|
||||
|
||||
__etext = .; /* define a global symbol at end of code */
|
||||
__DATA_ROM = .; /* Symbol is used by startup for data initialization */
|
||||
|
||||
.interrupts_ram :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
__VECTOR_RAM__ = .;
|
||||
__interrupts_ram_start__ = .; /* Create a global symbol at data start */
|
||||
*(.m_interrupts_ram) /* This is a user defined section */
|
||||
. += VECTOR_RAM_SIZE;
|
||||
. = ALIGN(4);
|
||||
__interrupts_ram_end__ = .; /* Define a global symbol at data end */
|
||||
} > m_data
|
||||
|
||||
__VECTOR_RAM = DEFINED(__ram_vector_table__) ? __VECTOR_RAM__ : ORIGIN(m_interrupts);
|
||||
__RAM_VECTOR_TABLE_SIZE_BYTES = DEFINED(__ram_vector_table__) ? (__interrupts_ram_end__ - __interrupts_ram_start__) : 0x0;
|
||||
|
||||
.data : AT(__DATA_ROM)
|
||||
{
|
||||
. = ALIGN(4);
|
||||
__DATA_RAM = .;
|
||||
__data_start__ = .; /* create a global symbol at data start */
|
||||
*(.data) /* .data sections */
|
||||
*(.data*) /* .data* sections */
|
||||
*(DataQuickAccess) /* quick access data section */
|
||||
KEEP(*(.jcr*))
|
||||
. = ALIGN(4);
|
||||
__data_end__ = .; /* define a global symbol at data end */
|
||||
} > m_data
|
||||
|
||||
__ram_function_flash_start = __DATA_ROM + (__data_end__ - __data_start__); /* Symbol is used by startup for TCM data initialization */
|
||||
|
||||
.ram_function : AT(__ram_function_flash_start)
|
||||
{
|
||||
. = ALIGN(32);
|
||||
__ram_function_start__ = .;
|
||||
*(CodeQuickAccess)
|
||||
. = ALIGN(128);
|
||||
__ram_function_end__ = .;
|
||||
} > m_qacode
|
||||
|
||||
__NDATA_ROM = __ram_function_flash_start + (__ram_function_end__ - __ram_function_start__);
|
||||
.ncache.init :
|
||||
{
|
||||
. = ALIGN(32);
|
||||
__noncachedata_start__ = .;
|
||||
*(NonCacheable.init)
|
||||
. = ALIGN(4);
|
||||
__noncachedata_init_end__ = .;
|
||||
} > m_data2
|
||||
. = __noncachedata_init_end__;
|
||||
.ncache :
|
||||
{
|
||||
*(NonCacheable)
|
||||
. = ALIGN(4);
|
||||
__noncachedata_end__ = .;
|
||||
} > m_data2
|
||||
|
||||
__DATA_END = __NDATA_ROM;
|
||||
text_end = ORIGIN(m_text) + LENGTH(m_text);
|
||||
ASSERT(__DATA_END <= text_end, "region m_text overflowed with text and data")
|
||||
ASSERT(text_end <= 0x60040000, "bootloader вышел за пределы бюджета 256 KB (граница Slot A, см. BOOTLOADER_FLASH_MAP.md)")
|
||||
ASSERT((__noncachedata_end__ - ORIGIN(m_data2)) <= LENGTH(m_data2), "m_data2 ncache overflow")
|
||||
|
||||
/* Uninitialized data section */
|
||||
.bss :
|
||||
{
|
||||
/* This is used by the startup in order to initialize the .bss section */
|
||||
. = ALIGN(4);
|
||||
__START_BSS = .;
|
||||
__bss_start__ = .;
|
||||
*(.bss)
|
||||
*(.bss*)
|
||||
*(COMMON)
|
||||
. = ALIGN(4);
|
||||
__bss_end__ = .;
|
||||
__END_BSS = .;
|
||||
} > m_data
|
||||
|
||||
.heap :
|
||||
{
|
||||
. = ALIGN(8);
|
||||
__end__ = .;
|
||||
PROVIDE(end = .);
|
||||
__HeapBase = .;
|
||||
. += HEAP_SIZE;
|
||||
__HeapLimit = .;
|
||||
__heap_limit = .; /* Add for _sbrk */
|
||||
} > m_data
|
||||
|
||||
.stack :
|
||||
{
|
||||
. = ALIGN(8);
|
||||
. += STACK_SIZE;
|
||||
} > m_data
|
||||
|
||||
/* Initializes stack on the end of block */
|
||||
__StackTop = ORIGIN(m_data) + LENGTH(m_data);
|
||||
__StackLimit = __StackTop - STACK_SIZE;
|
||||
PROVIDE(__stack = __StackTop);
|
||||
|
||||
.ARM.attributes 0 : { *(.ARM.attributes) }
|
||||
|
||||
ASSERT(__StackLimit >= __HeapLimit, "region m_data overflowed with stack and heap")
|
||||
}
|
||||
79
docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md
Normal file
79
docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# Карта Flash — bootloader + tft_app (A/Б) + файловая система ассетов
|
||||
|
||||
Единая карта QSPI NOR Flash (W25Q64/128/256/512, см. [bsp/qspi_flash](../../bsp/qspi_flash/README.md))
|
||||
для связки `firmware/bootloader` + `firmware/tft_app`. Дополняет
|
||||
[BOOT_FLAGS.md](BOOT_FLAGS.md) (сценарии исполнения) и [HAB_GUIDE.md](HAB_GUIDE.md) (подпись).
|
||||
|
||||
---
|
||||
|
||||
## 1. Принцип: один bootloader на любую ёмкость чипа
|
||||
|
||||
`bsp_qspi_flash` определяет чип и его размер в рантайме (`bsp_qspi_init()` → JEDEC ID →
|
||||
`bsp_qspi_flash_size()`), а не на этапе компиляции. На производстве возможен разброс чипов —
|
||||
минимум W25Q128 (16 МБ), также встречается W25Q512 (64 МБ).
|
||||
|
||||
Карта построена так, чтобы **один и тот же бинарник bootloader** работал на любой ёмкости:
|
||||
|
||||
- **Bootloader, Slot A, Slot Б — фиксированные смещения и размеры**, одинаковые на всех платах
|
||||
независимо от реальной ёмкости чипа. Именно это делает образ bootloader переносимым между платами
|
||||
с разным флешем без пересборки.
|
||||
- **Область файловой системы ассетов (спрайты/музыка) — не фиксирована.** Она занимает всё
|
||||
пространство от конца Slot Б до фактического конца чипа, размер вычисляется в рантайме через
|
||||
`bsp_qspi_flash_size()` при монтировании. На 16 МБ чипе это ~11.75 МБ, на 64 МБ — ~59.75 МБ.
|
||||
**Формат и владелец этой области (что её монтирует, кто и как обновляет ассеты) — открытый вопрос,
|
||||
вне рамок текущего плана bootloader'а.** Здесь фиксируется только адресный диапазон.
|
||||
|
||||
`bootutil` (MCUboot, Direct-XIP) знает только про Slot A и Slot Б через `sysflash.h` — про область
|
||||
ФС ему знать не нужно, коллизий с его логикой нет.
|
||||
|
||||
---
|
||||
|
||||
## 2. Карта
|
||||
|
||||
| Область | Смещение от `0x60000000` | Размер | Абсолютный адрес (начало) |
|
||||
| ---------------- | ------------------------- | -------------------------------------- | --------------------------- |
|
||||
| Bootloader | `0x000000` | 256 КБ (`0x040000`) | `0x60000000` |
|
||||
| Slot A (tft_app) | `0x040000` | 2 МБ (`0x200000`) | `0x60040000` |
|
||||
| Slot Б (tft_app) | `0x240000` | 2 МБ (`0x200000`) | `0x60240000` |
|
||||
| ФС ассетов | `0x440000` | `bsp_qspi_flash_size() - 0x440000` | `0x60440000` |
|
||||
|
||||
Все границы выровнены на 64 КБ блок (`BSP_QSPI_BLOCK_64K_SIZE`) — стирание region-ов через
|
||||
`bsp_qspi_erase_block_64k()` без частичных секторов.
|
||||
|
||||
Direct-XIP не использует scratch-область — метаданные состояния/версии образа хранятся в trailer
|
||||
самого образа в каждом слоте (стандартный механизм bootutil), отдельного региона под них не нужно.
|
||||
|
||||
---
|
||||
|
||||
## 3. Обоснование размеров
|
||||
|
||||
**Bootloader — 256 КБ.** Без SDRAM/дисплея/FreeRTOS: bring-up, USB CDC, FatFS, bootutil (Direct-XIP,
|
||||
без swap/scratch-машинерии), крипто-бэкенд (mbedTLS/tinycrypt), драйвер QSPI. Реальный размер ожидается
|
||||
существенно меньше — заложен запас на будущий рост (например RSA вместо ECDSA, расширенная диагностика).
|
||||
|
||||
**Slot A/Б — 2 МБ каждый.** Ассеты (спрайты, музыка) вынесены в отдельную область ФС и не входят в
|
||||
подписанный образ — в слоте только код: FreeRTOS, логика индикатора, шрифты как вшитые C-массивы (8bpp
|
||||
со сглаживанием). Ориентир: текущая production-прошивка (Debug-сборка, с шрифтами, FreeRTOS, FatFS)
|
||||
занимает ~1 МБ — 2 МБ даёт двукратный запас.
|
||||
|
||||
**Почему размеры не пересматриваются "по факту" после первой сборки.** Карта Flash — контракт,
|
||||
зашитый в уже прошитые на производстве bootloader'ы (обновляются только через SWD/USB ROM, не в поле).
|
||||
Смещения слотов фиксированы заранее с запасом, а не подгоняются под фактический размер первой сборки
|
||||
tft_app.
|
||||
|
||||
---
|
||||
|
||||
## 4. Открытые вопросы (не в рамках плана bootloader)
|
||||
|
||||
- Формат и владелец области ФС ассетов (спрайты/музыка): FAT/LittleFS/кастомный, подписывается ли,
|
||||
как обновляется (та же SD-логика, что и Slot A/Б, или отдельный механизм).
|
||||
- Точный layout `.ld`-скрипта tft_app для двух адресов слотов (Direct-XIP: код обычно не
|
||||
позиционно-независим — вероятно потребуется два варианта линковки под Slot A и Slot Б, либо PIC).
|
||||
|
||||
---
|
||||
|
||||
## 5. Ссылки
|
||||
|
||||
- [BOOT_FLAGS.md](BOOT_FLAGS.md) — XIP/DCD/сценарии исполнения кода.
|
||||
- [HAB_GUIDE.md](HAB_GUIDE.md) — подпись bootloader (HAB) vs подпись образов tft_app (`imgtool`).
|
||||
- [bsp/qspi_flash/README.md](../../bsp/qspi_flash/README.md) — поддерживаемые чипы, `bsp_qspi_flash_size()`.
|
||||
67
firmware/bootloader/CMakeLists.txt
Normal file
67
firmware/bootloader/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# firmware/bootloader/CMakeLists.txt Загрузчик — A/Б обновление tft_app через
|
||||
# microSD (Direct-XIP). Обновляется только через USB ROM + blhost / SWD.
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(
|
||||
bootloader
|
||||
VERSION 0.1.0
|
||||
LANGUAGES C ASM)
|
||||
|
||||
set(TARGET_NAME bootloader)
|
||||
|
||||
# Генерация version.h из шаблона
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/version.h.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated/version.h" @ONLY)
|
||||
|
||||
add_executable(
|
||||
${TARGET_NAME}
|
||||
src/main.c
|
||||
src/cli.c
|
||||
src/protocol.c
|
||||
${BSP_GENERATED}/clock_config.c
|
||||
${BSP_STARTUP_FILE}
|
||||
${BSP_SYSCALLS_FILE})
|
||||
|
||||
target_include_directories(${TARGET_NAME} PRIVATE src/)
|
||||
|
||||
target_include_directories(${TARGET_NAME}
|
||||
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||
|
||||
target_compile_definitions(
|
||||
${TARGET_NAME} PRIVATE __STARTUP_INITIALIZE_RAMFUNCTION __STARTUP_CLEAR_BSS
|
||||
__STARTUP_INITIALIZE_NONCACHEDATA)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Зависимости — минимум для Фазы 1 (bring-up + CDC). bsp_qspi_flash (доступ к
|
||||
# слотам) и bsp_button (downgrade-override) добавятся в Фазах 2-3.
|
||||
# -----------------------------------------------------------------------------
|
||||
target_link_libraries(${TARGET_NAME} PRIVATE bsp_board bsp_led bsp_tick
|
||||
bsp_usb_cdc bsp_boot_xip_no_dcd)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Linker script — вариант flexspi_nor с m_text, ограниченным бюджетом
|
||||
# bootloader (256 KB, см. docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md). Без SDRAM
|
||||
# (в отличие от firmware_test) — bootloader SDRAM не использует.
|
||||
# -----------------------------------------------------------------------------
|
||||
target_link_options(
|
||||
${TARGET_NAME}
|
||||
PRIVATE
|
||||
-Wl,--gc-sections
|
||||
-Wl,--print-memory-usage
|
||||
-Wl,-Map=${CMAKE_BINARY_DIR}/bootloader.map
|
||||
-Wl,--defsym=__stack_size__=0x1000
|
||||
-Wl,--defsym=__heap_size__=0x1000
|
||||
-T${CMAKE_SOURCE_DIR}/cmake/linker/MIMXRT1052xxxxx_bootloader_flexspi_nor.ld)
|
||||
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY
|
||||
${CMAKE_BINARY_DIR})
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Post-build: генерация .bin для прошивки через blhost
|
||||
# -----------------------------------------------------------------------------
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:${TARGET_NAME}>
|
||||
${CMAKE_BINARY_DIR}/bootloader.bin
|
||||
COMMAND ${CMAKE_SIZE} $<TARGET_FILE:${TARGET_NAME}>
|
||||
COMMENT "Generating bootloader.bin")
|
||||
266
firmware/bootloader/PLAN.md
Normal file
266
firmware/bootloader/PLAN.md
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
# Загрузчик (firmware/bootloader) — план разработки по фазам
|
||||
|
||||
## Статус
|
||||
|
||||
| Фаза | Статус | Примечание |
|
||||
|---|---|---|
|
||||
| 0 — Карта Flash | ✅ завершена | [docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md](../../docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md) |
|
||||
| 1 — Скелет (CDC + LED) | ✅ завершена | сборка/HAB/SWD-прошивка/ping-pong/debug — все пункты верификации пройдены на реальной плате, детали ниже |
|
||||
| 2 — bootutil (Direct-XIP) | не начата | |
|
||||
| 3 — SD-путь установки | не начата | |
|
||||
| 4 — SDRAM/W25Q smoke-test + LED-паттерны | не начата | |
|
||||
| 5 — HAB Release + service-tui | не начата | |
|
||||
|
||||
## Контекст
|
||||
|
||||
`service-tui` и `firmware_test` уже выпущены и работают на производстве. Следующий шаг —
|
||||
`firmware/bootloader/` (сейчас пустая директория, но сборочная инфраструктура под неё уже
|
||||
существует: пресеты `bootloader-debug/release` в CMakePresets.json, рецепты `just build::*`,
|
||||
`.vscode/launch.json`, HAB-конвейер) и `firmware/tft_app/`. Начинаем с загрузчика — он определяет
|
||||
контракт (flash-layout, формат образа, версия), под который потом пишется tft_app.
|
||||
|
||||
Архитектурные решения, принятые в обсуждении (не пересматриваются в рамках этого плана):
|
||||
|
||||
- **Boot-стратегия**: и bootloader, и tft_app исполняются XIP из W25Q. Bootloader без ITCM-копирования,
|
||||
без DCD — он не трогает SDRAM. tft_app сама поднимает SEMC в своём раннем startup (SDRAM — под её XIP,
|
||||
см. отдельный будущий план на tft_app).
|
||||
- **Схема обновления**: MCUboot **Direct-XIP**, два слота (A/Б) с полностью валидными образами каждый,
|
||||
без swap/scratch. Единственный полевой канал обновления — microSD. USB как канал заливки *образа*
|
||||
сознательно не делаем (SDP/blhost на производстве — это отдельный, не зависящий от кода bootloader,
|
||||
канал через NXP BootROM).
|
||||
- **Верификация образов tft_app** — через **bootutil** (MCUboot), не самодельный верификатор
|
||||
(см. исследование ниже). Подпись — `imgtool` (не HAB; HAB — только для самого bootloader).
|
||||
- **Обратная связь** — USB CDC ACM с тем же JSON-lines протоколом (`ping`/`pong`, `get_version`), что уже
|
||||
использует `firmware_test` и `tools/service_tui/app/firmware_client.py` — чтобы переиспользовать
|
||||
клиентский код на стороне service-tui. Плюс `LED_HEARTBEAT`/`LED_APP` (`bsp/led`) с кодированием
|
||||
состояний через паттерн мигания.
|
||||
- **Даунгрейд**: строгий version-gate по умолчанию; обход — удержание кнопки (`BSP_BUTTON_1`,
|
||||
`bsp/button`) при старте с валидным (но более старым) подписанным образом на SD.
|
||||
- **Два сценария производства** обслуживаются одним и тем же механизмом установки с SD
|
||||
(bootloader-only → потом массовая SD-установка, или сразу залитый бандл) — единственное отличие:
|
||||
top-level состояние "нет ни одного валидного слота" (ожидание SD, retry-цикл, статус на CDC/LED),
|
||||
которого нет в обычной работе.
|
||||
- **Smoke-test** (SDRAM + W25Q) — встроен в bootloader как опциональный, неблокирующий шаг: переиспользует
|
||||
`bsp_qspi_init()`/`bsp_qspi_read_jedec_id()` (bsp/qspi_flash, и так нужен для доступа к слотам) и
|
||||
`bsp_sdram_init()` (bsp/sdram) поверх новой общей функции подъёма SEMC, которую позже переиспользует
|
||||
и tft_app.
|
||||
|
||||
### Что нашли по MCUboot bootutil (исследование Explore-агента)
|
||||
|
||||
В `sdk/middleware/mcuboot_opensource/boot/nxp_mcux_sdk/` уже есть готовый порт bootutil под NXP MCUX SDK
|
||||
(`flashapi/flash_api.c` — 425 строк, ровно тот шим над flash-драйвером, который нужен и нам;
|
||||
`include/{sysflash,flash_map_backend,mcuboot_config}/*.h`; `boot.c`/`boot.h` — pattern main-loop
|
||||
загрузчика). В `sdk/boards/evkbimxrt1050/ota_examples/ota_mcuboot_basic/armgcc/` есть рабочий линкер-скрипт
|
||||
`MIMXRT1052xxxxx_flexspi_nor_mcuboot.ld` — тот же SoC, что и у нас. `bootutil_priv.h` требует ровно один
|
||||
из `MCUBOOT_OVERWRITE_ONLY`/`MCUBOOT_SWAP_USING_MOVE`/`MCUBOOT_DIRECT_XIP`/`MCUBOOT_RAM_LOAD` — включение
|
||||
`MCUBOOT_DIRECT_XIP` чисто вырезает всю swap/scratch-машинерию через `#if`. Итоговая оценка порт-кода:
|
||||
~500-1100 строк (flash-шим над `bsp_qspi_flash`, `sysflash.h` на 2 слота, `mcuboot_config.h`,
|
||||
статический `malloc`/`free`-пул — bootutil дважды дергает malloc даже без RTOS). Крипто-бэкенд
|
||||
(mbedTLS/tinycrypt) уже вендорен в `ext/`.
|
||||
|
||||
---
|
||||
|
||||
## Фаза 0 — Карта Flash и место в системе сборки
|
||||
|
||||
**Цель**: зафиксировать бинарный контракт, прежде чем писать код.
|
||||
|
||||
- Определить смещения: `BOOTLOADER` (фикс. `0x60000000`, бюджет размера — по факту размера
|
||||
bootloader.bin + запас), `SLOT_A`, `SLOT_B` (равный размер, с запасом под рост tft_app; ориентир —
|
||||
`bsp_qspi_flash_size()` минус бюджет bootloader, поделить пополам). Direct-XIP не требует scratch-области
|
||||
— метаданные версии/статуса живут в самом image trailer каждого слота (стандарт bootutil).
|
||||
Зафиксировать в `docs/mimxrt1052/` рядом с существующими `BOOT_FLAGS.md`/`HAB_GUIDE.md` (новый файл
|
||||
`BOOTLOADER_FLASH_MAP.md` или раздел в `DEV_ARCH.md`) — единый источник истины для линкер-скриптов,
|
||||
`sysflash.h` и будущего tft_app.
|
||||
- Зарегистрировать `firmware/bootloader` в корневом `CMakeLists.txt` (раскомментировать
|
||||
`add_subdirectory(firmware/bootloader)` — сейчас закомментировано вместе с `firmware/app`, добавить
|
||||
раздельно).
|
||||
|
||||
**Верификация**: ревью карты памяти (нет пересечений bootloader/Slot A/Slot B; согласуется с реальной
|
||||
ёмкостью W25Q чипа на плате). Код ещё не пишем — это чисто согласованный документ.
|
||||
|
||||
---
|
||||
|
||||
## Фаза 1 — Скелет: собирается, грузится, живой (CDC + LED)
|
||||
|
||||
**Цель**: минимальный bootloader, который проходит тот же путь bring-up, что и `firmware_test`
|
||||
(`firmware/test/src/main.c`), но без тестового раннера.
|
||||
|
||||
Новые файлы, по образцу `firmware/test/`:
|
||||
- `firmware/bootloader/CMakeLists.txt` — target `bootloader`, линкер `MIMXRT1052xxxxx_flexspi_nor.ld`
|
||||
(без SDRAM-варианта — bootloader её не использует), `target_link_libraries`: `bsp_board`, `bsp_led`,
|
||||
`bsp_tick`, `bsp_button`, `bsp_usb_cdc`, `bsp_qspi_flash`, `bsp_boot_xip` (если применимо, как в
|
||||
firmware_test) + generated/startup/syscalls как в `firmware/test/CMakeLists.txt`.
|
||||
- `firmware/bootloader/src/main.c` — bring-up: `board_hw_init()` → `bsp_led_init()` → `bsp_tick_init()`
|
||||
→ `bsp_usb_cdc_init()` → мигание `LED_HEARTBEAT` до готовности CDC (копия паттерна из
|
||||
`firmware/test/src/main.c:30-65`).
|
||||
- `firmware/bootloader/src/cli.c` + `protocol.c`/`.h` — урезанное подмножество протокола
|
||||
`firmware/test/src/protocol.h`: `ping`→`pong`, `get_version`→`version_response`, плюс новый
|
||||
`status`-эвент для состояний из Фазы 3/4 (без `test_begin`/`test_result`/`confirm_request` — те
|
||||
специфичны для firmware_test). Не шарить код с firmware_test напрямую (разные жизненные циклы
|
||||
сообщений) — копировать и урезать, как это уже сделано для `firmware_test_fatfs` vs будущего
|
||||
tft_app fatfs (см. комментарий в `firmware/test/fatfs/CMakeLists.txt:7-8`).
|
||||
- HAB unsigned yaml для Debug (по образцу существующих конфигов firmware_test в `tools/host/hab/`).
|
||||
|
||||
**Верификация**:
|
||||
1. ✅ `just build::build-bootloader-debug` — собирается (на хосте с `ARMGCC_DIR` вместо devcontainer —
|
||||
тоже работает, toolchain найден локально).
|
||||
2. ✅ `just host::flash-swd-bootloader-debug` (готовый рецепт, уже существовал в `just/host.just`) —
|
||||
прошивается, `LED_HEARTBEAT` мигает до подключения CDC, после — `LED_APP` включается.
|
||||
3. ✅ Ping/get_version по USB CDC ACM — `ping`→`pong`, `get_version`→`"0.1.0"`.
|
||||
4. ✅ `🐛 Debug: bootloader` в `.vscode/launch.json` — подключается, останавливается на `main`.
|
||||
|
||||
**Отличия от исходного плана (по факту реализации):**
|
||||
- Линкер-скрипт — не переиспользован общий `MIMXRT1052xxxxx_flexspi_nor.ld` (он нигде не использовался
|
||||
и не ограничивал `m_text` бюджетом bootloader), а сделана копия
|
||||
`cmake/linker/MIMXRT1052xxxxx_bootloader_flexspi_nor.ld` с `m_text` жёстко ограниченным 247 КБ
|
||||
(`ASSERT` на границу Slot A `0x60040000`, см. BOOTLOADER_FLASH_MAP.md) — превышение бюджета теперь
|
||||
ошибка линковки, а не тихий выход за пределы своей области. Фактически занято: 32.5 КБ (12.85%).
|
||||
- `bsp_boot_xip` не переиспользован как есть (он тянет `XIP_BOOT_HEADER_DCD_ENABLE=1`, что противоречит
|
||||
решению "bootloader SDRAM не трогает"). Добавлен `bsp_boot_xip_no_dcd` в `bsp/CMakeLists.txt`, третий
|
||||
вариант рядом с существующими `bsp_boot_xip`/`bsp_boot_ram`.
|
||||
- `status`-эвент из плана Фазы 1 отложен в Фазу 3/4 (там появится реальный смысл — состояния SD-установки
|
||||
и smoke-теста). Фаза 1 ограничена `ping`/`pong`/`get_version`/`version_response`/`error`, без
|
||||
`session_start` (не нужен — service-tui проверяет живость явным `ping`, как и HIL-фикстура
|
||||
`firmware_cdc` для firmware_test).
|
||||
- Найден и исправлен баг scaffold'а: `🐛 Debug: bootloader` в `.vscode/launch.json` ссылался на
|
||||
несуществующую задачу `build-and-rtt:firmware-test-debug` (copy-paste). Исправлено на
|
||||
`build:bootloader-debug` (уже существовала в `tasks.json`).
|
||||
- HAB Debug-конфиг (`tools/host/hab/hab_bootloader_debug.yaml`) уже существовал в исходном scaffold
|
||||
репозитория и уже был без DCD — менять не пришлось.
|
||||
- `bsp_qspi_flash` и `bsp_button` из Фазы 1 в CMakeLists.txt пока не добавлены — они не используются
|
||||
до Фазы 2 (доступ к слотам) и Фазы 3 (downgrade-override), добавятся вместе с кодом, который их
|
||||
реально вызывает.
|
||||
|
||||
---
|
||||
|
||||
## Фаза 2 — bootutil: порт + выбор слота (host-тестируемая логика)
|
||||
|
||||
**Цель**: интегрировать bootutil в режиме Direct-XIP, с флеш-шимом над `bsp_qspi_flash`.
|
||||
|
||||
- `firmware/bootloader/mcuboot_port/` (новая директория): `flash_map_backend.c/.h` (шим
|
||||
`flash_area_open/read/write/erase` → `bsp_qspi_read`/`bsp_qspi_write_page`/`bsp_qspi_erase_sector`,
|
||||
по образцу `sdk/middleware/mcuboot_opensource/boot/nxp_mcux_sdk/flashapi/flash_api.c`), `sysflash.h`
|
||||
(2 flash-area ID под Slot A/Б, без scratch), `mcuboot_config.h` (`MCUBOOT_DIRECT_XIP`,
|
||||
`MCUBOOT_IMAGE_NUMBER=1`, выбор сигнатурной схемы — рекомендация: ECDSA P-256, компактные ключи и
|
||||
быстрая проверка на Cortex-M7 без аппаратного крипто-ускорителя), статический malloc/free-пул (bootutil
|
||||
дважды вызывает malloc в `loader.c` даже без RTOS).
|
||||
- Подключить `sdk/middleware/mcuboot_opensource/boot/bootutil` как источник в CMake (аналогично тому, как
|
||||
`sdk/middleware/mcuboot_opensource/boot/nxp_mcux_sdk` подключает bootutil через свой `.cmake`-файл —
|
||||
использовать `middleware_mcuboot_nxp_bootutil_port.cmake` как референс, не копировать вслепую).
|
||||
- `firmware/bootloader/src/boot_select.c` — вызов `boot_go` (Direct-XIP путь), получение адреса entry
|
||||
point выбранного слота.
|
||||
|
||||
**Верификация — до всякого железа**:
|
||||
- Host-юнит-тесты (`tests/host/`, Unity + fff, по образцу существующих `tests/host/protocol/`,
|
||||
`tests/host/cli/`) с фейковым flash-буфером в памяти вместо `bsp_qspi_flash`: валидный образ в
|
||||
Slot A только → выбран A; оба слота валидны, версия Б выше → выбран Б; повреждённый TLV/подпись в
|
||||
одном слоте → игнорируется, выбран валидный; оба слота пустые/невалидные → `boot_go` возвращает
|
||||
"нет образа" (это и есть триггер top-level состояния Фазы 3).
|
||||
- Только после зелёных host-тестов — сборка `target-debug`/реальная плата с образом, подписанным
|
||||
`imgtool` (см. `sdk/middleware/mcuboot_opensource/scripts/imgtool.py`), прошитым вручную через SWD в
|
||||
Slot A — подтвердить, что bootloader реально прыгает в него (минимальный тестовый "app"-заглушка,
|
||||
которая просто включает LED, чтобы визуально подтвердить прыжок).
|
||||
|
||||
---
|
||||
|
||||
## Фаза 3 — SD-путь установки (единый для обоих производственных сценариев)
|
||||
|
||||
**Цель**: сканирование microSD, установка образа в слот, top-level состояние "нет валидного образа".
|
||||
|
||||
- `firmware/bootloader/fatfs/` — bare-metal FatFS-таргет по образцу `firmware/test/fatfs/`
|
||||
(свой `ffconf.h`, не шарить `firmware_test_fatfs` — bootloader и firmware_test взаимоисключающие
|
||||
прошивки на одной плате, зависимость от таргета с именем "firmware_test" в trust-anchor была бы
|
||||
неверной связью; переиспользуется общий `port/fatfs` уровнем ниже).
|
||||
- `firmware/bootloader/src/sd_update.c` — на старте и опционально периодически: смонтировать SD,
|
||||
найти `TFT_APP.BIN` (imgtool-подписанный) в корне, распарсить заголовок (через bootutil), сравнить
|
||||
версию с активным слотом:
|
||||
- новее → стереть неактивный (или любой, если оба невалидны) слот, записать постранично через
|
||||
`bsp_qspi_write_page`, вычитать обратно и сверить хэш перед тем как считать установку завершённой;
|
||||
- старше/равно и кнопка `BSP_BUTTON_1` не удержана при старте → пропустить;
|
||||
- старше и кнопка удержана → тот же путь установки, что и "новее" (подпись всё равно проверяется).
|
||||
- Top-level состояние "нет валидного слота": цикл ожидания SD с периодическим статусом на CDC
|
||||
(`status: waiting_for_sd`) и характерным LED-паттерном (Фаза 4 уточняет полный словарь паттернов) —
|
||||
выхода из цикла нет, пока установка не пройдёт успешно.
|
||||
|
||||
**Верификация (первая фаза, требующая реального железа)**:
|
||||
1. Чистая плата (только bootloader, оба слота пустые) + SD с валидным подписанным образом →
|
||||
автоустановка, переход к загрузке (проверить по CDC-статусам и LED).
|
||||
2. То же SD с образом версии ниже уже установленной, кнопка не нажата → отклонён, лог на CDC.
|
||||
3. То же, кнопка `BSP_BUTTON_1` удержана при старте → установлен.
|
||||
4. SD с образом без подписи/битым TLV → отклонён, плата остаётся на последнем валидном слоте (если был)
|
||||
или продолжает ждать (если не было), характерный error-паттерн LED.
|
||||
5. Сценарий 2 (бандл залит сразу в Slot A через SWD/SDP на этапе тестирования): SD не участвует,
|
||||
bootloader должен просто загрузить Slot A без обращения к SD-логике вообще.
|
||||
|
||||
---
|
||||
|
||||
## Фаза 4 — SDRAM/W25Q smoke-test + словарь LED-паттернов
|
||||
|
||||
**Цель**: диагностический, неблокирующий проход по SDRAM/W25Q + финальный набор статусов.
|
||||
|
||||
- Новая функция подъёма SEMC (например `bsp_sdram_configure()` рядом с существующим `bsp_sdram_init()`
|
||||
в `bsp/sdram/`) — переносит регистровую последовательность, которая сейчас зашита в DCD, в вызываемый
|
||||
C-код. Bootloader вызывает её только на время smoke-теста; tft_app (в своём будущем плане) будет
|
||||
вызывать её по-настоящему в раннем startup.
|
||||
- Smoke-test в `main.c`: `bsp_qspi_init()` (уже обязателен для доступа к слотам — статус даром),
|
||||
`bsp_sdram_configure()` + `bsp_sdram_init()` (опционально, по времени — не блокирует переход к
|
||||
загрузке приложения; результат только репортится).
|
||||
- Финальный словарь состояний на `LED_APP` (поверх уже реализованных в Фазах 1/3):
|
||||
ожидание SD / установка / ошибка образа / smoke-test fail / переход в app (гаснет).
|
||||
- `status`-события на CDC для каждого состояния — `tools/service_tui` сможет опрашивать их так же, как
|
||||
сейчас `FirmwareClient.ping()`/`get_version()`.
|
||||
|
||||
**Верификация**: прогон на заведомо исправной плате — все статусы (`waiting_for_sd`, `installing`,
|
||||
`smoke_pass`/`smoke_fail`, `booting`) наблюдаются в правильном порядке через CDC и глазами по LED.
|
||||
Полноценной аппаратной инъекции неисправностей не делаем (в проекте и для остальных HIL-тестов это не
|
||||
принято) — ограничиваемся проверкой на реальной плате в штатном состоянии.
|
||||
|
||||
---
|
||||
|
||||
## Фаза 5 — HAB, релизные пресеты, интеграция в service-tui
|
||||
|
||||
**Цель**: замкнуть производственный цикл для сценария "bootloader-only, потом SD".
|
||||
|
||||
- HAB Release yaml для bootloader (подписанный, `flags=0x08`) — по образцу существующих
|
||||
`just/build.just:154-176` (`hab-bootloader-debug/release` уже определены, нужен только рабочий
|
||||
`bootloader_hab.bin`).
|
||||
- `tools/service_tui`: обобщить/расширить `FirmwareClient` (или добавить параллельный тонкий клиент,
|
||||
переиспользующий его внутренности) так, чтобы после прошивки bootloader через SDP service-tui сразу
|
||||
проверял `ping`→`pong` и `get_version` — это и есть запрошенная "обратная связь, что загрузчик жив"
|
||||
для сценария 1 (плата отложена в кучу без tft_app).
|
||||
- Проверить, что уже существующие `just host::flash-production` / `just host::production`
|
||||
(`just/host.just:532-535`) корректно находят `bootloader_hab.bin` — сборка `app_hab.bin` пока
|
||||
недоступна (tft_app не реализован), так что сквозной прогон "production" целиком верифицируется только
|
||||
после отдельного плана на tft_app; в рамках этой фазы проверяем только bootloader-плечо.
|
||||
|
||||
**Верификация**: чистая плата → `just host::flash-production`-путь для bootloader (или его bootloader-only
|
||||
подмножество) → service-tui показывает "bootloader alive" на основе реального ping/version с платы.
|
||||
|
||||
---
|
||||
|
||||
## Ключевые файлы для переиспользования (не изобретать заново)
|
||||
|
||||
| Что нужно | Где смотреть образец |
|
||||
|---|---|
|
||||
| Bring-up последовательность | `firmware/test/src/main.c` |
|
||||
| JSON-lines протокол (ping/pong/version) | `firmware/test/src/protocol.h`, `.c` |
|
||||
| CLI-диспетчер команд | `firmware/test/src/cli.c` |
|
||||
| CMake-паттерн firmware-таргета + HAB post-build | `firmware/test/CMakeLists.txt` |
|
||||
| Bare-metal FatFS-таргет | `firmware/test/fatfs/CMakeLists.txt` |
|
||||
| Flash R/W/erase/JEDEC | `bsp/qspi_flash/include/bsp/qspi_flash.h` |
|
||||
| SDRAM verify (паттерн read/write) | `bsp/sdram/src/sdram.c` |
|
||||
| LED heartbeat/app конвенция | `bsp/led/include/bsp/led.h` |
|
||||
| Кнопки с дебаунсом | `bsp/button/include/bsp/button.h` |
|
||||
| bootutil NXP-порт (референс для шима) | `sdk/middleware/mcuboot_opensource/boot/nxp_mcux_sdk/` |
|
||||
| Линкер-скрипт mcuboot для RT1052 | `sdk/boards/evkbimxrt1050/ota_examples/ota_mcuboot_basic/armgcc/MIMXRT1052xxxxx_flexspi_nor_mcuboot.ld` |
|
||||
| imgtool (подпись образов) | `sdk/middleware/mcuboot_opensource/scripts/imgtool.py` |
|
||||
| CDC-клиент на стороне service-tui | `tools/service_tui/app/firmware_client.py` |
|
||||
| Host-юнит-тесты (Unity+fff паттерн) | `tests/host/protocol/`, `tests/host/cli/` |
|
||||
|
||||
## Общий принцип верификации по фазам
|
||||
|
||||
Логика без железа (выбор слота, парсинг TLV, версия-компар) — **host-тесты** (`just build::test-host`),
|
||||
используя уже принятую в проекте связку Unity+fff с фейковым flash-буфером — так же, как уже тестируются
|
||||
`cli`/`protocol`/`prio_queue`. Всё, что требует реального железа (SD, запись во flash, SEMC, LED,
|
||||
USB CDC) — только после того как логика зелёная на хосте, ручная проверка на плате по чек-листу каждой
|
||||
фазы. Не начинать следующую фазу, пока не пройдена верификация текущей.
|
||||
168
firmware/bootloader/README.md
Normal file
168
firmware/bootloader/README.md
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
# bootloader
|
||||
|
||||
> Загрузчик MIMXRT1052 — A/Б обновление `tft_app` через microSD (MCUboot,
|
||||
> Direct-XIP). Сам bootloader обновляется только через USB ROM (blhost) или
|
||||
> SWD — не в поле. Единственный канал диагностики: USB CDC ACM (JSON-lines,
|
||||
> тот же стиль протокола, что у `firmware_test`).
|
||||
>
|
||||
> Версия: `0.1.0` | Статус: Фаза 1 (скелет) завершена — см. [PLAN.md](PLAN.md)
|
||||
|
||||
---
|
||||
|
||||
## Содержание
|
||||
|
||||
- [Быстрый старт](#быстрый-старт)
|
||||
- [Архитектура](#архитектура)
|
||||
- [Карта Flash](#карта-flash)
|
||||
- [Протокол](#протокол)
|
||||
- [Roadmap](#roadmap)
|
||||
- [Версионирование](#версионирование)
|
||||
|
||||
---
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
### 1. Сборка
|
||||
|
||||
```bash
|
||||
just build::build-bootloader-debug
|
||||
just build::hab-bootloader-debug
|
||||
```
|
||||
|
||||
### 2. Прошивка
|
||||
|
||||
Для итеративной разработки — SWD (не требует смены boot-режима платы):
|
||||
|
||||
```bash
|
||||
just host::flash-swd-bootloader-debug
|
||||
# обязателен power cycle платы после прошивки
|
||||
```
|
||||
|
||||
Через USB ROM (SDP, плата в режиме Serial Downloader):
|
||||
|
||||
```bash
|
||||
just host::flash bootloader debug
|
||||
```
|
||||
|
||||
### 3. Подключение
|
||||
|
||||
USB CDC ACM (тот же порядок, что у `firmware_test`):
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
screen /dev/cu.usbmodemXXXX
|
||||
```
|
||||
|
||||
Проверка связи:
|
||||
|
||||
```json
|
||||
→ {"type":"cmd","cmd":"ping"}
|
||||
← {"type":"pong"}
|
||||
|
||||
→ {"type":"cmd","cmd":"get_version"}
|
||||
← {"type":"version_response","fw":"0.1.0"}
|
||||
```
|
||||
|
||||
### 4. Отладка
|
||||
|
||||
VSCode → `🐛 Debug: bootloader` (`.vscode/launch.json`) — пересобирает через
|
||||
`build:bootloader-debug`, подключается к GDB-серверу (`just host::debug-server`
|
||||
должен быть запущен), останавливается на `main`.
|
||||
|
||||
---
|
||||
|
||||
## Архитектура
|
||||
|
||||
Bring-up идентичен `firmware_test` (`firmware/test/src/main.c`), но без
|
||||
тестового раннера — bootloader не запускает тесты, только диагностический
|
||||
CLI:
|
||||
|
||||
```bash
|
||||
firmware/bootloader/
|
||||
├── CMakeLists.txt
|
||||
└── src/
|
||||
├── main.c — board_hw_init → led/tick/usb_cdc init →
|
||||
│ ожидание CDC (LED_HEARTBEAT мигает) →
|
||||
│ LED_APP on → главный цикл (poll + cli_process)
|
||||
│
|
||||
├── version.h.in — шаблон версии (CMake → generated/version.h)
|
||||
│
|
||||
├── cli.h / cli.c — IO-слой: буферизация строк, парсинг "type"/"cmd"
|
||||
│ (урезанное подмножество firmware_test/src/cli.c)
|
||||
│
|
||||
└── protocol.h / .c — сериализация исходящих событий → cli_send()
|
||||
```
|
||||
|
||||
Bootloader **без SDRAM** — DCD не используется (`bsp_boot_xip_no_dcd` вместо
|
||||
`bsp_boot_xip`). `tft_app` инициализирует SEMC сама в своём раннем startup
|
||||
(см. [BOOTLOADER_FLASH_MAP.md](../../docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md)).
|
||||
|
||||
Линкер: `cmake/linker/MIMXRT1052xxxxx_bootloader_flexspi_nor.ld` — `m_text`
|
||||
жёстко ограничен 247 КБ, с `ASSERT` на границу Slot A. Превышение бюджета —
|
||||
ошибка линковки, а не тихий выход кода за пределы своей области.
|
||||
|
||||
---
|
||||
|
||||
## Карта Flash
|
||||
|
||||
Полная карта, обоснование размеров и принцип "один bootloader на любую
|
||||
ёмкость чипа" — в
|
||||
[docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md](../../docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md).
|
||||
|
||||
| Область | Смещение | Размер |
|
||||
|---|---|---|
|
||||
| Bootloader | `0x60000000` | 256 КБ |
|
||||
| Slot A (tft_app) | `0x60040000` | 2 МБ |
|
||||
| Slot Б (tft_app) | `0x60240000` | 2 МБ |
|
||||
| ФС ассетов (спрайты/музыка) | `0x60440000` | остальное (рантайм) |
|
||||
|
||||
---
|
||||
|
||||
## Протокол
|
||||
|
||||
Транспорт и фреймирование — как у `firmware_test`
|
||||
([firmware/test/README.md](../test/README.md#протокол-v2)): USB CDC ACM,
|
||||
JSON-lines, максимум 128 байт на строку.
|
||||
|
||||
**Реализовано (Фаза 1):**
|
||||
|
||||
| Команда хоста | Ответ |
|
||||
|---|---|
|
||||
| `{"type":"cmd","cmd":"ping"}` | `{"type":"pong"}` |
|
||||
| `{"type":"cmd","cmd":"get_version"}` | `{"type":"version_response","fw":"X.Y.Z"}` |
|
||||
|
||||
Ошибки: `{"ok":false,"error":"PARSE_ERR"}` / `"UNKNOWN_CMD"` / `"LINE_TOO_LONG"`.
|
||||
|
||||
Без `session_start` — в отличие от `firmware_test`, bootloader не шлёт
|
||||
приветствие автоматически; живость проверяется явным `ping` (тот же паттерн,
|
||||
что использует HIL-фикстура `firmware_cdc` для `firmware_test`).
|
||||
|
||||
**Появится в следующих фазах** (см. [PLAN.md](PLAN.md)): `status`-события
|
||||
(`waiting_for_sd`, `installing`, `smoke_pass`/`smoke_fail`, `booting`) —
|
||||
Фазы 3-4.
|
||||
|
||||
---
|
||||
|
||||
## Roadmap
|
||||
|
||||
Полный план по фазам с целями и критериями верификации — [PLAN.md](PLAN.md).
|
||||
|
||||
| Фаза | Что делает |
|
||||
|---|---|
|
||||
| 0 ✅ | Карта Flash, регистрация в сборке |
|
||||
| 1 ✅ | Скелет: bring-up, USB CDC, ping/get_version, LED heartbeat |
|
||||
| 2 | bootutil (MCUboot Direct-XIP) — выбор слота, верификация подписи |
|
||||
| 3 | Установка образа с microSD, состояние "нет валидного слота" |
|
||||
| 4 | SDRAM/W25Q smoke-test, словарь LED-паттернов |
|
||||
| 5 | HAB Release, интеграция в service-tui |
|
||||
|
||||
---
|
||||
|
||||
## Версионирование
|
||||
|
||||
Версия задаётся `project(bootloader VERSION X.Y.Z)` в `CMakeLists.txt`,
|
||||
прокидывается через `configure_file(src/version.h.in → generated/version.h)`
|
||||
в `BOOTLOADER_VERSION_STR` — тот же механизм, что у `firmware_test`
|
||||
(см. [firmware/test/README.md#версионирование](../test/README.md#версионирование)).
|
||||
|
||||
`version.h` генерируется, не редактируется вручную.
|
||||
216
firmware/bootloader/src/cli.c
Normal file
216
firmware/bootloader/src/cli.c
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
/**
|
||||
* @file cli.c
|
||||
* @brief IO-слой и диспатчер сообщений для bootloader.
|
||||
*
|
||||
* Транспорт: USB CDC ACM через bsp_usb_cdc.
|
||||
*
|
||||
* Парсинг минималистичный: strstr по фиксированным полям (тот же подход,
|
||||
* что и в firmware_test/src/cli.c) — cJSON не используется намеренно, схема
|
||||
* входящих сообщений фиксирована.
|
||||
*
|
||||
* Входящие типы (Фаза 1):
|
||||
* "cmd" → handle_cmd() → protocol_send_pong() / protocol_send_version_response()
|
||||
*
|
||||
* Добавление новой команды типа "cmd":
|
||||
* 1. Добавить ветку if (strcmp(cmd_name, "FOO") == 0) в handle_cmd().
|
||||
*/
|
||||
|
||||
#include "cli.h"
|
||||
|
||||
#include "bsp/usb_cdc.h"
|
||||
#include "protocol.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ── Ключи полей JSON ──────────────────────────────────────────────────── */
|
||||
|
||||
static const char K_FIELD_TYPE[] = "\"type\"";
|
||||
static const char K_FIELD_CMD[] = "\"cmd\"";
|
||||
|
||||
/** @brief Буфер непрочитанного остатка chunk после вызова process_line(). */
|
||||
static uint8_t g_s_chunk_buf[CLI_LINE_BUF_SIZE];
|
||||
static size_t g_s_chunk_len = 0U;
|
||||
static size_t g_s_chunk_pos = 0U;
|
||||
|
||||
/* ── RX line buffer ────────────────────────────────────────────────────── */
|
||||
|
||||
static uint8_t g_s_line_buf[CLI_LINE_BUF_SIZE];
|
||||
static size_t g_s_line_len = 0U;
|
||||
|
||||
/* ── Парсинг полей ─────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* @brief Извлечь строковое значение в кавычках после двоеточия.
|
||||
*
|
||||
* @param[in] p_after_key Позиция сразу после ключа в строке JSON.
|
||||
* @param[out] p_out Буфер для результата.
|
||||
* @param[in] out_size Размер p_out (включая место под '\0').
|
||||
* @return true если значение найдено и помещается в p_out.
|
||||
*/
|
||||
static bool extract_string_value(const char *p_after_key, char *p_out, size_t out_size)
|
||||
{
|
||||
const char *colon = strchr(p_after_key, ':');
|
||||
if (colon == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *open_q = strchr(colon + 1U, '"');
|
||||
if (open_q == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
open_q++;
|
||||
|
||||
const char *close_q = strchr(open_q, '"');
|
||||
if (close_q == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t len = (size_t) (close_q - open_q);
|
||||
if (len == 0U || len >= out_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(p_out, open_q, len);
|
||||
p_out[len] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Извлечь значение поля "type".
|
||||
*/
|
||||
static bool parse_type_field(const char *p_line, char *p_out, size_t out_size)
|
||||
{
|
||||
const char *key = strstr(p_line, K_FIELD_TYPE);
|
||||
if (key == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return extract_string_value(key + sizeof(K_FIELD_TYPE) - 1U, p_out, out_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Извлечь значение поля "cmd".
|
||||
*/
|
||||
static bool parse_cmd_field(const char *p_line, char *p_out, size_t out_size)
|
||||
{
|
||||
const char *key = strstr(p_line, K_FIELD_CMD);
|
||||
if (key == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return extract_string_value(key + sizeof(K_FIELD_CMD) - 1U, p_out, out_size);
|
||||
}
|
||||
|
||||
/* ── Обработчики входящих сообщений ────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* @brief Обработать сообщение {"type":"cmd",...}.
|
||||
*/
|
||||
static void handle_cmd(const char *p_line)
|
||||
{
|
||||
const uint8_t MAX_CMD_LEN = 32U;
|
||||
char cmd_name[MAX_CMD_LEN];
|
||||
|
||||
if (!parse_cmd_field(p_line, cmd_name, sizeof(cmd_name)))
|
||||
{
|
||||
protocol_send_error("PARSE_ERR");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(cmd_name, "ping") == 0)
|
||||
{
|
||||
protocol_send_pong();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(cmd_name, "get_version") == 0)
|
||||
{
|
||||
protocol_send_version_response();
|
||||
return;
|
||||
}
|
||||
|
||||
protocol_send_error("UNKNOWN_CMD");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Диспатчить накопленную строку по полю "type".
|
||||
*/
|
||||
static void process_line(const char *p_line)
|
||||
{
|
||||
const uint8_t MAX_TYPE_LEN = 16U;
|
||||
char msg_type[MAX_TYPE_LEN];
|
||||
|
||||
if (!parse_type_field(p_line, msg_type, sizeof(msg_type)))
|
||||
{
|
||||
protocol_send_error("PARSE_ERR");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(msg_type, "cmd") == 0)
|
||||
{
|
||||
handle_cmd(p_line);
|
||||
return;
|
||||
}
|
||||
|
||||
protocol_send_error("UNKNOWN_CMD");
|
||||
}
|
||||
|
||||
/* ── Public API ────────────────────────────────────────────────────────── */
|
||||
|
||||
void cli_init(void)
|
||||
{
|
||||
g_s_line_len = 0U;
|
||||
g_s_chunk_len = 0U;
|
||||
g_s_chunk_pos = 0U;
|
||||
}
|
||||
|
||||
void cli_send(const char *p_resp)
|
||||
{
|
||||
bsp_usb_cdc_write((const uint8_t *) p_resp, strlen(p_resp));
|
||||
}
|
||||
|
||||
void cli_process(void)
|
||||
{
|
||||
if (g_s_chunk_pos >= g_s_chunk_len)
|
||||
{
|
||||
g_s_chunk_len = bsp_usb_cdc_read(g_s_chunk_buf, sizeof(g_s_chunk_buf));
|
||||
g_s_chunk_pos = 0U;
|
||||
}
|
||||
|
||||
while (g_s_chunk_pos < g_s_chunk_len)
|
||||
{
|
||||
uint8_t byte = g_s_chunk_buf[g_s_chunk_pos];
|
||||
g_s_chunk_pos++;
|
||||
|
||||
if (g_s_line_len >= (CLI_LINE_BUF_SIZE - 1U))
|
||||
{
|
||||
g_s_line_len = 0U;
|
||||
protocol_send_error("LINE_TOO_LONG");
|
||||
return;
|
||||
}
|
||||
|
||||
if (byte == (uint8_t) '\n')
|
||||
{
|
||||
if (g_s_line_len > 0U && g_s_line_buf[g_s_line_len - 1U] == (uint8_t) '\r')
|
||||
{
|
||||
g_s_line_len--;
|
||||
}
|
||||
g_s_line_buf[g_s_line_len] = '\0';
|
||||
size_t completed_len = g_s_line_len;
|
||||
g_s_line_len = 0U; /* ← сбросить ДО process_line */
|
||||
if (completed_len > 0U)
|
||||
{
|
||||
process_line((const char *) g_s_line_buf);
|
||||
}
|
||||
}
|
||||
|
||||
g_s_line_buf[g_s_line_len] = byte;
|
||||
g_s_line_len++;
|
||||
}
|
||||
}
|
||||
50
firmware/bootloader/src/cli.h
Normal file
50
firmware/bootloader/src/cli.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* @file cli.h
|
||||
* @brief IO-слой CLI для bootloader.
|
||||
*
|
||||
* Транспорт: USB CDC ACM (bsp_usb_cdc) — единственный канал.
|
||||
* Протокол: JSON-lines, каждая строка завершается '\n'. Урезанное
|
||||
* подмножество протокола firmware_test (firmware/test/src/cli.h).
|
||||
*
|
||||
* Входящие типы (Фаза 1):
|
||||
* {"type":"cmd", "cmd":"ping"}
|
||||
* {"type":"cmd", "cmd":"get_version"}
|
||||
*
|
||||
* Исходящие события формируются через protocol.h, а не напрямую через cli_send().
|
||||
* cli_send() остаётся публичным: его использует protocol.c как единственную
|
||||
* точку вывода.
|
||||
*/
|
||||
|
||||
#ifndef CLI_H_
|
||||
#define CLI_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/** @brief Максимальная длина входящей JSON-строки включая '\n'. */
|
||||
#define CLI_LINE_BUF_SIZE 128U
|
||||
|
||||
/**
|
||||
* @brief Инициализировать CLI. Сбрасывает внутренний буфер строки.
|
||||
*
|
||||
* Вызывать после bsp_usb_cdc_init() и до первого cli_process().
|
||||
*/
|
||||
void cli_init(void);
|
||||
|
||||
/**
|
||||
* @brief Отправить готовую JSON-строку через USB CDC.
|
||||
*
|
||||
* @param[in] p_resp NUL-terminated строка, завершённая '\n'.
|
||||
*
|
||||
* @note Неблокирующий. Если TX занят — запись теряется.
|
||||
*/
|
||||
void cli_send(const char *p_resp);
|
||||
|
||||
/**
|
||||
* @brief Обработать входящие байты, диспатчить сообщение при получении '\n'.
|
||||
*
|
||||
* Вызывать в главном цикле после bsp_usb_cdc_poll().
|
||||
* Неблокирующий: если данных нет — возвращается немедленно.
|
||||
*/
|
||||
void cli_process(void);
|
||||
|
||||
#endif /* CLI_H_ */
|
||||
63
firmware/bootloader/src/main.c
Normal file
63
firmware/bootloader/src/main.c
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* @file main.c
|
||||
* @brief bootloader — точка входа.
|
||||
*
|
||||
* Фаза 1 (скелет): bring-up + USB CDC ACM + ping/get_version. Без доступа
|
||||
* к Flash-слотам, без bootutil, без SD — это добавится в Фазах 2-3.
|
||||
*
|
||||
* Единственный канал хост↔плата: USB CDC ACM. Протокол: JSON-lines через
|
||||
* cli.c (урезанное подмножество протокола firmware_test).
|
||||
*
|
||||
* Последовательность старта (зеркалит firmware/test/src/main.c):
|
||||
* 1. board_hw_init() — тактирование, MPU, кэш, пины
|
||||
* 2. bsp_led_init() — оба LED выключены
|
||||
* 3. bsp_tick_init() — SysTick 1 мс
|
||||
* 4. bsp_usb_cdc_init() — PHY + стек + NVIC
|
||||
* 5. Ожидание CDC ready — LED_HEARTBEAT мигает
|
||||
* 6. cli_init() — сброс буфера
|
||||
* 7. Главный цикл — poll + cli_process
|
||||
*
|
||||
* Bootloader без SDRAM (см. docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md) — DCD
|
||||
* не используется (bsp_boot_xip_no_dcd).
|
||||
*/
|
||||
#include "board.h"
|
||||
#include "bsp/led.h"
|
||||
#include "bsp/tick.h"
|
||||
#include "bsp/usb_cdc.h"
|
||||
#include "cli.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const uint32_t CONNECT_BLINK_MS = 200U;
|
||||
const uint32_t ERROR_BLINK_MS = 250U;
|
||||
|
||||
board_hw_init();
|
||||
|
||||
bsp_led_init();
|
||||
bsp_tick_init();
|
||||
|
||||
if (bsp_usb_cdc_init() != BSP_OK)
|
||||
{
|
||||
bsp_led_toggle(LED_HEARTBEAT);
|
||||
bsp_delay(ERROR_BLINK_MS);
|
||||
}
|
||||
|
||||
/* Ожидать подключения хоста. LED_HEARTBEAT мигает — bootloader жив. */
|
||||
while (!bsp_usb_cdc_is_ready())
|
||||
{
|
||||
bsp_usb_cdc_poll();
|
||||
bsp_led_toggle(LED_HEARTBEAT);
|
||||
bsp_delay(CONNECT_BLINK_MS);
|
||||
}
|
||||
|
||||
bsp_led_on(LED_APP);
|
||||
|
||||
cli_init();
|
||||
while (1)
|
||||
{
|
||||
bsp_usb_cdc_poll();
|
||||
cli_process();
|
||||
}
|
||||
}
|
||||
38
firmware/bootloader/src/protocol.c
Normal file
38
firmware/bootloader/src/protocol.c
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* @file protocol.c
|
||||
* @brief Протокол bootloader — реализация сериализации.
|
||||
*/
|
||||
|
||||
#include "protocol.h"
|
||||
|
||||
#include "cli.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* ── Константы ─────────────────────────────────────────────────────────── */
|
||||
|
||||
/** @brief Размер внутреннего TX-буфера. */
|
||||
#define PROTO_BUF_SIZE 128U
|
||||
|
||||
/* ── Public API ────────────────────────────────────────────────────────── */
|
||||
|
||||
void protocol_send_pong(void)
|
||||
{
|
||||
cli_send("{\"type\":\"pong\"}\n");
|
||||
}
|
||||
|
||||
void protocol_send_version_response(void)
|
||||
{
|
||||
char buf[PROTO_BUF_SIZE];
|
||||
(void) snprintf(buf, sizeof(buf),
|
||||
"{\"type\":\"version_response\","
|
||||
"\"fw\":\"" BOOTLOADER_VERSION "\"}\n");
|
||||
cli_send(buf);
|
||||
}
|
||||
|
||||
void protocol_send_error(const char *p_code)
|
||||
{
|
||||
char buf[PROTO_BUF_SIZE];
|
||||
(void) snprintf(buf, sizeof(buf), "{\"ok\":false,\"error\":\"%s\"}\n", p_code);
|
||||
cli_send(buf);
|
||||
}
|
||||
48
firmware/bootloader/src/protocol.h
Normal file
48
firmware/bootloader/src/protocol.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* @file protocol.h
|
||||
* @brief Протокол bootloader — сериализация исходящих событий.
|
||||
*
|
||||
* Урезанное подмножество протокола firmware_test (firmware/test/src/protocol.h):
|
||||
* только то, что нужно для диагностики bootloader через service-tui. Без
|
||||
* test_begin/test_result/confirm_request/session_start — те специфичны для
|
||||
* тестового раннера firmware_test.
|
||||
*
|
||||
* Все функции формируют JSON-строку и отправляют через cli_send().
|
||||
* Без динамической памяти — каждая функция пишет в стековый буфер.
|
||||
*
|
||||
* Типы исходящих событий (Фаза 1):
|
||||
* pong — ответ на {"type":"cmd","cmd":"ping"}
|
||||
* version_response — ответ на {"type":"cmd","cmd":"get_version"}
|
||||
* error — ошибка протокола или парсинга
|
||||
*
|
||||
* status-события (waiting_for_sd/installing/smoke_pass/...) добавятся в Фазах 3-4.
|
||||
*/
|
||||
|
||||
#ifndef PROTOCOL_H_
|
||||
#define PROTOCOL_H_
|
||||
|
||||
#include "version.h"
|
||||
|
||||
/** @brief Строка версии bootloader, вставляемая в version_response. */
|
||||
#define BOOTLOADER_VERSION BOOTLOADER_VERSION_STR
|
||||
|
||||
/**
|
||||
* @brief Отправить pong — ответ на ping.
|
||||
*/
|
||||
void protocol_send_pong(void);
|
||||
|
||||
/**
|
||||
* @brief Отправить ответ на команду get_version.
|
||||
*
|
||||
* Формат: {"type":"version_response","fw":"X.Y.Z"}
|
||||
*/
|
||||
void protocol_send_version_response(void);
|
||||
|
||||
/**
|
||||
* @brief Отправить событие error.
|
||||
*
|
||||
* @param[in] p_code Короткий ASCII-код ошибки, напр. "PARSE_ERR".
|
||||
*/
|
||||
void protocol_send_error(const char *p_code);
|
||||
|
||||
#endif /* PROTOCOL_H_ */
|
||||
25
firmware/bootloader/src/version.h.in
Normal file
25
firmware/bootloader/src/version.h.in
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* @file version.h
|
||||
* @brief Версия bootloader — генерируется CMake из CMakeLists.txt.
|
||||
*
|
||||
* НЕ редактировать вручную. Версию менять в firmware/bootloader/CMakeLists.txt:
|
||||
* project(bootloader VERSION X.Y.Z)
|
||||
*/
|
||||
|
||||
#ifndef VERSION_H_
|
||||
#define VERSION_H_
|
||||
|
||||
/** @brief Мажорная версия. */
|
||||
#define BOOTLOADER_VERSION_MAJOR @bootloader_VERSION_MAJOR@
|
||||
|
||||
/** @brief Минорная версия. */
|
||||
#define BOOTLOADER_VERSION_MINOR @bootloader_VERSION_MINOR@
|
||||
|
||||
/** @brief Патч-версия. */
|
||||
#define BOOTLOADER_VERSION_PATCH @bootloader_VERSION_PATCH@
|
||||
|
||||
/** @brief Версия строкой для протокола: "X.Y.Z". */
|
||||
#define BOOTLOADER_VERSION_STR \
|
||||
"@bootloader_VERSION_MAJOR@.@bootloader_VERSION_MINOR@.@bootloader_VERSION_PATCH@"
|
||||
|
||||
#endif /* VERSION_H_ */
|
||||
Loading…
Reference in a new issue