时间轴

2025-12-26

init


BootROM

通常来说,SoC厂家都会做一个ROM在SoC的内部,这个ROM很小,里面固化了上电启动的代码(一经固化,永不能改,是芯片做的时候,做进去的);这部分代码呢,我们管它叫做BootROM,也叫作一级启动程序

芯片上电后先接管系统的是SoC厂家的BootROM,它要初始化硬件,然后加载程序到SRAM

初始化硬件

  • CPU的配置
  • 关闭看门狗
  • 初始化时钟
  • 初始化一些外设(比如 USB Controller、MMC Controller,Nand Controller等);

加载程序到SRAM

一款新的SoC一般会在芯片外挂一些存储设备(eMMC、Nand、Nor、SDCard等)和内存(SDRAM、DDR等)。

然后我们需要烧写程序让CPU执行,烧写程序,其实就是将可执行的二进制文件写到外部的存储设备上(eMMC、Nand、SD等)。系统上电启动的时候,会将他们读到内存中执行

上电后先接管系统的是SoC厂家的BootROM,其它可执行的程序(u-boot、Kernel)都放(烧写)到了外部存储器上;那么BootROM的代码除了去初始化硬件环境以外,还需要去外部存储器上面,将接下来可执行的程序读到内存来执行。

既然是读到内存执行,那么这个内存可以不可以是我们板载的 DDR呢?

理论上是可以的,但是,SoC厂家设计的DDR控制器一般会支持很多种类型的DDR设备,并且会提供兼容性列表,SoC厂家怎么可能知道用户PCB上到底用了哪种内存呢?

所以,直接把外部可执行程序读到DDR显然是不太友好的,因此一般来说,SoC都会做一个内部的小容量的SRAMBootROM将外部的可执行程序从外部存储器中读出来,放到SRAM去执行

那么 BootROM从具体哪个存储器读出二进制文件呢?

SoC厂家一般会支持多种启动方式,比如:

  • 从eMMC读取
  • 从SDCard读取
  • 从Nand Flash读取等等

上电的时候,需要告诉它,它需要从什么样的外设来读取后面的启动二进制文件;

一般的设计思路是,做一组Bootstrap Pin,上电的时候BootROM去采集这几个IO的电平,来确认要从什么样的外部存储器来加载后续的可执行文件;比如2 个 IO,2’b00 表示从Nand启动,2’b01表示从eMMC启动,2’b10 表示从SDCard启动等等;

当BootROM读到这些值后,就会去初始化对应的外设,然后来读取后面要执行的代码;这些IO一般来说,会做成板载的拨码开关,用于调整芯片的启动方式;

这里,读取烧写的二进制的时候,需要注意一些细节,比如SoC厂家告诉你,你需要先把SDCard初始化称为某种文件系统,然后把东西放进去才有效之类的;因为文件系统是组织文件的方式,并不是裸分区;你按照A文件系统的方式放进去,然后SoC的BootROM也按照A文件系统的方式读出来,才能够达成一致

SPL

芯片上电后BootROM会根据Bootstrap Pin去确定从某个存储器来读可执行的二进制文件到SRAM并执行;理论上来说,这个二进制文件就可以是我们的u-boot.bin文件了;也就是BootROM直接加载u-boot.bin

理论上是这样的,但是这里有一个问题,就是SRAM很贵,一般来说,SoC的片上SRAM都不会太大,一般4KB、8KB、16KB…256KB不等;但是呢,u-boot 编译出来却很大,好几百KB,放不下。

放不下怎么办?有两种办法:

  • 假设片内SRAM为4KB,uboot的前4KB程序实现uboot的重定位,即将uboot拷贝到SDRAM中运行
  • 做一个小一点的boot程序,先让BootROM加载这个小的程序,后面再由这个小boot去加载uboot;

方案一:uboot重定位

比如,我们的uboot有400KB,SRAM有4KB,外部SDRAM有64MB;

如果使用第一种方案的话,uboot的前面4KB被加载进入SRAM执行,uboot被截断,我们就需要保证在uboot的前4KB代码,把板载的SDRAM初始化好,把整个uboot拷贝到SDRAM,然后跳转到SDRAM执行

方案二:SPL

第二种方案的话,我们做一个小的uboot ,这个uboot就叫做SPLSecondary Program Loader),它很小很小(小于SRAM大小),它先被BootROM加载到SRAM运行,SPL做的事情最主要的就是要初始化内存控制器,然后将真正的大u-boot从外部存储器读取到SDRAM中,然后跳转到大uboot

启动流程

如上图所示:

  1. 上电后,BootROM开始执行,初始化时钟,关闭看门狗,关Cache,关中断等等,根据Bootstrap Pin来确定启动设备,初始化外设;
  2. 使用外设驱动,从存储器读取SPL;

—————- 以上部分是SoC厂家的事情,下面是用户要做的事情 —————-

  1. SPL被读到SRAM 执行,此刻,控制权以及移交到我们的SPL 了;
  2. SPL初始化外部SDRAM;
  3. SPL使用驱动从外部存储器读取uboot并放到SDRAM;
  4. 跳转到SDRAM中的uboot执行;
  5. 加载内核;

实际情况中,还需注意很多问题:

  • 编译阶段的链接地址,是否需要地址无关?
  • SPL的代码和uboot的代码是否有重合的地方?如果有,是否意味着SPL执行过的,跳转到uboot又要在执行一次?
  • 具体情况下,需要配置哪些硬件?怎么配置?

Rockchip引导流程

针对不同的解决方案,Rockchip提供了两种不同的启动加载程序方法,其步骤和生成的镜像文件也是完全不同的。

  • TPL/SPL加载:使用Rockchip官方提供的TPL/SPL就是我们上面说的小的uboot),该方式完全开源;
  • 官方固件加载:使用Rockchip idbLoader,它由Rockchip rkbin projectRockchip ddr init binminiloader bin组合而成,该方式不开源;

上面我们介绍了SPL,那什么是TPL?实际上将我们上面所说的SPL初始化SDRAM等硬件工作的部分独立出去,就是TPL。那么我们总结一下:

  • TPLTarger Program Loader,就是芯片级的初始化过程,这个时候的代码都是基于芯片平台的部分,它在启动过程中进行DDR初始化和一些其他的系统配置,以便后续的SPL能够正确地运行;
  • SPLSecondary Program Loader,它从存储设备中读取trust(如ATF/OP-TEE)和uboot二进制文件,将它们加载到系统内存中并运行它们,进而启动完整的操作系统

TPL和SPL的区别在于它们的职责不同。TPL主要负责初始化系统硬件,而SPL负责加载和运行其它软件组件,如trust和uboot。

此外,在一些特殊情况下,如加密启动或安全启动模式下,TPL还可能执行其他额外的任务。

启动阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
+--------+----------------+----------+-------------+---------+
| Boot | Terminology #1 | Actual | Rockchip | Image |
| stage | | program | Image | Location|
| number | | name | Name | (sector)|
+--------+----------------+----------+-------------+---------+
| 1 | Primary | ROM code | BootROM | |
| | Program | | | |
| | Loader | | | |
| | | | | |
| 2 | Secondary | U-Boot |idbloader.img| 0x40 | pre-loader
| | Program | TPL/SPL | | |
| | Loader (SPL) | | | |
| | | | | |
| 3 | - | U-Boot | u-boot.itb | 0x4000 | including u-boot and atf
| | | | uboot.img | | only used with miniloader
| | | | | |
| | | ATF/TEE | trust.img | 0x6000 | only used with miniloader
| | | | | |
| 4 | - | kernel | boot.img | 0x8000 |
| | | | | |
| 5 | - | rootfs | rootfs.img | 0x40000 |
+--------+----------------+----------+-------------+---------+

启动阶段涉及到了多个镜像文件:

  • 阶段一中的BootROM这个是SoC厂商提供的;
  • 阶段二方式需要提供一个idbloader.img
  • 阶段三实际上就是uboot的镜像文件了,这里有两种: uboot.img(还需要搭配trust.img)u-boot.itb(这个是因为它已经把ATF打包进去了);这两个文件里面除了都包含u-boot.bin原始二进制文件,又放了点其他东西,可以被idbloader.img识别,然后加载;
  • 阶段四和阶段五是内核镜像和根文件系统;

当我们讨论从eMMC/SD/U盘/网络启动时,它们涉及到不同的概念:

  • 第一阶段始终在BootROM中,它加载第二阶段并可能加载第三阶段(当启用SPL_BACK_TO_BROM选项时);
  • 从SPI闪存启动意味着第二阶段和第三阶段固件(仅限SPL和U-Boot)在SPI闪存中,第四/五阶段在其他位置;
  • 从eMMC启动意味着所有固件(包括第二、三、四、五阶段)都在eMMC中;
  • 从SD Card启动意味着所有固件(包括第二、三、四、五阶段)都在SD Card中;
  • 从U盘启动意味着第四和第五阶段的固件(不包括SPL和U-Boot)在磁盘中,可选地仅包括第五阶段;
  • 从Net/TFTP启动意味着第四和第五阶段的固件(不包括SPL和U-Boot)在网络上。

idbloader.img

idbloader.img文件是一个Rockchip格式的预加载程序,在SoC启动时工作,它包含:

  • 由Rockchip BootROM知道的IDBlock 头;
  • DDR初始化程序,由BootROM加载到SRAM,运行在SRAM内部;
  • 下一级加载程序,由BootROM加载并运行在DDR上;

u-boot.img

u-boot.bin是uboot源码编译后生成的原始二进制映像,可以直接烧录到设备的闪存中。而u-boot.img则是通过mkimage工具在u-boot.bin基础上增加了一个头部信息,这个头部信息可能也包括一些额外的数据,例如启动参数和内核映像地址等。

因此,通过使用u-boot.img而不是u-boot.bin,可以使引导ROM更容易地识别uboot映像,并更好地指导uboot在设备上正确启动。

u-boot.itb

u-boot.itb实际上是u-boot.img的另一个变种,也是通过mkimage构建出来的,里面除了u-boot.dtb和u-boot-nodtb.bin这两个uboot源码编译出来的文件之外,还包含了bl31.elf、bl32.bin、tee.bin等ARM trust固件。其中bl31.elf是必须要有的,bl32.bin、tee.bin是可选的,可以没有。

trust.img

ARM64的SOC还需要编译ATF (ARM Trust Firmware)ATF主要负责在启动uboot之前把CPU从安全的EL3切换到EL2然后跳转到uboot,并且在内核启动后负责启动其他的CPU

ATF将系统启动从最底层进行了完整的统一划分,将secure monitor的功能放到了bl31中进行,这样当系统完全启动之后,在CA或者TEE OS中触发了smc或者是其他的中断之后,首先是遍历注册到bl31中的对应的service来判定具体的handle,这样可以对系统所有的关键smc或者是中断操作做统一的管理和分配。ATF的code boot整个启动过程框图如下:

ATF

Rockchip提供的外部uboot加载的流程图

如上图所示:

  • 引导流程1是典型的使用Rockchip miniloader的Rockchip引导流程;
  • 引导流程2用于大多数SoC,使用U-Boot TPL进行DDR初始化,使用SPL加载加载u-boot.itb文件;

注1:如果loader1具有多个阶段,则程序将返回到BootROM,BootROM将载入并运行到下一个阶段。例如,如果loader1是TPL和SPL,则BootROM将首先运行到TPL,TPL初始化DDR并返回到BootROM,BootROM然后将加载并运行到SPL。

注2:如果启用了trust,在安全模式(armv8中的EL3)下,loader1需要同时加载trust和U-Boot,然后运行到trust中,trust在非安全模式(armv8中的EL2)下进行初始化,并运行到U-Boot。

注3:对于trust(在trust.img或u-boot.itb中),armv7仅有一个带或不带TA的tee.bin,armv8具有bl31.elf并且可选包含bl32。

注4:在boot.img中,内容可以是Linux的zImage和其dtb,可以选择grub.efi,也可以是AOSP boot.img,ramdisk可选。

参考文献