Linux系统启动之前还需要一段程序来进行引导工作,比如先初始化DDR内存等外设,然后将内核从外部的flash(nandflash、SD、EMMC等)中拷贝到DDR中,最后启动内核。这段程序就是BootLoader,它功能就是用于引导操作系统,类似于bios和windows的关系。UBOOT就是一款开源的BootLoader程序,可用于引导多种操作系统,并且支持多种体系结构(ARM、MIPCS、PPC、X86等),因此收到广泛的应用。
uboot 的全称是 Universal Boot Loader, uboot 是一个遵循 GPL 协议的开源软件, uboot 是一个裸机代码。
Uboot官方会定期发布各种版本。半导体厂商通常会较为关注这个事情,因为这个版本对于board的支持并不全面。不同的board需要在官方的版本上做一些修改,而这个修改工作通常由半导体厂商来做,比较自己对于自家的产品还是最清楚的,因此半导体厂商通常会选择一个官方的版本,然后再上边适配自家厂商的各种board。通常对于用户来说首选的也是去相应半导体厂商的官网上下载对应boarad的uboot来做一些定制化开发。
arch文件夹中存在于架构相关的代码,如下:
如上图,里边都是些架构相关的,如arm、mips、ppc等,本文主要是在armv7平台上演示,因此我们只关心arm即可,打开arm文件夹,如下:
cpu文件是arm的核心,进入cpu文件中有个u-boot.lds,就是uboot的链接脚本,是我们重点关注的东西。
dts是uboot的设备树文件,这里由各厂商自行维护,并不是所有的厂商都使用设备树来做驱动开发。
mach开头的文件是用于支持一些其他的machine的,本文中是用imx6ul,因此关注imx-common文件夹即可。
board 文件夹就是和具体的板子有关的,打开此文件夹,里面全是不同的板子,如下:
找到我们的主要关注的freescale(imx属于此公司),里边又分了很多freescale公司的各种信号的cpu及开发板型号。我们的移植主要是找到一块官方相近的板子,在此基础上来修改,此文件也是移植时需要修改的文件。
此文件夹为 uboot 配置文件, uboot 是可配置的,通常是选择一个官网的文件,再次基础上进行修改。配置文件统一命名为“xxx_defconfig”, xxx 表示开发板名字,这些 defconfig 文件都存放在 configs
文件夹,如下:
mx6ull_alientek_emmc_defconfig就是根据官网的版本修改而来的。通常在编译uboot之前需要先进行配置,执行make mx6ull_14x14_ddr512_emmc_defconfig,即使用configs文件夹下的x6ull_alientek_emmc_defconfig配置文件进行配置,配置完成会生成**.config文件**。
这个是顶层 Makefile 文件, Makefile 是支持嵌套的,也就是顶层 Makefile 可以调用子目录
中的 Makefile 文件。
u-boot.xxx 同样也是一系列文件,包括 u-boot、 u-boot.bin、 u-boot.cfg、 u-boot.imx、 u-boot.lds、
u-boot.map、 u-boot.srec、 u-boot.sym等。
uboot 配置文件, 使用命令“make xxx_defconfig”配置 uboot 以后就会自动生成
可以看出.config 文件中都是以“CONFIG_”开始的配置项,这些配置项就是 Makefile 中的变量,因此后面都跟有相应的值, uboot 的顶层 Makefile 或子 Makefile 会调用这些变量值。
在.config 中会有大量的变量值为‘y’,这些为‘y’的变量一般用于控制某项功能是否使能,为‘y’的话就表示功能使能。
要分析 uboot 的启动流程,首先要找到“入口”,找到第一行程序在哪里。程序的链接是由链接脚本来决定的,所以通过链接脚本可以找到程序的入口,打开链接脚本如下:
注:uboot是通过链接参数 -e 来指定链接起始地址的,makefile中CONFIG_SYS_TEXT_BASE这个宏来设置了链接起始地址,而这个宏是在include/config.h中定义的,如下图:
可以看出这个config.h是生成的,是在进行make xxx_defconfig时生成的,根据当前的配置来生成的,config.h头文件又包含了#include
如上图,最终这个链接起始地址就是0x87800000。
链接地址:也叫运行地址,程序定位的绝对地址,在编译连接时确定的地址,与位置相关的代码要放在对应的运行地址上。通常会把代码搬到链接地址上运行,以满足相对地址寻址的需求,保证程序的正确运行。
第三行定义了入口函数_start,此函数在arch/arm/lib/Vector.S中,如下:
可以看到start进来就是异常向量表。
上节可以看到进入到start后,首先进入到复位函数reset,此函数在在 arch/arm/cpu/armv7/start.S,如下
进入到reset后,跳转到save_boot_params函数,而save_boot_params函数只有一句,就是跳转到save_boot_params_ret函数,为毛这么设计,恕在下愚钝,此函数也在上图中,此函数就做了2件事,首先是在非HYP模式的情况下,将模式设置到SVC模式,然后关闭FIQ和IRQ中断。
然后reset继续运行:
首先读取 CP15 中 c1 寄存器的值到 r0 寄存器中,就是SCTLR系统控制寄存器的值,CR_V定义在arch/arm/include/asm/system.h中,#define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */,将重定位向量表控制位设置为0,则可以重新定位中断向量表位置,然后更新SCTLR寄存器并将_start的地址设置为中断向量表的起始地址。
然后继续看代码:
首先跳转到cpu_init_cp15,看名字应该是初始化CP15,这个函数较长,这里就不展开了,就是主要做了关闭MMU和CACHE的操作。
然后跳转到cpu_init_crit,如下:
那么接下来就主要重点看看lowlevel_init和main。
函数 lowlevel_init 在文件 arch/arm/cpu/armv7/lowlevel_init.S 中定义,如下
首先初始化SP堆栈指针,然后8字节对齐。接着压栈GD_SIZE字节(generic-asm-offsets.h中定义sizeof(struct global_data),为248),然后再对齐,并将sp存到R9寄存器。后边要进行函数调用了,因此先保存lr寄存器,然后调用s_init函数,最后恢复lr。
global_data是个管理uboot的关键数据结构,定义如下
s_init 函数定义在文件arch/arm/cpu/armv7/mx6/soc.c 中(由于上一步已经初始化完了sp指针,内存初始化是在bootrom内已完成的,因此可以调用c函数了),如下
如图,cpu类型如果为imx6u则直接退出,因此此函数什么也没做直接返回了。
_main 函数定义在文件 arch/arm/lib/crt0.S 中,如下
设置SP指针值并8字节对齐,然后存储到r0寄存器中,然后调用board_init_f_alloc_reserve函数。V7M是cortex-M系列的,不用考虑。
此函数定义在文件 common/init/board_init.c 中,如下
top中是上边存储的sp指针的值,主要是留出早期的 malloc 内存区域和 gd 内存区域。
此函数也在common/init/board_init.c 中,如下
此函数用于初始化 gd,其实就是清零处理,同时此函数还设置了gd->malloc_base的值,也就是early malloc 的起始地址。
此函数定义在common/boardf_f.c中,如下
由于没有定义CONFIG_SYS_GENERIC_GLOBAL_DATA宏,因此此函数重点就是1059行,从名字可以看出来应该是个运行初始化序列,其实就是运行init_sequence_f序列内的一些列初始化函数
由于此序列较长,里边包含个所有平台,因此阅读起来不太方便,取消掉不必关心的东西后,如下(正点原子整理)
此函数负责将 uboot 拷贝到新的地方去,此函数定义在文件 arch/arm/lib/relocate.S,如下
这里重定位的是__image_copy_start~__image_copy_end,从链接脚本可以看到这个地址范围内不只是text段,还包含了data段和rodata段。
重定位细节:
从上述可知,重定位主要工作分两步:1、把image搬到相应的重定位地址上 2、重定位符号
首先内存数据搬运大家都懂,那为什么要重定位符号呢?我们一起来研究研究
uboot中函数跳转都是用的b和bl,而这两个跳转指令是相对地址跳转,与链接位置无关,是基于当前pc+offset来跳转的,因此代码重定位后,函数调用还是能够正确的找到位置去调用的。
而code中的数据访问确实采用的绝对地址,搬运之后,访问数据的地址也必须得进行修正,负责无法获取到正确的数据,在uboot中链接时指定了-pie参数,这个参数会生成位置无关代码,会生成一个.rel.dyn 段,rel.dyn 段是存放.text 段中需要重定位地址的集合,uboot 就是靠这个.rel.dyn 来解决data段的重定位问题。主要思想就是在代码里使用的全局变量用重定位rel.dyn段里边,而在rel.dyn段里存储一个标记,检测到标记的话,表示地址需要重定位,给原地址加上一个偏移就能得到新的位置。而这个标记的值就是上文代码中的23(0x17)。具体细节其他博客有更详细的介绍,此处就不展开描述了。
函数 relocate_vectors 用于重定位向量表,此函数定义在文件也在relocate.S 中,如下
在之前的的2.3.3中介绍了board_init_f 函数,在此函数里面会调用一系列的函数来初始化一些外设和gd的成员变量。但是 board_init_f 并没有初始化所有外设,还需要做一些后续工作这些后续工作就是由函数 board_init_r 来完成的,board_init_r 函数定义在common/board_r.c中,代码如下
跟board_init_f类型,主要是运行init_sequence_r序列,也定义在文件 common/board_r.c 中
由于 init_sequence_f 的内容比较长,里面有大量的条件编译代码,这里为了缩小篇幅,将条件编译部分删除掉了,去掉条件编译以后的 init_sequence_r 定义如下: