1. 内容整体设计与思路拆解
1.1 一文说清ATF到底是什么
很多从事嵌入式Linux或Android底层开发的工程师,第一次接触ARM Trusted Firmware(简称ATF,现在官方常称TF-A)时,通常是一脸懵的。市面上讲U-Boot、讲Linux内核的资料一大把,但系统讲ATF的资料却少得可怜。这导致不少人有个误解:以为ATF是个类似U-Boot的引导程序,把U-Boot加载起来就完事了。
实际完全不是这样。
ATF是一套运行在ARM处理器安全侧(Secure World)的固件集合,它不仅仅负责上电引导,更核心的职责是提供安全运行时服务、可信启动流程、以及在不同特权级和安全状态之间安全切换的机制。你在设备上看到的armv8/armv9架构、TrustZone、PSCI电源管理、安全启动(Secure Boot)、TEE(可信执行环境)这类词,最终落地都绕不开ATF。
为什么很多人看得懂U-Boot却看不懂ATF?最根本的原因是视角不同。U-Boot解决的是“怎么把内核加载到内存并跳过去”的问题,而ATF解决的是“在处理器刚上电、还处于最高特权级EL3时,如何以安全可控的方式建立整个系统的执行环境”的问题。它站在系统启动的最顶端,是安全信任链的根。
这篇文章的来源,是我对一个基于Rockchip RK3588平台的商业项目做ATF基线审计和移植时的完整复盘。从架构阅读、安全功能审计,到平台适配和启动调试,前前后后花了几周时间。我会把整个思路、关键代码路径、和踩过的坑全部梳理出来,给准备接触ATF或者正在做平台移植的工程师一条相对完整的路线。
1.2 ATF在整个启动链路中的位置
要理解ATF,首先得把它放进完整的启动链路里看。以经典的ARMv8-A平台为例,典型启动顺序是:
- 芯片内部的BootROM(固化在硅片里的程序)执行,根据拨码或烧录状态决定从eMMC、SD、USB等介质读取下一级镜像。
- 读取到的第一份镜像就是ATF的BL1(Boot Loader stage 1)。BL1做基础硬件初始化(时钟、DDR、串口等的最小集),然后从可信存储位置加载BL2。
- BL2继续初始化,加载BL31(EL3 Runtime Firmware)、可选的BL32(TEE OS)、以及BL33(通常是U-Boot或UEFI)。
- BL31常驻内存,一直留在EL3,向非安全侧的Linux内核提供PSCI等运行时服务。
- Linux内核在EL2(虚拟化场景)或EL1(非虚拟化场景)正常运行,所有安全相关请求通过SMC(Secure Monitor Call)指令陷入EL3交给BL31处理。
这个流程里最容易被忽略的一点是:ATF不止在启动阶段干活,它是一个“启动后依然活跃”的运行时组件。BL31常驻DDR里一个被保护的region,类似你的电脑里有一个独立于操作系统之外的安全固件一直在跑——这么理解就贴切了。
对于做系统移植的工程师来说,你的任务时间线一般是这样:芯片原厂给你一套完整可启动的ATF源码和烧录包——你验证它在你的板子上能跑——然后根据你的外设、内存颗粒、调试口差异做适配——最后把安全启动和TEE方案集成进去。看起来步骤不多,但每一步都有很多暗坑。
1.3 为什么需要专门做一次“工程审计”
我在很多项目里见过一种危险做法:直接把芯片原厂提供的ATF二进制丢进烧录工具,能开机就认为万事大吉。短期看没毛病,长期看隐患极大。因为ATF是整个系统安全信任链的地基,它的错误不像应用层软件那样崩溃几次就能暴露,往往是在特定攻击场景或者极端边界条件下才会触发。
所以我建议,凡是涉及安全功能的设备,都应该把ATF源码做一次系统性的审计。审计的核心目标包括:
- 确认工程量级和组成部分,知道谁在什么时候执行、访问哪些硬件资源。
- 梳理信任链,搞清楚每个镜像的加载和校验方式,确认是否存在被绕过验证的可能。
- 检查TOCTOU(Time-of-check Time-of-use)问题,即“校验时是好的、运行时被换了”的经典攻击面。
- 验证BL31提供的SMC接口是否严格做了调用来源和参数校验,这部分是攻击者最容易接触到的接口。
- 评估平台配置的合理性,比如BL31运行基址、内存权限配置、PSCI功能是否完整。
这听起来像安全团队的活,但实际做平台移植的工程师也同样需要掌握。因为你不知道什么时候就要自己改一处平台代码,不懂审计原则的人很容易随手引入一个安全隐患。
2. ARM Trusted Firmware源码架构全景拆解
2.1 源码目录结构与各目录职责
拿到TF-A源码后,首先要做的是把目录结构看懂。TF-A的代码仓库地址是https://github.com/ARM-software/arm-trusted-firmware(后来被迁移到了TrustedFirmware.org托管),官方的参考实现是主线master分支。整个仓库的顶层目录结构大致如下:
bl1/:BL1阶段源码,负责最基础的启动。bl2/:BL2阶段源码,负责加载和认证后续镜像。bl31/:BL31运行时固件源码,包含SMC分发、PSCI实现、运行时服务框架。bl32/:BL32(TEE OS)的启动集成代码,实际TEE OS本体(如OP-TEE)是独立仓库。plat/:平台相关代码,这是你做移植时最常动的地方,按厂商分目录存放。drivers/:各类驱动,包括GIC、UART、DDR、IO存储器等。include/:通用头文件,包括架构定义、平台接口定义、常量定义等。lib/:通用库,如标准C库子集、跳转表、内存操作等。common/:通用代码,如启动流程的基本框架、Blob验证逻辑等。services/:各类运行时服务,包括标准SMC服务、PSCI服务、SPD(Secure Payload Dispatcher)等。tools/:各种工具,包括证书生成工具cert_create、FIP打包工具fiptool等。fdts/:设备树源文件,ATF也使用设备树描述平台硬件信息。
初次接触的人容易犯一个错误:钻进bl1的汇编代码里出不来。其实你不必逐行抠汇编。ATF强调可移植性,通用流程代码用C写的部分很多,只有极小部分例如CPU reset、异常向量表、上下文切换才用汇编。你真正需要精读汇编的地方并不多。
2.2 从编译脚本理解构建系统
TF-A的构建系统是精修的Makefiles体系,顶层Makefile是入口。通常的编译方式是:
make PLAT=versal BL33=/path/to/u-boot.bin DEBUG=1 V=1 all这个命令里几个关键参数:
PLAT:指定平台目录,比如versal对应Xilinx versal平台,rk3588对应Rockchip平台,fvp是ARM官方提供的固定虚拟平台。BL33:指定Non-Secure侧下一级镜像路径。ARM标准流程里BL33通常是U-Boot或UEFI。DEBUG:设为1时会生成带调试符号、开启LOG_LEVEL高等级输出的版本。V=1:显示完整编译命令,排查编译错误时非常有用。
编译产生的镜像文件:
bl1.bin:BL1镜像,烧录到片内SRAM或可信ROM区域。bl2.bin:BL2镜像。bl31.bin:BL31镜像,完全独立的裸机固件。bl32.bin:如果配置了OPTEE则产出。fip.bin:将BL2、BL31、BL33等打包在一起的FIP格式镜像。bl1-bl2.bin:一种BL1+BL2合并镜像,烧录方案更简单。
我强烈建议你用DEBUG=1做开发调试,用LOG_LEVEL=50查看详细日志。但要注意,正式发布版本务必关掉DEBUG,否则日志输出本身就是严重的信息泄露风险。
2.3 启动流程中的信任链传递机制
ATF的启动流程核心就是“分级加载、逐级认证”。这套机制是我审计时花时间最多的部分,因为它直接决定了“到底什么能信”。
整体思路很简单:每一级引导程序在加载下一级之前,都必须先对下一级的镜像做安全校验,校验通过才会跳转。这形成了一条信任链:
- 芯片BootROM是第一信任根,它验证BL1。BootROM的代码在硅片出厂时固化了,无法修改,理论上最可信。
- BL1验证BL2。BL1中内置了平台根公钥的hash,用这个公钥验证BL2的签名。
- BL2验证BL31、BL32、BL33。BL2中包含了证书解析和验签逻辑。
用生活化类比就是:银行金库的钥匙在手,开门,验证押运员(BL2),验明正身后再让他开第二道库门,放行长进来(BL31),行长再验证押运的那箱钱是不是真钞(BL33)。
实际代码里,bl2_plat_handle_post_image_load等接口就是BL2完成镜像加载后的挂载点。如果你看到这些接口里有额外的平台校验逻辑,要注意是否弱化了原有的安全策略。
2.4 关键数据结构:上下文切换与Boot Layout
ATF里最核心的数据结构,一是cpu_context,二是boot_params。
cpu_context保存了CPU寄存器状态,用于EL3与Normal World(非安全侧)之间的切换。你可以把它想成一个银行金库的交接记录表:每次访问金库(进EL3)和离开金库(回Non-secure)都要记录当前状态,下次回来能快速恢复现场。
boot_params则描述了每个BL阶段的加载位置和大小。在ARMv8下,BL1通常放在片内SRAM或特殊的ROM地址,BL2可能放在SRAM或DDR可信区域,BL31常驻DDR保留区域。
平台移植时你最重要的任务之一,就是合理规划这些内存布局。以RK3588为例,BL31运行在DDR顶部保留的特定区域,BL2则放在DDR安全区域。如果内存布局没规划好,轻则启动卡死,重则某个安全功能静默失效——后一种情况尤其危险。
3. 安全固件工程审计要点与实操方法
3.1 审计清单:从代码层面找问题
经验来看,工程级的安全审计不必像安全研究那样追求0-day发现,更应该关注“可用性、配置合理性和最小化攻击面”。按这个目标,我整理的审计重点如下:
- 默认配置是否开启完整的安全启动链?
- 每个SMC接口是否做了调用来源校验(即调用者的Safety/EL级别)?
- 全局变量是否都初始化?是否有敏感的调试打印?
- 平台内存映射表(MMU Table)是否存在过度授权的映射区域?
- 是否有绕过认证后直接加载镜像的代码路径?
- BL31中是否存在未使用的、但暴露给Normal World的调试接口?
- PSCI API是否符合ARM FFH规范?是否暴露了不必要的扩展接口?
我习惯用grep快速搜索危险调用模式,比如ssh相关的打印、callback、debug等关键词,然后逐步跟踪函数调用路径。但是单纯grep很难发现逻辑漏洞,更有效的方式是分别对关键函数走“白盒阅读+动态验证”。
3.2 信任根与固件验证机制的深度解析
信任根通常以两种形式存在:一是芯片BootROM内的固定公钥,二是BL1内部的公钥hash(即建立信任根的公钥列表)。在ATF源码中,BL1的验证逻辑定义在drivers/auth/目录下的认证框架中。
在认证的过程中,trusted board boot(TBB)配置很关键。当你开启TRUSTED_BOARD_BOOT编译选项时,BL2会通过auth_mod_verify_signature对所有加载镜像执行RSA或ECDSA验签。验证内容包括:
- 镜像头格式是否正确。
- 签名是否有效(基于平台证书的验签)。
- 镜像哈希是否与证书中声明一致。
- 镜像是否过期或不在允许的版本序列(如果是防回滚场景)。
代码里plat_get_rotpk_info就是平台获取Root Of Trust Public Key的接口。这里坑很多——有些平台对可重编程存储区(比如eMMC RPMB分区)的信任处理得很模糊,如果你把ROTPK存在可写存储中,攻击面就被放大了。
3.3 审计发现的典型安全薄弱点
在这类审计中,我总结出几类容易被忽略的风险点,这里列一下:
- 调试接口:许多原厂代码在定义平台宏时保留了大量调试日志,包含DDR初始化Details、内存地址、启动参数。攻击者如果拿到串口访问权限,这些信息能显著降低攻击门槛。
- SMC参数未校验完全:BL31里的某些SMC接口,对参数来源的
exception level判断不严格,或缺少对SMC调用者身份(caller_sec_state)的校验。理论上,Normal World的普通应用(EL0)也可能触发某些本应只允许EL1/EL2调用的接口。 - 信息泄露:BL31处理SMC失败时,部分错误码直接回显寄存器内容或缓冲区内容,可能导致地址布局泄露。
- 内存设置
RO与RW权限混乱:有些平台为省事将BL31的部分数据段映射为可读写,但这些数据一旦可被写入,可能覆盖安全策略表或跳转指针。 - 未禁用的弱密码算法:部分代码还残留对SHA-1的支持,虽然未必被实际使用,但如果你审计粒度不足,很容易忽略。
从工程角度说,我建议做审计时同步开启Secure Boot,并在目标板上跑一轮TF-A Tests,或者直接跑Linux内核的KASAN级别压力。只有动态+静态结合才能发现这类交叉问题。
3.4 一份适合团队执行的审计流程参考
审计不是一个人天马行空读代码,建议按下面的流程来:
- 先读文档:
docs/目录下的firmware-design.rst、trusted-board-boot.rst、以及platform-porting-guide.rst是必读。这一层先建立全貌。 - 拉一个干净的tag版本:不要审计一堆本地修改,CI拉主线或厂商release tag即可。
- 按启动顺序逐模块审查:BL1 → BL2 → BL31 → SPD/TEE。
- 对每个平台函数(
plat_xxx)标出调用点,并记录函数职责。 - 采用code review工具(如Gerrit、GitLab MR review)把审计过程记录下来,方便追溯。
- 最后跑实际硬件验证:启动、休眠唤醒、热插拔CPU、TEE功能、安全启动失败注入测试。
另外,ATF官方有一个docs/下“security center”的指引,整理了最近公布的漏洞列表,建议每隔几个月对照检查自己平台是否需要同步更新补丁。
4. 平台移植完整落地指南
4.1 移植的前置条件和环境准备
做ATF平台移植,你手上最好先有这几样东西,缺一不可:
- 目标板的芯片手册,特别是存储映射、GIC配置、串口基地址、DDR控制器寄存器。
- 原厂提供的可启动uboot镜像和内核镜像(用来做对照验证)。
- 一块能稳定供电、有串口输出的目标板。JTAG调试器强烈建议准备,启动阶段的串口输出来得太晚。
- 主机的交叉编译工具链,通常是
aarch64-linux-gnu-前缀的GCC工具链。 - 如果有TrustZone调试工具(如ARM DS、JTAG),会在调试时省很多时间。
环境上,在Ubuntu 22.04这类常见发行版上安装工具链即可:
sudo apt update sudo apt install gcc-aarch64-linux-gnu make device-tree-compiler git clone https://github.com/ARM-software/arm-trusted-firmware.git建议选择稳定的版本如v2.8、v2.9、v2.10或厂商维护分支。如果你用的是老平台,直接切主线可能编译不过,保持“够用即可”的策略。
4.2 新建平台目录和最小启动配置
TF-A平台代码的骨架非常简单,核心是在plat/vendor/board/下创建你的平台目录,并实现一组固定的平台接口。
最小化平台目录结构通常是:
plat/mycompany/myboard/ ├── platform.mk ├── myboard_def.h ├── myboard_common.c ├── myboard_bl2.c ├── myboard_bl31.c └── myboard_helpers.S第一步看platform.mk,里面指定了平台编译选项、包含路径、源文件列表。一个最基本的内容大致如下:
PLAT_INCLUDES := -Iplat/mycompany/myboard/include PLAT_BL_COMMON_SOURCES := \ plat/mycompany/myboard/myboard_common.c \ plat/mycompany/myboard/myboard_helpers.S \ drivers/arm/gic/v3/gicv3_main.c \ drivers/arm/gic/v3/gicv3_helpers.c BL2_SOURCES += \ plat/mycompany/myboard/myboard_bl2.c BL31_SOURCES += \ plat/mycompany/myboard/myboard_bl31.c第二步实现平台初始化函数,比如platform_setup、plat_get_next_bl_params、plat_get_stack_info等。对调试来说,第一步要保证串口输出能工作。以ARM PL011串口为例:
void bl31_early_platform_setup2(u_register_t arg0, u_register_t arg1, u_register_t arg2, u_register_t arg3) { /* UART base address from platform definition */ console_pl011_register(PLAT_MYBOARD_UART_BASE, PLAT_MYBOARD_UART_CLK_IN_HZ, PLAT_MYBOARD_UART_BAUDRATE, &console_data); }这里console_pl011_register就是把寄存器基址注册成标准console。很多新手卡在没有输出,其实就是这一步没做对,或者基址不对,或者UART时钟频率算错了。这个频率参数要查芯片手册的UART时钟树,不是随便写的。
4.3 平台内存布局规划与中断控制器配置
内存布局规划是移植的核心,它直接决定你能不能正常进入BL31。ATF通过链接脚本(.ld.S)来布置镜像段,平台头文件里定义各镜像加载基址。我的建议是:
- BL1尽量放在片内SRAM,因为DDR控制器还没初始化,代码必须能在不含DDR的环境中运行。
- BL2可以放在SRAM也可放DDR的安全区域,但BL2运行前DDR已经可用(BL1初始化完DDR)。
- BL31常驻DDR安全区域,这个区域必须在内存控制器上被保护,防止Non-secure侧的Linux意外改写。
以RK3588为例,它的ATF代码中会把BL31放在系统DRAM的高端保留区。你在看代码时如果看到#define PLAT_RK3588_TRUSTED_SRAM_BASE这类宏,就是这个用途。
中断控制器方面,ATF BL31需要把GIC(Generic Interrupt Controller)配置好,因为PSCI的CPU热插拔、系统挂起/恢复都依赖于GIC的正确初始化。GIC版本以v3为主,初始化逻辑包括:
- 设置GICD_CTLR的Group0和Group1使能。
- 配置SPI(Shared Peripheral Interrupt)中断的路由和触发方式。
- 配置GICC/GICR基址。
代码里主要是gicv3_driver_init和gicv3_distif_init函数。如果GIC配错,系统一进BL31就直接死掉。
4.4 标准开机已验证:从编译到进U-Boot
给大家一个我常用的最小验证路径:
- 编译ATF:
make PLAT=yourplatform DEBUG=1 V=1 BL33=/path/to/u-boot.bin all - 确认产物
bl31.bin、fip.bin存在。 - 烧录:按厂商工具烧到启动介质,可能是SPL+eMMC分区或FIP容器。
- 通过串口观察启动日志。
如果一切正常,你会看到类似这样的日志:
NOTICE: BL31: v2.9(release):v2.9 NOTICE: BL31: Built : ... INFO: BL31: Initializing runtime services INFO: BL31: Preparing for EL3 exit to normal world INFO: Entry point address = 0x...然后U-Boot打印出现。到这一步,平台移植的最小闭环已经完成。
4.5 常见移植踩坑实录
坑1:串口只出BL1日志,BL2卡死。大概率是BL2的DDR初始化或内存访问异常。检查BL2的bl2_platform_setup是否访问了尚未就绪的控制器,或者SRAM布局重叠。
坑2:BL31进入后立刻重启。多数是GIC或定时器配置问题。建议在BL31初始化代码里逐步加NOTICE日志,找到具体挂点。
坑3:U-Boot起来但Linux不能启动。往往不是ATF问题,而是传给内核的DTB或启动参数不对,但背锅的往往是ATF。先用U-Boot的booti命令单独验证内核能否启动。
坑4:PSCI调用返回错误码。核心是BL31是否注册了PSCI服务。看启动日志里Registered PSCI信息,没有的话多半是条件宏没配置对。
坑5:安全启动验证失败,设备变砖。这种场景要非常小心,务必开发阶段先关闭验签,验证完再开启。并且保留烧录工具能从MaskROM模式恢复的能力。
5. 深入实战:从审计需求到自定义扩展常见问题与排查
5.1 如何扩展一个自定义SMC服务
很多时候默认的ATF无法满足业务需求,要在EL3加一个自定义SMC接口。举个例子,比如你想实现一个只在安全世界可见的“设备唯一ID”读取接口。
首先你要在BL31的服务框架里注册一个handler。代码长这样:
static uintptr_t my_smc_handler(uint32_t smc_fid, u_register_t x1, u_register_t x2, u_register_t x3, u_register_t x4, void *cookie, void *handle, u_register_t flags) { switch (smc_fid) { case MY_SVC_GET_UID: /* 做一些安全检查,比如确保调用来自EL1以上 */ if (!is_caller_secure(flags)) { SMC_RET1(handle, SMC_UNK); } write_my_uid_to_buffer(x1); SMC_RET1(handle, 0); default: SMC_RET1(handle, SMC_UNK); } } DECLARE_RT_SVC(my_smc_service, MY_SVC_START, MY_SVC_END, my_smc_handler);然后在Linux侧,你可以通过smc指令直接发起调用,或者用arm_smccc_smc接口从内核发起。扩展SMC接口时最重要的是做两件事:一是把MY_SVC_START/MY_SVC_END的编号范围定义好,不要跟ARM标准SMC分配段冲突;二是在合法范围内明确调用者的权限和安全状态,否则很容易开个后门。
5.2 基于真实硬件排查启动流程故障
这里分享一个在RK3588平台上的具体排查案例。现象是板子偶尔冷启动卡死,热启动正常。
我先从U-Boot、内核日志出发没找到线索,转向ATF端。用JTAG读寄存器,发现BL31在初始化GIC时卡在一个等待循环里。进一步阅读代码发现有一个平台宏控制GICR基址的偏移,当芯片型号的core数量不同时会差一截。原厂默认配置按“16核”去初始化,但该板子实际只有8核,结果访问了不存在的GICR frame,导致总线hang。
修复方法是把平台宏改成根据DTB解析CPU数量,让GICR基址随核心数偏移。
这种情况是典型的“默认能跑但特定硬件才暴露”的平台问题,也正是为什么每一步启动日志和JTAG都不可少的原因。
5.3 常用调试工具与日志分析方法
ATF代码库内建了NOTICE、WARN、ERROR、INFO等日志级别。编译时通过LOG_LEVEL控制输出等级,一般:
#define LOG_LEVEL 50 /* 输出所有消息 */在BL31中打印时,可以用fmt字符串加参数。要注意在BL1阶段DDR未初始化,串口打印受限于BL1所在介质(通常是片内SRAM的驱动代码),某些外设如DMA不能用。
硬件调试方面强烈推荐使用JTAG,比如ARM官方的Arm Development Studio或开源调试器OpenOCD配合gdb。可以做到:
(gdb) target remote :3333 (gdb) hbreak bl31_main (gdb) continue (gdb) info registers在启动早期,一条指令就能让整个系统挂死,所以打印日志是基础,JTAG才是“重型武器”。
5.4 平台移植后的必要测试清单
移植完成不是终点,我个人测试时会至少覆盖下列场景:
- 冷启动和热启动各100次,确认无偶发问题。
- 进入和退出Suspend(系统休眠)200次,确认PSCI路径稳定。
- CPU热插拔操作,确认CPU0以外的核能稳定上线和下线。
- TEE功能(比如OPTEE启动、TA加载)来回切换,确认BL31与BL32的交互正常。
- 非安全世界恶意SMC调用测试,确认异常接口返回错误而不是挂死。
- 开启安全启动后,做一次签名错误注入测试,确认设备拒绝启动并进入恢复模式。
每次测试都要记录日志,偶尔会把LOG_LEVEL调到最高跑一轮,排查掉潜在信息过载或格式问题。
5.5 常用问题排查速查表
| 现象 | 可能原因 | 建议排查步骤 |
|---|---|---|
| BL1启动卡死 | SRAM初始化、串口时序 | 检查JTAG读PC值,确认卡在哪条指令 |
| BL2加载失败 | FIP打包错误、加载地址重叠 | 用fiptool查看FIP内容,核对BL2基址 |
| BL31重启 | GIC初始化错误、定时器配置错误 | 分阶段加日志,确认挂点 |
PSCICPU_ON失败 | 未正确配置电源域、GICR基址错误 | 检查代码中的psci_validate_power_state实现 |
| 安全启动验签失败 | ROTPK不匹配、根证书不正确 | 检查BL1中ROTPK是否与烧录证书对应 |
| 内核启动后无法访问TEE | SPD未编译进BL31 | 确认编译时SPD选项正确,比如SPD=opteed |
| 板子变砖 | BL31损坏或验签失败 | 从MaskROM/串口恢复模式重刷,开发期务必保留无校验版本 |
这张表是我每次换新板子时都会拿出来过一遍的检查清单,所有端口、分区、烧录路径都是按厂商手册逐一核对的,不能凭经验猜。
6. 末尾随想与经验拾遗
说一组我个人的小技巧。很多人面对ATF这种数千行C/汇编的代码库会有畏难情绪,我也一样。但真正上手后会发现,它其实遵循一个很清晰的模式:每个BL阶段都有固定的setup流程,平台代码只要按接口填空就行。心态摆正以后,阅读速度会快很多。
第二个实用习惯是“保持与主线同步”。即使你在做厂商分支,我也建议每隔几个月拉一次上游TF-A的更新,对比一下安全补丁。很多厂商分支长期不合并主线的安全修复,这在产品生命周期很长的情况下尤其危险。
第三个建议是,设计上尽量把安全相关的逻辑集中在一个独立的模块里,不要在平台代码里到处发散。比如你要加自定义SMC服务,就在services/下新建一个目录,不要塞进plat/的某个c文件里。这样做的好处是审计、测试、复用都容易,而且避免平台适配代码和业务逻辑耦合。
最后送大家一句话:ATF的文档和代码永远是最好的老师,遇到问题先查docs/,再搜代码,最后再问社区或者厂商。这个过程本身就是提升嵌入式底层能力最有效的方式。希望这篇基于实践的拆解能帮你少走一点弯路。