1. 先别急着跑代码,ATF到底是什么
做ARM嵌入式底层开发的人,早晚会撞上Arm-Trusted-Firmware(ATF)这个名字。如果你是从裸机或者只跑Linux内核的层面切入,第一次看到“EL3”、“Secure Monitor”、“PSCI”这些术语时,大概率会有点懵。网上搜ATF,出来的资料要么是官方文档那种标准得让人打瞌睡的叙述,要么是零散的移植笔记,很少有人把它的架构逻辑、源码组织、安全链路、平台移植放在一起讲清楚。
这篇文章是我自己从“能用”到“敢改”再到“敢审计”的一段总结。我不会只贴代码注释,也不会通篇翻译文档,而是按我实际看代码、改板子、调启动的顺序来走一遍。读完你应该能回答几个关键问题:ATF在整个ARM系统里到底站在什么位置?BL1、BL2、BL31、BL32、BL33这一串编号分别干什么?项目里要把一个新CPU平台支持起来,最小改动点在哪里?以及,从安全工程审计的角度看,哪些地方是必须较真的。
适合谁看?三种人最合适:一是做BSP或系统启动开发的工程师,马上要接触ATF或已经调得焦头烂额;二是做安全评估、固件审计的朋友,想从源码层面搞清信任链的构造;三是刚入行的嵌入式学习者,被各种术语绕晕,需要有人用大白话把框架捋直。建议至少有一点ARM64基础,比如了解异常级别和TrustZone,没有的话也能看懂大半,遇到涉及指令集的细节我会给简单解释。
2. 全局架构全景:从复位向量到操作系统的“接力赛”
2.1 异常级别与TrustZone:EL3为什么是“管理员房间”
ATF解决的问题,根源在ARM架构对安全和非安全世界的硬隔离设计。ARMv8-A把执行环境按异常级别(Exception Level)从高到低分成了EL3、EL2、EL1、EL0。EL0是普通应用,EL1是操作系统内核,EL2是虚拟化层,EL3是所有级别中权限最高的,直接管辖TrustZone的状态切换。
如果用一个不太严谨但很好理解的比喻:EL3相当于整栋楼的管理员房间,不仅要管自家(安全世界)的设施,还能决定楼道里哪些门能开、哪些门不能开。普通操作系统运行在EL1,它想请求安全服务(比如读取安全存储、校验签名镜像),不能直接碰安全硬件,只能通过一条特殊的指令SMC陷入EL3,由EL3里的Secure Monitor代码代为执行。这就是ATF的核心舞台:它常驻EL3,既负责引导,也负责运行时安全服务。
很多新手最初困惑的一点是:ATF里的“世界”概念。TrustZone把整个系统逻辑上切成安全世界(Secure World)和非安全世界(Normal World)。安全世界可以访问所有物理地址空间,非安全世界则要受限于安全地址空间隔离。EL3处于世界切换的枢纽位置,任何从Non-secure到Secure的跳转,都必然经过EL3的上下文切换。
2.2 BL1、BL2、BL31、BL32、BL33:五个阶段的职责划分
理解ATF最简单的方式,是把整个启动过程看成一棒接一棒的接力赛。每个“BL”是某个启动阶段(Boot Loader stage)的缩写,它们的职责边界非常清晰。
- BL1(Boot ROM阶段):芯片上电后,固化在片上ROM里的代码。这个阶段能做的事情极少,因为此时DRAM还没初始化,可用的RAM通常只有SRAM或cache-as-RAM。BL1的使命非常简单:初始化少量时钟和存储,把BL2从Boot Media(通常是FIP包)中加载到安全RAM里,并完成对BL2镜像的认证,然后跳转过去。BL1自身不销毁,它的代码会在后续阶段继续驻留,用于升级或回退路径。
- BL2(可信引导加载器):运行在EL1 Secure世界,负责更完整的平台初始化,主要是内存控制器、串口、定时器等基础外设,然后从FIP里加载BL31、BL32(可选)、BL33(通常是U-Boot或UEFI)等镜像,逐个做签名验证,再跳转。它相当于一个“安全检查站”,所有后续镜像都由它验证过才放行。
- BL31(EL3运行时固件):跳入BL31后,CPU进入EL3并常驻在此。BL31不再是临时的“引导代码”,而是一个小型运行时系统,提供Secure Monitor服务、PSCI电源管理、SMC分发、安全中断处理等。操作系统启动后,底层CPU开关、核心上下电、系统重启这些操作,都要通过PSCI协议请求BL31来完成。
- BL32(可信操作系统):可选项,比如OP-TEE、Trusty等。它运行在EL1 Secure世界,提供REE(Rich Execution Environment)侧请求的加解密、安全存储、指纹等业务。
- BL33(非安全世界引导):一般就是U-Boot或者UEFI。它运行在Non-secure EL2或EL1,继续初始化Linux需要的环境,最终把内核加载起来。
这几者的依赖关系是:BL1 → BL2 → BL31 →(BL32并行)→ BL33 → 内核。注意BL31不是“引导完就退役”,而是会一直活到系统关机。
2.3 FIP镜像与FDT:整个启动链路的“物流单”
BL1、BL2、BL31、BL32、BL33这些镜像不是散落在存储介质上的,而是被打包进了一个叫FIP(Firmware Image Package)的文件里。FIP里带有一张镜像表,BL2解析这张表,按顺序读取并认证每一个镜像。
FIP的结构可以用一句话概括:文件头加上一堆带UUID的镜像块。每个子镜像有唯一的UUID标识,比如BL31有固定UUID,U-Boot有另一个UUID。BL2拿到FIP后,逐个镜像校验,校验通过的加载到指定内存,最后按依赖顺序启动。这里有个很实际的问题:当你改了U-Boot,只需要重新打包FIP,而不需要把BL1、BL2全部重新烧一遍。所以实际产品里最常见的部署方式是把BL1放到ROM里不动,BL2和FIP放在可升级的存储分区中,日常发布只更新FIP。
ARM Linux生态里还有一种数据载体叫FDT(Flattened Device Tree),在ATF里通常作为平台参数传给BL33。ATF在跳转BL33前,会把一些平台信息(比如电源管理状态、启动原因)填充到设备树里,U-Boot或内核通过它了解固件层的服务能力。很多人忽略这一点,结果移植新板子时发现U-Boot能起来但拿不到正确的唤醒源信息,问题往往就出在这。
3. 源码工程审计:从目录结构读到ATF的设计哲学
3.1 顶层目录到底在告诉你什么
拿到ATF源码,第一件事不是急着找main函数,而是先大致扫一遍目录。ATF的代码组织延续了ARM开源项目的风格,一眼看过去非常清楚:
bl1/、bl2/、bl31/:各个启动阶段的入口和主流程,跨平台代码都在这。plat/:平台相关代码,这是移植的重点。里面按厂商或开发板划分,比如arm/、allwinner/、rockchip/等。drivers/:各种外设驱动,包括GIC、PL011串口、DP、MTD等,按功能模块划分。lib/:通用库,包括自旋锁、延迟、解析器、崩溃上报等。include/:头文件,其中include/lib/下有很多架构抽象定义。tools/:编译辅助工具,比如FIP打包工具、证书生成工具。docs/:官方设计文档,移植前建议至少阅读plat-porting-guide.rst和firmware-design.rst。
一个很容易踩的坑是:很多人觉得ATF既然是“固件”,应该像MCU固件那样整个工程是一个整体。实际上ATF的编译会分别为BL1、BL2、BL31生成独立的二进制,通过tools/fiptool打包到一个FIP镜像中。所以你在Makefile层面可以看到,平台只需要配置BL_SOURCES_PLAT、BL2_SOURCES_PLAT、BL31_SOURCES_PLAT这些变量,编译系统会自动把各个阶段的源文件组合起来。
3.2 启动流程主线:从reset_handler到bl31_main
ATF的入口不叫main,而是reset_handler。因为上电时CPU可能是从BL1、也可能是从直接跳到BL2的复位地址开始的(在调试模式或某些仿真场景下可自定义冷/热启动入口)。reset_handler里会完成CPU最底层的初始化,比如异常向量表设置、MMU配置、栈空间准备,这部分代码主要写在bl1/aarch64/bl1_entrypoint.S和bl31/aarch64/bl31_entrypoint.S之类的汇编文件里。
理解这段汇编的关键是要分清Cold boot和Warm boot:冷启动指整个系统从掉电状态恢复,需要重新初始化DRAM、加载镜像;热启动指单个CPU核心从低功耗状态唤醒,此时大部分系统状态还在。BL1只在冷启动时执行,BL31则要同时处理两种场景。
BL31的C语言主入口在bl31/bl31_main.c的bl31_main()函数。它的主要工作非常清晰:先初始化运行时的服务框架(runtime_svc_init)、配置PSCI、注册SMC处理函数,最后调用bl31_prepare_next_image_entry()准备跳转到BL33。这里的设计很精妙:BL31把自身变成一个持续驻留EL3的“微型内核”,而把控制权“借”给BL33去跑操作系统。
3.3 关键基础设施:自旋锁、运行上下文、控制台与内存管理
ATF自己有一套并发原语,最常见的spinlock在lib/locks/下。因为多核CPU在启动时可能同时进入BL31,如果不加锁去操作共享数据结构(比如回收队列、CPU状态表),很容易出现竞态。别以为固件里面的并发问题少,实际上CPU热插拔、suspend/resume时多个核心同时请求PSCI服务是常态,锁用不好就会出现莫名其妙挂死。
运行上下文(cpu_context)是另一个核心结构。每次世界切换发生时,BL31要把当前CPU的通用寄存器、系统寄存器、栈指针等保存到对应的cpu_context里,再恢复目标的上下文。这部分代码在上下文切换性能上极讲究,因为Linux的每个系统调用里如果频繁快速切换,固件层的开销会直接影响业务响应。
控制台(console)虽然看似不起眼,但在调试时是生死攸关的。ATF的控制台驱动设计成了分层:console_putc、console_getc、console_flush,平台只需注册一个控制台实例。需要注意的是不同阶段(BL1、BL2、BL31)可能各自要用不同的串口或相同的串口,需要在平台代码里正确初始化时钟和引脚,否则就是最经典的“完全没输出”问题。
内存管理上,ATF本身不跑复杂虚拟内存,MMU配置也相对静态,但需要严格划分“安全DRAM”和“非安全DRAM”。很多平台用TZASC(TrustZone Address Space Controller)硬件把一部分DRAM标记为安全,BL31、BL32的代码和数据就驻留在安全DRAM中,普通操作系统永远不能直接访问,从而保证固件不被用户态篡改。
4. 安全引导链路分析:信任是一环一环传递过来的
4.1 链式验证:从“能跑”到“敢信”
ATF默认的安全启动模型是“链式信任”(Chain of Trust)。每级镜像启动前都要验证下一级镜像的签名。BL1由芯片厂家固化在ROM里,它的公钥Hash存储在OTP(一次性可编程存储)中;BL2镜像由BL1用该公钥验签;BL2再持有可信的公钥列表,去验证BL31、BL32、BL33的签名。
这个设计有个很关键的取舍:一级的公钥被烧死在OTP,出厂后不可改;如果未来要更换密钥,就需要提供密钥更新机制(KUP)。ATF的认证框架在drivers/auth/下实现了完整的RSA/ECDSA验签、哈希比对、证书链解析。实际生产环境里通常是建立CA证书体系,ATF端只保存根CA公钥,每次镜像对应唯一证书。
很多工程团队在调试阶段会关闭签名验证,比如把TRUSTED_BOARD_BOOT置为0,编译出不带验签的版本。这方便,但千万不要把这个版本直接发布到量产设备上。我见过某些消费类设备出厂固件就是关掉验证的,结果攻击者篡改FIP里的U-Boot就能完全控制设备,代价非常大。调试用的版本和发布用的版本应当通过构建配置分开管理,最好不要在同一个FIP里混用发布版BL1和调试版BL2。
4.2 KEY、证书与打包:FIP的生成链路
ATF的构建系统沉淀了一整套工具链。通常你会在编译时生成一个_pki目录,里面有若干OpenSSL配置文件,用它们生成根CA证书、镜像内容证书,最后用cert_create工具签名。对初学者来说不用自己从头写证书脚本,官方提供的tools/cert_create已经能覆盖大部分需求。
实际操作中,构建一个带验签FIP的大致流程是:
- 生成私钥和证书:
cert_create -r -o ...等命令生成各级证书。 - 把BL2、BL31、BL33等镜像通过
fiptool create放入FIP,同时把对应的证书也塞进FIP。 - 把BL1和BL2的bin文件烧到Boot Media,BL2会在启动时自己找到并读取FIP。
这里容易踩的一个坑是BL1本身是ROM里固定的,BL1验签BL2时使用的公钥不能比BL1里烧录的公钥更靠后。换句话说,如果BL1不支持某个算法,BL2就不能用那个算法签名。所以在选择密码算法套件时,必须考虑芯片BootROM的能力。如果BL1是ARM参考实现,通常是支持RSA和ECDSA的,但具体哪种曲线、密钥长度,要和芯片原厂确认。
4.3 安全中断与SMC分发:BL31的运行时防护
启动完成后,BL31最重要的职能就是SMC分发。Linux内核的smc调用会携带一个function ID,ATF的运行时服务框架根据这个ID分发给对应服务。比如ARM_SIP_SVC、ARM_OPTEE_SVC,各服务分别注册自己的handler,函数签名基本固定:
uintptr_t handle_svc(uint32_t smc_fid, u_register_t x1, u_register_t x2, u_register_t x3, u_register_t x4, void *cookie, void *handle, uint64_t flags);关于安全中断,要特别留意GIC配置。BL31运行时会把安全中断(比如来自安全定时器、安全存储的中断)路由到EL3,保证即使非安全世界死机了,安全世界的中断处理也能正常进行。这一块是工程审计时最容易被忽略的点,很多人调好启动就不管GIC了,结果系统休眠唤醒时中断路由一团糟。
5. 平台移植落地指南:把我的代码跑在目标板卡上
5.1 移植前先想清楚三个问题
平台移植看似复杂,核心其实就是让ATF认识你的芯片和开发板。动手写代码之前,想清楚三件事:你的Boot Media是什么,存放FIP的位置和读取方式;你的内存格局,有多少安全DRAM、多少非安全DRAM;你的启动目标是谁,BL33是U-Boot还是其他固件。
ATF提供了多个参考平台,建议从一个相近的参考平台复制出你自己的目录。比如你的SoC和QEMU的ARM版很像,就先从plat/arm/board/fvp或者plat/qemu/复制,再逐步替换成目标芯片的配置。不要从零开始写平台代码,那是纯浪费时间。
5.2 搭建最小平台目录
每个平台目录至少要包含几个核心文件:
platform.mk:定义平台相关源文件、编译器标志、镜像链接地址。plat_setup.c:早期初始化,比如串口、定时器、GIC、内存区划分。plat_pm.c:PSCI相关回调(电源管理)。plat_topology.c:描述CPU核心列表和电源域,用于PSCI和多核启动。include/platform_def.h:定义地址常量,如安全RAM基地址、UART基地址、中断号。
platform_def.h是移植时最重要的头文件。每个宏都有讲究,比如PLAT_PHY_ADDR_SPACE_SIZE决定了地址翻译范围,MAX_MMAP_REGIONS决定MMU映射表大小,PLAT_MAX_PWR_LVL决定PSCI电源域深度,设少了之后想支持CPU suspend就会发现接口报错。我在一次移植里吃过亏:把PLAT_MAX_PWR_LVL设成了2,想支持core、cluster、system三档电源管理时,PSCI代码直接断言失败,改回3才正常。
5.3 最小可启动配置的搭建步骤
为了让你看得更明白,我把最简启动路径写成一个操作步骤列表。这是一个典型的最小配置,不是通用模板,但按这个思路去填平台目录一定对:
- 在
plat/vendor/board/下创建目录,例如plat/myvendor/myboard/。 - 从
plat/arm/board/fvp/复制platform.mk、plat_setup.c、plat_pm.c等文件,先保留FVP的参考实现。 - 修改
include/platform_def.h,替换成目标芯片的UART、GIC、中断号和内存地址。 - 在
platform.mk里把BL31_SOURCES_PLAT指向你的plat_setup、plat_pm等源文件。 - 编译,确认至少BL31能打印日志。
- 断点调试:在
bl31_main()里打印CPU号,确认多核启动。 - 接入U-Boot,调整BL33加载地址,确保ATF跳转后U-Boot能运行。
- 最后做安全特性:开Trusted Boot、开安全中断、设置TZASC。
每步之间都应该有明确的验证点。千万别把串口、GIC、PSCI、TZASC所有这些改动一次性合入,例程里任何一步出问题,你都很难定位是哪个环节的锅。
5.4 电源管理:PSCI回调梳理
PSCI是ATF与操作系统之间的电源管理协议。Linux内核在cpuidle、CPU hotplug、suspend/resume时会调用PSCI服务,最终执行到BL31里平台注册的plat_psci_ops。该结构体包含cpu_standby、cpu_on、cpu_off、cpu_suspend、system_off、system_reset等回调。
回调实现中要特别注意CPU的上下文保存与恢复。以cpu_on为例,BL31要把被唤醒CPU的上下文从cpu_context中恢复,设置好入口地址再跳过去。很多平台在调suspend时,会发现唤醒后系统日志乱掉,或者卡在同步原语,这通常就是上下文保存不完整,比如漏了CNTKCTL_EL1、CONTEXTIDR_EL1这类寄存器导致的。
PSCI还有一个容易忽略的affinity info查询功能,Linux用它在启动阶段探测CPU拓扑。如果你的plat_topology.c里描述的拓扑与实际硬件不一致,比如把cluster数写错,那操作系统可能只枚举出一部分核心。
5.5 内存布局与固件栈设计:把每一块RAM说清楚
内存布局是整个ATF移植最容易翻车的地方。ATF自身运行的安全RAM要避开操作系统的物理内存起始区、DMA预留区、以及U-Boot占用区。典型布局是:安全RAM放在DRAM的顶部或专用SRAM里,BL31的代码段、数据段、栈区都在其中。
用platform_def.h里的宏来控制内存大小,比如:
#define PLAT_SECURE_MEM_SIZE 0x100000 #define PLAT_MAX_RAM_SIZE 0x80000000如果安全RAM区域太小,BL31镜像会放不下,编译时不会报错,但加载时可能覆盖其他镜像的数据,引发极其诡异的启动报错。我遇到过一个问题:BL31里大量使用__attribute__((section(".tzdata")))定义的数据段,链接脚本卡得很死,一旦超过预留区,整个镜像直接加载失败。经验是给自己的安全RAM留足余量,比如把BL31镜像大小的两倍作为预留,同时开启编译期的section检查。
6. 安全固件工程审计:不是跑起来就万事大吉
6.1 审计清单:从信任链到硬件隔离
做固件工程审计,建议把代码审查、配置审查、形态审查分开推进。下面这个清单是面向ARM固件的常规检查点,我能保证这几项在多数项目里都能直接派上用场。
- 信任根与密钥管理:BL1固化的公钥是否只存在于OTP,是否支持回滚保护。
- 认证算法强度:是否用弱哈希,密钥长度是否足够(RSA-2048以下、SHA-1都要亮红灯)。
- FIP内容完整性:是否对BL31/BL33镜像逐个验签,是否校验镜像大小和哈希。
- 内存隔离:安全RAM区域是否被TZASC正确保护,BL31的MMU表是否把非安全外设排除在外。
- 中断路由:安全中断响应能否绕过Non-secure世界。
- 热启动路径:warm boot入口是否做身份复合校验,防止绕过正常启动。
- 调试接口:JTAG/SWD是否在生产固件中关闭,是否开放UART为root shell。
- 崩溃处理:panic后会打印哪些信息,是否会把敏感寄存器内容泄露。
审计不是简单看一下有没有开Trusted Board Boot,还要看实现是否经得起“强制失败”测试。比如故意把FIP里的BL33改坏,观察BL2会不会拒绝启动;如果BL2在验签失败后仍然尝试执行,那这件事就严重了。
6.2 常见安全问题
ATF项目里发生过的安全隐患,很大一部分集中在整数溢出、越界读写、SMC参数校验缺失等。典型例子:SMC传入某个长度值,固件没有做边界检查就直接在buffer里memcpy,攻击者就可能在EL3执行任意代码。
一个经典的审计点是SMC处理里的fid高8位识别和宽度校验。许多工程代码里handler对合法范围很宽松,导致别名指令被错误分发。建议参考ATF官方对mt、fast、64这些bit的严格判断矩阵,逐bit核对,而不是简单用if fid == 0x82000001这种写法。
另一个问题是“悄悄关闭了调试后端”。有些团队在发布固件时才加编译宏把调试串口关掉,但代码中仍有在生产版固件中可触发的panic路径,打印出的泄露信息包括安全RAM地址、内存映射关系等。审计时最好查看串口使能对应的编译选项,确认生产版里console_putc直接被编译掉,而不是靠运行时标志位临时关。
6.3 我常用的审计辅助手段
审计ATF代码,只靠人眼不够。我常用的组合拳是:
- 用
checkstack.pl检查栈压栈深度。 - 用
smatch或cppcheck做静态分析,重点扫SMC入口和镜像解析代码。 - 用
gold linker加--print-memory-usage看内存占用,快速定位是否有section膨胀。
还有一个简单但高效的测试方法:把ARM平台放到QEMU上,配合GDB逐步执行BL31的SMC handler,喂一个畸形fid,观察行为。这种方法可以在不依赖硬件的情况下把异常路径测个七七八八。
7. 调试与问题排查:遇到重启和挂死别慌
7.1 串口完全无输出
遇到“烧进去完全没反应”,先别怀疑BL1没跑。按这个顺序排查:
- 检查串口引脚和波特率:ATF参考串口通常默认是115200 8N1,但部分开发板拨码或时钟不同。
- 确认BL1的串口驱动初始化是否引用了正确的UART基址和时钟源,尤其是外设时钟没使能时,寄存器写不进。
- 确认BL1有没有可能跳进错误的分支。看BL1的
_start汇编,验证复位地址与链接地址是否一致。 - 如果BL1能打印但BL2没反应,多半是FIP地址找不到或FIP解析失败。检查
plat_get_ns_image_entrypoint和plat_get_bl2_load_info里的地址是否与烧写位置一致。
7.2 BL31启动后跳转BL33就挂
BL31能把日志打到“Jumping to BL33”但U-Boot起不来,这个问题九成出在内存地址冲突。比如BL31把BL33加载的地址和自身使用的安全RAM页表重叠,或者ATF传递的设备树地址没有正确传给U-Boot。打开ATF的LOG_LEVEL到LOG_LEVEL_VERBOSE,查看最终的mmap映射和跳转参数,再对照U-Boot的链接配置,基本能定位。
还有一个隐蔽点:U-Boot对ATF传过来的参数格式有要求,比如warm boot入口、D TB地址必须以约定的寄存器传参方式给它。有些平台擅自改了传参形式,U-Boot能跑一半就死。此时最好回头确认bl31_params结构里的bl33_ep_info设置。
7.3 CPU热插拔或suspend后无法恢复
如果启动没问题,但在Linux里执行echo 0 > /sys/devices/system/cpu/cpuX/online或systemctl suspend后系统卡住,优先排查PSCI回调和GIC配置。最常见的问题一个是上下文保存不完整,一个是唤醒时中断被屏蔽导致CPU进入WFI后没有中断唤醒它。
我建议在地板调试时给PSCI每个回调加一行trace打印,把affinity level和state id都打出来。对比正确唤醒和失败唤醒的日志差异,基本能看出是CPU_ON没被调用,还是被调用后SPR丢失。另外,GIC的EOI问题也会导致这个现象:安全中断在唤醒时错误地pending,CPU一回来就进中断风暴。
7.4 工具链版本和编译环境
ATF对工具链版本其实有最低要求,但实际中影响最大的往往是GCC版本对bit field的处理方式不同,导致某些结构体大小变化。建议固定一个CI镜像里的交叉编译器版本。如果使用了第三方GCC,比如带安全加固的编译套件,要注意它可能默认开启-fstack-protector-all,这会让固件体积明显膨胀,最终挤占预留的RAM区域。
8. 写在最后:一点项目实战体会
说一个我自己的经验。早期接触ATF时,我习惯把它当成普通裸机工程,一看bl31_main就直接找有没有“主循环”,结果被各种回调注册、事件驱动、状态机的设计绕得晕头转向。后来我换了个思路:把ATF当操作系统内核来看,它有自己的任务、锁、内存管理、异常向量表,只是形式更小、更静态。从那之后再去看代码,理解速度快了非常明显。
如果你正准备在真实项目里集成ATF,我另外还有几条建议:一开始不要开安全启动和TZASC,先把基本启动链路拉通;拿到一个新平台,先在QEMU或FVP仿真板上跑通参考配置,给自己建立一个“能正常工作的参照物”;平台移植的文档虽然长,但plat-porting-guide.rst值得精读一遍,很多细节比如plat_log_get_prefix、plat_get_soc_version都是在文档里才能看到的。
这块东西内容量不小,一篇文章很难面面俱到。但不管是为了快速把boot跑起来,还是为了给产品做固件安全评估,先把上面的架构脉络梳理清楚,心里就有底了。后面的坑,咱们在调试日志里再慢慢聊。