1. 从一块“黑屏”板子说起我为什么花几周去啃ATF源码做ARM平台BSP的老哥应该都有这种经历移植完U-Boot满怀信心地烧进板子串口打印停在某个地址之后再无反应CPU像是被谁按了暂停键。我几年前第一次调RK平台的启动流程时就遇到这个情况反复确认U-Boot没问题、DDR初始化没问题最后翻到Trusted Firmware-AATF的BL31阶段才找到根因——不是U-Boot挂了而是固件根本没有把控制权安全地交到下一个阶段。那是我第一次意识到现代ARM平台尤其是Armv8/Armv9架构下ATFArm Trusted Firmware-A才是系统启动链路上最容易被忽略、也最不能忽略的一环。它运行在最高的特权级别EL3负责初始化安全世界、传递启动参数、处理安全监控调用还要管理CPU热插拔、系统挂起、可信启动。这些活儿U-Boot干不了Linux内核也不该碰全压在ATF身上。ARM官方将ATF定义为一组安全固件参考实现覆盖从Armv8-A到Armv9-A的各类应用处理器。对BSP工程师来说它不只是“跳板”而是整个系统的信任根与运行时代理对做安全审计的人来说它又是攻击面最集中的EL3代码出了问题就是“一锅端”。这篇内容基于我对ATF源码的实际阅读和平台移植经验写出来分几个层面展开先讲清楚ATF在启动链路里到底扮演什么角色、源码仓库怎么组织、BL31的运行时服务拆解然后落到平台移植需要改哪些文件、编译体系怎么适配再把安全固件审计的几个关键检查项理一遍最后分享一些实测中踩过的坑和排查思路以及一套从看懂到能改的学习路径。适合正在做ARM平台BSP、安全固件集成或者对可信启动有兴趣的工程师参考。2. 启动链路中的EL3角色ATF到底管住哪一段旅程2.1 从ROM Code到BL33一次完整启动的时序拆解ARMv8架构引入了异常级别Exception Level概念EL0到EL3数字越大特权越高。ATF跑在EL3这是比EL2虚拟化和EL1内核更高的特权层整个系统的安全世界和非安全世界都由它统筹。一次典型启动的流程是这样的芯片上电后固化在芯片内部的ROM Code先执行完成最基础的时钟、存储控制器初始化。ROM Code加载并验证BL1Boot Loader stage 1BL1是ATF的第一段代码主要工作是把下一段固件加载到SRAM并完成验证。BL1加载BL2Boot Loader stage 2BL2负责加载BL31、BL32如OP-TEE、BL33通常是U-Boot或UEFI并负责建立一套认证链。BL31运行在EL3常驻内存接收来自非安全世界的SMCSecure Monitor Call请求向内核提供PSCI电源管理服务、SIP厂商自定义服务等。BL33进入EL2或EL1启动操作系统。关键点在于BL31之后EL3的代码并没有退出历史舞台。它作为secure monitor常驻每次CPU进入电源状态切换、每次内核想访问安全世界资源都得通过SMC陷入EL3由ATF处理完成后返回。所以ATF不是“启动完就没用了”而是内核整个生命周期里的一个常驻服务层。我见过不少同事觉得“ATF就是引导一下”直到排查一个suspend/resume唤醒失败的问题追了一周发现是BL31里PSCI的CPU_ON实现没有正确配置目标CPU的启动地址才意识到这套固件的复杂程度远超预期。如果你负责的平台要支持深度睡眠、热插拔、安全启动ATF的代码质量直接决定系统的稳定性上限。2.2 为什么U-Boot和内核都不适合接管EL3很多刚从MCU转到应用处理器开发的工程师会问已经跑着U-Boot了为什么还需要一段EL3固件这涉及特权级别的隔离语义。U-Boot虽然也能在EL3下跑但问题在于U-Boot的逻辑核心是引导OS它没有常驻服务的设计无法在内核运行期间继续响应SMC调用同时U-Boot的代码审计面远大于ATF如果让它常驻EL3攻击者只要利用一个网络协议栈漏洞或者文件系统解析漏洞就可能直接以EL3权限执行代码。所以ATF的定位非常清晰在EL3这个最高特权层只保留最小必要功能——启动认证、电源管理服务接口、安全监控调用分发。代码量小意味着可审计、可验证、形式化方法能覆盖而U-Boot和内核暴露在不可信输入下即使被攻破也拿不到EL3的权限。这也解释了为什么Armv8.4之后引入了RAS、SVE、MTE等大量新特性时ATF都会同步更新支持代码——安全的边界在最高特权层功能越丰富这里越要克制但又要保证扩展能力。ATF用一套以BL31常驻runtime为核心的架构在“最小特权”和“功能可用”之间找到了平衡点。源码里大量使用C和少量汇编核心代码不到两万行这一点在工程上是深思熟虑的。3. 源码仓库全景从顶层Makefile到平台定义3.1 目录结构与构建产物ATF源码托管在TrustedFirmware.org仓库名TF-A。拿到代码后第一件事是看目录结构这里的信息量比想象中大。bl1/ BL1阶段源码 bl2/ BL2阶段源码 bl31/ BL31运行时源码核心都在这里 bl32/ 可选的可信OS如OP-TEE的SPD common/ 通用代码如启动、异常处理 plat/ 平台相关代码各厂商/platform按目录组织 lib/ 通用库含el3_runtime、psci、gic等 drivers/ 外设驱动包括ARM的GIC、串口、TZC400等 fdts/ 设备树源文件用于构建平台配置 make_helpers/ 构建辅助脚本 docs/ 设计文档、移植指南、安全文档 tools/ 证书生成、固件打包等工具构建一个平台固件本质上就是通过Makefile的PLAT参数选择平台目录然后连带bl1、bl2、bl31或bl31-only编译。调试早期可以先编BL311用现有BL1/BL2跳过认证加载直接看BL31行为。3.2 BL31的入口逻辑从汇编到C的交接BL31的入口函数是bl31_entrypoint由bl31目录下的汇编代码实现。这段汇编做的事情极其关键配置当前CPU的异常向量表VBAR_EL3设置EL3相关的系统寄存器SCR_EL3、SCTLR_EL3、CPTR_EL3等初始化栈指针为C代码执行准备环境如果你的平台开启了ENABLE_RUNTIME_ACCESS_ENTITY还需要考虑内存属性进入C函数bl31_mainbl31_main里依次做三件事bl31_early_platform_setup早期平台配置如串口、Interrupt控制器、bl31_plat_arch_setup架构级内存映射配置、bl31_platform_setup普通平台初始化最后调用bl31_lib_init完成运行时框架注册再通过bl31_prepare_next_image_entry准备跳转到BL33。这里有个细节容易踩坑BL31需要在一块约定好的内存里运行而且这块内存在整个系统生命周期内不能被非安全世界访问。很多平台是使用SRAM或者DDR中的保留区域通过TZCTrustZone Controller做硬件隔离。如果TZC配置错了BL31访问自己数据时会触发权限错误启动直接卡死串口还没有任何报错。3.3 编译命令与常见配置项最简单的一次构建以qemu平台为例make PLATqemu DEBUG1 bl31DEBUG1很关键它会打开LOG_LEVEL到INFO甚至VERBOSE同时去掉优化方便GDB调试。正式发布时建议DEBUG0并打开优化选项。常用宏配置需要重点看这几个LOG_LEVEL控制日志详细程度40是INFO50是VERBOSE。跑通之前先用VERBOSE能省很多排查时间。ENABLE_PIE地址无关代码让BL31可以被加载到不同地址。PROGRAMMABLE_RESET_ADDRESS告诉ATF当前CPU的启动地址是否可编程影响BL31的加载方式。GIC_ENABLE_V4_EXTN是否启用GICv4扩展。ARM_BL31_IN_DRAMBL31是否放在DRAM里部分平台没有足够SRAM时开启。SPD选择Secure Payload Dispatcher比如spdopteed启用OP-TEE协同。构建系统会输出bl1.bin、bl2.bin和bl31.bin三个文件最终通过fiptool打包成fip.bin。用到的fiptool在tools/fiptool目录下它会按照特定布局把BL2、BL31、BL32、BL33等镜像和证书打包。打包的时候注意--tb-fw、--soc-fw这些参数对应的是Trusted Board Boot要求的镜像、证书和密钥信息。4. BL31不只是跳板运行时服务、PSCI与SMC分发机制4.1 Runtime Service框架与SMC调用的派发路径BL31正常启动后会进入一个事件循环通过异常向量表接收来自低特权级的SMC请求。SMC调用其实很像系统调用调用方将功能号放在x0寄存器里ATF根据功能号定位到对应的Runtime Service运行时服务去处理处理后结果再通过x0返回。每次SMC请求来的时候ATF会跳到handle_smc先根据SMC_RT_MASK找服务类型常见的有ARM_SIP_SVC厂商自定义服务比如电源管理、厂商专用调试接口ARM_FFA_SVCFF-A规范里定义的接口ARM_SPD_SVCIoT可信固件框架相关PSCI电源管理协调接口OPTEED_SVCOP-TEE相关以PSCI为例内核调用cpu_suspend时最后会触发SMC陷入EL3ATF里的PSCI实现会配合GIC把当前CPU的上下文保存到内存再调用平台回调完成真正的电源状态切换。请求返回时BL31负责恢复上下文让CPU看起来像什么都没发生过一样继续往下执行。实际上BL31的PSCI代码是整个ATF中最复杂的部分之一因为它处理了大量CPU状态机运行、待机、挂起、关闭、热插拔。每个状态之间的合法迁移都被严格定义平台需要通过plat_psci_ops结构体提供各种回调函数——CPU上电、下电、挂起等。很多移植工作没做好现象就是系统能正常启动但一执行热插拔或者深度睡眠就系统复位或者死锁。4.2 GIC初始化与中断控制BL31还负责初始化GICGeneric Interrupt Controller。GIC是现代ARM系统中断管理的核心它分成两个主要部分GICDDistributor分发器和GICRRedistributor重分发器加上CPU接口GICC/GICR与CPU核的接口。ATF里的GIC驱动在drivers/arm/gic支持GICv2、GICv3和GICv4。初始化时主要做这些事配置GICD的全局设置如Group0/Group1中断的路由策略初始化每个CPU接口的优先级掩码启用或禁用特定中断的转发设置唤醒中断的配置这些配置看似琐碎却直接影响系统稳定性。ARM GIC手册里有一句话说得很直白GIC的错误配置可能导致中断丢失或重复触发而这些在开发早期很难发现往往到高负载压力测试时才暴露。4.3 SPD机制与OP-TEE的握手如果你的产品需要可信执行环境TEEATF通过SPDSecure Payload Dispatcher与OP-TEE等可信OS衔接。在BL31启动时spdopteed会触发BL32通常是OP-TEE OS的加载和初始化然后BL31与BL32之间建立一组双向调用通道。具体实现上BL31通过opteed_init等函数将OP-TEE的入口信息记录下来之后内核里对TEE发出的SMC请求会被ATF转发到TEETEE的返回值再经ATF送回内核。这个“转发”逻辑被封装成标准的SMC调用处理方式ATF本身不关心TEE内部做了什么只负责安全地切换上下文保存/恢复寄存器状态以及保证非安全世界不能直接跳进TEE的地址空间。我在一个项目上踩过SPD相关的坑同时把SPD选成opteed但BL32镜像没有正确加载BL31一直跳不到TEE初始化表现就是内核探测不到TEE设备。后来发现是fip打包时把BL32的镜像位置放错了ATF从payload里找不到OPTEE的入口地址。这类问题一般串口会有一行明确报错但如果你直接跳过BL2在调试自己的BL31启动流程报错位置会更隐蔽。5. 平台移植落地从选择参考平台到跑通第一个FIP5.1 平台目录的组织方式与选择策略ATF的plat/目录下按厂商和芯片型号组织平台代码比如arm/board下面有fvp、juno等各SoC厂商也会放自己的目录。移植的第一步是先找到一个与目标芯片最接近的参考平台复制出来改不建议从零开始写平台代码——ATF的platform ops接口非常多从power、topology到bl31 setup有几十个回调一个人从零写全出错的概率极高。一个典型的平台目录需要关注这些文件plat/vendor/board/platform.mk plat/vendor/board/plat_bl31_setup.c plat/vendor/board/plat_psci.c plat/vendor/board/plat_gic.c plat/vendor/board/plat_topology.c plat/vendor/board/plat_sip_svc.cplatform.mk里会定义平台需要编译哪些源文件、使用哪些宏、BL31加载地址等关键参数。注意ATF里几乎所有平台相关参数都是通过编译期的宏传递的比如BL31_BASE、BL31_LIMIT这些必须和你在SoC datasheet里看到的内存映射一致也不难理解为什么平台移植的第一步就是把内存地址图核对清楚。也可以直接用ARM官方的FVPFixed Virtual Platform先搭一套编译和调试流程FVP的好处是无需硬件跑通之后再把平台相关代码替换成实际芯片。我的习惯是先看参考平台编译链接都通了再做最小启动验证然后逐步加入PSCI、GIC、TZC等模块。5.2 串口初始化与最小启动验证移植后第一次跑通通常以BL31串口输出NOTICE: BL31: v2.8等日志为准。这一步看似简单却依赖串口驱动、时钟配置、引脚复用等一堆前置条件。常见做法是在bl31_early_platform_setup阶段初始化串口使用编译期指定的UART基地址和波特率。ATF自带drivers/ti/uart或drivers/arm/pl011等驱动。如果是第三方芯片可能需要自己适配。串口初始化建议放在最早的位置因为之后每一步出错都需要从打印信息去定位。实践中有两个细节值得注意一是串口地址必须是物理地址且这段地址的MMU映射在bl31_plat_arch_setup里就准备好了二是波特率时钟源如果可配置要和外围的UART实际频率对得上否则打印出来全是乱码。我自己就吃过亏同一个板子上PULL频率和内部高频时钟源接错串口输出乱码三天才定位到是频率源问题。5.3 电源管理回调与CPU拓扑定义跑通最小启动之后建议立刻做PSCI。原因很简单Linux内核启动时一定会询问PSCI版本而且后期热插拔、cpuidle都要走PSCI接口。如果PSCI实现不完整内核要么panic要么在低功耗模式下彻底睡死。PSCI移植的核心是填一个plat_psci_ops结构体static const struct plat_psci_ops plat_psci_ops { .cpu_on platform_cpu_on, .cpu_off platform_cpu_off, .cpu_suspend platform_cpu_suspend, .system_suspend platform_system_suspend, .system_reset platform_system_reset, .system_off platform_system_off, };每个回调里完成对应的SoC底层操作给目标CPU核上电、配置启动地址、触发事件唤醒、关闭PLL或隔离电源域等。还有平台拓扑结构描述也就是plat_core_pos_to_aff_map这类函数用来告诉ATF当前系统中CPU簇和核的亲和层级关系。拓扑写错会导致热点调度异常和内核启动阶段affinity相关的报错。另外有三项配置常被忽略但影响很大COLD_BOOT_SINGLE_CPU的含义MULTI_CLUSTER是否开启PROGRAMMABLE_RESET_ADDRESS如果SoC支持自定义CPU启动地址BL31可以不用依赖BL1引导次核如果不支持需要事先把warm reset入口地址烧录到某个寄存器。这些配置项在PSCI实现里以宏的形式出现选错不会让你编译报错但运行时一定会在某些时序上冒出来。5.4 FIP打包与烧录布局当BL1/BL2/BL31/BL33都编译好后用fiptool打包成单一FIP文件。命令形如fiptool create --tb-fw build/plat/release/bl2.bin \ --soc-fw build/plat/release/bl31.bin \ --nt-fw u-boot.bin \ fip.bin打包时要注意镜像顺序和加载地址。BL2加载BL31时会从FIP里读取BL31的entry point并跳转BL31再根据FIP里的信息加载BL33。地址写错的表现通常是在BL31报一个Failed to load image的错或者直接无输出。如果平台没有完整的BL1/BL2逻辑支持也可以采用RESET_TO_BL31模式跳过BL1和BL2直接把BL31作为启动最初始阶段这种方式对调试最友好——不需要处理证书、不需要考虑FIP的载荷只要烧进去的BL31能被CPU正确执行就行。很多SoC厂商的spl加载BL31后就是走的这条路径。6. 安全固件工程审计从信任链检查到攻击面收敛6.1 信任根与认证链设计ATF安全性的基础是信任链Chain of Trust。从某种程度说ATF的安全不是“绝对安全”而是“信任根正确且链路上每一步都被验证”。Arm Trusted Board BootTBB的机制是出厂时把ROTRoot of Trust密钥的公钥烧入芯片的OTP一次性可编程存储器或者eFuse里。BL1验证BL2时用ROT公钥验签BL2的证书BL2用同样的信任链验证BL31、BL32、BL33。每一级都校验镜像的哈希和签名任何一环被篡改都会中断启动。在源码审计时重点检查这些位置plat_match_rotpk确认ROTPK是否与平台OTP内容一致。cert_create工具生成证书时的密钥长度与签名算法。BL2中的load_auth_image调用链确保每个镜像都走了认证。TRUSTED_BOARD_BOOT这个编译宏是否为1很多移植版默认关闭这个功能导致安全启动形同虚设。如果不需要完整TBBArmv8.5的Arm CCA和后续的RMERealm Management Extension提供了更多隔离机制不过那是更大的话题一般平台用不上了解一下方向即可。6.2 内存隔离与TrustZone配置审计ATF运行期间所有EL3数据都要放在安全内存里。审计时核实两个层次一是ATF自己数据段用的是什么内存、TZC规则是否正确覆盖二是BL31的MMU翻译表是否允许非安全世界访问到这块区域。一个常见弱点是平台为了调试方便把某些debug串口或测试寄存器映射成非安全可访问攻击者利用这些外设可以直接控制DDR的某些属性、篡改ATF正在用的数据结构。所以审计ATF时别只盯着ATF源码还要把整个SoC的内存映射和外设访问权限当成一个整体看TZC400/TZC380这类TrustZone控制器配置更要先确认到位。另外注意CCFCache Coherent Fabric或者系统总线层面的安全属性设置很多SoC不是单靠TZC拦住一切的L2 Cache或SLC的某些操作还能绕过。这一步需要不断和SoC原厂的参考手册比对。6.3 常见审计项清单我给自己做项目审查时会固定跑一套checklist供你参考BL31的栈是否在安全内存栈溢出时是否会吞掉别的关键数据ATF里是否开了STACK_PROTECTOR这种栈保护宏SMC的传入参数有没有做合法性校验比如访问内存的地址是不是被限制在非安全世界自己的地址空间操作号是否越界PSCI回调里有没有对电源状态的非法组合做防御比如同一CPU被反复ON两次会不会重复上电GIC中断路由配置里有没有把Group0的安全中断错误地发给非安全核BL31日志是否会在release包里暴露安全内存的地址信息或者密钥相关内容是否使能了ENABLE_AMU、ENABLE_PAUTH等安全增强特性以及是否依赖对应的硬件支持这些点其实在ARM的文档里都能找到对应源码实现的位置。建议安全审计的结论不要只写“没问题”要写清楚当前固件实际启用了哪些防御特性、哪些特性因为硬件条件不支持仍然关闭。这才是工程上说得通的审计成果。6.4 攻击面最小化的工程实践ATF虽然小但也是攻击面尤其是通过网络或USB暴露出来的DFU功能或者厂商调试用的SIP接口。这些接口一般只应在开发阶段开启量产固件里应当移除否则等于给攻击者留了后门。个人经验是量产固件编译时严格检查下面几个宏ENABLE_DEBUG_FS和ENABLE_DEBUG_LOCAL_FS应为0。SIP_SVC的服务接口只保留确有必要的那几个。串口日志级别至少关到NOTICE以下。如果不做动态调试把CONFIG_ARM_BL31_IN_DRAM这种把运行时代码放内存且不易验证的方案绕过掉。7. 实测中最容易翻车的场景与排查方法论7.1 场景一BL31串口打印“NULL pointer”之后死循环这个情况多出现在平台代码里某个回调返回了NULL或者错误码而ATF通用代码没有仔细校验。比如plat_my_core_pos()返回异常值、拓扑里查不到当前CPU编号、GIC驱动初始化时获取不到有效GIC地址。排查时我会先开DEBUG1把日志打到VERBOSE再用JTAG/GDB挂上BL31的串口打印看卡在哪个函数。因为ATF的汇编入口和C函数之间栈帧清晰GDB在QEMU和FVP上都很好调大部分时候问题都是平台回调里某个宏或配置不一致。也可以直接在可疑函数里手动加NOTICE打印ATF本身有完整的日志系统加上编译期管控打印的粒度可以控制得很细。我第一次调ATF时就是靠一行一行加NOTICE定位到一个UART驱动没有初始化完成的问题。7.2 场景二内核启动一半CPU挂死但串口无任何报错典型的表现是启动到smp_init时系统直接没有输出。这种往往是BL31的secondary CPU boot地址没配对或者启动次核的序列在GIC路由上没配好。内核启动次核时通过PSCI调用CPU_ONBL31要把启动地址写到目标CPU的特定寄存器或RAM并触发该核从warm reset入口开始执行。若代码跳转过去发现入口处没有任何有效指令序列CPU就会取指异常而死而且异常现场在EL3里是静默的非安全世界什么都不显示。排查办法是先在PSCI的cpu_on回调里加日志确认是否进入了BL31、进入之后是否真正执行了目标CPU的上电代码再对照参考平台看warm reset地址设置的具体流程。如果是GIC路由问题内核侧会卡在__cpu_up或普通中断分发处可以把GIC初始化里的中断路由先关闭只保留组0的SGI用来排除外围中断带来的问题。7.3 场景三BL31移入DDR后随机性崩溃把BL31放在DDR会有很多坑功耗管理时DDR要被断电或自刷新如果BL31自己就存在DDR里要么把那段内存处理成永远不掉的保留区要么就要考虑低功耗时BL31的代码和栈的存活问题。我在一个低功耗项目上把BL31放在DDR预留区挂起后DDR进入自刷新那个区域的内容应该还在但因没有做缓存clean唤醒后读到的是脏数据整个BL31直接跳飞。后来在挂起前强制对BL31数据段做clean和invalidate问题才消失。所以只要涉及到suspend内存一致性的处理要特别仔细。类似问题在L1/L2 cache层也会发生dcache_op_all这类操作一定要按ATF接口的要求调用不要自己发明写法。8. 从看懂到能改BSP工程师的ATF能力成长路线8.1 学习材料的选择与阅读顺序阅读ATF源码门槛说高也高说低也低。TSG对工程师最友好的部分是doc目录有非常详细的移植指南、中断管理框架说明、PSCI集成说明。建议从三份文档读起docs/plat/porting-guide.rstATF平台移植指南docs/design/psci-pd-tree.rstPSCI电源域树设计docs/design/trusted-board-boot.rstTBB信任链设计再配合ARM Architecture Reference ManualAArch64里关于异常级别、SCR_EL3、SCTLR_EL3、VBAR_EL3这些寄存器的章节基本就能串起来。直接刷源码的话先看bl31/bl31_main.c和bl31/aarch64/bl31_entrypoint.S理清入口流程之后再看lib/el3_runtime里的上下文切换到底做了什么。8.2 仿真环境与真机调试验证的互补建议先在FVP或QEMU上跑通ATF的最小启动因为在这类虚拟环境里可以用GDB直接看EL3的寄存器状态而且断了也不心疼。QEMU对ATF支持得较早make PLATqemu DEBUG1 bl31基本开箱即用。真机调试的核心工具是JTAG调试器和trace32、openocd这类工具链配合串口日志使用。真机的好处是能验证芯片特有的低功耗、时钟、电源管理行为这些都是仿真环境模拟不了的。实际操作中我会先靠仿真环境理顺“ATF代码逻辑”再在真机上用串口打印验证“SoC行为”。两者互为补充效率最高。8.3 一个独立动手实验的建议如果你想真正练ATF建议不要去改一个特别复杂的量产平台而是自己做一个最小化CPU核的虚拟板卡练习在QEMU里配置一个自定义平台仅包含UART和GIC让BL31跑起来。实现一个简单的SIP SMC接口从内核发起SMC调用在BL31里完成某个自定义功能再把结果返回。尝试打开TBB用cert_create工具生成证书模拟信任链。在BL31里加入一个恶意反例尝试实现某种越权访问然后思考如何通过TrustZone和内存隔离拦下这个行为。这四个步骤做完你对ATF的理解会远远超过身边大多数人。9. 写在最后的一些个人体会做ATF这几年我最大的体会是它不像内核那样有大量文档和社区分享很多东西藏在源码的每个细节和SoC原厂的应用笔记里踩坑的成本极高。但如果耐心把它读透它的学习曲线会比内核更陡收益也更大——理解了ATF你就真正站到了整个系统安全边界的顶点看内核、看Hypervisor、看TEE都会有完全不同的视角。如果你正准备接触ATF建议先从QEMU自带的参考平台开始不要上来就直接上自己的板子。先熟悉构建流程、入口代码、SMC派发路径再针对芯片文档去落实电源管理和安全配置就能少走很多弯路。如果你在移植过程中遇到什么奇怪的问题欢迎把报错贴过来讨论。ATF的资料本身就少大家多交流总比自己一个人对着EL3寄存器发呆要好。——一个在EL3边缘反复横跳过的BSP工程师。