ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ARM可信固件(ATF)源码架构解析与安全移植实战指南

ARM可信固件(ATF)源码架构解析与安全移植实战指南 直接上一块新板卡bootrom跑完0x00000000还没跳到你的裸机main函数ARM核到底是靠谁在EL3把整个系统安全地扶起来的答案基本就是Arm Trusted Firmware圈里人叫ATF少数场景下也叫TF-A。这块固件不显山不露水但几乎所有跑Linux的ARMv8/AArch64平台从手机SoC到服务器芯片再到工控MPU底层都离不开它。你要是搞嵌入式内核、搞固件安全、或者正准备把Linux往一块新板子上bring-upATF源码早晚要啃一遍。这篇东西我就围绕ATF源码的架构全景、安全审计要点以及平台移植落地三个方向把我在实际工程里看到的东西完整梳理一遍。代码版本以主流使用的TF-A 2.8/2.9为主线不同版本目录会有小差异但骨架基本一致。内容偏实操适合有基础裸机开发经验、想深入固件层的工程师。1. ATF源码架构全景与代码布局1.1 ATF到底解决什么问题先明确一个概念。ATF不是操作系统它是在EL3异常级别运行的固件层。ARMv8引入异常级别模型之后系统的特权级别从高到低是EL3、EL2、EL1、EL0。常规操作系统跑在EL1虚拟化场景下Hypervisor跑EL2而EL3就是最高特权层能访问所有内存和外设也负责安全世界与普通世界之间的切换。你可能会问为什么EL3不能直接塞一个小内核或者干脆空着因为现代SoC的安全需求不答应。Secure Boot、TEE可信执行环境、PSCI电源管理、安全的系统电源开关机、以及各种firmware接口服务都需要在一个比OS更高的特权层完成。ATF就是ARM官方提供的这套EL3固件标准实现。对于芯片原厂来说在ATF基础上改平台代码比从零手写EL3监控器靠谱得多因为架构设计和安全模型已经由ARM把关了。我们常说的“安全固件”具体到ATF身上包含三层东西一是引导阶段的信任链保证加载的每一段固件镜像都是经过签名的、可信的二是运行阶段的安全服务比如通过SMCSecure Monitor Call指令向EL3请求服务三是与TEE协同的通道机制让REE侧普通世界跑Linux能通过BL32OP-TEE这类TEE内核完成加解密等安全操作。1.2 源码目录结构逐层拆解把ATF源码clone下来顶层目录是这么排的$ git clone https://github.com/ARM-software/arm-trusted-firmware.git $ cd arm-trusted-firmware ls bl1 bl2 bl31 bl32 common docs drivers fdts include lib plat make_helpers services tools几个关键目录要首先搞明白bl1Boot Loader stage 1一般是烧在ROM或者BootROM后面的片内SRAM负责最基础的CPU初始化、内存初始化有时只初始化部分、以及验证并加载BL2。BL1必须尽量精简因为它运行的环境不会有任何外部内存做支撑只依赖片内SRAM。bl2Boot Loader stage 2由BL1验证后加载负责更完整的平台初始化并加载BL31以及可选的BL32、启动镜像。BL2也是一个可信固件镜像但在有些平台设计中BL2运行在DDR里代码位置更加灵活。bl31这是运行时固件Runtime Firmware常驻内存运行在EL3提供SMC运行时服务、PSCI实现、以及世界切换逻辑。BL31是ATF最核心的部分后续所有安全监控功能都在这层。bl32可选的TEE OS镜像比如OP-TEE或者Trusty不直接编译在ATF tree里但ATF运行时需要通过BL31里的SPDSecure Payload Dispatcher与BL32通信。plat平台相关代码每个SoC或者开发板一个子目录。这块就是移植主要动手的地方。common公共代码比如启动流程的通用部分、多核处理、镜像加载等。lib各类库包括libc的轻量实现、firmware image package解析、锁、MMU管理、CPU操作封装。services标准服务最核心的是spd目录下的TEE调度器还有标准SMC服务。drivers外设驱动注意这里不是Linux驱动是固件里用到的驱动包括串口、定时器、GIC通用中断控制器、TZCTrustZone Controller等。看完目录之后你就明白了ATF的编译产物不是一个大而全的bin而是分了BL1、BL2、BL31等多个镜像。它们最终会被合到一个叫FIPFirmware Image Package的包里由BootROM或前级loader加载。1.3 启动流程从BL1到BL31的一次完整接力ATF启动流程是典型的接力模式。拿ARM官方FVP平台举例整个信任链是这样推进的SoC上电后BootROM先执行初始化少量SRAM把BL1镜像搬运到SRAM并跳转。BL1完成CPU基本设置、设定异常向量表、初始化串口以输出早期日志然后检查GPIO/拨码等启动源从Flash/MMC等介质中加载FIP包。BL1从FIP包中解析BL2镜像验签、解密如果有的话、搬运到SRAM然后刷新cache/TLB跳转BL2。BL2做更完整的平台初始化包括DDR初始化、时钟配置、TZC配置等然后从FIP加载BL31可能还有BL32放入安全内存区域。BL31在EL3接手设置运行环境为每个CPU建立上下文。如果是多核系统主核继续运行从核在等待。BL31最后切换到EL2/EL1把控制权交给下一个loader如U-Boot或直接交给Linux内核。这个过程可以类比成一个机场地勤交接BL1好比机务工程师只负责接机并检查登机桥BL2是地勤调度把旅客行李装载好BL31就是塔台从起飞前到飞行中的一切关键控制都由它盯守。实际移植中你可能不需要改动BL1——很多SoC方案里BL1直接由芯片ROM实现了公共代码也基本不动主要工作集中在plat目录下的平台代码。这块后面专门讲。2. 安全固件工程审计要点2.1 信任链与Secure Boot的原理分析ATF的一个核心价值是提供安全启动信任链。所谓信任链就是最终系统运行的可信状态是通过一级一级的验证传导出来的。每一级固件在跳转到下一级之前必须完成对下一级镜像的完整性和来源验证。这个链条在真实SoC里通常长这样芯片出厂时芯片ROM里固化了一个根公钥哈希RKH或者把公钥烧进eFuse/OTP。BootROM加载BL1验证BL1签名如果BL1在外部介质。BL1验证BL2镜像签名。BL2验证BL31、BL32镜像签名。如果开了Trusted Board BootTBB每个镜像会附带一个X.509证书证书包含镜像哈希由私钥签名。验证方用公钥验签再对镜像做哈希比对。实际调试中TBB的证书链构建常常是移植时最让人头疼的一环。tools/cert_create工具负责生成这些证书tools/fiptool负责打包和解析FIP镜像。你需要为平台生成一系列私钥/公钥并在BL1/BL2代码里配置公钥的存放位置。以ARM平台为例证书链文件定义在drivers/auth/mbedtls/mbedtls_cert.c公钥默认情况下以特定格式嵌入image。如果你在自定义硬件上移植并开启TBB最常遇到的坑是公钥平台配置不对BL1做验签时直接卡死或报TRUSTED_BOOT_BOOT_NVCTR_FLAG错误。证书链中的non-volatile counter值与烧录到OTP里的值不匹配。NAND/NOR Flash的读写时序没有在BL2阶段带起来导致FIP加载失败。所以我的建议是第一次移植先把TBB关掉快速跑通启动再回头开安全启动。不然踩到证书链的坑里你连串口日志都看不到就无从调试。TRUSTED_BOARD_BOOT0这种编译选项就是干这个的。2.2 BL31运行时服务审计SMC与PSCIBL31的另外一个核心是运行时服务。它对外暴露两类主要入口SMC和PSCI。SMC是一条ring-0以下级别的指令实际上它从EL1/EL2发生陷入EL3调用方式是把服务号和参数放进通用寄存器然后执行smc #0。ATF在EL3收到SMC后根据服务号路由给不同的service handler。常见服务有ARM_SIP_SVC平台自定义SIP服务用于SoC厂商私有功能比如设置DVFS、读写安全寄存器。ARM_SPD_SVC与安全世界的TEE通信。ARM_OSDM_SVC用于调试和性能监控。服务分发逻辑在services/arm_arch_svc/arm_arch_svc.c、services/spd/tspd/tspd_main.c等文件里核心的SMC调度框架在bl31/bl31_main.c里调用runtime_svc_init()通过注册表的方式管理服务。PSCIPower State Coordination Interface就更重要了它实际上承担了CPU超频调压、核心上下电、系统挂起/恢复等电源管理的职责。Linux内核底层的CPU hotplug和cpuidle最终都会在EL3调用PSCI。所以你在做平台移植时几乎必须把PSCI实现弄对否则系统启动后稍微执行一个idle操作就会死掉。典型PSCI实现涉及PSCI_VERSION返回值要符合ARM的版本定义。CPU_ON把某个从核带起来这需要访问该核的启动地址寄存器通常需要平台代码提供具体地址。SYSTEM_OFF/SYSTEM_RESET整机断电/复位需要调用PMIC或者复位控制器的寄存器操作。这里有必要提醒PSCI不是纯软件协议它和具体硬件的电源域、时钟域紧密相关。每家SoC对这些操作的寄存器配置都不一样。比如瑞芯微的PSCI和NXP i.MX8M的PSCI从代码角度看结构类似但底层寄存器、需要协同的单位完全不同。所以移植PSCI时一定以芯片手册和原厂BSP为基准别照搬其他平台代码否则在硬件行为上很容易出现潜伏的时序冲突。2.3 源码白盒审计关注点AR-TF虽然由ARM和各大厂商共同维护但并不意味着代码绝对没有隐患。做固件安全审计时有几个位置我会特别注意BL1校验逻辑BL1是信任根之一但很多SoC会直接把BL1放在BootROM里这意味着BL1的代码实际上是芯片出厂写死的不容易被替换。可如果BL1从外部介质加载那这个校验逻辑就是攻击者第一个尝试绕过的点必须硬到骨头里。镜像解析代码FIP包解析和X.509证书解析逻辑属于信外部输入的部分很容易出缓冲区溢出问题。ATF社区历史上也修过几个解析器相关的CVE审计时要重点看是否有边界的检查、长度字段是否可信。SMC服务输入EL1侧发来的SMC参数是攻击面服务处理函数需要对参数range做检查不然一个恶意的普通世界程序可能通过SMC打到EL3这是非常严重的权限提升路径。中断处理路径BL31中允许安全中断和普通中断存在中断路由、优先级管理处理不当可能导致死锁。内存权限配置BL31的页表权限、DRAM区域的TZC配置是否按最小权限原则划分。一旦TZC没配好普通世界就能访问安全世界的内存TEE等于形同虚设。用工具辅助的话checkpatch.pl能查代码风格但真正的审计要靠Coverity、Fortify、CodeQL这类静态分析工具。不过说句大实话静态扫描工具只能帮人快速找出常见bug模式真正的安全审计最后拼的还是人读代码能力。我的习惯是先把每个BL的Makefile、bl*_main.c、平台内存映射过一遍梳理出信任边界再去抠边界上的输入处理代码。3. 平台移植落地实操指南3.1 移植之前必须吃透的几个硬件细节很多朋友移植ATF最容易犯的错误是拿到一个BSP包就开始改代码改完烧进去发现起不来然后回头一行行读log追。我建议动手前先把这些硬件信息确认清楚省得后面走弯路内存布局你的SoC里SRAM有多少DDR的物理起始地址是多少哪些地址是安全内存区域TZASC保护范围内这些数字直接决定BL1/BL2代码和运行数据的存放位置。串口/UART早期日志是通过哪个UART打印的时钟频率多少波特率多少寄存器基址是多少这决定了你在plat里写的console驱动能不能用。很多移植失败表现上是“死机”实际上只是串口没输出。中断控制器版本GIC是GICv2还是GICv3安全中断配置怎么设计的直接影响BL31的中断处理模型。电源管理控制CPU_ON需要给从核发的启动序列是什么是否需要通过ATF操作SFI、PCTL等模块。TZCTrustZone Controller你希望哪个内存区域是安全世界专用的DDR哪个范围要配置给BL31编译工具链ATF用AArch64交叉编译工具链你机器上有没有aarch64-linux-gnu-gcc版本太老也可能出问题。用aarch64-none-elf-也行但必须确保工具链支持目标CPU特性。把这些细节列成一张表直接贴在工位上后续改代码随时对照。3.2 最小的平台移植目录与配置ATF里新增一个平台技术上不能只改一个文件。最小化移植通常需要创建plat/vendor/board/ ├── platform.mk ├── plat_def.h ├── plat_sip_svc.c # 可选私有SIP服务 └── bl31_plat_setup.c以NXP i.MX8M系列为例它的平台代码在plat/nxp/imx8m包含了imx8mm.c等文件里面实现了从串口初始化到内存映射、PSCI回调的整套逻辑。你要是做一个完全新平台可以拿这个做参照因为i.MX8M的平台代码相对简单直接不像某些大SoC那样嵌套了大量配置宏。platform.mk里需要定义的东西包括# 平台名称编译的时候会被 -DPLATplat 引用 PLAT_FAMILY : myboard # 指定BL31的链接基址这个必须和硬件设计的内存映射一致 BL31_BASE : 0x1000 # 指定BL31最大镜像大小 BL31_LIMIT : 0x10000 # 指定需要编译进去的源码文件 BL31_SOURCES plat/myboard/plat_bl31_setup.c BL31_SOURCES plat/myboard/plat_psci.c ...关于BL31_BASE我多说一句。这个地址不是随便定的它必须落在BL31运行时可用的内存区域内且不能被BL2或者后续的OS覆盖。大多数平台上BL31被放在DDR的安全区域或者片内容量较大的SRAM比如8MB~32MB的SRAM里。你在配置时一定要和TZC的配置同步否则BL31自己会被TZC挡住访问自己所在的内存运行到一半直接出异常。3.3 从零添加一个新平台的完整步骤假设我们手里有一块以Cortex-A53为核心的板子DDR起始地址0x60000000串口基址0x21c0000GIC基址0x2f000000想要让ATF在上面跑通并跳转到U-Boot。完整操作流程大致是第一步创建平台目录结构mkdir -p plat/myboard touch plat/myboard/platform.mk touch plat/myboard/plat_def.h touch plat/myboard/plat_bl31_setup.c touch plat/myboard/plat_psci.c第二步在platform.mk里写清楚基础平台定义指定上面提到的BL31_BASE等参数。如果你的板上没有单独SRAM可以暂时先借一块DDR但要注意启动顺序。第三步写plat_def.h定义串口基址、CPU编号、内存常量等#define MYBOARD_UART_BASE 0x21c0000 #define MYBOARD_UART_CLK 24000000 #define MYBOARD_UART_BAUD 115200 #define MYBOARD_PRIMARY_CPU_ID 0 #define MYBOARD_TZRAM_BASE 0x10000000 #define MYBOARD_TZRAM_SIZE 0x40000第四步实现bl31_plat_setup.c中必须的早期配置。这里重点看bl31_early_platform_setup它需要调用串口的初始化把BL31的运行时控制台搞定。没有串口日志就没有调试依据。第五步实现PSCI回调。如果只是最简单的单核启动可以先只实现PSCI_VERSION、PSCI_AFFINITY_INFO这种几乎不用碰硬件的服务然后逐步加CPU_ON。plat_psci.c里通常要有static int myboard_pwr_domain_on(u_register_t mpidr) { unsigned int cpu_id MPIDR_AFFLVL0_VAL(mpidr); // 这里写上把CPU_启动地址写到该核的启动寄存器 mmio_write_64(MYBOARD_CPU_RELEASE_ADDR cpu_id * 8, BL31_BASE); dsb(); sev(); return PSCI_E_SUCCESS; }第六步编译。在ATF根目录执行make PLATmyboard CROSS_COMPILEaarch64-linux-gnu- BL33u-boot.bin路径 all fip这里的BL33就是Non-Secure世界下一个阶段镜像一般是U-Boot或Linux的引导器它会被fiptool打入FIP中。BL2负责把它加载到内存并跳转。第七步烧录。把FIP写入启动介质通过BootROM引导。这个过程因SoC而异有的需要用Toolbox有的直接用fastboot烧。3.4 移植中的内存映射与TZC配置平台移植里最需要小心的是内存映射。BL31会建立自己的页表把需要访问的外设地址映射为设备内存把安全内存映射为普通内存。这个映射关系定义在plat_get_next_bl_params和各个plat_setup调用中。比较常见的配置方式是把TZRAMBL31用安全内存映射成普通内存带读写权限不可执行选项视平台而定。把串口、GIC、定时器这类外设映射成设备内存不可缓存。把DDR区域映射成普通内存但根据TZC的划分安全区域可能只允许EL3访问。TZC配置和系统安全关系非常大。举个例子如果BL31不希望普通世界的DMA访问安全内存需要在TZC里把安全内存的filter配置成仅安全访问。这块寄存器操作通常由平台代码里自己写因为每家SoC的TZC集成方式都不一样有的有单独TZC400有的内置在interconnect里。很多工程师在移植初期图省事把TZC配置注释掉系统确实也能跑但这种跑法只是“裸奔式启动”。到了做安全认证或者正式产品化的时候TZC配置不完整会导致安全世界数据暴露在普通世界一旦有漏洞被利用整个TEE被一把梭穿。所以移植过程中TZC配置别偷懒从一开始就把安全边界立好后面少很多返工。3.5 平台移植的验证清单平台移植有没有做对不能只看能启动还要有一套验证手段。基于ATF调试手段我的习惯性检查路径是确认BL1/BL2阶段串口日志打印正常说明基础console代码没问题镜像加载链没断。确认BL31阶段日志正常且能看到“NOTICE: BL31: Initializing runtime services”这行说明runtime服务初始化跑过了。确认Linux内核启动后dmesg | grep psci能看到PSCI version。如果PSCI初始化异常内核会直接死掉或者CPU_IDLE完全不可用。尝试在线执行cat /sys/devices/system/cpu/cpu1/online看CPU hotplug是否正常CPU1能不能online。如果CPU_ON回调实现有问题这一步会死机。确认安全世界通信正常可以跑一个op-tee的xtest简单case验证SPD通道。这里提一句日志里有个常规经验启动日志到BL31阶段后如果戛然而止优先怀疑SMC相关的runtime service初始化因为在runtime_svc_init阶段会逐个调用服务注册函数任何一个服务初始化函数里访问了错误地址就会触发EL3异常而EL3异常处理如果没配好系统看起来就是“死机无日志”。4. 常见问题与排错实录4.1 移植过程中最常踩的五个坑第一个坑串口没有日志或者日志是乱的。这种问题80%出在UART时钟配置上串口的Baudrate计算不是只靠寄存器值还依赖UART输入时钟。时钟源是24MHz和25MHz分频寄存器一样的情况下实际波特率差4%以上打印出来全是乱码。排查时先确认datasheet里的UART时钟数再用一个简单的loopback程序验证UART驱动本身是否正确。第二个坑BL31启动后死机且死机点不固定。这种问题典型原因是BL31的栈和BSS段位置没定义好可能是platform.mk里BL31_BASE设得太大结果BL31镜像正文区覆盖了已放进去的BL32或者U-Boot。当BL31运行到某个栈操作时就踩到别的镜像数据导致频率不定的异常。解决办法是把BL31镜像区域、BL32镜像区域、BL33镜像区域的布局当成一个整体来规划不要在画内存地图时不考虑段间距。第三个坑FIP烧写后BL2校验不过提示hash mismatch或者certificate验证失败。这常见于你打开TBB之后没有更新证书链或者重新生成私钥后没有同步更新BL1固件里的公钥。尤其是BL1里固化了公钥你换了新私钥签BL2BL1还用旧公钥验签必然失败。复现时要么把TBB临时关掉要么确保所有签名、公钥、私钥成套替换。第四个坑PSCI CPU_ON一个核就死机但日志没有明确异常。这种情况多半在从核启动地址写错或者启动地址指向的内存映射在从核视角不可访问。Cortex-A53的启动地址一般通过SoC的CPU_RELEASE_ADDR寄存器指定从核唤醒后会跳到这个地址但跳过去之前从核的MMU还没开所以地址必须是物理地址不能是虚拟地址。很多新手把BL31_BASE的虚拟地址填进去于是从核跳去一个不存在的地址直接挂。第五个坑开了EL3异常处理之后系统在正常运行中随机进入异常日志显示Synchronous Exception from EL1但是PC地址是某个内核函数。这说明EL3的异常级别模型本身没问题问题往往出在中断没有路由好。如果在BL31里开了安全中断抢占但GIC的SPI没有按安全/普通分类配置Linux内核某个中断就会误入EL3异常处理返回时上下文恢复逻辑不对系统就崩了。对应方案是核对GICD_IGROUPR/IGRPMODR配置把普通中断全部分配到Group 1安全中断放Group 0。4.2 调试手段从JTAG到串口日志ATF的调试手段相对受限因为EL3代码里很多地方不开串口日志你就没法知道走到哪了。好在这边有几种组合拳可以用。第一优先是串口日志。ATF里日志级别可以编译时调整从LOG_LEVEL_INFO到LOG_LEVEL_VERBOSE不等。建议调试阶段把LOG_LEVEL开到50VERBOSE以上make LOG_LEVEL50。不过日志越多时序变化越大有些竞态在打开日志时暴露得很明显所以调试中日志级别变化也是一个排错手段。第二是JTAG。如果芯片支持CoreSight调试你可以在BL31入口设置硬件断点单步查看SMC处理路径。这种方式在Linux与ATF之间的交互特别有用在ATF的SMC入口处打断点看看从EL1来的SMC参数到底是什么。我有一次排查一个DVFS问题就是通过JTAG看SMC参数才发现Linux驱动传的service ID不对导致EL3走了default分支。第三是Linux侧的ftrace和perf。这些工具不能直接看到EL3代码但可以辅助分析PSCI调用频率和返回时间帮你在软件层定位是否到了EL3、以及EL3处理是否超时。结合一些业务异常特征可以反向推断ATF侧的延迟点。4.3 EL3异常处理与回溯技巧ATF源码里bl31/aarch64/ea_delegate.S和bl31/ehf.c做了大量的异常处理逻辑。如果你打开了EHFException Handling Framework它会注册多个异常优先级用于安全中断和serror处理。当EL3发生同步异常时默认行为是打印异常类型和当前ELR、SPSR并尝试进入panic。这个输出信息其实很有用。比如日志里看到PANIC at PC : 0x1004a8那你要做的事就是把BL31的elf文件用addr2line转成源码行号aarch64-linux-gnu-addr2line -e build/myboard/debug/bl31/bl31.elf -f -C 0x1004a8这样能直接看到是哪个C函数或者汇编位置出问题。这个方法在开发过程中用得相当频繁很多看似毫无头绪的panic转换出来往往都是某个空指针解引用或者某个寄存器配置读到了非法地址。4.4 加速调试的编译期技巧除了上面这些手段我强烈建议常备两个编译选项组合DEBUG1不开优化符号齐全配合addr2line很方便。ENABLE_ASSERTIONS1ATF内部有大量assert判断条件失败会直接panic并输出文件行号debug阶段打开能省很多时间。release版本里这些断言会被编译掉性能会好。但移植期间不要追求性能先保证正确。另外ARM_ARCH_MAJOR、ARM_ARCH_MINOR这样的编译选项也会影响代码分支比如ARMv8.2以上的CPU会走一些新特性路径。如果目标CPU是Cortex-A55建议在platform.mk里明确指定ARM_ARCH_MAJOR : 8 ARM_ARCH_MINOR : 2不给全的话某些默认配置可能走通用路径虽然也能跑但可能在特定指令上性能偏差或兼容性问题。5. 移植完成后如何验证安全固件状态启动和基本功能跑通不等于平台移植就结束了。安全固件的验证是另一大块工作。对ATF来说验证分成几个层级基础功能验证冷启动、热重启、CPU hotplug、suspend/resume都正常。安全服务验证通过SMC调用一些基础安全功能确认EL3收到的参数、返回的结果都正确。完整安全验证开启Secure Boot确认篡改任一镜像都会启动失败开启TEE确认REE侧和TEE侧通信正常。性能验证SMC的调用延迟、CPU hotplug的上下电时间等影响用户体验。安全验证里有一个常见误区是只在开发板上跑测试没在正式样机的secure boot配置下验证。理论上测试板软硬件和正式样机配置有差异尤其是eFuse里烧录的公钥和OTP计数器状态可能不同如果不去匹配真实配置有些安全机制在真机上就失效了。实操上我会在移植完后用脚本把整个启动链重新过一遍每次先篡改BL2的某个字节确认BootROM能拦住再篡改BL31的某个字节确认BL2能拦住最后篡改BL33的U-Boot镜像确认BL2能拦住。只有这些篡改测试都挡住整个信任链才算真正生效。6. 关于ATF源码学习和维护的一点心得源码学习这块我自己走过的路径是先啃ARM官方文档尤其是Trusted Firmware-A的Design Guide和Porting Guide再对照源码里ARM FVP平台代码做实验。FVP虽然只是虚拟平台但它把标准平台的启动链路和PSCI、SMC服务都完整实现了跟着它把代码走一遍比直接上手真机效率高很多。跑通FVP后再去看具体SoC平台代码会轻松一大截。维护上ATF版本更新很快硬件平台代码的演进路线也比较激进。做产品的话建议把ATF版本固定在某个长期稳定版上并做完整的本地代码管理别频繁追master。因为社区经常改接口内核侧与ATF侧的兼容性也可能出现错位。我踩过一次某个平台把ATF从2.6升到2.8BL31接口改动导致旧内核的PSCI调用参数解释不一致系统起来后CPUidle直接睡死。后面花了几天时间排查最后发现就是版本兼容性这个原因。另外一个小技巧ATF的commit message写得很规范很多bug修复的commit都带验证过程和影响范围说明。读历史commit是理解代码设计意图的捷径。碰到一个宏定义不明白先去看看是谁加的、为什么加比直接搜代码块要快得多。最后再分享一个我在实际调试中的体会ATF移植和Linux内核移植在思路上完全不同。内核里有大量现成驱动可以直接复用而ATF的plat代码几乎没有通用逻辑每一块板子都有自己独特的内存图、中断路径和电源管理序列。所以你问“ATF移植难不难”答案其实取决于你对自家硬件的理解程度。硬件寄存器手册看得越细ATF移植的坑就越少反过来如果你连DDR控制器初始化方式都没搞清就开始改代码那ATF的每个报错都会是“薛定谔的bug”。动手之前先花一周把datasheet啃透这是我在这个领域最值得的投入。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进