拓冰建站拓冰建站
首页 / 资讯中心 / 正文

ATF源码架构解析:安全启动与平台移植实战

做底层固件这几年ARM体系里的安全启动和运行时固件一直是最“劝退”又最值得啃的一块。很多人U-Boot玩得很溜设备树、内核启动也调得飞起但一见到Arm Trusted Firmware——圈里习惯叫ATF——就有点发怵BL1、BL2、BL31、BL32、BL33这些名字到底对应哪一段为什么它在安全启动里是不可替代的那根轴换一块新板子平台移植到底要从哪里下手这篇文章我想从源码架构全景讲起把ATF在整个系统中的位置、安全固件工程审计的几个核心维度以及平台移植落地的实际操作一次讲透。内容比较适合正在做SoC bringup的工程师、接触过TrustZone但还没系统看过ATF的朋友以及准备在自己的板卡上接入OP-TEE或安全启动的团队参考。我会尽量少讲空泛概念多给能直接用的判断方法和代码路径。1. 先理解ATF在系统里的位置1.1 ARMv8异常模型与EL3的“特权天花板”要理解ATF绕不开ARMv8的异常模型。AArch64模式下CPU有四个异常级别EL0跑应用EL1跑操作系统内核EL2跑虚拟化或U-Boot这类引导程序EL3是最高特权级别也是Secure world和Normal world之间的切换点。普通系统的启动路径往往是BootROM - BL1 - BL2 - BL31 - U-Boot - Kernel其中BL31之后ATF的runtime firmware会常驻在EL3U-Boot和Kernel都降级到EL2/EL1去跑。这样设计的原因很直接EL3像大厦的总保安室所有楼层之间的权限切换必须从这里过而U-Boot和Kernel本身没有资格管理EL3。很多初学者问为什么不能用U-Boot做安全启动因为U-Boot通常跑在EL2甚至EL1它没有权限去配置TrustZone地址空间控制器也没办法安全地切换Secure world和Normal world上下文。ATF天生就是为EL3设计的所以哪怕是只做最基本的安全启动ATF也都是绕不开的底座。1.2 ATF解决的核心问题ATF全称Arm Trusted Firmware现在开源仓库一般叫trustedfirmware-a简称TF-A。它的核心使命可以概括成四件事建立从SoC上电到正常世界引导程序之间的可信启动链也就是Trusted Boot。提供BL31常驻运行时服务包括PSCI电源管理、SMC调用分发、系统复位与关机。为Trusted OS比如OP-TEE提供EL3之下的隔离执行环境入口。充当Secure world与Normal world之间的消息网关所有跨世界调用都要经过EL3。这四件事听着抽象实际上一块开发板上电后CPU先执行固化在BootROM里的代码然后经过BL1、BL2一级级校验信任最终把控制权交给BL31BL31再拉起BL32如果有TEE和BL33通常是U-Boot。整个过程就像机场安检链条BootROM是最初的闸机ATF是贯穿全程的安检流程U-Boot和Kernel都只是被检查通过的旅客。1.3 这篇文章的阅读路线我建议你按这样的顺序读先看第二个章节把BL1到BL33的启动链条搞清楚然后进入第三个章节从安全审计的角度理解ATF为什么安全最后才是第四个章节的平台移植。移植部分我把步骤拆得比较细就算你手上的板卡和示例平台差异很大改动的思路也是共通的。如果你完全是新手建议先压下移植的冲动在QEMU或者FVP上把ATF编译跑一遍再回头读源码。这个过程会帮你省掉后面大量调试时间。2. 源码目录里的启动链条BL1到BL33各管一段2.1 源码仓库结构与版本选择TF-A的源码根目录下有bl1、bl2、bl31、bl32这几个核心代码目录注意没有bl33目录因为BL33属于正常世界引导程序通常是U-Boot或者UEFIATF只负责把它加载起来。除此之外还有plat、drivers、lib、include、tools等目录plat放平台定制代码drivers放串口、GIC、TZASC等驱动抽象lib里有MMU表、缓存、内存管理这类公共库。版本选择上我建议优先用维护中的LTS版本。ATF主线迭代很快新的平台支持、安全补丁几乎每个月都在变。直接拉master虽然能体验到最新特性但API变化也容易踩坑。v2.9、v2.10这些LTS版本经过大量平台验证拿来移植和生产最稳妥。2.2 五个BL阶段的职责边界ATF把启动过程拆成多个阶段每个阶段的可信度依赖上一阶段的验证形成一条链。下面这个表格把各阶段职责整理得很清楚阶段运行位置主要职责典型实现BL1BootROM内或片上SRAM最小信任根初始化CPU验证并加载BL2AP Trusted ROMBL2片上SRAM或DDR安全区可信启动加载器验证并加载BL31、BL32、BL33Trusted Boot FirmwareBL31安全RAM常驻EL3运行时固件提供PSCI、SMC等服务Runtime FirmwareBL32安全DDR或TZRAM可选的Trusted OS比如OP-TEETrusted OSBL33普通DDR正常世界引导程序通常是U-Boot或UEFINormal World BootloaderBL1通常固化在SoC内部芯片出厂就有你没法改。你真正能改的是BL2、BL31以及BL32/BL33的集成方式。BL2有点像机场地勤它把关卡之间的行李全部核对一遍BL31则像总机台启动完成后所有跨世界电话都得从这里转接。2.3 FIP镜像格式与烧写布局ATF的镜像打包方式是FIP全称Firmware Image Package。FIP内部有一个TOC结构每段镜像都有类型、版本、偏移量等信息由fiptool生成和解析。典型打包命令长这样fiptool create \ --tb-fw build/plat/release/bl2.bin \ --soc-fw build/plat/release/bl31.bin \ --tos-fw build/plat/release/bl32.bin \ --nt-fw u-boot.bin \ fip.bin烧写时BootROM先加载FIPBL1会通过平台定义的存储介质驱动找到FIP中的BL2镜像然后按TOC偏移去拉取。开发阶段很多人喜欢把BL2直接丢到SRAM里绕过FIP解析这种方式调试快但量产时必须走完整FIP和验签流程。2.4 信任链安全引导如何一级级验签安全启动的核心是“信任根”也就是RoT。ATF的默认信任链是BL1内嵌一个公钥或公钥哈希它用这把公钥去验BL2的证书签名BL2再用自己证书里携带的下一级公钥去验BL31、BL32和BL33的证书。这条链上的每个环节都必须保证前一个环节绝对可信。如果RoT泄露整条链就崩溃了。所以在安全固件工程审计中我第一个会看的就是BL1公钥的存储方式和BL2验签失败后的处理逻辑如果失败后还允许继续启动那这个产品就谈不上安全固件。开发阶段可以把TRUSTED_BOARD_BOOT设为0跳过验签量产必须置1。真机上开启后每次启动都要做证书解析、哈希校验和RSA验签通常会增加几百毫秒到一秒级别的启动时间这点需要提前给产品团队打预防针。3. 安全固件工程审计从哪几个维度看代码质量3.1 异常级别与权限边界审计ATF最敏感的部分是EL3与EL1/EL2之间的边界。审计时我通常会顺着异常入口捋一遍重点关注以下几个方面所有SMC入口是否校验了调用方当前的异常级别和安全状态。接收Normal world传来的指针参数时是否验证地址范围不在安全内存区间。BL31在处理唤醒、复位等事件时是否先恢复安全上下文再切回Normal world。有一次我在客户代码里发现他们为了让某个调试命令能操作寄存器直接放开了BL31中一个SMC handler的访问范围任何EL1代码都能触发。这种问题通过静态代码扫描很难发现必须在评审时盯着“入口参数校验”这个点反复问如果这是一个恶意程序调用会发生什么。3.2 SMC调用约定与处理路径ATF的运行时代理由SMC指令触发。AArch64下执行SMC会陷入EL3ATF根据SMC函数ID分发到对应service。SMC调用约定SMCCC定义了函数ID的格式包括调用类型、是否快速调用、调用者安全状态等。ATF里每个service会注册自己的handler比如标准服务、PSCI服务、OP-TEE服务。审计时我会重点看handler对函数ID的解析逻辑尤其是通配或范围判断的地方。如果某个函数ID前缀判断写得太宽Normal world可能调用到Secure world的调试接口。举一个常见反面例子某些平台的SMC handler用(func_id 0xFF) 0x01当作“安全服务标识”结果把厂商自定义的高位前缀也匹配进去了。这类问题在代码走查时很容易被忽略但危害极大。3.3 内存隔离与MMU映射审计ATF在EL3启用了自带的MMU管理库xlat_tables它负责把安全RAM、外设寄存器、DDR区域映射到EL3的页表中。审计时我关注的是页表属性安全内存区域是否为EL3可读写且不允许Normal world映射。外设寄存器是否配置为Device memory避免乱序访问。页表本身放在哪里如果页表可被Normal world改写那MMU保护形同虚设。此外真正的内存总线隔离不能只靠MMU。TrustZone地址空间控制器TZASC需要在BL31启动早期配置把DDR按区域分成Secure和Non-Secure。ATF只负责配置软件页表TZASC寄存器还是得平台代码在bl31_platform_setup里完成。审计时我会同时检查这两层少了哪一层都会出现内存越权风险。3.4 密钥、证书与mbedTLS集成ATF的验签能力通过集成mbedTLS库来实现。编译时如果开启了Trusted Boot就需要指定MBEDTLS目录ATF会用它做X.509证书解析和RSA验签。证书链的生成由tools/cert_create完成通过GENERATE_COT1和ROT_KEY_PATH等选项指定密钥路径。审计时我会确认这几个点ROT私钥是否只存在于构建机有没有被提交到代码仓库。证书中的公钥哈希是否和BL1内嵌的RoT一致。验签流程是否对证书“有效期”做了检查。X.509证书带有效期有些开发板会把有效期设置得太短量产一段时间后固件无法过验最后还得重新签发证书并升级。值得提醒的是ATF从较新版本开始支持多根信任链dualroot CoT允许一个板卡同时信任两套根密钥。这种方式适合复杂的供应链场景但对普通产品来说单根信任链已经足够多根反而会增加密钥管理的复杂度。4. 平台移植落地从零添加一个新板卡4.1 先搭起最小平台目录ATF的移植单位是“平台”所有平台代码放在plat/厂商/板级目录下。一个最小的平台通常需要如下几个文件plat/example_company/example_board/ ├── platform.mk ├── include/platform_def.h ├── plat_common.c ├── plat_setup.c ├── plat_helpers.S └── bl31_plat_setup.cplatform.mk负责告诉编译系统这个平台需要哪些源文件、链接脚本、编译选项。platform_def.h存放地址和尺寸宏比如安全RAM基地址、BL33加载地址、GIC基地址、串口基地址等。plat_setup.c实现早期初始化bl31_plat_setup.c实现BL31运行时的平台初始化。我第一次移植时犯过一个错误故步自封地照抄FVP的platform_def.h结果板子的串口地址和GIC地址完全不同ATF连早期打印都出不来。所以拿到一块新板卡第一件事是打开对应SoC的TRM手册把外设基地址列成一张表再填写platform_def.h。4.2 最关键的几个平台接口BL31启动过程中平台层必须提供若干接口。常见的有bl31_early_platform_setup2早期初始化包括串口、GIC、内存信息。bl31_platform_setupBL31执行期间调用配置TZASC、PSCI等。bl31_plat_get_next_bl_params返回下一个要启动镜像的入口参数。plat_get_system_counter_freq返回系统计数器频率。这里给出一段示意代码展示如何设置BL33的入口参数entry_point_info_t *bl31_plat_get_next_bl_params(void) { entry_point_info_t *next_image_info; next_image_info bl31_plat_get_next_bl_params_common(); next_image_info-pc BL33_BASE; next_image_info-spsr SPSR_64(MODE_EL2, MODE_SP_ELX); next_image_info-args.arg0 DTB_BASE; return next_image_info; }注意BL33_BASE和DTB_BASE都要和U-Boot的实际链接地址、内核加载地址匹配。如果U-Boot编译时链接在0x60000000但ATF把它加载到0x70000000大概率会直接跑飞。4.3 调试三件套串口、GIC、计数器平台bringup刚开始最重要的不是安全特性而是能看见输出。串口是最先要调通的模块。在BL2和BL31的早期初始化里必须调用串口驱动做初始化。ATF对串口驱动的抽象已经做得比较通用你只需要把基地址、波特率、时钟频率填对。GIC其次重要因为PSCI的CPU开关和中断路由都依赖GIC。ATF要求GIC在BL31阶段初始化并把安全中断配到Group 0把普通中断留给Non-secure世界。系统计数器频率容易被忽略但它影响PSCI挂起和唤醒的时间计算。常见做法是在platform_def.h里定义PLAT_SYSCNT_FREQ这个值必须是SoC的系统计数器实际时钟频率不是CPU主频。调试时建议在BL1、BL2、BL31的每个阶段出入口都加一行打印。ATF的打印函数是tf_printf用起来和printf一样方便NOTICE(BL31: platform setup start\n);4.4 PSCI与CPU唤醒的实现PSCI是ATF提供的最核心电源管理服务。平台层要填充psci_ops结构体实现cpu_on、cpu_off、system_off、system_reset等回调。对于多核平台cpu_on的实现通常是在目标CPU的mailbox地址写入要执行的入口函数然后通过sev指令唤醒它。这里我踩过一个很深的坑mailbox内存需要保证缓存一致性。在写入唤醒地址后一定要执行缓存操作和内存屏障否则目标CPU读到的可能是旧值或乱序值导致唤醒后跑飞到未知地址。ATF内部提供了flush_dcache_range接口写mailbox后记得调用。4.5 接入OP-TEEBL32怎么加进来如果产品需要Trusted OS编译时需要把OP-TEE的镜像作为BL32集成进来。常见做法是在platform.mk里定义BL32_BINARY和BL32_SOURCE路径并在FIP打包时用--tos-fw参数添加BL32镜像。BL32需要一段安全DRAM区域具体地址由SoC的TrustZone地址空间控制器决定。平台代码要在BL31阶段把这一段DDR配置为Secure然后在bl31_plat_get_next_bl_params里把BL32入口参数传给BL31。OP-TEE启动后ATF会通过标准SMC接口与它协作完成安全世界上下文的切换。4.6 编译、运行与烧录编译命令以我常用的一个平台为例make PLATexample_board \ CROSS_COMPILEaarch64-none-elf- \ BL33../u-boot/u-boot.bin \ BL32../optee/tee.bin \ DEBUG1 \ V1生成的关键产物包括bl1.bin、bl2.bin、bl31.bin、bl32.bin、fip.bin。烧写时BL1通常由SoC出厂BootROM加载我们需要把fip.bin烧到平台指定的存储偏移位置。如果使用QEMU验证可以用-bios bl1.bin之类的参数具体取决于QEMU版本。我先强调一点移植ATF的过程中真正花时间的是调地址、调GIC、调缓存一致性而不是写代码。ATF平台接口已经封装得很稳定大部分时间其实在看TRM。5. 常见问题与排查技巧实录5.1 串口没输出怎么定位是哪个阶段挂的我遇到最多的情况是BL31以后没有日志。这种问题第一步不是看代码而是确认日志是“完全没有”还是“BL31以前有之后没了”。如果完全没输出先查BL1/BL2阶段的串口基地址初始化如果BL31以前有、之后没了通常是BL31代码在早期setup阶段崩溃可能原因是GIC配置错误、页表映射缺失或者链接脚本内存布局不对。调试这类问题我习惯先把ATF的DEBUG级别打开再用tf_printf逐行标记关键函数出入口。串口不输出时还要检查是不是用了错误的console驱动。ATF的驱动是模块化的不同SoC的串口外设寄存器差异很大选错驱动即使基地址对也什么都打印不出来。5.2 编译报错工具链、mbedTLS、宏定义问题ATF对工具链版本比较敏感建议使用官方发布的AArch64裸机工具链而不是某些发行版自带的旧版交叉编译器。常见问题整理成一张表报错现象常见原因解决方法aarch64-none-elf-gcc: command not found工具链未安装或未加入PATH安装官方AArch64工具链检查环境变量MBEDTLS_DIR not set启用了Trusted Boot但没指定mbedTLS路径下载mbedTLS源码编译时指定MBEDTLS_DIRpathundefined reference to plat_get_xxx平台接口未实现或函数名拼写错误对照TF-A平台API列表逐个检查section.bss is not within region链接脚本中RAM区域过小调整platform_def.h中的安全内存尺寸定义PLAT_XLAT_TABLE_SIZE too small页表空间不够映射区域过多增大PLAT_XLAT_TABLE_SIZE宏编译问题相对好解决把报错定位到具体文件后基本都是配置或接口缺失问题。5.3 卡在BL31进不了U-Boot如果BL31启动后一直没有跳转到U-Boot优先检查这几个点FIP里有没有打包BL33镜像。有时你改了BL33路径但没重新打包FIP系统里跑的还是旧FIP。BL33入口地址和U-Boot实际链接地址是否一致。不一致时通常会在U-Boot非常早期的代码里跑飞。DTB地址是否落在U-Boot期望的位置。如果U-Boot在启动后要从固定地址读取设备树这个地址错了内核就没法启动。有没有配置GIC的Group 0中断路由。有些平台需要把安全定时器中断配置正确否则BL31会一直卡在中断等待里。排查时我会先在bl31_plat_get_next_bl_params入口和出口各打一行确认ATF确实把下一个镜像参数传出去了再去看U-Boot侧有没有打印。5.4 开启安全启动后启动失败开发阶段跳过验签一切正常一旦把TRUSTED_BOARD_BOOT置1启动就卡住多半是证书链生成的问题。检查顺序如下有没有生成rot_key.pem并把它放到正确路径。证书中的公钥哈希是否和BL1内嵌的RoT匹配。mbedTLS版本是否与ATF要求匹配。证书有效期是否合理。我见过一个比较隐蔽的问题构建机系统时间不准签出来的证书直接过期。开Trusted Boot以后ATF在验签时发现证书无效就会拒绝启动。排查半天最后发现是构建环境的NTP没同步。这种细节虽然在代码审计里不容易发现但一旦遇到就是批量性的。5.5 调试工具和日志推荐除了tf_printfATF还支持用JTAG对EL3进行调试。有条件的话我强烈建议在开发板上预留JTAG/SWD口配合ARM Development Studio或OpenOCD直接在BL31断点调试。没有JTAG时用串口打印加GPIO翻转的方法也能定位大多数问题。做法很简单在怀疑出问题的函数前后分别拉高拉低一个GPIO用示波器看GPIO变化迅速判断程序执行到哪里。我在实际移植中最深的一点体会是把ATF当成“一个跑在EL3的小型操作系统”来对待而不是单纯的“引导代码”。它有自己的页表管理、运行时服务注册表、电源管理框架和系统调用分发路径。顺着这个思路去读代码、加接口、做移植会比把它当成一堆bl2/bl31文件拼拼凑凑要顺得多。第一次在自己定制的板卡上看到BL31成功跳到U-Boot时那一刻的成就感应该就是做底层固件最值得回味的时刻了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门