1. uboot概述
u-boot 是一个主要用于嵌入式系统的引导加载程序,可以支持多种不同的计算机系统结构。u-boot最先是由德国DENX软件中心团队开发,后续众多有志于开放源码bootloader移植工作的嵌入式开发人员将各个不同系列嵌入式处理器的移植工作不断展开和深入,以支持了更多的嵌入式操作系统的装载与引导。
2. armv8 u-boot的启动
2.1 4个特权等级/4个安全状态之间的跳转模型:
armv8分为Secure World和Non-Secure World(Normal World),四种异常级别从高到低分别为EL3,EL2,EL1,EL0。
Secure World就是可以执行可信的firmware和app,比如密码支付,指纹识别等一系列依赖安全保证的服务。
Non-Secure World就是我们常见的u-boot,linux,qnx等裸机程序或者操作系统。
EL3具有最高管理权限,负责安全监测和安全模式切换。
EL2主要提供了对虚拟化的支持。
EL1是一个特权模式,能够执行一些特权指令,用于运行各类操作系统,在安全模式则是可信任OS。
EL0是无特权模式,所有APP应用都在EL0。
其中:
EL0的所有异常(同步异常和异步异常)都可以将core切到EL1中
EL1的所有异步异常、hvc/smc指令 都可以将core切到EL2中
EL2的所有异步异常、smc指令 都可以将core切到EL3中
所有的返回指令,都是ERET
2.2 启动时镜像之间的跳转模型:
上图详细概括了arm官方推荐的armv8的启动层次结构:
官方将启动分为了BL1,BL2,BL31,BL32,BL33阶段,根据顺序,芯片启动后首先执行BL1阶段代码,接着验签启动BL2,BL2根据具体设计启动BL31或者BL33,BL32只有在有BL31时才可能会存在并被验签加载启动。
上图中的BL1,BL2,BL31,BL32,BL33分别对应如下功能:
- BL1:是一切信任的根,一般就是固化在ROM中的一段启动加载代码,用于引导bl2,并对bl2进行验签保证可信任执行;
- BL2:一般是在flash中的一段可信安全启动代码,它的可信建立在bl1对它的验证,主要完成一些平台相关的初始化,比如对ddr的初始化等,并在完成初始化后寻找BL31或者BL33进行执行;如果找到了BL31则不会继续调用BL33,如果没有BL31则BL33必须有;
- BL31:BL31不像BL1和BL2是一次性运行的,它作为最后一道可信任固件存在,在系统运行时通过smc指令陷入EL3调用系统安全服务或者在Secure World和Non-Secure World之间进行切换;在完成BL31初始化后会去寻找BL32或者BL33进行验签后加载执行;
- BL32:OPTee OS + 安全app,它是一个可信安全的OS运行在EL1并在EL0启动可信任APP(上述的指纹验证等app),并在Trust OS运行完成后通过smc指令返回BL31,BL31切换到Non-Seucre World继续执行BL33;
- BL33:非安全固件,也就是我们常见的UEFI firmware或者u-boot也可能是直接启动Linux kernel;
启动BL1,BL2,BL31,BL32则是一个完整的ATF信任链建立流程(ARM Trusted Firmware),像常见的PSCI(Power State Coordination Interface)功能则是在ATF的BL31上实现;
以下是参在ATF代码做出的总结,当然了也是比较理想的场景:
可是在你的实际使用中:
BL32可能不是S-EL1,也是有可能是S-EL2的
BL33可能不是EL1,也是有可能是EL2的
BL1 BL2 BL33 BL32 BL33 每一级镜像,也许不是aarch64的,也许是aarch32的
如上的场景中,看似也就那么回事吧,无非就是不同特权等级之间,调用同步异常指令或返回指令,切来切去而已。那么如果是相同的特权等级,那么如何切换呢?
如uboot(EL1)到kernel(EL1), 至少有以下三种方式:
2.3 u-boot,u-boot-spl,u-boot-tpl的关系:
对于一般嵌入式而言只需要一个u-boot作为bootloader即可,但是在小内存,或者有atf的情况下还可以有spl,tpl;
- spl:Secondary Program Loader,二级加载器
- tpl:Tertiary Program Loader,三级加载器
出现spl和tpl的原因最开始是因为系统sram太小,rom无法在ddr未初始化的情况下一次性把所有代码从flash,emmc,usb等搬运到sram中执行,也或者是flash太小,无法完整放下整个u-boot来进行片上执行。所以u-boot又定义了spl和tpl,spl和tpl走u-boot完全相同的boot流程,不过在spl和tpl中大多数驱动和功能被去除了,根据需要只保留一部分spl和tpl需要的功能,通过CONFIG_SPL_BUILD和CONFIG_TPL_BUILD控制;一般只用spl就足够了,spl完成ddr初始化,并完成一些外设驱动初始化,比如usb,emmc,以此从其他外围设备加载u-boot,但是如果对于小系统spl还是太大了,则可以继续加入tpl,tpl只做ddr等的特定初始化保证代码体积极小,以此再次从指定位置加载spl,spl再去加载u-boot。
从目前来看,spl可以取代上图中bl2的位置,或者bl1,根据具体厂商实现来决定,有一些芯片厂商会将spl固化在rom中,使其具有从emmc,usb等设备加载u-boot或者其他固件的能力
当然在有atf的情况下可以由atf加载u-boot,或者由spl加载atf,atf再去加载u-boot。甚至在快速启动的系统中可以直接由spl启动加载linux等操作系统而跳过启动u-boot;在上图中arm官方只是给出了一个建议的启动信任链,具体实现都需要芯片厂商来决定;
3. u-boot源码整体结构和一些编译配置方式
3.1 编译配置方式
u-boot使用了同Linux一样的编译配置方式,即使用kbuild系统来管理整体代码的配置和编译,通过defconfig来定制各种不同厂商的芯片bootloader二进制程序。编译只需要注意通过环境变量或者命令行参数的方式引入一个交叉编译工具即可:
CROSS_COMPILE:定义交叉编译工具链,可以是aarch64-linux-gnu-,arm-none-eabi-或者ppc-linux-gnu-等等;
u-boot有几个配置是需要由对应board配置的。
SYS_ARCH,SYS_CPU,SYS_SOC,SYS_BOARD,SYS_VENDOR,SYS_CONFIG_NAME;
一般在board/vendor/board/Kconfig中可全部定义,部分SYS_CPU,SYS_SOC也可以在arch/xxx/Kconfig中定义,根据这几个配置即可确定使用的cpu架构,厂商,板级信息,soc信息。Makefile会自动根据上述信息进入对应目录组织编译规则,一般如果没有自己对应的这些board信息,需要自己在对应目录建立这些Kconfig和在configs中建立defconfig。
在configs目录中保存了uboot中所有支持的board配置,比如要使用rk3399的evb板的配置信息使用如下方式即可编译出来:
在configs目录中保存了uboot中所有支持的board配置,比如要使用rk3399的evb板的配置信息使用如下方式即可编译出来:
make CROSS_COMPILE=aarch64-linux-gnu- evb-rk3399_defconfig
make
如果没有对应的defconfig可以找一个与自己板级信息类似的defconfig生成一个.config,再通过menuconfig来完成自己board的配置,并最后通过savedefconfig保存为自己board的defconfig:
make CROSS_COMPILE=aarch64-linux-gnu- evb-rk3399_defconfig
make menuconfig
make savedefconfig
cp defconfig configs/my_defconfig
下面是evb rk3399的定义:
CONFIG_SYS_ARCH="arm"
CONFIG_SYS_CPU="armv8"
CONFIG_SYS_SOC="rk3399"
CONFIG_SYS_VENDOR="rockchip"
CONFIG_SYS_BOARD="evb_rk3399"
CONFIG_SYS_CONFIG_NAME="evb_rk3399"
根据CONFIG_SYS_BOARD的定义还会为每个源文件自动包含include/configs/xxxx.h头文件,evb rk3399则是include/configs/evb_rk3399.h头文件。这个头文件可在其中定义board的一些关键配置,系统的ram大小,环境变量的起始地址和大小,GIC基地址,时钟频率,是否开启看门狗等定义,可根据具体需求来定义。
u-boot使用Kconfig和include/configs/xxx.h来灵活的确定u-boot编译流程及最终生成的文件。比如当定义CONFIG_SYS_CPU为"armv8",CONFIG_SYS_ARCH为"arm"时,即确定了目标架构为armv8会自动根据Makefile进入对应目录进行编译链接。
3.2 u-boot源码结构
- arch:各种架构的启动初始化流程代码,链接脚本等均在此目录对应的架构中存放;
- board:包含了大部分厂商的board初始化代码,基本平台化相关的代码都在对应的board目录中,早期的一些board代码在arch/xxx/xxx-mach中,现在基本不会放在arch目录下面了;
- cmd:包含了大量实用的u-boot命令的实现,比如md,cp,cmp,tftp,fastboot,ext4load等命令的实现,我们也可以在此处添加自己实现的命令;
- common:包含了u-boot的核心初始化代码,包括board_f,board_r,spl等一系列代码;
- configs:包含了所有board的配置文件,可直接使用;
- drivers:大量驱动代码的存放处;
- dts:编译生成dtb,内嵌dtb到u-boot的编译规则定义目录;
- env:环境变量功能实现代码;
- fs:文件系统读写功能的实现,里面包含了各类文件系统的实现;
- include:所有公用头文件的存放路径;
- lib:大量通用功能实现,提供给各个模块使用;
- net:网络相关功能的实现;
- scripts:编译,配置文件的脚本文件存放处;
- tools:测试和实用工具的实现,比如mkimage的实现代码在此处;