AI SoC一般集成很多个核,ARM系列的A核、M核、R核,RISCV核、定制核等。之前讲了很多ATF安全启动,都是基于A核首先启动,然后进行安全启动。
这里有一个问题:A核安全还是M/R核安全?答案显而易见:M/R核更安全。
那么下一个问题,既然M核更安全,那为什么不把安全启动和读取密钥这些系统中最安全的活交给M核?想到这里本篇的主角TFM就登场了,同样是ARM的开源软件,在多核SoC里面使用其分担A核中ATF的核心安全功能,配合使用,就完美了!
纯使用ATF一般是手机的方案,因为手机上只有A核没有M核、R核这些。但是AI SoC及其他多核SoC有安全需求的还是使用M核加载比较好。
1. TFM跟ATF对比
1.1 为什么M核比A核安全?

嵌入式 / 车载 / 安全芯片里非常经典的结论:M 核天生就比 A 核更难被攻破,更适合做安全根(RoT)
原因不是 M 核 "性能更强",而是架构简单、攻击面极小、隔离天然、权限闭环。
M核与A核安全特性对比表
| 对比维度 | Cortex‑M 核(M 核) | Cortex‑A 核(A 核) |
|---|---|---|
| M核是"微控制器",A核是"应用处理器" | - 跑 裸机 / RTOS - 代码量小、启动快、逻辑简单 - 几乎没有复杂的 MMU、异常级别、多任务调度 | - 跑 Linux / Android - 有完整 OS、驱动、文件系统、网络、应用 - 代码量巨大、漏洞天然更多 |
| A 核权限层级多,攻击面巨大 | - 只有普通线程模式、处理异常模式 - 搭配 TrustZone for M(Secure / Non‑secure) - 权限模型极其简单,几乎没有"提权路径" | - 有完整异常等级:EL0(应用)→ EL1(内核)→ EL2(Hypervisor)→ EL3(安全 monitor) - 攻击者只要找到一个内核漏洞,就能:提权 → 读写任意内存 → 劫持控制流 → 攻破 TEE |
| M 核没有 MMU,没有虚拟内存,天然更可控 | - 只有 MPU(内存保护单元):规则简单、配置静态 - 没有复杂页表、没有缺页 - 无法通过篡改页表来越权访问 - 硬件行为可预测,攻击难度陡增 | - 靠 MMU 做虚拟地址,带来:页表、缺页异常、内核态/用户态切换、大量复杂逻辑 - 每一个都是潜在漏洞点 |
| M 核通常不跑大型 OS,攻击面极小 | - 只做安全服务:加密、验签、安全存储、随机数 - 代码量小、功能单一 - 不联网、不跑复杂应用 - 外设访问严格受限 | - Linux:百万行驱动、网络协议栈、文件系统、各种用户态程序、外设驱动一大堆 - 天然就是漏洞重灾区 |
| M 核可以物理/硬件上完全隔离 | - 在真实 SoC 里:M 核可以独占一部分 SRAM(安全内存) - TZASC、外设安全配置可以做到:A 核对 M 核安全区完全不可见、不可访问 | - A 核的安全世界(TEE)和非安全世界(Rich OS)共用 CPU、缓存、总线 - 侧信道、时序攻击更容易渗透 |
| M 核执行环境固化,不容易被篡改 | - M 核固件一般在 OTP、ROM 或加密 Flash 中 - 启动流程短、信任链短 - 一旦配置好安全策略,很难动态修改 | - 内核可加载模块、驱动动态加载、设备树可被篡改 - 流程长、环节多,任何一环失守,全线崩溃 |
| 真正关键:M 核是为"可靠、控制、安全"设计的 | - 设计目标:实时、可靠、低功耗、控制安全 - 硬件隔离原生支持 - 安全是原生基因,不是补丁 | - 设计目标:高性能、多任务、跑复杂系统、兼容各种软件 - 安全是附加功能(TrustZone、TEE) |
A 核是用来 "干活、跑系统" 的,天生复杂、漏洞多;M 核是用来 "看门、管安全" 的,天生简单、隔离强、攻击面极小。
所以好的做法是:安全根(RoT)放 M 核(TF‑M),A 核(ATF+TEE)只做辅助隔离。
1.2 TFM和ATF搭配

在 A+M 核 SoC 中,ATF(TF-A)负责 A 核侧的 EL3 安全监控与世界切换,TF-M 负责 M 核侧的 SPE 安全服务与隔离 ;二者通过硬件隔离 + 安全启动链 + 标准化 IPC/SMC配合,可将高风险 A 核与安全资产彻底隔离,是最安全的架构方案。
- ATF(Trusted Firmware-A / TF-A):A 核的安全基石
运行环境 :A 核(Cortex‑A)的EL3 最高特权级,仅安全模式。
核心职责:
- 构建完整安全启动信任链(BL1→BL2→BL31→BL32)。
- 管理安全 / 非安全世界切换(SMC 异常捕获、路由、返回)。
- 提供PSCI 电源管理、安全中断、系统监控等底层服务。
- 隔离 A 核侧的TEE OS(如 OP‑TEE)与 Rich OS(Linux/Android) 。
安全价值 :守住 A 核的最高权限入口,防止非安全侧越权访问安全资源。
- TF-M(Trusted Firmware‑M):M 核的安全堡垒
运行环境 :M 核(Cortex‑M33/M55 等)的Secure Processing Environment(SPE) ,硬件隔离。
核心职责:
-
实现PSA RoT(平台安全根) :安全启动、加密、安全存储(ITS/PS)、固件更新、平台认证。
-
管理SPE/NSPE 隔离(TrustZone 或物理核隔离),提供标准化 PSA 安全 API。
-
托管密钥、证书、敏感数据,仅通过受控接口对外服务。
-
安全价值 :将最敏感的安全资产与服务放在攻击面最小的 M 核 SPE 中Trusted Firmware。
1.2.1 安全协同方案(A 核被攻击时如何保护系统)
方案总览:双信任根 + 硬件强隔离 + 跨核安全通信
- M 核(TF‑M) :作为Primary RoT,托管所有核心安全服务与密钥。
- A 核(ATF+TEE) :作为Secondary RoT,负责 A 核侧的安全监控与隔离。
- 关键原则 :A 核永远不能直接访问 M 核 SPE 的内存与资源 ,所有交互必须通过标准化、可审计的安全通道Trusted Firmware。
安全启动链协同(从上电就建立信任)
css
BootROM(不可变,信任根)
↓ 验证并加载
SCFW(可选,M核系统控制器,配置安全策略)
↓ 验证并加载
ATF BL1 → BL2 → BL31(EL3)
↓ BL31 验证并加载
A核 TEE OS(BL32,如OP‑TEE)
↓ 同时/按序
M核 TF‑M(SPE)→ 验证并启动M核NSPE
安全要点:
- ATF 与 TF‑M 的镜像均由 BootROM/SCFW 做签名验签,任何篡改导致启动失败。
- M 核 TF‑M 优先或与 A 核 ATF 并行启动,尽早建立安全根,防止 A 核未就绪时的攻击。
- OTP 存储 ROTPK(根公钥) ,作为整个系统信任链的起点。
1.2.1 硬件隔离配置(最核心的安全屏障)
-
物理核隔离:
- M 核专用于运行 TF‑M(SPE) ,A 核运行 Rich OS 与 TEETrusted Firmware。
- 通过TZASC(TrustZone Address Space Controller)与MPU,彻底划分 A/M 核的内存、外设访问权限。
- A 核无任何权限直接读写 M 核 SPE 的内存与寄存器Trusted Firmware。
-
中断与 DMA 隔离:
- 安全中断仅路由到 M 核 TF‑M 或 A 核 EL3,非安全侧无法拦截 / 篡改。
- DMA 控制器配置为仅允许非安全侧访问指定内存区域,防止 DMA 攻击窃取安全数据。
1.2.1 运行时通信
跨核安全通信(唯一受控交互方式)
(1)A 核 → M 核(请求安全服务)
- A 核非安全侧(Linux)→ 通过TEE Client API → A 核 TEE OS(OP‑TEE)。
- TEE OS → 发起SMC 调用 → A 核 EL3(ATF BL31)。
- ATF → 校验请求合法性 → 通过硬件 mailbox/IPC 控制器 → 发送到 M 核。
- M 核 TF‑M → 接收、鉴权、执行安全服务(加密 / 存储 / 验签)→ 返回结果。
- 结果沿原路径返回,全程加密与审计。
(2)M 核 → A 核(安全事件通知)
- M 核 TF‑M → 通过安全中断 或mailbox → 通知 A 核 ATF/TEE。
- 仅用于安全告警、状态同步、固件更新触发 等,不传输敏感数据。
通信安全规则:
- 最小权限接口 :仅暴露必要的 PSA 安全 API,不开放底层寄存器 / 内存访问。
- 请求鉴权 :对每个跨核请求做身份验证与权限检查。
- 数据加密 :交互数据使用会话密钥加密,防止窃听与篡改。
- 审计日志 :所有安全操作记录到安全存储(ITS) ,不可篡改。
1.2.3 安全服务部署策略(降低 A 核风险)
-
必须放在 M 核 TF‑M 的服务(A 核绝对不能碰):
- 根密钥、设备唯一密钥、证书存储(ITS/PS)。
- 安全启动验签、固件更新验签、平台完整性认证。
- 硬件随机数生成、核心加解密算法(AES/RSA/ECC)。
-
可放在 A 核 TEE 的服务(降低 M 核负载):
- 业务相关的安全逻辑(如支付、DRM)。
- 需 A 核侧快速响应的安全计算。
-
A 核非安全侧(Linux) :仅做业务逻辑 ,不持有任何长期密钥,所有安全操作均通过 TEE→TF‑M 链路发起。
1.2.4 异常与攻击应对(A 核沦陷后如何止损)
- 隔离熔断 :M 核 TF‑M 检测到 A 核异常(如频繁非法请求、篡改),立即切断跨核通信,冻结 A 核访问安全服务的权限。
- 安全状态回滚 :A 核被攻破后,M 核 TF‑M 可触发安全恢复流程 ,重新验签并加载 A 核固件,不影响安全根。
- 最小暴露原则 :即使 A 核完全失控,M 核 SPE 的密钥与服务依然安全,系统核心资产不会泄露。
2. TFM代码初探

有了上面的介绍,在方案选型的时候你的心里大概有了一个了解。下面来实操下代码。对于看代码,可以让AI辅助。
2.1 代码下载
bash
git clone https://git.trustedfirmware.org/TF-M/trusted-firmware-m.git/
在cmake时还会自动下载mbedtls等依赖包
2.2 代码编译
- 编译工具安装:
arm-none-eabi (GNU Arm Embedded Toolchain 10-2020-q4-major) 10.2.1 20201103 (release)
官网:
https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
如果本地有编译工具,修改下面代码指定就可以:
bash
--- a/config/config_base.cmake
+++ b/config/config_base.cmake
@@ -12,7 +12,7 @@
set(TFM_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/toolchain_GNUARM.cmake CACHE FILEPATH "Path to TFM compiler toolchain file")
set(TFM_PLATFORM "" CACHE STRING "Platform to build TF-M for. Must be either a relative path from [TF-M]/platform/ext/target, or an absolute path.")
-set(CROSS_COMPILE arm-none-eabi CACHE STRING "Cross-compilation triplet")
+set(CROSS_COMPILE /XXX/arm-gnu-toolchain-14.3.rel1-x86_64-arm-none-eabi/bin/arm-none-eabi CACHE STRING "Cross-compilation triplet")
-
编译前首先确认python3是否缺少
Ninja包,缺少的话使用pip安装:
pip3 install ninja -
随后进入到TF-M根目录下进行cmake配置:
cmake -S . -B cmake_build -DTFM_PLATFORM=arm/mps2/an521 -DTFM_TOOLCHAIN_FILE=toolchain_GNUARM.cmake -DTEST_NS=ON -DTEST_S=ON -DTFM_PSA_API=ON -DBL2=OFF参数说明:
TFM_PLATFORM:宏用于指定平台,使用的是arm-mps2-an521平台,该平台模拟的是cortex-m33。
DTFM_TOOLCHAIN_FILE:使用编译器类型,这里指定使用arm-none-eabi编译工具链。
TEST_NS=ON:编译none-secure测试套。
TEST_S=ON:编译secure测试套。
DTFM_PSA_API=ON:使用编译PSA接口。
BL2=OFF:不使用BL2安全启动,qemu-system-arm运行BL2的镜像会发生异常。
注:更多编译宏选项详细请参考docs/getting_started/tfm_build_instruction.rst -
进入到
cmake_build文件夹:执行
make编译成功后会在cmake_build文件夹下生成bin文件夹。


2.3 代码运行
2.3.1 qemu运行
使用qemu运行,首先就是安装qemu,在ubuntu可以使用命令安装
perl
sudo apt install qemu-system-arm -y
然后执行: qemu-system-arm -machine help 看是否有mps2-an521

qemu-system-arm -M mps2-an521 -kernel "tfm_s.axf" -device loader,file="tfm_ns.bin",addr=0x00100000 -serial stdio -display none
qemu-system-arm -M mps2-an521 -kernel "tfm_s.axf" -serial stdio -display none
2.3.2 Arm Development Studio运行
arm studio:官网上:trustedfirmware-m.readthedocs.io/en/latest/g... 需要付费用户,找了一个可以自己试试 gitcode.com/Premium-Res...
学习的话,可以让claude或者openclaw去读官网文档:trustedfirmware-m.readthedocs.io/en/latest/g... 然后结合代码及ARM官网developer.arm.com/ 的手册给你解释。
同时这些网上的资料也是AI做架构方案、代码审核、测试等的必备技能,需要写进你的skill里面。
2.4 架构初探
参考:zhuanlan.zhihu.com/p/651683753
TF-M是基于PSA架构开发的安全固件,本文基于v1.5v版本。下图是官方的图,详细的TF-M功能。

简要的框图如下:

其核心就是PSA架构,SPM管理分区线程、IPC等机制。分区通过分区线程的形式运行,并提供一种或多种服务。NSPE运行着RTOS,在RTOS上运行着task,task需要用到安全相关服务时,可以调用PSA client APIs请求安全服务,通过IPC机制由SPM通知并调度对应分区线程运行,执行此次的服务请求。然后返回结果给client。
2.4 代码初探
参考:blog.csdn.net/weixin_4308... 代码入口: tfm/secure_fw/spm/cmsis_psa/main.c
scss
Main()
->tfm_arch_set_msplim((uint32_t)®ION_NAME(Image$$, ARM_LIB_STACK, $$ZI$$Base)); //设置堆栈限制
->fih_delay_init();
->tfm_core_init()
->tfm_hal_set_up_static_boundaries
->configure_mpu(rnr++, base, limit, XN_EXEC_OK, AP_RO_PRIV_UNPRIV); //配置各个内存边界的读写属性
->tfm_hal_platform_init() //核心函数,需要重点展开分析
->__enable_irq(); //使能中断
->stdio_init(); //初始化串口
-> corstone1000_watchdog_init() //初始化看门狗
-> fwu_metadata_init()
-> FWU_METADATA_FLASH_DEV.Initialize(NULL) //初始化FWU metadata所在的FLASH,就是 32MB 那个
-> flash_info = FWU_METADATA_FLASH_DEV.GetInfo()
-> corstone1000_host_watchdog_handler_init()
->tfm_plat_otp_init()
->tfm_plat_provisioning_perform()
-> tfm_plat_otp_read //读OTP值
->tfm_arch_config_extensions()
->SCB->NSACR |= SCB_NSACR_CP10_Msk | SCB_NSACR_CP11_Msk;
->SCnSCB->CPPWR |= SCnSCB_CPPWR_SUS11_Msk | SCnSCB_CPPWR_SUS10_Msk;
->SPMLOG_DBGMSGVAL("TF-M isolation level is: ", TFM_LVL);
->SPMLOG_INFMSG("\033[1;34m Booting TFM v"VERSION_FULLSTR"\033[0m\r\n"); /* Print the TF-M version */
->tfm_arch_clear_fp_status(); //汇编代码
->tfm_core_handler_mode(); //发送SVC命令,触发tfm_spm_init(),这个函数是PSA的重点
//tfm_core_handler_mode()函数定义如下,它会调用SVC命令,并且,传递一个参数 TFM_SVC_SPM_INIT,最终,会调用到SVC_Handler, 最后,根据ID,调用到 tfm_spm_init()函数,后面会重点讲到tfm_spm_init()函数
arduino
__attribute__ ((naked)) void tfm_core_handler_mode(void)
{
__ASM volatile("SVC %0 \n"
"BX LR \n"
: : "I" (TFM_SVC_SPM_INIT));
}
PendSV响应 PendSV(可悬起的系统调用),它和SVC 协同使用 SVC异常是必须立即得到响应的,应用程序执行SVC 时都是希望所需的请求立即得到响应。 PendSV 则不同,它是可以像普通的中断一样被抢占挂起的
PendSV_Handle
SVC响应
arduino
SVC_Handler
->BL tfm_core_svc_handler
->tfm_core_svc_handler
-> case TFM_SVC_SPM_INIT: tfm_spm_init() //初始化SPM
-> case TFM_SVC_GET_BOOT_DATA: tfm_core_get_boot_data_handler(svc_args);
-> case TFM_SVC_PREPARE_DEPRIV_FLIH: tfm_flih_prepare_depriv_flih (struct partition_t *)svc_args[0], (uintptr_t)svc_args[1]);
-> case TFM_SVC_FLIH_FUNC_RETURN: tfm_flih_return_to_isr(svc_args[0], (struct context_flih_ret_t *)msp);
-> default: svc_args[0] = SVC_Handler_IPC(svc_number, svc_args, exc_return); //根据Client端调用SVC命令时,传入的ID,进入不同的case处理函数
tfm_spm_init
scss
tfm_spm_init
->tfm_pool_init(conn_handle_pool,POOL_BUFFER_SIZE(conn_handle_pool),sizeof(struct conn_handle_t),
CONFIG_TFM_CONN_HANDLE_MAX_NUM)
->load_a_partition_assuredly //从 section: .part.load 段中,依次获取一个 一个的partition_load_info_t
-> p_ptldinf->psa_ff_ver //检查Magic是否是:0x5F5F0000
->UNI_LIST_INSERT_AFTER(head, partition, next) //加到链表头部
-> load_irqs_assuredly(partition) //从partition中,获取到struct irq_load_info_t
->p_irq_info->init(p_partition, p_irq_info)
-> mailbox_irq_init() //初始化mailbox中断
-> tfm_hal_irq_enable(p_irq_info->source) //使能中断
-> backend_instance.comp_init_assuredly(partition, service_setting) // backend_instance 定义在backend_ipc.c中
-> backend_instance.system_run() //开始调度
在section: .part.load 段中, 定义了一下 tfm_ns_mailbox_agent_load,该结构体 **是Cmake文件中,通过解析 yaml后,自动生成的
安全服务处理依赖于TF-M实现的IPC机制。基于连接响应的服务器与客户端模型。
由psa_connect → psa_call → psa_close的过程:

3. TFM代码移植适配

如果我们做一个新的SoC项目,要使用TFM,但是大概率原生的TFM不支持你这个芯片。所以就需要移植适配,这也是程序员基本的活的来源。主要有两种思路:
- 根据CPU类型找一个一样或者相似的,架构一样,这样编译器可以进行编译运行。复制过来,改下文件夹名字和产品名字,确保能编译过去,然后上板调试,先注释的方式跑通大流程,例如点灯、串口初始化。然后就是漫长的调试过程。优点就是比较快,缺点就是代码附带很多复制过来不需要的特性,不纯净,适合新手。
- 另外一种方法就是适合老手的,直接新加一个空的新的产品文件夹,然后需要什么代码就自己新建文件,完全自己写。这样的代码比较简洁,如果写代码是高手,bug也少。
3.1 软硬件选型
- 首先要根据业务选择一个适合的核,例如M3、M4、M5等,根据性能和安全需求。
- 核确定下来,那么其指令集,架构也确定下来了,就需要选编译器,一般开源的就用gcc
- 软件代码框架选择,例如使用TFM,那么选一个近期的文档基线版本
- 然后TFM里面找相同或者相近的核的定制代码,方法就是全局搜索核的名字就可以,或者ARM架构的名字,例如ARMV8.
- 然后就是编译出来第一版的bin文件
3.2 软件移植
软件的修改首先就是编译工具和编译脚本的修改,这里不详细说明,后续讲代码的文章再说。
3.2.2 bin文件组织
有了bin文件后,TFM的bin文件主要有BL1_1/BL1_2/BL2,那么这些bin文件放Flash的那些地方,跟ATF的bin文件,还有SoC里面其他核的bin文件怎么组织,那些放Flash,那些比较大,或者加载比较靠后就可以放UFS。需要统一的进行规划设计。
BL1_1肯定是要放SoC内部的BootROM,不可以修改作为信任根,为什么要把BL1分成两部分?因为BootROM比较贵空间有限,把安全认证相关的放进去就行了,BL1_2是放Flash的,先被搬运到SRAM再执行去认证搬运BL2,然后BL2就开启了其他核的链式加载过程。
所以对应Flash的地址规划需要清晰,进行统一的设计。地址确定好,就可以把这些地址在代码进行修改。
3.3.3 驱动修改
根据我们SoC上使用的串口,修改串口地址等配置。等串口可以打出来,就可以正式的调试了,这就有戏了。
第二个比较大的驱动就是flash驱动,这个搞好,就可以从Flash读取bin文件进行加载下一个bin文件运行了。
最后,一般SoC的所有核的软件有一份统一的代码由repo管理,需要把TFM的代码单独建一个git仓库,然后修改系统编译脚本(buildroot或者其他cmake等编译工具)集成进去,还要跟系统中的编译器进行兼容,尽量跟其他系统用相同的,这样配置源码编译环境就简单很多。
需要注意编译TFM会下载一些库文件,例如CMSIS等,进行集成的时候可以本地下载后直接推到git仓库集成,或者使用Conan包管理器,进行统一管理,方便升级和隔离。
后记:
篇幅有限,写了点介绍和要点,基本一文就熟悉了TFM的来龙去脉,后续介绍代码会有另外的文章。另外想学什么可以让AI去给你写资料学习。AI解决不了你不知道的问题,这里就是拓宽知识面。
利用AI最有效的是你知道但不懂的东西,非常收益。但是你懂的东西,AI会没你专业。你都完全不知道的东西,AI给你讲了你也不懂。总结就是不懂(学到了)、懂(AI是SB)、不知道(AI在对牛弹琴)。
"啥都懂一点,啥都不精通,
干啥都能干,干啥啥不是,
专业入门劝退,堪称程序员杂家"。
欢迎各位自己有博客公众号的留言:申请转载,多谢!
后续会继续更新,纯干货分析,欢迎分享给朋友,欢迎点赞、收藏、在看、划线和评论交流!
公众号:"那路谈OS与SoC嵌入式软件",欢迎关注!
个人文章汇总:thatway1989.github.io