ARM TFM-1介绍及代码下载运行适配

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 核与安全资产彻底隔离,是最安全的架构方案。

  1. 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 核的最高权限入口,防止非安全侧越权访问安全资源。

  1. 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 核(请求安全服务)

  1. A 核非安全侧(Linux)→ 通过TEE Client API → A 核 TEE OS(OP‑TEE)。
  2. TEE OS → 发起SMC 调用 → A 核 EL3(ATF BL31)。
  3. ATF → 校验请求合法性 → 通过硬件 mailbox/IPC 控制器 → 发送到 M 核。
  4. M 核 TF‑M → 接收、鉴权、执行安全服务(加密 / 存储 / 验签)→ 返回结果。
  5. 结果沿原路径返回,全程加密与审计

(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 代码编译

  1. 编译工具安装:

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")
  1. 编译前首先确认python3是否缺少Ninja包,缺少的话使用pip安装:
    pip3 install ninja

  2. 随后进入到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

  3. 进入到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)&REGION_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不支持你这个芯片。所以就需要移植适配,这也是程序员基本的活的来源。主要有两种思路:

  1. 根据CPU类型找一个一样或者相似的,架构一样,这样编译器可以进行编译运行。复制过来,改下文件夹名字和产品名字,确保能编译过去,然后上板调试,先注释的方式跑通大流程,例如点灯、串口初始化。然后就是漫长的调试过程。优点就是比较快,缺点就是代码附带很多复制过来不需要的特性,不纯净,适合新手。
  2. 另外一种方法就是适合老手的,直接新加一个空的新的产品文件夹,然后需要什么代码就自己新建文件,完全自己写。这样的代码比较简洁,如果写代码是高手,bug也少。

3.1 软硬件选型

  1. 首先要根据业务选择一个适合的核,例如M3、M4、M5等,根据性能和安全需求。
  2. 核确定下来,那么其指令集,架构也确定下来了,就需要选编译器,一般开源的就用gcc
  3. 软件代码框架选择,例如使用TFM,那么选一个近期的文档基线版本
  4. 然后TFM里面找相同或者相近的核的定制代码,方法就是全局搜索核的名字就可以,或者ARM架构的名字,例如ARMV8.
  5. 然后就是编译出来第一版的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

相关推荐
千寻girling3 小时前
不知道 Java 全栈 + AI 编程有没有搞头 ?
前端·人工智能·后端
小码哥_常3 小时前
Spring Boot 实现网络限速:让流量“收放自如”
后端
johnrui4 小时前
SpringBoot-JdbcTemplate
java·spring boot·后端
Victor3564 小时前
MongoDB(72)如何创建用户和角色?
后端
Victor3565 小时前
MongoDB(71)如何启用MongoDB身份验证?
后端
想打游戏的程序猿5 小时前
工具与协议层——Agent 如何连接世界
后端·ai编程
希望永不加班5 小时前
SpringBoot 过滤器(Filter)与请求链路梳理
java·spring boot·后端·spring
0xDevNull5 小时前
Java实现Redis延迟队列:从原理到高可用架构
java·开发语言·后端
糖炒栗子03265 小时前
Go 语言环境搭建与版本管理指南 (2026)
开发语言·后端·golang