u-boot 是一个主要用于嵌入式系统的引导加载程序,可以支持多种不同的计算机系统结构。u-boot最先是由德国DENX软件中心团队开发,后续众多有志于开放源码bootloader移植工作的嵌入式开发人员将各个不同系列嵌入式处理器的移植工作不断展开和深入,以支持了更多的嵌入式操作系统的装载与引导。
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
官方将启动分为了BL1,BL2,BL31,BL32,BL33阶段,根据顺序,芯片启动后首先执行BL1阶段代码,接着验签启动BL2,BL2根据具体设计启动BL31或者BL33,BL32只有在有BL31时才可能会存在并被验签加载启动。
上图中的BL1,BL2,BL31,BL32,BL33分别对应如下功能:
启动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), 至少有以下三种方式:
对于一般嵌入式而言只需要一个u-boot作为bootloader即可,但是在小内存,或者有atf的情况下还可以有spl,tpl;
出现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官方只是给出了一个建议的启动信任链,具体实现都需要芯片厂商来决定;
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进入对应目录进行编译链接。