Skip to content

feat(firmware/c_board): Introduce bootloader and DFU flashing pipeline#28

Merged
qzhhhi merged 2 commits intomainfrom
dev/bootloader
Mar 2, 2026
Merged

feat(firmware/c_board): Introduce bootloader and DFU flashing pipeline#28
qzhhhi merged 2 commits intomainfrom
dev/bootloader

Conversation

@qzhhhi
Copy link
Member

@qzhhhi qzhhhi commented Mar 2, 2026

  • Add firmware/c_board/bootloader target to implement download, write, and manifest flows via TinyUSB DFU.
  • Establish image layout and metadata management, featuring a validation chain with CRC32 and SHA256 (HASH suffix).
  • Prioritize app image validation on startup, jumping from the bootloader to the app upon successful verification.
  • Enable DFU Runtime in the application layer, allowing reboot requests into DFU mode via the .boot_mailbox.
  • Split APP and BOOTLOADER linker scripts, introducing MAILBOX and .flash_sector_cache sections.
  • Add post-build processing to generate c_board_app.bin and c_board_app.dfu.
  • Update Docker/CI packaging to include bootloader artifacts and switch the default c_board release artifact to .dfu.
  • Update lint targets and add dfu-util to the build image dependencies.

Note: Rollbacks are not supported once the flashing process begins; a full flash cycle must be completed.

引入 Bootloader 与 DFU 刷写管道

概述

本 PR 为 c_board 固件引入完整的 Bootloader 与 USB DFU(Device Firmware Update)刷写流水线,包含镜像布局与元数据管理、校验链(CRC32 + SHA‑256 HASH 后缀)、Boot Mailbox 协议以便应用请求进入 DFU、以及构建/CI 发布工件变更(将 .dfu 作为默认发布固件)。

Bootloader(firmware/c_board/bootloader)

  • 新增整体 bootloader 子项目及构建配置(CMake)。
  • flash 管理:
    • flash/layout.hpp:定义应用区 [0x08010000, 0x08100000) 与 8 个扇区映射。
    • flash/metadata.hpp:单例元数据管理器,多槽位状态机(kEmpty/kFlashing/kReady/kFatal),提供 begin_flashing()/finish_flashing() 与扫描/擦除逻辑,保存 image_size 与 image_crc32。
    • flash/writer.hpp:按扇区缓冲的分阶段写入器,128KB 缓冲区、按扇区比对避免不必要擦写、使用 UnlockGuard 保护 flash 操作。
    • flash/validation.hpp:完整镜像验证链,向量表校验、CRC32 计算、SHA‑256 校验(期望二进制尾部含 MAGIC +"HASH" + 32 字节 SHA‑256)。
  • DFU 实现:
    • usb/dfu.hpp + usb/dfu.cpp:DFU 单例管理下载、manifest、abort、detach、poll 等;校验块序号/边界、写入会话管理、manifest 完成后计算 CRC 并写入元数据、触发重启。
    • usb/usb_descriptors.hpp/.cpp:USB 描述符与动态序列号生成,支持 DFU 运行时接口。
  • 工具与基础设施:
    • crypto/sha256.hpp:头文件实现 SHA‑256(供 bootloader 使用)。
    • utility/jump.hpp:跳转到应用的低级例程(设置 MSP/VTOR、清理中断等)。
    • utility/assert.hpp:断言工具。
    • utility/boot_mailbox.hpp(bootloader 侧的实现):用于检测/消费来自应用的 DFU 请求。
  • 主程序:
    • src/main.cpp:启动时初始化外设并决定进入 DFU 或跳转到应用;若应用镜像通过验证则跳转到 0x08010000,否则进入 DFU 循环。

应用层变更(app)

  • 在应用端启用 DFU Runtime 支持(tusb 配置):
    • app/include/tusb_config.h:CFG_TUD_DFU_RUNTIME = 1(DFU 运行时使能),CFG_TUD_DFU 保持禁用于应用本身。
  • USB 接口与回调:
    • app/src/usb/usb_descriptors.hpp:新增 DFU Runtime 接口、描述符与字符串项(调整接口枚举和配置长度)。
    • app/src/usb/vendor.cpp:新增 tud_dfu_runtime_reboot_to_dfu_cb 回调,调用 boot_mailbox.request_enter_dfu() 并触发系统复位以进入 DFU 引导。
  • Boot Mailbox(应用端声明):
    • app/src/utility/boot_mailbox.hpp:定义 .boot_mailbox section 的全局变量(magic/request、clear()/request_enter_dfu()),与 bootloader 共享位置协议。
  • 应用启动调整:
    • app/src/app.cpp:在主程序中将 SCB->VTOR 设置为 0x08010000 并在初始化时清除 boot_mailbox。

后构建处理与发行工件

  • app/CMakeLists.txt:新增 post-build 流程:
    • objcopy 生成 .bin;
    • .scripts/append_image_hash 脚本向二进制追加 36 字节后缀(4 字节 MAGIC "HASH" + 32 字节 SHA‑256);
    • 使用 dfu-suffix 将设备/板信息嵌入 .dfu;
    • 生成 c_board_app.dfu 并输出提示信息。
  • .scripts/append_image_hash:新增 Python 脚本(将 SHA‑256 与 MAGIC 附加到固件文件)。
  • 构建产物命名与 CI:
    • Dockerfile / Dockerfile.build_firmware:构建镜像中加入 dfu-util 依赖;构建脚本改为产出 librmcs-bootloader-${board}.elf 与 librmcs-firmware-${board}-${version}.dfu(不再默认输出固件 ELF)。
    • .github/workflows/release-package.yml:上传步骤改为支持多个工件(包含 bootloader 与 firmware .dfu)。
    • .scripts/lint-targets.yml:将 bootloader 的 src/include 加入 lint 检查。

链接脚本与内存布局

  • STM32F407XX_APP.ld:应用链接脚本修改:
    • FLASH 起点移动到 0x08010000(应用区),新增 MAILBOX 区域(0x1000FFC0,64 字节),CCMRAM/FLASH 大小相应调整;引入 .boot_mailbox 段与符号。
  • STM32F407XX_BOOTLOADER.ld:新增完整 bootloader 链接脚本,定义 bootloader 的内存映射、段布局与符号(为 bootloader 构建提供独立脚本)。

安全性与校验

  • 镜像完整性:
    • CRC32(多项式 0xEDB88320)用于运行时快速校验;
    • SHA‑256 验证通过在二进制尾部追加 4 字节 MAGIC + 32 字节 HASH 来完成额外验证。
  • 启动时校验顺序:元数据就绪 → 大小/边界检查 → 向量表有效性 → CRC32 → SHA‑256;只有全部通过才跳转到应用。
  • DFU 过程中的安全与一致性:分块顺序检查、地址边界限制、写入前对比以避免盲目擦写、manifest 时写入元数据并再次验证。
  • 设计说明:刷写开始后不支持回滚;一旦开始刷写,需完成整个 flash 周期以恢复一致性。

关键参数与实现细节(要点)

  • 应用区:0x08010000 — 0x08100000(exclusive)。
  • Bootloader 占据低地址(链接脚本定义)。
  • Boot Mailbox 放置于 CCMRAM 顶部的 MAILBOX 区(0x1000FFC0),大小 64 字节,用于应用请求进入 DFU。
  • DFU 传输缓冲大小:CFG_TUD_DFU_XFER_BUFSIZE = 1024(用于分块/写入)。
  • 元数据采用多槽位设计、状态迁移与擦除/重扫描策略。
  • Flash 写入缓冲能力:128KB 扇区缓存,按扇区提交与比较优化。
  • 构建产物:默认生成 c_board_app.dfu(包含 HASH 后缀与 DFU 元数据),并附加 bootloader ELF 产物到 release 包。

需注意/待评估项

  • 回滚不可用:当前实现声明“刷写开始后不支持回滚”,需要在部署或 UX 文档中明确风险。
  • 镜像元数据与槽位策略的健壮性(异常中断/断电场景)建议进一步审视与压力测试。
  • 审计点:sha256 实现、CRC 计算与 flash 写操作在边界/未对齐数据下的正确性、以及 reset/poll 触发时序(DFU manifest → 重启)需要在硬件上验证。

- Add `firmware/c_board/bootloader` target to implement download, write, and manifest flows via TinyUSB DFU.
- Establish image layout and metadata management, featuring a validation chain with CRC32 and SHA256 (`HASH` suffix).
- Prioritize app image validation on startup, jumping from the bootloader to the app upon successful verification.
- Enable DFU Runtime in the application layer, allowing reboot requests into DFU mode via the `.boot_mailbox`.
- Split APP and BOOTLOADER linker scripts, introducing `MAILBOX` and `.flash_sector_cache` sections.
- Add post-build processing to generate `c_board_app.bin` and `c_board_app.dfu`.
- Update Docker/CI packaging to include bootloader artifacts and switch the default c_board release artifact to `.dfu`.
- Update lint targets and add `dfu-util` to the build image dependencies.

Note: Rollbacks are not supported once the flashing process begins; a full flash cycle must be completed.
@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

Walkthrough

该 PR 在 c_board 上加入完整引导加载程序与 USB DFU 支持,包含引导邮箱、DFU 协议实现、分区写入器、镜像验证(CRC32 + SHA‑256)、构建与打包改动以生成 bootloader 与 DFU 工件,并对应用端添加 DFU 运行时钩子。

Changes

Cohort / File(s) Summary
CI 与 构建配置
​.github/workflows/release-package.yml, Dockerfile, Dockerfile.build_firmware, .scripts/lint-targets.yml
发布工作流与镜像构建更新:支持多个输出工件(增加 bootloader 与 .dfu),CI 镜像安装 dfu‑util,并将 bootloader 源加入 lint 目标。
构建/打包脚本
.scripts/append_image_hash
新增脚本:对固件二进制附加 4 字节魔数 + 32 字节 SHA‑256 后缀,用于后续 DFU 工件生成。
CMake 与 应用后处理
firmware/c_board/CMakeLists.txt, firmware/c_board/app/CMakeLists.txt, firmware/c_board/app/include/tusb_config.h
添加 bootloader 子目录,应用端生成 .dfu 的后构建流水线(objcopy -> append hash -> dfu‑suffix),并调整 TinyUSB 配置以开启 DFU 运行时支持。
应用端源代码与 USB
firmware/c_board/app/src/app.cpp, firmware/c_board/app/src/usb/usb_descriptors.hpp, firmware/c_board/app/src/usb/vendor.cpp
集成启动邮箱清除、设置 VTOR 到应用起始、添加 DFU 运行时字符串/接口与实现 tud_dfu_runtime_reboot_to_dfu_cb() 回调触发进入 DFU 的请求。
共享工具:应用端启动邮箱
firmware/c_board/app/src/utility/boot_mailbox.hpp
新增启动邮箱结构(magic/request)并在特定 section 中定义实例,用于应用向引导加载程序请求 DFU。
Bootloader 构建目标
firmware/c_board/bootloader/CMakeLists.txt
新增 bootloader 可执行与 tinyusb OBJECT 库目标,配置 include、链接与专用 linker script。
Bootloader USB / DFU 实现
firmware/c_board/bootloader/src/usb/dfu.hpp, .../dfu.cpp, .../usb_descriptors.hpp, .../usb_descriptors.cpp, firmware/c_board/bootloader/include/tusb_config.h
新增 Dfu 单例实现 DFU 会话管理(download/manifest/abort/detach/poll、错误映射)、USB 描述符提供与 TinyUSB 配置(DFU enabled)。
Flash 管理与元数据
firmware/c_board/bootloader/src/flash/layout.hpp, .../metadata.hpp, .../writer.hpp, .../validation.hpp, .../unlock_guard.hpp
新增闪存地址/扇区映射、元数据槽管理(begin/finish 抢占、轮换)、按扇区缓冲写入器、镜像验证函数(向量表、CRC32、SHA‑256 校验)及 HAL Flash 锁定 Guard。
密码学实现
firmware/c_board/bootloader/src/crypto/sha256.hpp
新增头文件内联 SHA‑256 实现,提供上下文/初始化/更新/最终输出函数供镜像哈希计算使用。
Bootloader 工具与入口
firmware/c_board/bootloader/src/utility/assert.hpp, .../boot_mailbox.hpp, .../jump.hpp, .../main.cpp
新增断言工具、引导邮箱(bootloader 侧)、跳转到应用的安全例程与 bootloader 主入口实现(USB 初始化、DFU 循环或跳转应用)。
链接脚本与 HAL 头
firmware/c_board/bsp/cubemx/STM32F407XX_APP.ld, firmware/c_board/bsp/cubemx/STM32F407XX_BOOTLOADER.ld, firmware/c_board/bsp/cubemx/Core/Inc/main.h
添加 bootloader 专用 linker script、调整 APP ld 脚本以为应用保留 mailbox 区域,并在 HAL 头中包含 flash_ex 扩展头文件。

Sequence Diagrams

sequenceDiagram
    participant Host as Host (PC)
    participant Device as Device (Bootloader)
    participant Flash as Flash 存储
    participant Metadata as Metadata
    Host->>Device: USB DFU 连接并发送 download blocks
    activate Device
    Device->>Device: Dfu::download 验证地址/块序与边界
    Device->>Flash: Writer.write(分扇区缓冲并编程)
    Flash-->>Device: 编程完成
    Device->>Metadata: 更新/写入临时状态(flashing)
    Device-->>Host: 返回 DFU_STATUS_OK
    Host->>Device: 发送 manifest(完成)
    Device->>Device: Dfu::manifest 验证向量表与 CRC/SHA
    Device->>Metadata: finish_flashing(写就绪元数据)
    Metadata-->>Device: 元数据写入确认
    Device->>Device: 请求系统重置(pending reset)
    deactivate Device
Loading
sequenceDiagram
    participant App as Application
    participant USB as TinyUSB (DFU runtime)
    participant Mailbox as BootMailbox (shared)
    participant Device as Device (将重启为 Bootloader)
    App->>USB: 用户触发 DFU 运行时重启命令
    USB->>App: 调用 tud_dfu_runtime_reboot_to_dfu_cb()
    App->>Mailbox: boot_mailbox.request_enter_dfu()
    Mailbox-->>App: 标记已设置
    App->>Device: 执行同步屏障并触发系统重置
    Device->>Device: 启动引导加载程序,检查 mailbox
    Mailbox-->>Device: 检测到 DFU 请求
    Device->>Device: 进入 DFU 模式并初始化 USB DFU 服务
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 分钟

Possibly related PRs

Poem

🐰 我在 mailbox 旁静候,字节跳跃如胡萝卜,
引导之光唤醒闪存,DFU 舞步轻又快,
CRC 与 SHA 护安全,扇区轮转唱支歌,
重启一跳入新天,固件稳稳落我怀。

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题清晰地总结了此次变更的主要目的:为 firmware/c_board 引入启动加载程序和DFU刷写管道,与变更集的核心内容(添加完整的bootloader模块、DFU功能、镜像验证和刷写流程)完全相关。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev/bootloader

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qzhhhi
Copy link
Member Author

qzhhhi commented Mar 2, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (7)
firmware/c_board/bootloader/CMakeLists.txt (1)

32-35: 考虑显式列出源文件以提高构建可靠性。

使用 file(GLOB_RECURSE ... CONFIGURE_DEPENDS) 虽然方便,但 CMake 官方文档指出 CONFIGURE_DEPENDS 在某些构建系统中可能无法可靠地检测新增文件。对于源文件相对稳定的嵌入式项目,这通常可以接受,但显式列出源文件(如 TinyUSB 源文件的处理方式)可以避免潜在的构建问题。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/c_board/bootloader/CMakeLists.txt` around lines 32 - 35, The current
use of file(GLOB_RECURSE C_BOARD_BOOTLOADER_SOURCES CONFIGURE_DEPENDS ...) is
fragile; replace the glob with an explicit list assigned to
C_BOARD_BOOTLOADER_SOURCES (or include a separate generated/list file)
enumerating all .c/.cpp bootloader sources (same symbols:
C_BOARD_BOOTLOADER_SOURCES and the CMakeLists.txt target that consumes it) and
remove CONFIGURE_DEPENDS so new/removed files are tracked reliably by CMake;
list each TinyUSB and board-specific source file explicitly and update any
add_executable/add_library calls that use C_BOARD_BOOTLOADER_SOURCES
accordingly.
Dockerfile.build_firmware (1)

24-27: 建议统一 bootloader 产物命名以提升可追溯性。

当前 firmware 文件带版本号,而 bootloader 文件不带版本号,后续归档/比对会不一致。

♻️ 建议修改
-        cp "${build_dir}/bootloader/${board}_bootloader.elf" \
-            "/output/librmcs-bootloader-${board}.elf"; \
+        cp "${build_dir}/bootloader/${board}_bootloader.elf" \
+            "/output/librmcs-bootloader-${board}-${LIBRMCS_PROJECT_VERSION}.elf"; \
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile.build_firmware` around lines 24 - 27, The bootloader output file
lacks the project version while the firmware file includes
${LIBRMCS_PROJECT_VERSION}, causing inconsistent artifact names; update the cp
that copies "${build_dir}/bootloader/${board}_bootloader.elf" to write
"/output/librmcs-bootloader-${board}-${LIBRMCS_PROJECT_VERSION}.elf" (matching
the firmware pattern), ensuring you reference the same build_dir, board and
LIBRMCS_PROJECT_VERSION variables as used for the firmware cp command.
firmware/c_board/app/CMakeLists.txt (1)

60-62: 建议显式用 Python 解释器调用脚本,减少环境依赖。

当前直接执行脚本路径依赖可执行位与 shebang;改为显式 python3 调用更稳健。

♻️ 建议修改
-    COMMAND ${LIBRMCS_PROJECT_ROOT}/.scripts/append_image_hash
+    COMMAND python3 ${LIBRMCS_PROJECT_ROOT}/.scripts/append_image_hash
             -o $<TARGET_FILE_DIR:c_board_app>/c_board_app.dfu
             $<TARGET_FILE_DIR:c_board_app>/c_board_app.bin
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/c_board/app/CMakeLists.txt` around lines 60 - 62, 当前 CMake COMMAND
直接调用脚本路径 (${LIBRMCS_PROJECT_ROOT}/.scripts/append_image_hash) 依赖可执行位与
shebang,改为显式使用 Python 解释器来降低环境依赖:在 CMakeLists 中将该 COMMAND 改为调用 `python3`(或
`${PYTHON_EXECUTABLE}` 若已定义)并传入相同参数 so that the script is invoked as `python3
${LIBRMCS_PROJECT_ROOT}/.scripts/append_image_hash -o
$<TARGET_FILE_DIR:c_board_app>/c_board_app.dfu
$<TARGET_FILE_DIR:c_board_app>/c_board_app.bin`, ensuring the target names
($<TARGET_FILE_DIR:c_board_app>/c_board_app.dfu and .../c_board_app.bin) remain
unchanged.
firmware/c_board/bootloader/src/crypto/sha256.hpp (1)

25-25: kSha256BlockSize 命名可更精确

这里的 32 实际是摘要长度(digest size),不是 SHA-256 的消息分组大小(64 字节)。建议改名为 kSha256DigestSize,减少语义歧义。

♻️ 建议修改
-inline constexpr size_t kSha256BlockSize = 32U;
+inline constexpr size_t kSha256DigestSize = 32U;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/c_board/bootloader/src/crypto/sha256.hpp` at line 25, Rename the
misleading constant kSha256BlockSize to kSha256DigestSize and keep its value as
32U; update the declaration in sha256.hpp and replace every usage of
kSha256BlockSize across the codebase (e.g., in functions, structs, tests, and
any static_asserts) to refer to kSha256DigestSize so the name reflects the
digest length (not the 64‑byte message block size) and adjust any related
comments/documentation to match.
firmware/c_board/app/src/usb/usb_descriptors.hpp (1)

49-50: 建议将 DFU 字符串索引提取为常量。

Line 49 与 Line 165 目前都硬编码 4,后续调整字符串表时容易漂移,建议统一为命名常量。

♻️ 建议改法
+    static constexpr uint8_t kStringIndexDfuRuntime = 4;
+
     uint16_t const* get_string_descriptor(uint8_t index, uint16_t langid) {
@@
-            case 4: str = kDfuRuntimeString; break;
+            case kStringIndexDfuRuntime: str = kDfuRuntimeString; break;
@@
         TUD_DFU_RT_DESCRIPTOR(
-            kItfNumDfuRuntime, 4, DFU_ATTR_CAN_DOWNLOAD | DFU_ATTR_WILL_DETACH, 1000, 1024),
+            kItfNumDfuRuntime,
+            kStringIndexDfuRuntime,
+            DFU_ATTR_CAN_DOWNLOAD | DFU_ATTR_WILL_DETACH,
+            1000,
+            1024),

Also applies to: 164-165

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/c_board/app/src/usb/usb_descriptors.hpp` around lines 49 - 50,
Introduce a named constant for the DFU string index (e.g., DFU_STRING_INDEX or
kDfuStringIndex) and replace the hard-coded literal 4 used in the switch case
and the other occurrence(s) (the branch that currently uses 4 at both the case
in the descriptor resolver and the other check at/around lines 164-165) with
that constant; update any comments/uses in the same scope so all references to
the DFU string index use the new constant (search for the case 4 and the other 4
occurrence and swap them to the single constant) to avoid drift when the string
table changes.
firmware/c_board/app/src/app.cpp (1)

24-24: 避免在 VTOR 设置中硬编码应用基址。

Line 24 直接写 0x08010000U,后续若链接脚本或分区调整,这里可能静默失配并把中断向量指向错误地址。建议改为共享布局常量(与 bootloader 跳转地址同源),至少先提取为命名常量避免魔法值。

♻️ 建议改法(最小改动)
+namespace {
+constexpr uint32_t kAppVectorTableAddress = 0x08010000U; // TODO: 改为共享 flash layout 常量
+}
+
 int main() {
-    SCB->VTOR = 0x08010000U;
+    SCB->VTOR = kAppVectorTableAddress;
     librmcs::firmware::app.init().run();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/c_board/app/src/app.cpp` at line 24, Replace the hardcoded VTOR
value (SCB->VTOR = 0x08010000U) with a shared, named symbol used by the
bootloader/linker so the vector base tracks layout changes; expose a single
source of truth (e.g., APPLICATION_BASE or an extern linker symbol like
_app_start/_app_vector) in a header both bootloader and app include, then set
SCB->VTOR to that symbol (properly cast to uint32_t) and ensure the symbol
aligns to a vector table boundary. This removes the magic literal and keeps
bootloader jump address and VTOR in sync.
firmware/c_board/bootloader/src/flash/metadata.hpp (1)

54-56: 元数据地址与擦除扇区建议统一由 layout.hpp 提供。

当前地址与 sector 是硬编码,和 linker/layout 存在双源配置风险;后续内存布局调整时容易漏改,导致误擦写。建议把 region/sector 常量集中到 firmware/c_board/bootloader/src/flash/layout.hpp,本文件仅引用。

♻️ 建议重构
-    static constexpr uintptr_t kMetadataStartAddress = 0x0800C000U;
-    static constexpr uintptr_t kMetadataEndAddress = 0x08010000U;
+    static constexpr uintptr_t kMetadataStartAddress = kMetadataRegionStartAddress;
+    static constexpr uintptr_t kMetadataEndAddress = kMetadataRegionEndAddress;
@@
-        erase.Sector = FLASH_SECTOR_3;
+        erase.Sector = kMetadataRegionFlashSector;

并在 firmware/c_board/bootloader/src/flash/layout.hpp 中维护 kMetadataRegionStartAddress / kMetadataRegionEndAddress / kMetadataRegionFlashSector

Also applies to: 184-185

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/c_board/bootloader/src/flash/metadata.hpp` around lines 54 - 56, The
metadata start/end addresses are hardcoded in kMetadataStartAddress and
kMetadataEndAddress; move these constants into the central flash layout and
reference them instead: add kMetadataRegionStartAddress,
kMetadataRegionEndAddress and kMetadataRegionFlashSector to
firmware/c_board/bootloader/src/flash/layout.hpp and replace uses of
kMetadataStartAddress/kMetadataEndAddress (and the other hardcoded sector values
noted elsewhere) to include layout.hpp and use the new kMetadataRegion* symbols
so the region/sector definitions are single-sourced.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@firmware/c_board/app/CMakeLists.txt`:
- Around line 63-66: add_custom_command 的 COMMAND 中不能直接使用 shell
重定向;把当前以字面参数传入的重定向修正为通过 shell 执行:将原来调用 dfu-suffix ... -a
$<TARGET_FILE_DIR:c_board_app>/c_board_app.dfu > /dev/null 的片段改为通过 /bin/sh -c
包裹整个命令字符串(包含重定向),即在 add_custom_command 的 COMMAND 中调用 /bin/sh -c "dfu-suffix -v
0xA11C -p 0xD401 -d 0x0100 -a $<TARGET_FILE_DIR:c_board_app>/c_board_app.dfu >
/dev/null";在 CMakeLists 中定位包含 dfu-suffix 调用的 add_custom_command 并替换为该包装形式以确保重定向被
shell 解析。

In `@firmware/c_board/bootloader/src/flash/validation.hpp`:
- Around line 19-38: The reset handler range check in is_vector_table_valid()
currently allows any address < kAppEndAddress, but the verified image only spans
kAppStartAddress up to kAppStartAddress + size (the region covered by the
CRC/SHA in the other validation code). Update is_vector_table_valid() to
constrain reset_handler_addr to be >= kAppStartAddress and < (kAppStartAddress +
image_size / validated_size) — i.e. use the same image size variable used by the
CRC/SHA routine (the "size" used in the CRC/SHA checks) instead of
kAppEndAddress so the reset vector cannot point into unvalidated/old code;
reference is_vector_table_valid(), kAppStartAddress, reset_handler_addr, and the
CRC/SHA size variable used in the validation block.

In `@firmware/c_board/bootloader/src/flash/writer.hpp`:
- Around line 100-107: The current advance_buffer(uint32_t offset, size_t size)
allows non-contiguous writes that lead to unwritten gaps being left as 0xFF
after the "erase sector + write prefix [0, buffered_size_)" strategy; update the
logic to prevent unordered writes by either (A) enforcing contiguous writes
inside the active sector: check has_active_sector_ and require offset ==
buffered_size_ (or offset <= buffered_size_ only if you intend to allow
overwrite inside the already-buffered range) and assert/error otherwise, or (B)
implement a full-sector read-modify-write path: when a write targets a
non-contiguous offset, read the entire sector into the buffer, merge the
incoming data at the offset, and set buffered_size_ = sector_size (or the merged
highest dirty byte) before erasing+writing; apply the same fix to the related
logic referenced around lines 117-131 that updates buffered_size_ and performs
commit/flush so gaps cannot be left as 0xFF.
- Around line 34-39: The runtime parameter checks in write() currently use
utility::assert_debug (lines checking !data.empty(), address range against
kAppStartAddress/kAppEndAddress, end64 <= kAppEndAddress, and the UINT32_MAX
overflow check) which becomes assume under NDEBUG; replace these with
runtime-safe validation—either call utility::assert_always for invariant
enforcement or (preferably) perform explicit input validation and
return/propagate an error code or boolean from write() when the checks fail so
invalid runtime inputs are handled deterministically instead of being optimized
away.

In `@firmware/c_board/bootloader/src/usb/usb_descriptors.hpp`:
- Around line 65-76: The bootloader's update_serial_string() currently formats
the raw HAL_GetUIDw* values directly, while the app uses mix_uid_entropy()
before formatting, causing inconsistent USB serials after DFU; update
update_serial_string() to call the same mix_uid_entropy() path (or move the
mixing+formatting into a shared helper used by both bootloader and app), e.g.
obtain the UID words via HAL_GetUIDw0/1/2, pass them through mix_uid_entropy()
and then use write_hex_u16(...) to write into serial_string_.data() (cursor
handling remains the same) so both sides use the exact same algorithm for serial
generation.

In `@firmware/c_board/bootloader/src/utility/jump.hpp`:
- Around line 10-12: 在执行跳转前对从镜像读取的向量表条目做最小合法性校验:验证 app_entry 的最低位为 1(确保为 Thumb
函数指针)、验证 app_stack 的对齐(至少 8 字节对齐且不为 0),并可选检查 app_stack 所在于合法 RAM 区间。将这些检查加入设置
VTOR 和切换栈(使用 app_stack、app_entry、app_reset_handler)的逻辑里,若任一检查失败则不修改
VTOR/堆栈并立即返回错误或进入 fail-fast 路径。

---

Nitpick comments:
In `@Dockerfile.build_firmware`:
- Around line 24-27: The bootloader output file lacks the project version while
the firmware file includes ${LIBRMCS_PROJECT_VERSION}, causing inconsistent
artifact names; update the cp that copies
"${build_dir}/bootloader/${board}_bootloader.elf" to write
"/output/librmcs-bootloader-${board}-${LIBRMCS_PROJECT_VERSION}.elf" (matching
the firmware pattern), ensuring you reference the same build_dir, board and
LIBRMCS_PROJECT_VERSION variables as used for the firmware cp command.

In `@firmware/c_board/app/CMakeLists.txt`:
- Around line 60-62: 当前 CMake COMMAND 直接调用脚本路径
(${LIBRMCS_PROJECT_ROOT}/.scripts/append_image_hash) 依赖可执行位与 shebang,改为显式使用
Python 解释器来降低环境依赖:在 CMakeLists 中将该 COMMAND 改为调用 `python3`(或
`${PYTHON_EXECUTABLE}` 若已定义)并传入相同参数 so that the script is invoked as `python3
${LIBRMCS_PROJECT_ROOT}/.scripts/append_image_hash -o
$<TARGET_FILE_DIR:c_board_app>/c_board_app.dfu
$<TARGET_FILE_DIR:c_board_app>/c_board_app.bin`, ensuring the target names
($<TARGET_FILE_DIR:c_board_app>/c_board_app.dfu and .../c_board_app.bin) remain
unchanged.

In `@firmware/c_board/app/src/app.cpp`:
- Line 24: Replace the hardcoded VTOR value (SCB->VTOR = 0x08010000U) with a
shared, named symbol used by the bootloader/linker so the vector base tracks
layout changes; expose a single source of truth (e.g., APPLICATION_BASE or an
extern linker symbol like _app_start/_app_vector) in a header both bootloader
and app include, then set SCB->VTOR to that symbol (properly cast to uint32_t)
and ensure the symbol aligns to a vector table boundary. This removes the magic
literal and keeps bootloader jump address and VTOR in sync.

In `@firmware/c_board/app/src/usb/usb_descriptors.hpp`:
- Around line 49-50: Introduce a named constant for the DFU string index (e.g.,
DFU_STRING_INDEX or kDfuStringIndex) and replace the hard-coded literal 4 used
in the switch case and the other occurrence(s) (the branch that currently uses 4
at both the case in the descriptor resolver and the other check at/around lines
164-165) with that constant; update any comments/uses in the same scope so all
references to the DFU string index use the new constant (search for the case 4
and the other 4 occurrence and swap them to the single constant) to avoid drift
when the string table changes.

In `@firmware/c_board/bootloader/CMakeLists.txt`:
- Around line 32-35: The current use of file(GLOB_RECURSE
C_BOARD_BOOTLOADER_SOURCES CONFIGURE_DEPENDS ...) is fragile; replace the glob
with an explicit list assigned to C_BOARD_BOOTLOADER_SOURCES (or include a
separate generated/list file) enumerating all .c/.cpp bootloader sources (same
symbols: C_BOARD_BOOTLOADER_SOURCES and the CMakeLists.txt target that consumes
it) and remove CONFIGURE_DEPENDS so new/removed files are tracked reliably by
CMake; list each TinyUSB and board-specific source file explicitly and update
any add_executable/add_library calls that use C_BOARD_BOOTLOADER_SOURCES
accordingly.

In `@firmware/c_board/bootloader/src/crypto/sha256.hpp`:
- Line 25: Rename the misleading constant kSha256BlockSize to kSha256DigestSize
and keep its value as 32U; update the declaration in sha256.hpp and replace
every usage of kSha256BlockSize across the codebase (e.g., in functions,
structs, tests, and any static_asserts) to refer to kSha256DigestSize so the
name reflects the digest length (not the 64‑byte message block size) and adjust
any related comments/documentation to match.

In `@firmware/c_board/bootloader/src/flash/metadata.hpp`:
- Around line 54-56: The metadata start/end addresses are hardcoded in
kMetadataStartAddress and kMetadataEndAddress; move these constants into the
central flash layout and reference them instead: add
kMetadataRegionStartAddress, kMetadataRegionEndAddress and
kMetadataRegionFlashSector to firmware/c_board/bootloader/src/flash/layout.hpp
and replace uses of kMetadataStartAddress/kMetadataEndAddress (and the other
hardcoded sector values noted elsewhere) to include layout.hpp and use the new
kMetadataRegion* symbols so the region/sector definitions are single-sourced.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23d1fb6 and 298ad09.

📒 Files selected for processing (31)
  • .github/workflows/release-package.yml
  • .scripts/append_image_hash
  • .scripts/lint-targets.yml
  • Dockerfile
  • Dockerfile.build_firmware
  • firmware/c_board/CMakeLists.txt
  • firmware/c_board/app/CMakeLists.txt
  • firmware/c_board/app/include/tusb_config.h
  • firmware/c_board/app/src/app.cpp
  • firmware/c_board/app/src/usb/usb_descriptors.hpp
  • firmware/c_board/app/src/usb/vendor.cpp
  • firmware/c_board/app/src/utility/boot_mailbox.hpp
  • firmware/c_board/bootloader/CMakeLists.txt
  • firmware/c_board/bootloader/include/tusb_config.h
  • firmware/c_board/bootloader/src/crypto/sha256.hpp
  • firmware/c_board/bootloader/src/flash/layout.hpp
  • firmware/c_board/bootloader/src/flash/metadata.hpp
  • firmware/c_board/bootloader/src/flash/unlock_guard.hpp
  • firmware/c_board/bootloader/src/flash/validation.hpp
  • firmware/c_board/bootloader/src/flash/writer.hpp
  • firmware/c_board/bootloader/src/main.cpp
  • firmware/c_board/bootloader/src/usb/dfu.cpp
  • firmware/c_board/bootloader/src/usb/dfu.hpp
  • firmware/c_board/bootloader/src/usb/usb_descriptors.cpp
  • firmware/c_board/bootloader/src/usb/usb_descriptors.hpp
  • firmware/c_board/bootloader/src/utility/assert.hpp
  • firmware/c_board/bootloader/src/utility/boot_mailbox.hpp
  • firmware/c_board/bootloader/src/utility/jump.hpp
  • firmware/c_board/bsp/cubemx/Core/Inc/main.h
  • firmware/c_board/bsp/cubemx/STM32F407XX_APP.ld
  • firmware/c_board/bsp/cubemx/STM32F407XX_BOOTLOADER.ld

@Alliance-Algorithm Alliance-Algorithm deleted a comment from coderabbitai bot Mar 2, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
firmware/c_board/bootloader/src/usb/usb_descriptors.hpp (1)

73-78: 建议补充序列号写入后的游标一致性断言。

这里的游标推进依赖 serial_string_ 模板格式(含 - 分隔位)。建议在循环后增加内部契约断言,防止后续模板改动时出现静默越界。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/c_board/bootloader/src/usb/usb_descriptors.hpp` around lines 73 -
78, Add an internal contract assertion after the loop that validates the cursor
did not overrun and ended where the serial-string template expects: compute a
pointer to the buffer end (e.g., auto* end = serial_string_.data() +
serial_string_.size()) and then assert(cursor <= end) and, if you can compute
the exact expected position from the template, assert(cursor == expected_end);
reference the existing symbols serial_string_, uid and write_hex_u16 when adding
the check to the same scope as the loop so template changes will trigger the
assertion rather than silent bounds errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@firmware/c_board/bootloader/src/usb/usb_descriptors.hpp`:
- Around line 73-78: Add an internal contract assertion after the loop that
validates the cursor did not overrun and ended where the serial-string template
expects: compute a pointer to the buffer end (e.g., auto* end =
serial_string_.data() + serial_string_.size()) and then assert(cursor <= end)
and, if you can compute the exact expected position from the template,
assert(cursor == expected_end); reference the existing symbols serial_string_,
uid and write_hex_u16 when adding the check to the same scope as the loop so
template changes will trigger the assertion rather than silent bounds errors.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 298ad09 and b4082bb.

📒 Files selected for processing (1)
  • firmware/c_board/bootloader/src/usb/usb_descriptors.hpp

@qzhhhi qzhhhi merged commit c7f0d45 into main Mar 2, 2026
5 checks passed
@github-project-automation github-project-automation bot moved this from Todo to Done in RMCS Slave SDK Mar 2, 2026
@qzhhhi qzhhhi deleted the dev/bootloader branch March 2, 2026 19:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant