第8篇:SBSFU------安全启动状态机详解
SBSFU(Secure Boot and Secure Firmware Update)是本系统的"总调度官"。它在系统复位后第一个获得执行权(在 SE Core 之后),负责配置所有安全机制、验证固件签名、管理固件更新,最终决定是跳转到用户程序还是进入固件下载模式。本篇将深入解析 SBSFU 的状态机架构、启动流程和各模块的协同工作机制。
1. SBSFU 的角色定位
1.1 在系统中的位置
系统启动顺序:
═══════════════════════════════════════════════════
上电 / 复位
│
▼
Reset_Handler (0x08000000) ← CPU 硬件行为
│
▼
SystemInit() ← 系统时钟初始化
│
▼
SE_Startup() ← SE 数据段初始化 (__arm_data_init)
│
▼
┌─────────────────────────┐
│ SBSFU_main() │ ← 本篇主角
│ ════════════════ │
│ · 配置安全机制 │
│ · 验证固件签名 │
│ · 管理状态机 │
│ · 跳转/加载/更新 │
└──────────┬──────────────┘
│
┌────┴────┐
▼ ▼
UserApp Local Loader
(正常) (下载模式)
1.2 核心职责
| 职责 | 说明 |
|---|---|
| 安全配置 | 初始化 MPU 隔离、激活 HDP(硬件调试保护)、检查 Option Bytes |
| 固件验证 | 调用 SE 服务(通过 CallGate)验证 Active Slot 中固件的签名和完整性 |
| 启动仲裁 | 验证通过 → 跳转 UserApp;验证失败 → 进入 Local Loader |
| 固件更新 | 通过 YMODEM 接收 .sfb 文件,解密、验证、写入 Download Slot |
| 状态管理 | 维护固件镜像的状态机(NEW/SELFTEST/VALID/INVALID),管理断路续传 |
| 错误处理 | 处理签名验证失败、Flash 写入失败、看门狗超时等异常 |
1.3 SBSFU 与 SE 的分工
┌──────────────────────────────────────────────────────┐
│ SBSFU (调度者) │
│ │
│ · 什么时候验证固件? · 从哪里下载固件? │
│ · 验证失败怎么办? · MPU 什么时候配置? │
│ · 跳转到哪个地址? · LED 怎么闪? │
│ │ │
│ "决策和调度" │ │
└─────────────────────────┼────────────────────────────┘
│
│ CallGate (SE_CallGate)
▼
┌──────────────────────────────────────────────────────┐
│ SE Core (运算器) │
│ │
│ · AES 解密固件 · ECDSA 验证签名 │
│ · SHA384 计算哈希 · 密钥保管 │
│ │
│ "计算和验证" │
└──────────────────────────────────────────────────────┘
SBSFU 是"发号施令者",SE 是"安全运算器"。SBSFU 不持有任何密钥,也不知道加密算法的细节------它只知道"去 CallGate 调用服务 X,检查返回值"。
2. SBSFU 启动时序
2.1 完整启动流程图
系统上电 / 复位
│
▼
┌──────────────────────┐
│ Reset_Handler │ 读取向量表 0x08000000
│ (startup_*.s) │ 初始化栈指针 (MSP)
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ SystemInit() │ 配置系统时钟 (HSE/PLL → 170MHz)
│ (system_stm32g4xx.c)│ 配置 Flash 等待周期
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ __main() │ 标准 C 运行时初始化
│ · 初始化 .data │ (由编译器自动插入)
│ · 清零 .bss │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ main() │ SBSFU 主入口
│ (Core/Src/main.c) │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ HAL_Init() │ HAL 库初始化
│ SystemClock_Config()│ 重新确认时钟配置
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ SE_Startup() │ SE 数据段初始化
│ · __arm_data_init() │ · 复制 SE .data
│ · SE_SetSystemCore │ · 清零 SE .bss
│ Clock() │ · 设置 SE 时钟
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ SFU_BOOT_MPU_ │ 配置 MPU 隔离
│ Init() │ · SE 代码区: 特权只读
│ (sfu_mpu_isolation. │ · SE RAM区: 特权读写
│ c) │ · SE 密钥区: 特权只读
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ SFU_BOOT_HDP_ │ 激活硬件调试保护
│ Activate() │ · HDP 激活后调试器无法
│ │ 访问受保护区域
└──────────┬───────────┘
│
▼
╔═══════════════════════════╗
║ SFU_BOOT_Svc_CheckOpt- ║ 检查 Option Bytes
║ ionBytes() ║
╚═══════════════╤═══════════╝
│
┌──────────┴──────────┐
│ │
配置正确 配置错误
│ │
▼ ▼
╔══════════╗ ╔══════════════════╗
║ 验证固件 ║ ║ LED 快闪 (250ms) ║
║ 签名+哈希 ║ ║ 死循环等待修复 ║
╚═════╤════╝ ╚══════════════════╝
│
┌────────┴────────┐
│ │
验证通过 验证失败
│ │
▼ ▼
╔══════════════╗ ╔══════════════════╗
║ 跳转 UserApp ║ ║ LED 慢闪 (3s) ║
║ 设置 MSP ║ ║ 进入 Local Loader║
║ 跳转 PC ║ ║ 等待 YMODEM 下载 ║
╚══════════════╝ ╚══════════════════╝
2.2 关键步骤详解
步骤 1: MPU 隔离配置
MPU(Memory Protection Unit)是 ARMv7-M 架构的关键安全硬件。STM32G4 的 MPU 支持最多 8 个区域,SBSFU 利用它实现 SE 的运行时隔离。
SBSFU 中的 MPU 配置 (sfu_mpu_isolation.c):
═══════════════════════════════════════════════
区域 0: SE 代码区
地址: 0x08000200 - 0x08005FFF
权限: 特权模式只读, 用户模式不可访问
作用: 防止任何代码读取或修改 SE Core 和密钥
区域 1: SE RAM 区
地址: 0x20018000 - 0x20019FFF
权限: 特权模式读写, 用户模式不可访问
作用: 保护 SE 的运行时数据
区域 2: SE 接口区
地址: 0x08006000 - 0x080069FF
权限: 特权模式只读, 用户模式只读
作用: UserApp 可以调用接口函数但不可修改
区域 3: SBSFU 代码区
地址: 0x08006B00 - 0x0800FFFF
权限: 特权模式只读
作用: 保护 SBSFU 自身不被篡改
步骤 2: HDP 激活
HDP(Hardware Debug Protection)是 STM32G4 特有功能。激活后,调试器(ST-LINK/J-LINK)无法访问 HDP 保护的 Flash 区域------即使 RDP 为 Level 0。
c
// HDP 激活在 RAM 中执行(因为激活代码本身在 HDP 区域内)
// SB_HDP_Code 段位于 RAM 地址 0x2001A000
// 执行完后 HDP 立即生效,Flash 中的 SE 和 SBSFU 代码被调试锁定
步骤 3: Option Bytes 检查
c
SFU_BOOT_Svc_CheckOptionBytes() 检查:
1. PCROP 是否已配置保护 SE 密钥区
2. WRP 是否已配置保护 SBSFU 代码区
3. DBANK 是否已禁用
4. BOR 电平是否合适
任何一项不正确 → LED 快闪 (250ms 周期) → 需要重新烧录 Option Bytes
3. SBSFU 状态机(FSM)详解
3.1 状态定义
SBSFU 的有限状态机(FSM)定义在 sfu_fsm_states.h 中:
┌─────────────────────────┐
│ 系统复位 │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ │
│ SFU_BOOT_STATE │ 启动状态
│ ──────────────── │
│ 检查安全配置 │
│ 验证活动固件 │
│ │
└─────┬──────────┬────────┘
│ │
验证通过 │ │ 验证失败或无固件
│ │
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ │ │ │
│ SFU_JUMP_ │ │ SFU_IDLE_STATE │ 空闲状态
│ TO_APP_STATE │ │ ───────────── │
│ │ │ 等待下载命令 │
│ 跳转到用户 │ │ │
│ 应用程序 │ └────────┬─────────┘
│ │ │
└──────────────┘ 收到下载开始命令
(从这里出去后 │
SBSFU 不再运行) ▼
┌──────────────────┐
│ │
│ SFU_DOWNLOAD_ │ 下载状态
│ STATE │
│ ───────────── │
│ 通过 YMODEM │
│ 接收 .sfb 固件 │
│ │
└────────┬─────────┘
│
下载完成
│
▼
┌──────────────────┐
│ │
│ SFU_INSTALL_ │ 安装状态
│ STATE │
│ ───────────── │
│ 验证固件头签名 │
│ 解密固件 │
│ 校验 SHA384 │
│ 写入 Active Slot │
│ │
└────────┬─────────┘
│
┌────────┴────────┐
│ │
安装成功 安装失败
│ │
▼ ▼
┌──────────┐ ┌──────────────┐
│ 系统复位 │ │ SFU_ERROR_ │ 错误状态
│ (重启后 │ │ STATE │
│ 加载新 │ │ ───────── │
│ 固件) │ │ 报告错误 │
└──────────┘ │ 等待恢复 │
└──────────────┘
3.2 状态详细说明
| 状态 | 进入条件 | 核心操作 | 退出条件 |
|---|---|---|---|
| SFU_BOOT_STATE | 系统复位 | 配置安全机制、检查 Option Bytes、验证活动固件 | 验证通过→JUMP_TO_APP;失败→IDLE |
| SFU_IDLE_STATE | 无有效固件或验证失败 | LED 慢闪(3秒周期)、等待串口命令 | 收到下载命令→DOWNLOAD |
| SFU_DOWNLOAD_STATE | 收到下载请求 | YMODEM 接收 .sfb、CRC 校验每包 | 传输完成→INSTALL;超时→ERROR |
| SFU_INSTALL_STATE | 固件下载完成 | 验证头签名、AES 解密、SHA384 校验、写 Flash | 成功→复位;失败→ERROR |
| SFU_ERROR_STATE | 任何步骤出错 | 记录错误码、LED 指示、等待超时复位 | 超时→复位到 BOOT |
| SFU_JUMP_TO_APP_STATE | 固件验证通过 | 设置 MSP→UserApp栈顶、设置 PC→UserApp Reset_Handler | 不再返回 |
3.3 状态机在 sfu_boot.c 中的实现
c
// sfu_boot.c 中的状态机主循环 (简化版)
void SFU_BOOT_Run_FSM(void)
{
SFU_FsmStateTypeDef current_state = SFU_BOOT_STATE;
while (1)
{
switch (current_state)
{
case SFU_BOOT_STATE:
// 配置安全机制
SFU_BOOT_MPU_Init(); // MPU 隔离
SFU_BOOT_HDP_Activate(); // 硬件调试保护
// 检查 Option Bytes
if (SFU_BOOT_Svc_CheckOptionBytes() != SFU_SUCCESS) {
current_state = SFU_ERROR_STATE;
SFU_ERROR_SetError(SFU_ERR_OPTION_BYTES);
break;
}
// 验证活动固件
if (SFU_BOOT_ValidateActiveFw() == SFU_SUCCESS) {
current_state = SFU_JUMP_TO_APP_STATE;
} else {
current_state = SFU_IDLE_STATE;
}
break;
case SFU_IDLE_STATE:
// LED 慢闪等待
SFU_COM_BlinkLED(SFU_BLINK_3S);
// 等待串口命令或按钮
if (SFU_COM_WaitForCommand() == SFU_CMD_DOWNLOAD) {
current_state = SFU_DOWNLOAD_STATE;
}
break;
case SFU_DOWNLOAD_STATE:
// 初始化 YMODEM 接收
SFU_LOADER_Init();
// 接收固件文件
if (SFU_LOADER_ReceiveFirmware() == SFU_SUCCESS) {
current_state = SFU_INSTALL_STATE;
} else {
current_state = SFU_ERROR_STATE;
}
break;
case SFU_INSTALL_STATE:
// 验证头签名 + 解密 + 完整性校验 + 写 Flash
if (SFU_LOADER_InstallFirmware() == SFU_SUCCESS) {
NVIC_SystemReset(); // 重启,加载新固件
} else {
current_state = SFU_ERROR_STATE;
}
break;
case SFU_ERROR_STATE:
// 处理错误
SFU_ERROR_Process();
break;
case SFU_JUMP_TO_APP_STATE:
// 跳转到 UserApp
SFU_BOOT_JumpToApplication();
// 这里永远不会返回
break;
}
}
}
3.4 FLOW_CONTROL_STEP:安全流程控制宏
SBSFU 的启动流程不是简单的顺序调用,而是通过 FLOW_CONTROL_STEP 宏进行流程控制的。它类似于一个"安全流水线"------每一步完成后检查结果,失败则立即终止:
c
// sfu_boot.c 中的实际流程控制
void SFU_BOOT_SVC_SecureBoot(void) {
FLOW_CONTROL_STEP(SFU_BOOT_SVC_CheckRDP, SFU_ERR_RDP);
FLOW_CONTROL_STEP(SFU_BOOT_SVC_CheckPCROP, SFU_ERR_PCROP);
FLOW_CONTROL_STEP(SFU_BOOT_MPU_Init, SFU_ERR_MPU);
FLOW_CONTROL_STEP(SFU_BOOT_HDP_Activate, SFU_ERR_HDP);
FLOW_CONTROL_STEP(SFU_BOOT_SVC_CheckOptionBytes, SFU_ERR_OPTION_BYTES);
FLOW_CONTROL_STEP(SFU_BOOT_ValidateActiveFw, SFU_ERR_FW_VALIDATION);
}
FLOW_CONTROL_STEP(func, error_code) 宏的定义非常简洁:
c
#define FLOW_CONTROL_STEP(func, error_code) \
do { \
if ((func) != SFU_SUCCESS) { \
SFU_ERROR_SetError(error_code); \
return SFU_ERROR; \
} \
} while(0)
设计哲学:
- Fail-Fast:任一步骤失败,立即停止后续所有操作,不会"将错就错"
- 错误可追溯:每个步骤有独立的错误码,通过 LED 闪烁或串口日志可以快速定位
- 无冗余代码 :避免了
if-else的深层嵌套,代码简洁可读
3.5 固件状态机:从 NEW 到 VALID 的完整路径
SBSFU 不仅管理自身的 FSM 状态,还为每个固件镜像维护一套独立的状态机。这套状态机利用了 Flash 的"只能从 1 写为 0"特性,通过向 FwImageState 字段逐步写入特定值来编码固件的生命周期:
固件状态转换路径:
═══════════════════════════════════════════════════════
NEW (32×0xFF, 32×0xFF, 32×0xFF)
│ 固件刚被下载到 Download Slot
│ 尚未经过任何验证
│
│ SBSFU 重启后首次看到此状态:
│ ① 验证固件头签名 (ECDSA P-384) → 通过
│ ② 解密固件 (AES-256-CBC) → 成功
│ ③ 校验完整性 (SHA-384) → 通过
│ ④ 执行 Swap(如果需要)
│ ⑤ 写入状态: 32×0x00 (标记 SBSFU 自检通过)
│
▼
SBSFU SELF-CHECK PASS
│ 32×0xFF, 32×0xFF, 32×0x00
│ SBSFU 层面的验证全部通过
│ 但这不代表 App 本身没问题!
│
│ SBSFU 跳转到 UserApp 执行
│ UserApp 运行后应该:
│ ① 执行内部自检 (CRC/校验和/关键外设测试)
│ ② 写入状态: 32×0x00 (标记 App 自检通过)
│
▼
SELFTEST (32×0xFF, 32×0x00, 32×0x00)
│ App 自检通过!
│ 但用户可能还没确认
│
│ UserApp 需要显式调用:
│ SE_APP_SetActiveFwState(SE_ACTIVE_FW_STATE_VALID)
│
▼
VALID (32×0x00, 32×0x00, 32×0x00)
│ 完全确认: 此固件可正常运行
│ 这是唯一允许长期运行的状态
│
│ 如果下次启动时 SBSFU 检测到状态不是 VALID:
│ → 可能回滚到上一个有效版本
│
异常路径:
INVALID
│ 任何验证失败时的终点状态
│ SBSFU 拒绝执行此固件
│ → 进入 Local Loader,等待有效固件
状态编码的巧妙设计:
- Flash 擦除后全为
0xFF,所以 NEW 状态自然就是"刚擦除后的状态" - 每前进一步,将对应段从
0xFF改写为0x00 - 由于 Flash 只能从 1 写为 0,状态只能单向推进(不能"回退"到更早的状态)
- 这种设计天然防止了状态回滚攻击
ENABLE_IMAGE_STATE_HANDLING 宏 :
这是一个编译开关,启用后 SBSFU 会严格检查固件状态。如果状态不是 VALID 或 SELFTEST,SBSFU 可以拒绝执行,即使固件签名本身是有效的。这为现场部署提供了额外的一层安全保障。
4. 固件镜像管理
4.1 双镜像方案的 Flash 布局
Flash 地址 内容 大小
══════════════════════════════════════════════════════════════════
0x08000000 ┌──────────────────┐
│ SBSFU + SE │ ← 不可变区域 64 KB
│ (向量表/SE/SBSFU)│
0x08010000 ├──────────────────┤
│ │
│ Active Slot #1 │ ← 活动固件槽位 216 KB
│ (Fw Header + │ SBSFU 验证此处的固件
│ UserApp Body) │ 验证通过 → 跳转执行
│ │
0x08046000 ├──────────────────┤
│ Swap Area │ ← 交换区 8 KB
│ (安全交换缓冲) │ 更新时临时存放旧固件头
0x08048000 ├──────────────────┤
│ │
│ Download Slot #1│ ← 下载固件槽位 216 KB
│ (接收新固件) │ YMODEM 下载的目的地
│ │ 安装时从此处复制到 Active Slot
│ │
0x0807E000 └──────────────────┘
4.2 固件镜像操作模块
| 文件 | 职责 |
|---|---|
sfu_fwimg_common.c |
通用操作:读取固件头、验证魔数、获取版本信息 |
sfu_fwimg_no_swap.c |
无交换区更新:直接从 Download 复制到 Active(单镜像方案) |
sfu_fwimg_swap.c |
有交换区更新:先将 Active 固件头复制到 Swap,再安装新固件(双镜像方案) |
sfu_new_image.c |
新镜像检测:检查 Download Slot 是否有待安装的固件 |
4.3 安全固件更新流程
使用双镜像 + Swap 区的安全更新流程:
1. UserApp 运行中,收到新固件 .sfb
│
▼
2. UserApp 将 .sfb 写入 Download Slot (0x08048000)
│ 使用 YMODEM 协议通过 UART 接收
│
▼
3. UserApp 触发系统复位
│
▼
4. SBSFU 启动,检测到 Download Slot 有新固件
│ (检查 Download Slot 的固件头魔数)
│
▼
5. SBSFU 通过 SE 验证新固件头签名
│ 调用: SE_CallGate(SE_CRYPTO_HL_AUTHENTICATE_METADATA, ...)
│ 失败 → 拒绝安装,保持旧固件
│
▼
6. SBSFU 将 Active Slot 的固件头复制到 Swap Area
│ 这是"断路保护"的关键: 如果安装过程断电,
│ Swap Area 中保存了旧固件头的副本
│
▼
7. SBSFU 解密并验证新固件体 (SHA384)
│ 调用: SE_CallGate(SE_CRYPTO_LL_AUTHENTICATE_FW_*, ...)
│ 失败 → 从 Swap Area 恢复旧固件头
│
▼
8. SBSFU 将新固件从 Download Slot 复制到 Active Slot
│ 逐个 Flash 页擦除和编程
│
▼
9. SBSFU 清除 Swap Area,标记更新完成
│
▼
10. 系统复位,加载新固件
4.4 断路保护机制
如果安装过程第 5-8 步期间断电...
情况 A: 第 5 步之前断电
→ Active Slot 未受影响
→ 重新上电后,SBSFU 重新检测并安装
→ 旧固件保持完好
情况 B: 第 6 步 (Swap) 完成但第 8 步未完成
→ Active Slot 已经部分被覆盖
→ SBSFU 检测到 Swap Area 中有旧固件头副本
→ SBSFU 从 Swap 恢复旧固件头
→ 旧固件可以继续运行(固件头被恢复,虽然固件体可能
部分损坏,但 SBSFU 会重新验证签名拒绝执行)
情况 C: 第 8 步完成 (新固件安装完成)
→ Swap Area 被清除
→ 重新上电后,SBSFU 验证新固件
→ 验证通过 → 启动新固件
→ 验证失败 → 进入 Local Loader 等待重新下载
5. 本地加载器(Local Loader)
5.1 什么情况下进入 Local Loader?
进入 Local Loader 的触发条件:
══════════════════════════════════
1. 系统中没有任何有效的用户固件
→ Active Slot 的固件头魔数不是 "SFU1"
2. 用户固件的签名验证失败
→ SE_APP_ValidateFw() 返回 SE_ERROR
→ 固件可能被篡改或损坏
3. 用户按下按钮强制进入
→ 在复位时检测到 User Button 按下
→ 允许用户在固件正常运行时强制进入下载模式
4. 系统从 Download Slot 安装固件失败
→ 安装过程出错,Active Slot 中没有有效固件
5.2 Local Loader 的工作流程
┌──────────────────────────────────────────────────┐
│ Local Loader 流程 │
│ │
│ 1. 初始化 UART (115200, 8N1) │
│ · PA2 (TX), PA3 (RX) - ST-LINK VCOM │
│ │
│ 2. LED 慢闪 (3秒周期) │
│ · 通知用户设备等待固件下载 │
│ │
│ 3. 串口输出欢迎信息 │
│ "SBSFU Local Loader v2.6.2" │
│ "Waiting for YMODEM transfer..." │
│ │
│ 4. YMODEM 接收循环 │
│ ┌────────────────────────────┐ │
│ │ 接收 128 字节数据包 │ │
│ │ 验证 CRC16 │ │
│ │ 发送 ACK/NAK │ │
│ │ 累积到 Download Slot │ │
│ │ │ │
│ │ 如果收到 EOT (End of │ │
│ │ Transmission): │ │
│ │ → 停止接收 │ │
│ └────────────────────────────┘ │
│ │
│ 5. 验证固件头签名 (ECDSA P-384) │
│ ┌────────────────────────────┐ │
│ │ SE_CallGate(SE_CRYPTO_HL_ │ │
│ │ AUTHENTICATE_METADATA) │ │
│ │ │ │
│ │ 失败 → LED 快闪, 返回步骤2 │ │
│ └────────────────────────────┘ │
│ │
│ 6. 解密固件 (AES-256-CBC) │
│ ┌────────────────────────────┐ │
│ │ SE_CallGate(SE_CRYPTO_LL_ │ │
│ │ DECRYPT_INIT/APPEND/ │ │
│ │ FINISH) │ │
│ └────────────────────────────┘ │
│ │
│ 7. 验证固件体完整性 (SHA384) │
│ ┌────────────────────────────┐ │
│ │ SE_CallGate(SE_CRYPTO_LL_ │ │
│ │ AUTHENTICATE_FW_*) │ │
│ │ │ │
│ │ 失败 → LED 快闪, 返回步骤2 │ │
│ └────────────────────────────┘ │
│ │
│ 8. 安装固件到 Active Slot │
│ ┌────────────────────────────┐ │
│ │ · 保存旧固件头到 Swap Area │ │
│ │ · 擦除 Active Slot │ │
│ │ · 写入新固件 │ │
│ │ · 清除 Swap Area │ │
│ └────────────────────────────┘ │
│ │
│ 9. 系统复位 │
│ NVIC_SystemReset() │
│ → 重新进入 SBSFU │
│ → 验证刚安装的固件 │
│ → 跳转到 UserApp │
└──────────────────────────────────────────────────┘
6. MPU 隔离实现(sfu_mpu_isolation.c)
6.1 MPU 简介
Memory Protection Unit(MPU)是 ARM Cortex-M 处理器内置的内存保护硬件。在 STM32G4(Cortex-M4)上:
- 支持最多 8 个可编程区域
- 区域大小必须是 2 的幂,且起始地址与大小对齐
- 区域大小范围:32 字节 ~ 4GB
- 可配置权限:无访问 / 特权只读 / 特权读写 / 只读 / 读写 / 不可执行
- 可配置为背景区域(覆盖所有未显式编址的内存)
6.2 SBSFU 中的 MPU 配置
SBSFU MPU 区域配置 (8个区域):
═══════════════════════════════════════════════════════
区域#0: SE Core 代码区
Base: 0x08000200 Size: 24KB
Attr: 特权只读 + 不可执行(用户模式)
作用: 防止任何代码读取或执行 SE Core
区域#1: SE 密钥区
Base: 0x08000400 Size: 512B
Attr: 特权只读 + 不可执行(用户模式) + 设备属性
作用: 保护 PCROP 密钥区
区域#2: SE RAM 区
Base: 0x20018000 Size: 8KB
Attr: 特权读写 + 不可执行(用户模式)
作用: 保护 SE 运行时数据
区域#3: SE 接口区
Base: 0x08006000 Size: 2KB
Attr: 只读(所有模式) + 可执行
作用: UserApp 可以调用接口函数但不可修改
区域#4: SBSFU 代码区
Base: 0x08006B00 Size: 38KB
Attr: 特权只读 + 可执行
作用: 保护 SBSFU 代码不被篡改
区域#5: SBSFU RAM 区
Base: 0x2001A100 Size: 24KB
Attr: 特权读写 + 可执行(特权)
作用: SBSFU 的栈和堆
区域#6: 外设区
Base: 0x40000000 Size: 512MB
Attr: 读写(所有模式) + 不可执行 + 设备属性
作用: 允许访问外设寄存器 (GPIO/UART/Flash Controller)
区域#7: SRAM2 (UserApp RAM)
Base: 0x20000000 Size: 96KB
Attr: 读写(所有模式) + 可执行
作用: UserApp 的 RAM (MPU 切换前)
6.3 MPU 切换过程
当 SBSFU 跳转到 UserApp 时,需要"打开" UserApp 所需的内存权限:
c
void SFU_BOOT_JumpToApplication(void)
{
// 1. 禁用 MPU (临时)
MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
// 2. 重新配置 MPU 区域
// 移除 SBSFU 代码区的保护 (UserApp 不需要访问)
// 保留 SE Core 和 SE RAM 的保护
// 3. 重新启用 MPU
MPU->CTRL |= MPU_CTRL_ENABLE_Msk;
// 4. 设置 UserApp 的栈顶指针
uint32_t app_stack = *(uint32_t*)SLOT_ACTIVE_1_START;
__set_MSP(app_stack);
// 5. 跳转到 UserApp 的 Reset_Handler
uint32_t app_reset = *(uint32_t*)(SLOT_ACTIVE_1_START + 4);
((void(*)(void))app_reset)();
// SBSFU 的生命至此结束
}
7. Flash 操作接口
7.1 内部 Flash 操作函数
sfu_low_level_flash_int.c 封装了 STM32G4 内部 Flash 的操作:
| 函数 | 功能 | 约束 |
|---|---|---|
SFU_FLASH_INT_Erase() |
擦除连续的 Flash 页 | 必须以页(2KB)为单位 |
SFU_FLASH_INT_Write() |
写入数据到 Flash | 必须双字(8 字节)对齐 |
SFU_FLASH_INT_Read() |
从 Flash 读取数据 | 无特殊约束 |
SFU_FLASH_INT_WriteProtect() |
设置写保护 | 页粒度 |
SFU_FLASH_INT_ConfigPCROP() |
配置 PCROP | 必须 64 字节对齐,最小 512 字节 |
7.2 安全写入流程
Flash 安全写入 (Read-Modify-Write):
══════════════════════════════════════
1. 读 (Read)
读取目标 Flash 页的全部内容到 RAM 缓冲区
2. 修改 (Modify)
在 RAM 缓冲区中修改需要变更的部分
3. 擦除 (Erase)
解锁 Flash → 擦除目标页 → 等待完成
4. 编程 (Program)
将 RAM 缓冲区的内容逐双字写入 Flash
每个双字写入后等待 BSY 标志清除
5. 验证 (Verify)
回读 Flash 内容,与预期数据比对
不一致 → 报错
6. 锁定 (Lock)
重新锁定 Flash 控制器
7.3 Flash 操作的约束
| 约束 | 说明 |
|---|---|
| 双字对齐写入 | STM32G4 的内部 Flash 必须以 64 位(双字)为单位编程。单字节写入将导致硬件错误 |
| 页擦除 | 擦除最小单位是 2KB 的 Flash 页。不能只擦除几个字节 |
| 写前擦除 | Flash 的编程操作只能将位从 1 变成 0。要写入新数据,必须先擦除(全部变为 1) |
| 写保护检测 | 如果目标页被 WRP 保护,写入操作会失败。SBSFU 不修改自己的受保护页 |
| PCROP 不可编程 | PCROP 保护区域连 CPU 本身也无法通过数据总线写入 |
8. 安全保护机制
8.1 保护层次总览
静态保护 (编译时/烧录时)
════════════════════════════
┌─────────────────────────────────────────────────┐
│ PCROP (Proprietary Code Read-Out Protection) │
│ 保护: SE 密钥区 (0x08000400-0x080005FF) │
│ 效果: CPU 只能执行,不能读取 │
│ 调试器完全无法访问 │
├─────────────────────────────────────────────────┤
│ WRP (Write Protection) │
│ 保护: SBSFU 代码区 (0x08000000-0x0800FFFF) │
│ 效果: 任何代码无法修改 SBSFU 自身 │
├─────────────────────────────────────────────────┤
│ RDP (Read Protection) Level 1 │
│ 保护: 全片 Flash │
│ 效果: 调试器连接 → 自动擦除芯片 │
├─────────────────────────────────────────────────┤
│ BOR (Brown-Out Reset) Level 3 │
│ 保护: 低电压攻击 │
│ 效果: 电压低于阈值时触发复位 │
└─────────────────────────────────────────────────┘
运行时保护
══════════════
┌─────────────────────────────────────────────────┐
│ MPU (Memory Protection Unit) │
│ 保护: SE 代码区/RAM区 │
│ 效果: 运行时防止非法内存访问 │
├─────────────────────────────────────────────────┤
│ HDP (Hardware Debug Protection) │
│ 保护: 前 64KB Flash │
│ 效果: 调试器无法访问受保护区域 │
├─────────────────────────────────────────────────┤
│ IWDG (Independent Watchdog) │
│ 保护: 系统死锁 │
│ 效果: 定时器超时未刷新 → 硬件复位 │
├─────────────────────────────────────────────────┤
│ TAMPER (入侵检测) │
│ 保护: 物理攻击 (打开外壳等) │
│ 效果: 检测到入侵 → 擦除敏感数据 + 复位 │
└─────────────────────────────────────────────────┘
8.2 IWDG vs WWDG:看门狗策略
SBSFU 可以同时配置两种看门狗来应对不同的失效模式:
| 特性 | IWDG(独立看门狗) | WWDG(窗口看门狗) |
|---|---|---|
| 时钟源 | 独立内部 LSI (32 kHz) | 系统时钟 (APB1) |
| 独立性 | 完全独立,不受系统时钟故障影响 | 依赖系统时钟正常工作 |
| 窗口机制 | 无(只要在超时前刷新即可) | 有(必须在特定窗口内刷新,太早或太晚都复位) |
| 适用场景 | 系统死锁/死循环/时钟停止 | 软件时序异常(刷新频率过快或过慢) |
| 超时范围 | 125μs ~ 32.7s | 409μs ~ 49.9ms |
为什么同时使用两种看门狗?
IWDG 守护的场景: WWDG 守护的场景:
┌─────────────────────────┐ ┌─────────────────────────┐
│ 系统卡死在 while(1){} │ │ 中断服务程序执行超时 │
│ 主时钟 HSE/PLL 失效 │ │ RTOS 任务调度异常 │
│ 程序跑飞到死循环 │ │ 某个任务刷新 IWDG 过频 │
│ │ │ (可能掩盖了真正的卡死) │
│ IWDG 靠独立时钟 → │ │ │
│ 系统时钟挂了照样复位 │ │ WWDG 检测"异常频繁" │
└─────────────────────────┘ └─────────────────────────┘
在 SBSFU 的安全启动阶段,通常启用 IWDG 作为基础保护。如果固件验证过程中出现死循环或死锁,IWDG 会在超时后强制复位系统,确保设备不会永久失去响应。
8.3 PCROP_RDP:密钥保护的"安全开关"
在 G4 系列中,PCROP 保护的完整激活需要设置一个特殊的 Option Byte:PCROP_RDP。
PCROP_RDP 的作用:
PCROP_RDP = 0 (默认):
· PCROP 仅在 RDP Level 1 时才生效
· 在 RDP Level 0 下,PCROP 区域仍可被读取
· 适用于开发阶段: 调试时可以读取密钥区域
PCROP_RDP = 1 (安全模式):
· PCROP 在 RDP Level 0 和 Level 1 下都生效!
· 即使调试器可以连接(RDP Level 0),PCROP 区域仍受保护
· 适用于生产阶段: 即使未锁 RDP,密钥也不可读
关键含义:
在开发阶段,你可能需要 RDP Level 0 来方便调试,
但又不想暴露密钥。设置 PCROP_RDP 可以实现:
· 调试 UserApp (非 SE 区域) ✓
· 读取密钥区域 ✗ (PCROP 阻止)
这是生产化过程中一个容易被忽略但至关重要的配置。在开发阶段完成后,应该在保留 RDP Level 0 调试能力的同时,设置 PCROP_RDP = 1 来保护密钥。
8.4 保护测试函数
c
// sfu_test.c / test_protections.c (UserApp)
// UserApp 可以通过菜单触发各种保护测试
// 测试 1: 固件篡改检测
void SFU_TEST_CorruptImage(void) {
// 故意将 Active Slot 固件头的某个字节翻转
// 下次复位后 SBSFU 会检测到签名验证失败
// → 进入 Local Loader
}
// 测试 2: 安全用户内存保护 (MPU)
void SFU_TEST_SecureUserMemory(void) {
// 试图从 UserApp 访问 SE RAM 区 (0x20018000)
// → MPU 触发 MemManage Fault
// → 系统复位
}
// 测试 3: 独立看门狗 (IWDG)
void SFU_TEST_IWDG(void) {
// 启动 IWDG 但不刷新
// → IWDG 超时 → 系统复位
}
// 测试 4: TAMPER 入侵检测
void SFU_TEST_TAMPER(void) {
// 配置 TAMPER 引脚 (PA0)
// 将 PA0 拉低 → TAMPER 事件
// → 系统复位
}
9. 调试与跟踪
9.1 LED 状态指示
| LED 状态 | 含义 | 位置 |
|---|---|---|
| 绿色 LED 灭 | UserApp 正在运行 | main.c |
| 绿色 LED 慢闪 (3秒周期) | Local Loader 等待 YMODEM 下载 | sfu_loader.c |
| 绿色 LED 快闪 (250ms周期) | Option Bytes 配置错误 | sfu_boot.c |
| 绿色 LED 常亮 | 严重错误(通常伴随死循环) | sfu_error.c |
9.2 串口调试输出
sfu_com_trace.c 通过 UART 输出运行状态信息:
SBSFU 启动时的串口输出:
══════════════════════════
[BOOT] System reset detected
[BOOT] SE Startup OK
[BOOT] MPU initialized
[BOOT] HDP activated
[BOOT] Option Bytes check: PASS
[BOOT] Checking Active Slot #1...
[BOOT] FW Magic: SFU1 -> Valid
[BOOT] FW Version: 1
[BOOT] FW Size: 32768 bytes
[BOOT] Authenticating FW Header... PASS
[BOOT] Verifying FW Integrity... PASS
[BOOT] FW State: VALID (0xFF...)
[BOOT] Jumping to UserApp at 0x08010000
总结
SBSFU 是 X-CUBE-SBSFU 系统的"大脑",它将 SE Core 提供的安全运算能力转化为一个完整的安全启动和固件更新解决方案:
- 启动流程:从 Reset_Handler 到 UserApp,经过 MPU 配置、HDP 激活、Option Bytes 检查、固件签名验证四个关卡
- 状态机:SFU_BOOT → SFU_IDLE → SFU_DOWNLOAD → SFU_INSTALL → 系统复位的完整闭环
- 双镜像管理:Active Slot + Download Slot + Swap Area 的组合实现了安全的断路续传
- 多层防护:PCROP + WRP + MPU + HDP + IWDG + TAMPER 的纵深防御体系
- 调试支持:LED 闪烁编码 + UART 日志输出帮助开发者快速定位问题
核心设计思想总结:
- 不可变性:SBSFU 和 SE Core 代码受写保护,一次烧录永不改变
- 最小权限:SE 只暴露必要的服务接口,UserApp 无法访问密钥
- 防御纵深:每层保护独立工作,即使一层被突破仍有其他层守护
- 故障安全:任何异常导致系统复位,不会进入不确定状态
本文基于 X-CUBE-SBSFU v2.6.2 源码结构撰写,覆盖 sfu_boot.c 状态机、sfu_fsm_states.h 状态定义、sfu_fwimg_swap.c 双镜像交换、sfu_loader.c 本地加载器、sfu_mpu_isolation.c MPU 配置等核心文件。
下一篇,我们将深入 UserApp 的应用层,看用户程序如何实现固件下载和 YMODEM 升级。