1. 课程导入
在嵌入式安全领域,Trusted Firmware-M(TF-M)已经成为 Armv8-M 平台上构建安全启动与安全服务的事实标准。它把安全启动、安全分区、安全服务、非安全调用接口以及多核/多执行环境协同整合到一套可配置的软件栈中。本课程围绕“平台移植”与“启动工程”两条主线展开,目标不是简单罗列 API,而是让学习者能够把 TF-M 从参考平台迁移到自有芯片或开发板,并理解从上电复位到安全服务可用的完整启动链路。
学习目标:完成本课程后,你应当能够独立完成以下任务:
- 理解 TF-M 的软件分层、安全域划分与启动阶段划分;
- 掌握平台移植所需的最小适配层:启动代码、时钟、串口、Flash 驱动、隔离配置与链接脚本;
- 能够配置构建系统,生成 BL1、BL2、SPM、安全分区与非安全应用镜像;
- 能够定位启动失败、镜像校验失败、分区跳转异常等典型问题;
- 能够基于日志与调试器分析启动时序,完成一次可复现的移植验证。
核心概念:安全启动、隔离与启动阶段
TF-M 的核心思想是“先建立信任,再提供服务”。上电后,不可变的 BL1(Boot ROM 或一级引导)首先验证 BL2 的完整性与真实性;BL2 再根据镜像清单验证 SPM(Secure Partition Manager)、安全分区和非安全应用。验证通过后,系统进入安全处理环境,由 SPM 初始化安全分区并对外提供 PSA 安全服务。非安全侧通过 PSA Client API 发起请求,经隔离边界进入安全侧处理。
这里有两个关键概念必须区分清楚:启动阶段与执行环境。启动阶段描述的是“时间顺序”,例如 BL1→BL2→SPM→安全分区→非安全应用;执行环境描述的是“空间隔离”,例如 Secure Processing Environment(SPE)与 Non-Secure Processing Environment(NSPE)。移植时,时间顺序由启动代码和镜像布局决定,空间隔离由 SAU/MPU、IDAU 和链接脚本共同决定。两者必须同时正确,系统才能稳定运行。
整体框架:从参考平台到自有平台的移植路径
TF-M 的源码结构已经为移植提供了清晰的“接缝”。典型移植工作可以归纳为四层:芯片启动层、平台适配层、安全配置层和构建集成层。芯片启动层负责复位向量、时钟初始化、内存初始化;平台适配层提供 UART、Flash、看门狗、定时器等驱动;安全配置层定义 SAU/MPU 区域、中断目标态和链接脚本;构建集成层则把上述内容接入 CMake 与 Kconfig,生成可烧录镜像。
移植时最容易忽略的是“启动顺序与驱动依赖”。例如,BL2 在验证镜像前可能需要读取 Flash,而 Flash 驱动又依赖时钟;如果时钟初始化放在 BL2 之后,就会导致校验失败。因此,建议把最小启动依赖画成一张依赖图,再逐项确认。
| 移植层次 | 关键文件/对象 | 典型任务 | 验证方式 |
|---|---|---|---|
| 芯片启动层 | startup_*.c、vector table、SystemInit | 复位入口、时钟、RAM 初始化 | 调试器停在 Reset_Handler |
| 平台适配层 | uart、flash、timer 驱动 | 串口输出、Flash 读写、延时 | BL1 打印启动日志 |
| 安全配置层 | SAU/MPU、linker script、partition | 隔离区域、内存边界、分区清单 | 安全/非安全访问测试 |
| 构建集成层 | CMakeLists、Kconfig、manifest | 镜像生成、签名、打包 | 生成 BL2 与 TF-M 镜像 |
方法流程:一次可复现的移植验证
建议按以下顺序推进,每一步都保留可回退的基线:
- 建立基线:先在官方参考平台上编译并运行,确认工具链、Python 依赖和烧录流程正常。
- 替换启动代码:把参考平台的 startup 与链接脚本替换为自有芯片版本,确保能跑到 BL1 的 C 入口。
- 打通串口:实现最小 UART 输出,让 BL1/BL2 的日志可见,这是后续排障的基础。
- 接入 Flash 驱动:实现读、写、擦除接口,并验证 BL2 能读取镜像头。
- 配置隔离:根据内存映射设置 SAU/MPU 区域,确认安全与非安全边界。
- 生成并烧录镜像:使用构建系统生成 BL2、SPM、安全分区与非安全应用,按地址烧录。
- 启动验证:观察日志中 BL1→BL2→SPM→NSPE 的阶段标记,确认安全服务可被调用。
# 典型构建与烧录流程(示意)
cmake -B build -DTFM_PLATFORM=your_board -DTFM_TOOLCHAIN_FILE=toolchain_GCC.cmake
cmake --build build --target install
# 生成镜像后按链接脚本地址烧录
pyocd flash -t your_target build/bin/bl2.bin
pyocd flash -t your_target build/bin/tfm_s_ns_signed.bin
案例与场景:从“能启动”到“能服务”
某团队把 TF-M 移植到一颗 Cortex-M33 芯片上,BL1 和 BL2 都能正常打印日志,但非安全应用始终无法调用 PSA Crypto 服务。排查后发现两个问题:一是 SAU 配置把安全分区的外设区域错误地划给了非安全侧,导致 SPM 访问被拦截;二是非安全向量表的起始地址与链接脚本不一致,BL2 跳转后取到了错误的栈指针。修正后,系统顺利进入 NSPE,PSA Client API 返回成功。这个案例说明:启动成功不等于服务可用,隔离配置与向量表一致性必须单独验证。
学习建议:每完成一个移植步骤,都用“最小可观测证据”确认结果,例如一条串口日志、一个断点、一次寄存器读取。不要等到全部集成后再调试,否则问题定位成本会成倍增加。
常见误区与复盘
- 误区一:把 TF-M 当成普通 RTOS 移植。TF-M 的核心是安全隔离与信任链,启动顺序和内存边界比任务调度更关键。
- 误区二:忽略链接脚本与 SAU 的一致性。链接脚本决定“代码放在哪”,SAU 决定“谁能访问”,两者不一致会导致随机异常。
- 误区三:先调非安全应用,后补安全启动。正确顺序是先保证 BL1/BL2/SPM 可信,再接入非安全侧。
- 误区四:没有保留基线日志。移植过程中应保留参考平台的启动日志作为对照,便于快速判断差异。
注意:不同芯片的 Flash 控制器、时钟树和中断路由差异很大,移植时不要直接复制参考平台的全部配置。应先确认最小启动路径,再逐步启用高级功能,避免多个变量同时变化导致问题难以定位。