尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深度拆解ARM Trusted Firmware:源码结构、EL3安全机制与平台移植实战

深度拆解ARM Trusted Firmware:源码结构、EL3安全机制与平台移植实战 接手过安全固件或者做过ARM平台启动定制的人大概都有过这种体验面对Arm Trusted FirmwareATF这一坨官方开源工程第一次翻源码时完全不知从哪里下嘴。目录结构几百个文件BL1、BL2、BL31、BL32、BL33满天飞make参数有一大堆平台移植的文档散落在docs目录各处真板子上电后要么卡死要么直接滚回U-Boot里跑裸机。这篇文章不是泛泛的框架介绍我从源码结构和实际移植两个维度做一次深度拆解把ATF的架构全景、安全边界和落地步骤摊开讲尤其是那些文档里不会明说、只有真正改过代码之后才会发现的细节。适合正准备把ATF接到自己板子上的嵌入式工程师也适合想彻底搞明白安全固件启动链路的技术爱好者。先摊一个我的结论ATF本质上不是一个bootloader它是ARMv8架构里EL3世界的地基。它负责两件核心事情一是把系统从冷启动的顶层安全状态一步一步引导到非安全世界的操作系统二是在系统运行期间长驻EL3提供安全服务比如PSCI电源管理、安全世界和普通世界之间的切换、SPM/FF-A等高级特性的基础设施。想真正理解ATF不能只盯着代码做静态分析一定要结合ARM TrustZone硬件模型和SoC的启动ROM行为一起看。下面我按照一条实际踩过坑的路径来讲。1. 为什么设备启动必须先过EL3这一关1.1 ARMv8异常级别和安全态EL3到底管什么理解ATF的前提是先理解ARMv8的异常级别模型。ARMv8把处理器运行特权分为EL0到EL3四个级别EL0是最低权限的应用态EL1是内核态EL2是虚拟化层EL3是最高特权级。与此同时系统还有一个独立的安全态维度安全世界和非安全世界TrustZone技术通过这个隔离让安全侧的内存、外设、中断对普通世界完全不可见。大部分开发者的日常工作是待在EL1和EL0的普通世界做驱动、做应用、做内核定制根本感觉不到EL3的存在。但对于一个完整系统来说EL3是必须的因为ARMv8硬件规定高特权级代码负责向低特权级世界提供服务如果EL3没有可信代码驻留开机之后普通世界就缺少一个可信的依赖根。ATF就是这个EL3世界的主要实现它由ARM官方维护以源码形式开源SoC厂商和ODM厂商在上面做平台适配最终烧进固件镜像里。以往做单片机开发上电就是把向量表、时钟、串口逐个初始化然后跳main函数。到了ARMv8的Application Processor世界启动逻辑变得更严格CPU复位后从ROM里的BootROM或BL0开始执行这段ROM是芯片出厂烧死的不可改写。BootROM会加载BL1BL1经过初步的内存和串口初始化后加载BL2BL2负责加载BL31、BL32和BL33之后BL31接管系统成为EL3常驻固件。整条链路都发生在安全世界里直到BL33才跳转到普通世界的bootloader通常是U-Boot或UEFI。所以ATF的全称虽然叫Trusted Firmware但它的意义远不止安全两个字更准确地说它是整个启动流水线在安全世界里的一段基础管道。没有这条管道系统连真正启动到一个OS的资格都没有。1.2 冷启动与热启动ATF的双重身份ATF需要应对两种启动场景。冷启动即power-on之后的完整流程从BL1依次执行到BL33热启动则是指系统从suspend状态唤醒或者CPU hotplug情况下某个core重新进入启动流程。热启动不必把所有镜像重新加载一遍BL31在冷启动阶段已经把需要的运行环境准备好了唤醒时只需要走一遍PSCI CPU_ON的流程。这两者之间的区别在固件工程上会产生一个很实际的问题代码必须区分自己到底是被冷启动进来的还是热启动进来的。ATF里的bl31_entrypoint会检查reset handler的进入方式对应到平台代码里就需要分别实现plat_setup_psci_ops、plat_setup_boot和plat_setup_cpu这类不同的初始化路径。很多做移植的人第一次改代码时没意识到这个区别把每次启动都当成冷启动处理结果在suspend/resume测试时系统唤醒后各种不稳定。这个坑后面第5节会详细展开。冷启动和热启动对系统资源的要求也不同冷启动需要把DDR、GIC、UART等外设全部初始化热启动假设大部分外设保持原状只需要恢复异常级别状态和缓存配置。场景入口执行内容依赖条件冷启动BL1复位入口加载BL2、初始化内存/外设、分发镜像BootROM已把BL1拷到SRAM热启动BL31唤醒路径恢复上下文、重新启用MMU/缓存冷启动已完成BL31初始化1.3 没有ATF的裸板启动问题出在哪抛开安全不说如果坚持不用ATF直接在BootROM之后把U-Boot加载到EL3执行有没有可能把系统跑起来能但代价非常大。首先U-Boot本身没有为EL3做运行时服务支持即使能起来也无法提供标准PSCI接口来管理CPU的电源状态。今天的Linux内核在ARMv8平台上绕过PSCI基本没法用因为cpu_ops里的psci是内核做cpu hotplug和suspend的标准依赖。其次没有ATF就意味着没有安全世界和普通世界的隔离机制。TrustZone硬件本身是存在的但没人帮内核完成安全侧的状态配置、安全中断的路由和Secure Monitor调用SMC的默认处理。普通世界一旦试图调用SMC指令如果没有EL3的安全监控器兜底系统会直接触发未定义指令异常。也就是说跑得起来也是瘸腿的随时可能崩。从这个角度ATF不是可选项而是嵌入式Linux系统在ARMv8平台上的基础设施类似于x86平台上的SMM固件只是它更开放、可以自己改、自己移植。2. 源码级拆解BL1/BL2/BL31在干什么2.1 三个启动阶段各自的职责边界ATF代码从功能上分为四个镜像BL1、BL2、BL31以及可选的BL32。BL1是启动ROM阶段之后的第一段可信代码体积最小通常放在SoC内部的SRAM里运行职责是把BL2从存储介质加载到SRAM然后跳转。BL1没有DDR的依赖因为DDR控制器可能还没初始化完它只操作SRAM和芯片基础寄存器。BL2是二级启动加载器此时DDR已经可用它负责从flash/eMMC/NOR等存储设备里把BL31、BL32、BL33镜像加载到内存指定位置。关键点是BL2要做信任链校验也就是TBBRTrusted Board Boot Requirements利用证书和验签机制确认每个被加载镜像的来源可信。BL2在完成所有加载和验证后会跳转到BL31自己退出历史舞台。BL31是EL3 Runtime Firmware这是ATF里最复杂的组件。它从BL2接手EL3世界完成GIC、串口、MMU、运行时服务注册、中断路由等全套初始化然后常驻EL3等待普通世界通过SMC指令请求调用安全服务。BL31还负责在普通世界内核和secure世界之间完成上下文切换也就是world switch。这个切换不是简单的跳转因为它要保存和恢复包括CNT_CTL_EL0、VPIDR_EL2、SCR_EL3在内的一批系统寄存器一个地方出错整个系统就崩。镜像运行位置生命周期核心职责BL1SRAM启动早期用完即走从BootROM接手加载并验签BL2BL2SRAM或DDR启动中期功能完成后释放加载BL31/BL32/BL33传递平台参数BL31DDR或SRAM平台相关常驻EL3提供PSCI、SMC服务、世界切换BL32DDR安全区常驻安全EL1运行可信OS如OP-TEEBL33DDR普通区常驻普通EL2/EL1U-Boot/UEFI最终启动内核2.2 BL32与BL33可选的安全世界OS和你的BootloaderBL32是一个可选组件它运行在安全世界EL1通常是OP-TEE这样的可信执行环境OS。如果你的产品不需要TEE功能可以直接把BL32裁掉ATF只维护BL31和BL33之间的切换。很多评估板的默认配置是不带BL32的因为每加一个安全组件就多一层攻击面和适配成本。BL33就是普通世界的第一个程序绝大多数情况下是U-Boot。这里有个理解关键点U-Boot并非运行在EL3而是在BL31把它前置到普通世界EL2之后才开始的。也就是说ATF的BL31在跳转到U-Boot之前已经把SCR_EL3设置成non-secure状态通过eret指令实现异常返回式跳转。所以U-Boot在运行过程中实际上完全处于普通世界它看到的是一套已经被ATF管好的系统。这些组件之间的衔接通过bl_params结构体完成。BL2在加载完所有镜像之后会构建一个bl_params链表里面包含每个镜像的入口地址、内存布局、ep_info和image_info。BL31启动时解析这个链表找到BL33的入口在完成EL3运行时初始化后跳过去。这个参数传递机制是ATF平台移植里的核心环节改动任何一个镜像的加载地址都要同步更新bl_params。2.3 PSCI与运行时服务为什么一定要放在EL3运行时服务Runtime Services是ATF长驻阶段的主要工作。PSCIPower State Coordination Interface是其中最重要的服务负责CPU电源管理、系统suspend/resume、关机重启等操作。内核执行cpu_off、cpu_on、system_off时会通过SMC指令陷入EL3由BL31里的PSCI实现处理。之所以把PSCI放在EL3而不是EL1或EL2是因为CPU电源管理涉及电平和时钟域的物理控制这些寄存器往往只对最高特权级开放且部分操作需要在所有core之间的同步放在EL3可以统一仲裁。还有一层原因是PSCI标准要求由可信固件实现避免普通世界内核直接被篡改电源状态。安全固件工程的核心价值也在这里电源管理、系统重启这类关键控制指令都集中在一个受信的地方处理。除了PSCIATF还预置了其他运行时服务框架比如SDEISoftware Delegated Exception Interface、FF-A、SPM等。插件式架构通过DECLARE_RT_SVC宏把服务注册到EL3的运行时服务表中普通世界通过SMC function id来路由到不同服务。这个设计让新服务扩展变得很轻松不需要改动BL31主体代码这也是后来FF-A等新标准能快速叠加到ATF上的原因。3. 工程审计视角结合代码仓库和编译链路看可信边界3.1 源码结构与关键目录ATF的源码工程布局比较规整顶层目录按功能模块划分做平台移植时主要关心的是plat和drivers这两个目录做安全审计时主要看lib和services。目录内容移植时需要关注的程度bl1/bl2/bl31/bl32各启动阶段主体代码一般不动plat各SoC平台适配代码核心必须改drivers外设驱动串口、GIC、定时器等按需改lib通用库MMU、cache、el3运行时一般不动servicesPSCI、SDEI、SPM等服务实现按需配置tools/fiptoolFIP镜像打包工具直接使用docs设计文档和移植指南强烈建议读arm/board目录下是ARM官方评估板的完整参考实现包括FVP、Juno、SGI等平台这些代码质量很高是移植到自研板最好的起点。我的建议是先评估你的SoC跟哪块官方板最接近比如都是Cortex-A53四核就可以把qemu或fvp的platform代码拷贝过来改比从零写平快得多。3.2 从Reset到BL31 Entrypoint的代码路径审计做源码评测不能只看结构得跟着执行路径走一遍。以比较典型的AArch64冷启动为例简化后的调用路径是这个样子CPU复位后BootROM跳转到bl31_entrypoint不对冷启动第一个ATF代码是bl1_entrypoint在bl1/aarch64/bl1_entrypoint.S里。这段汇编完成最早的异常向量建立、CPU核启动状态检测、清零BSS段。BL1随后执行bl1_main里面调bl1_early_setup初始化串口和基础外设再调bl1_plat_setup完成平台内存检测最后通过load_auth_image把BL2从BootROM指定的设备里加载出来。BL2的入口是bl2_entrypoint对应bl2/aarch64/bl2_entrypoint.S它会重建MMU和缓存然后调bl2_main。bl2_main里调bl2_plat_preload_setup、load_and_auth_bl31、load_and_auth_bl32如果存在、load_and_auth_bl33最后syncing_all_cachesbl2_run_next_image跳转。BL31的入口是bl31_entrypoint这是整个ATF里调试价值最高的函数。它会依序初始化bl31_early_platform_setup、bl31_plat_arch_setup、bl31_platform_setup然后注册运行时服务bl31_main最后调bl31_prepare_next_image_entry跳转BL33。这里每一步对应源码里搜索函数名都能找到具体实现。审计时有个技巧值得分享抓关键词——_setup函数都是平台相关初始化_run_next_image都是镜像跳转_runtime_setup都是EL3长驻前的最终配置。顺着这三个词代码路径就清晰了。3.3 编译产物、FIP打包与固件可信边界评价编译ATF产出的是各个BL的elf、bin以及用fiptool工具打包出来的fip.bin。FIP的全称是Firmware Image Package本质上是把多个镜像拼成一个容器带一个TOCTable of Contents来描述每个镜像的偏移、大小和校验信息。BootROM的BL1在加载BL2时需要知道BL2放在哪里FIP恰恰提供了这种自描述能力用户不用在固定偏移上死记硬背每个镜像的位置。从安全审计的角度看ATF的可信边界要做到信任根可追溯。BL1被BootROM通过OTP或eFuse里的根密钥验证BL2被BL1验证BL31/BL32/BL33被BL2验证每一级都有自己的证书格式和验签密钥。要把这条链真正落地还得在平台代码里实现plat_get_rotpk_info等函数把公钥哈希烧写到芯片的一次性可编程区域。很多产品只把ATF跑起来但没做验签这是个很常见的工程妥协能不能接受取决于你的威胁模型。用危言耸听一点的方式说如果BL2不去验签BL33未来任何攻击者只要改一下flash上U-Boot的二进制系统就直接被劫持了安全固件相当于白做。4. 平台移植落地在真实板卡上跑通ATF的关键动作4.1 最小移植闭环半小时理清platform目录和Makefile平台移植的第一步不是写代码而是复制正确的参考平台。比较合适的路径是先把plat/arm/board/fvp整个copy成plat/mycompany/myboard然后全局替换平台前缀FVP为MYBOARD把平台名从fvp改成myboard再照着以下三个文件逐个改platform_def.h定义内存地址、UART基址、IRQ安全路由、栈大小、镜像加载地址。这个头文件是移植的关键任何地址没对上系统都会在某个神秘节点死掉。platform.mk声明平台源文件、编译flag、需要链接的库。新增自定义驱动时就是在这里加路径。plat_setup.c / plat_topology.c实现plat_get_next_bl_params、plat_get_syscnt_freq、plat_setup_topology等平台回调函数。编译时最关键的参数组合是这样make PLATmyboard ARCHaarch64 CROSS_COMPILEaarch64-linux-gnu- LOG_LEVEL40 DEBUG1 bl31LOG_LEVEL40对应LOG_LEVEL_VERBOSE可以把LOG打印调到最大移植初期务必开verbose不然出现问题后信息量太少根本没法定位。DEBUG1会保留符号信息并把优化等级调低gdb调试也依赖它。4.2 交叉编译环境搭建一套脚本搞定ATF本身不挑编译系统Linux、macOS都能编。但如果你需要把ATF和配套的U-Boot、内核、rootfs一起做成一个完整镜像烧给目标板那建议在同一个Linux环境里统一管理避免工具链碎片化。以我常用的环境为例在Ubuntu或国产Linux发行版aarch64环境上先装基础工具链sudo apt-get install gcc-aarch64-linux-gnu build-essential git make如果平台代码里用了固定的编译选项比如arm compiler 5系列的某些语法风格就需要额外调整但ATF官方代码整体是标准的GCC-friendly没有这类依赖。国内做信创项目时还会遇到在银河麒麟v10 for aarch64主机上直接交叉编译ATF的场景此时不需要额外下载编译器系统自带的gcc-aarch64-linux-gnu直接就能用脚本也没什么特殊的。下面是我用的一套参考变量配置export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu- export PLATmyboard export FIP_ALIGNMENT16把这些写进setenv.sh以后每次开终端source一下就能直接进make环节不用每次敲一长串参数。另外ATF的make系统支持在命令行里覆盖平台宏比如要临时改UART基址可以用make PLATmyboard MYBOARD_UART_BASE0x10009000 bl31这样避免频繁改头文件重编调试早期特别有用。4.3 FIP打包、烧录和启动验证编译完成之后产物里有个build/myboard/release/fip.bin。如果make目标只是bl31不会自动打包FIP需要显式执行make PLATmyboard BL33../u-boot/u-boot.bin BL32../optee/os.bin fip把U-Boot的bin和OP-TEE的bin通过BL33和BL32变量传给fiptool不加BL32就是纯ATFU-Boot的最小组合。FIP生成之后还需要把它跟BL1一起放到启动介质里。不同SoC的启动介质不同有的烧SD卡固定扇区有的用烧录器直接烧NOR flash这一步跟你的BootROM引导方案强相关需要对照芯片手册确认。验证启动是否成功最直接的方式就是串口输出。正常的ATF日志会依次出现NOTICE: BL1: v2.8(release):myboard NOTICE: BL1: Built : ... INFO: BL2: v2.8(release):myboard INFO: BL2: Loading image 3 INFO: BL31: v2.8(release):myboard INFO: BL31: Entry point address 0x... INFO: BL33: loading image 5看到BL33: loading image 5说明ATF已经把U-Boot加载完接下来会跳到普通世界。如果打印停在这之前那就要按第5节的排查链路去定位。4.4 内存布局与地址对齐最容易埋雷的地方我移植ATF时吃过大亏的一个点是内存布局。ATF每个镜像有自己独立的加载地址和运行地址定义在platform_def.h里。比如BL31的基址如果跟DDR里已有数据冲突启动时会静默覆盖造成后患。建议从一开始就把ATF镜像安排到DDR的末尾区域把前面完整留给OS。另外FIP镜像对齐也常出问题。fiptool默认的4KB对齐通常够用但如果你从SD卡或eMMC加载镜像BootROM可能要求更严的对齐比如64KB这时需要用make PLATmyboard FIP_ALIGNMENT65536 fip把FIP对齐调整到64KB否则烧录后的镜像可能在部分SoC上无法引导。这属于典型的经验文档里不写栽了跟头才知道的细节。5. 调试实录信任固件起不来时的完整排查链路5.1 串口无输出先从这条链路查移植初期最常见的故障是上电后串口一片死寂。这时候不要急着怀疑BootROM烧坏了按下面这个顺序排查确认BootROM是否真的加载了BL1。检查芯片的启动模式引脚确认是从SD卡/flash/eMMC的正确路径启动。确认BL1的串口驱动用对了基址。ATF的串口驱动要求平台在platform_def.h里定义PLAT_UART_BASE这个地址必须严格匹配SoC的UART0寄存器基址错一个字节都不行。这块是ATF移植最典型的翻车点。确认波特率设置。ATF默认写死PLAT_CLOCK_IN_HZ和PLAT_UART_CLOCK_IN_HZ这两个时钟值如果时钟计算错误打印出来是乱码。此时把BAUD_RATE改到115200后再看。用JTAG调试器或逻辑分析仪看BL1是否真的执行了串口初始化指令。如果连这一步都没到检查复位向量和BL1_RW_BASE地址是否一致。一个非常容易误导人的现象是BootROM已经加载了BL1但因为ATF的日志等级默认是LOG_LEVEL_NOTICE很多INFO级别的输出本来就不会打印。所以一开始就把LOG_LEVEL40开满可以减少大量无效怀疑。5.2 BL31 Panic排查MMU、中断控制器与内存冲突如果串口打印已经能出现BL31的信息但紧接着出现类似PANIC at PC : 0x000000000000xxxx这种panic和裸机开发的死机不一样它往往是ELF3内部的数据异常或同步异常。最常见的原因有三个第一个是MMU配置错误。BL31会建立自己的页表把设备内存映射为device-nGnRnE属性、普通内存映射为normal memory属性如果某个外设的寄存器地址没有在plat_get_mmap里声明BL31一旦访问它就会触发Permission fault。排查方法是打开DEBUG1编译然后用addr2line把PC换算成源码行号aarch64-linux-gnu-addr2line -e build/myboard/debug/bl31/bl31.elf 0x000000000000xxxx翻译出的源码位置基本能直接定位到是哪个平台的mmap函数漏了条目。第二个是GIC配置不当。BL31需要完成GIC的初始化包括安全中断路由和优先级设置。在自研板上GIC基址可能和官方评估板不同如果PLAT_GIC_BASE没改对BL31在处理第一个中断时就会panic。建议初期先关掉GIC支持等串口PSCI通了再逐步打开。这点做的越晚排查越难。第三个是内存布局冲突。BL31运行时的栈和堆以及它保护的镜像存放区域如果跟U-Boot或内核的内存重叠系统可能在频率很低的负载下就随机panic。检查方法是在platform_def.h里把PLAT_LAYOUT_MARSHALLING相关的区域规划好后用grep确认每个镜像地址互不冲突。5.3 QEMU仿真三板斧在PC上提前练手很多细节其实没必要在真实板子上来回烧录验证用QEMU先把流程跑通效率更高。ATF官方支持QEMU平台用它启动的命令是qemu-system-aarch64 -machine virt -cpu cortex-a53 -M 512 -nographic \ -bios build/qemu/release/bl1.bin \ -d guest_errors这里-bios直接指定BL1QEMU模拟BootROM加载BL1的过程ATF代码本身看不出区别。如果想调试BL31加上-s -S参数配合GDB就能实现源码级调试。QEMU的virt平台跟真实SoC有差异不能完全替代真板验证但在移植起步阶段用来验证代码逻辑、踩通编译链路已经绰绰有余。如果要在QEMU上完整验证FIP启动用qemu-system-aarch64 -machine virt -cpu cortex-a53 -nographic \ -bios bl1.bin -d fip.binQEMU会从-d fip.bin指定的文件里读取FIP并加载BL2后续镜像整个过程和真实SoC的BootROM行为高度相似。我现在做新平台适配第一版通常都是先在QEMU上把整个FIP启动链路跑通再落到真实板卡对接BootROM细节。5.4 常见故障速查表现象常见原因排查手段解决方向串口完全无输出BootROM启动源配置错误或UART基址错误用JTAG确认PC值检查启动引脚和PLAT_UART_BASE串口输出乱码时钟频率参数错误核对SoC手册中的UART时钟源调整PLAT_CLOCK_IN_HZBL1进度卡住不打印BL2BL2镜像不在FIP预期位置用fiptool查看FIP TOC结构重新make fip并确认烧录偏移BL2加载BL33时报签名错误BL33没做签名或验签公钥不匹配关闭TBBR验证先跑通流程配置TRUSTED_BOARD_BOOT0或正确生成证书BL31进入后系统随机panicGIC基址错误或内存布局冲突addr2line定位PC修正PLAT_GIC_BASE和内存区域定义热启动或suspend/resume后挂死PSCI ops里热启动路径初始化缺失对比冷启动和热启动分支代码完善plat_setup_psci_ops中CPU_ON/OFF回调6. 移植时容易被低估的典型场景最后聊几个我实际做平台移植时花时间最多的场景属于网上资料少、但真项目一定会遇到的类型。第一个是异构核系统。比如一个大核集群加一个小核集群的SoC不同core的启动方式不一样。ATF的bl31会为每个cluster建立不同的拓扑结构体现在plat_core_pos_by_mpidr这类函数上。很多刚开始接触这种SoC的人会把所有核的cpu_ops配成一样结果小核启动时行为就异常。建议先把plat_topology.c里的core位置映射写得足够清楚再往后走。第二个是安全启动集成。ATF的TBBR验签不能只靠ATF自身还需要配套的证书生成工具链。典型做法是使用tools/cert_create工具生成证书链并配置TRUSTED_BOARD_BOOT1。做这块时一定要提前把Decryption Key、Signing Key的保管方案定下来不然后期固件升级时会发现密钥管理一团乱。工程上我见过不少项目初始化固件阶段图省事不开TBBR最后产品要过安全认证时才开始补结果改动量巨大得不偿失。第三个是SPM/FF-A相关的能力预留。如果你预判未来产品会用到安全分区管理或者虚拟化安全扩展那么在最早移植ATF时就把ARM_SPMC_MANIFEST_DTS这些配置预留好哪怕当前版本不启用也至少编译通过。避免未来升级大版本时兼容性问题叠加那时候再来解耦工作量会成倍增长。另一个容易被忽略的细节是运行时服务的缓存维护。BL31在普通世界和安全世界之间切换时需要确保没有脏数据残留在缓存里把安全数据泄露给普通世界。ATF对此有完整的el3_exit路径处理在bl31_context_mgmt.c里能看到非常细致的cm_setup_context和cm_el1_sysregs_context_save/restore逻辑。做安全审计时要重点看这块是否有裁剪或遗漏平台自定义代码里如果临时禁了缓存维护就是一个极其危险的后门。以我个人的体会做ATF移植最忌讳的就是只在文档层面理解不真正跑一块板子或者反过来不看源码全靠逐字段试错。正确节奏是多花时间在阅读plat/arm/board下的参考实现再对照自己板子改。第一次上板不追求一次成功把每次失败当成一次源码审计的机会把panic地址转换为源码位置的过程走顺了后续就快了。改完启动之后养成保留一份最小可复现的配置组合的习惯比如一个纯净的最小ATFU-Boot往后排查问题时你会感谢自己当初没图省事把QEMU环境扔掉。
返回列表