在接触嵌入式Linux,启动流程一直是比较重要值得详细说明的知识点。早期接触芯片如NXP的I.MX6ULL时,就了解了基于U-Boot、Kernel、Rootfs组成的启动流程。后续熟悉了更复杂的芯片如全志T113-i时,了解到了U-Boot SPL概念;后续接触瑞芯微的RK3568时,TrustZone和TF-A机制,启动机制更加复杂;需要更系统的梳理,才能理解相关的概念。
本节目录如下所示。
对于最初的嵌入式平台来说,主要由U-Boot、Kernel、Rootfs三部分组成。
系统上电后的执行顺序如下所示。
BootROM => U-Boot => Linux Kernel => System Rootfs
对于上述流程,各部分说明如下。
注意:上面提到的空间如SD卡、NAND、EMMC等存储设备,不支持直接执行代码,需要加载到外部DRAM中执行。基于此特性,只要集成相应的驱动,其它介质理论上也支持作为内核启动的存储设备或者接口,如USB、ETHERNET、UART、SPI FLASH、SATA等,不过因为BootROM未实现相应驱动,才无法实现。
典型的按照这个流程启动的芯片,整理表格如下。
| Boot stage number | Terminology #1 | Actual program name | Image Name |
|---|---|---|---|
| 1 | Primary Program Loader | ROM code | BootRom |
| 2 | - | U-Boot | U-Boot.bin |
| 3 | - | Kernel | zImage/*.dtb |
| 4 | - | rootfs | rootfs |
启动进一步详细的说明如下所示。
注: Linux内核中定义的起始进程为”/sbin/init,/etc/init,/bin/init,/bin/sh”中任选一个,需要在文件系统中包含,否则系统会加载失败。
早期的SOC芯片,例如I.MX6ULL就是按照这个流程设计。对于嵌入式Linux的SOC来说,虽然支持EMMC、SDCard、Nand FLASH等多种存储介质存储应用代码,但这些空间不能直接执行命令,需要将数据加载到内存中才能执行。这里的内存主要指内部的SRAM和外部的DRAM(如DDR3、DDR4等),其中内部SRAM容量很小,一般不超过256KB,只能运行少量的代码;外部DRAM则容量较大,例如I.MX6ULL最大可以支持3GB,部分芯片可以支持8G或者更大。
对于启动BootROM直接加载U-Boot的场景,其容量远远超过内部SRAM的大小,因此完整的U-Boot只能加载到外部DRAM中执行,这就需要在ROM中除了继承存储固件外,还需要集成外部DRAM驱动的实现。理论上这也能满足需求,毕竟直接加载U-Boot的方式,在中低端SOC仍然有大量的市场。不过对于性能更强的芯片,适用DRAM的型号五花八门,特别是部分即使ROM也只能做到部分兼容,这就限制了芯片的使用场景。另外传统加载方式还有一个更致命的缺点,因为ROM代码在芯片设计时固化,如果实现安全相关代码整合,遭遇漏洞时除了芯片升版召回无法修复;如果不支持安全相关认证校验,允许任意固件更新,在某些场景时不可接受的。因此有个启动后在内部执行,无法外部监测且支持随时更新的固件方案被设计用于处理此类问题;U-Boot SPL方案正是在这个背景下设计来解决问题。
U-Boot SPL的工作原理,正是通过前面提到内部SRAM也可以执行代码实现的。内部SRAM容量很小只能运行少量的代码,那么裁剪U-Boot代码,只实现外部DRAM初始化和搬运数据的功能,同时集成一些认证签名的实现,容量就可以做到很小,满足在内部SRAM控制执行的需求。例如接触到的全志的T113_i、瑞芯微rk3506都是使用此方案实现启动。比起传统的启动方式,可以灵活适配多种DRAM方案,实现固件签名认证的安全功能,越来越成为嵌入式开发的主流方案。
增加U-Boot SPL后,整个启动流程如下所示。
BootRom => U-Boot SPL => U-Boot => Linux Kernel => System Rootfs
按照这个框架启动流程如下。
| Boot stage number | Terminology #1 | Actual program name | Image Name |
|---|---|---|---|
| 1 | Primary Program Loader | ROM code | BootRom |
| 2 | - | U-Boot SPL | U-Boot-spl.bin |
| 3 | - | U-Boot | U-Boot.bin |
| 4 | - | Kernel | zImage/*.dtb |
| 5 | - | rootfs | rootfs |
整个流程和上面类似,只不过在BootROM和U-Boot执行中间多了个步骤,整理起来如下所示。
U-Boot SPL可以将BootROM实现的DRAM驱动转换为由固件U-Boot SPL实现,这样就可以随时修改适配DRAM驱动,增加了灵活性和兼容性;另外也可以很方便的扩展存储介质的支持。例如开源awboot就支持从SPI FLASH启动,如果能够裁剪满足SRAM执行容量,理论上SATA、网络存储等方式也可以支持。
对于我接触过的方案,全志的官方SDK中提供的SPL源码,大部分以静态库的方式链接实现,可在”brandy/brandy2.0/spl-pub“中查看;瑞芯微的官方SDK则直接提供二进制文件,不提供源码。从开发者角度其实降低了使用难度,不过另一方面,隐藏了太多的硬件信息,如果希望了解和学习uboot spl的底层实现适配硬件的功能,就比较困难了。可以看到,U-Boot SPL是对旧有启动流程的优化,只是在U-Boot之前单独实现DRAM,FLASH等必要驱动的固件;这样就不需要固化在芯片ROM中,厂商可通过SDK软件版本升级随时更新迭代,进行适配。替换了BootROM集成硬件驱动的功能,U-Boot SPL的方案实现更简单,可扩展性更好。另外一个重要原因是uboot spl在芯片内部SRAM中执行,不容易被外部手段监视,从事一些安全相关的限制功能更难被监控破解。
至此,U-Boot SPL的主要功能总结如下。
U-Boot SPL是优化启动流程的一种方式,可以降低产品ROM代码异常的风险,因此目前应用也越来越广泛。另外U-Boot SPL也是实现安全加密应用的必要一环,通过内部SRAM执行U-Boot SPL进行后续文件的加载和鉴权,可以避免外部监听和大部分干扰操作。不过参考了各大厂家的SDK,即使使用了U-Boot SPL方案,大部分还是以库或二进制文件的方式提供,如瑞芯微的RK35xx系列就是提供的二进制文件,全志的t113x系列也提供的是库配合部分代码,不提供直接的源码。这部分属于厂商独立的开发,涉及到底层的实现,未选择开源。
当然,这里U-Boot SPL和Trust Zone并没有直接关联,不过理解U-Boot SPL有助于去理解TrustZone执行流程,下面详细进行说明。
在智能手机,智能家居飞速发展的当下,指纹、面部识别、虹膜识别成为现代电子产品功能的一部分。如何存储管理这些个人身份数据信息,保证安全就成了迫切需求。为了在复杂形式下确保设备的安全性,ARM在ARMv7-A架构后提供了TrustZone硬件解决方案。TrustZone在概念上将SoC的硬件和软件资源划分为安全(Secure World)和非安全(Normal World)两个世界,所有需要保密的操作在安全世界执行(如指纹识别、密码处理、数据加解密、安全认证等),其余操作在非安全世界执行(如用户操作系统、各种应用程序等),安全世界和非安全世界通过一个名为Monitor Mode的模式进行转换,通过隔离的方式来确保应用在非授权的情况下不能访问到系统级的保密信息。
那么什么是TF-A呢,TF-A总称Trust Firmware-A,是支持启动TrustZone功能的固件。它规定了启动TrustZone的工作流程,提供启动对应的固件,从而保证硬件安全功能。ARM TrustZone技术是硬件范围的安全方法,针对高性能计算平台上的大量应用,包括安全支付、数字版权管理(DRM)、企业服务和基于Web的服务。TrustZone技术与Cortex-A处理器紧密集成,并通过AMBA-AXI总线和特定的TrustZone硬件IP块在系统中进行扩展;ARM TrustZone技术是从硬件层次上提供的安全机制,此系统方法意味着可以保护安全内存、加密块、键盘和屏幕等外设,从而可确保它们免遭软件攻击。
上面这些是arm推出TrustZone技术的功能宣讲的说明,不过在实践中,突破系统相关安全项往往需要对于设备实体的操作;这就说明TrustZone最重要的目的不是防止黑客技术,是防止消费者从软硬件层面对产品的关键系统进行读写、调试等高权限的操作;这部分可以参考以下博文进行相关的了解。
会对TrustZone有更深刻的认知。这里展示ARM提供TrustZone启动的执行框架。

看到这个图,上来肯定是迷惑的,毕竟这张图有很多知识点可以讲,我们先从每一块的名字讲解。
详细整理表格如下所示。
| 简称 | 完整名称 | 功能说明 |
|---|---|---|
| BL1 | Boot stage 1 | 启动第一阶段,一般为BootROM |
| BL2 | Boot stage 2 | 启动第二阶段,对应BootLoader SPL |
| BL31 | Boot stage 3_1 | 启动第三阶段(第一部分),提供安全的Runtime |
| BL32 | Boot stage 3_2 | 启动第三阶段(第二部分),提供安全的系统固件(Trust OS) |
| BL33 | Boot stage 3_3 | 启动第三阶段(第三部分),系统启动流程(U-Boot加载) |
理解了这几个参数,图上执行流程就比较简单了。
可以看到,每个阶段都有着相应的执行意义;BL1、BL2可以看作系统启动的预处理流程,执行必要的CPU、FLASH、SDRAM初始化、安全系统的基础初始化,保证系统的运行。BL31、BL32、BL33虽然流程上启动有先后顺序,但在启动后是并行执行的;分别执行在不同CPU环境中,这样就从硬件上实现了隔离,具有更高的安全性。在系统最终运行中,BL31、BL32与BL33后续启动的Linux Kernel并行运行,主要提供安全相关的服务。
从CPU的视角看,如下是一个标准的启用了ARM TrustZone技术后的CPU特权模式等级架构图。对于64位aarch64指令集的CPU,它的特权等级分为EL0、EL1、EL2、EL3,其中根据 CPU 所处的世界又分为安全EL0、安全EL1或者非安全EL0、非安全EL1。对于32位arm指令集的CPU,它的特权等级分为Mon、Hyp、SVC、ABT、IRQ、FIQ、UND、SYS、USER 模式,其中SVC、ABT、IRQ、FIQ、UND、SYS、USER也如64位一样有安全和非安全模式之分。在此图中,ARM Trust Firmware对应BL31,Trust-OS对应BL32。

不过这里有个注意点,TrustZone是硬件支持的特性。只有需要启动此特性时,才需要TF-A的流程来保证硬件的安全性。如果不启动,理论上不需要BL2,BL31,BL32的启动流程,但BL33表示U-Boot启动,是必须存在的。当不支持TrustZone时,启动流程就回归了上面的U-Boot/U-Boot SPL启动流程。
关于TF-A的源码,参考地址如下。
# arm提供的tf-a源码
git clone https://github.com/ARM-software/arm-trusted-firmware.git
这里以RockChip官网的启动选项来展示TF-A的实际运行流程,具体网址:rockchip官网启动说明

按照流程来说,详细理解这张图对应关系如下。
另外上面也提到了,阶段1始终在ROM中,它可以加载阶段2,也可以直接加载阶段3,如果加载阶段3,按照注释的说明,就会执行trust.img;
如果直接加载阶段3,这就和最初的U-Boot启动流程一致。
BootROM => U-Boot => Linux Kernel => System Rootfs
如果启用TrustZone机制,则必须加载阶段2,此时流程如下。

在运行过程中,loader(BL1,BL2),Trust-OS(BL32)运行在安全世界,BL31则是非安全世界和安全世界切换的桥梁(Secure Monitor),U-Boot(BL33),Kernel,Rootfs运行在非安全世界。
至此,关于U-Boot SPL启动流程,以及ARM的TF-A机制进行了部分讲解。当然这部分只涉及启动的部分,主要用于芯片移植中需要,大部分开发者并不需要接触。不过理解了这个机制,就可以理解更新的芯片如全志H618,RockChip的RK3568,RK3588,ST的STM32MP1这类芯片的启动流程,可以更好的理解SDK的编译流程,事半功倍。另外本篇也参考了相关资料和文件,如果希望深入了解,可进行查看。
直接开始下一章节说明: 异构多核芯片工作处理