嵌入式学习笔记之四 (uboot启动流程)

1.boot ROM阶段

CPU 上电之后执行的第一段代码并不是 uboot 代码,因为 uboot 存储在启动介质(存储介质),如eMMC Flash、NAND Flash、SD 卡,CPU 想要执行启动介质中的 uboot 代码,就需要将 uboot 代码从启动介质取出,加载到内存中去执行。那么就需要某个程序来做这些工作。i.MX6ULL 内部有一块 ROM,存储着一段代码,这部分代码是 SOC 设计时实现的,上电后会首先运行,uboot 的引导就由这段代码实现,我们称这段代码为 boot ROM。boot ROM 会去读取设置好的硬件启动信息(硬件启动信息一般通过拨码开关进行配置,不同的引脚电平,对应不同的启动模式,不同的启动介质,关于启动这部分内容可以去参考硬件手册),判断启动介质,然后初始化内存以及启动介质,最后读取 uboot 代码程序到内存中开始运行,因此 boot ROM 需要做这么几个工作:CPU 初始化,时钟初始化,内存初始化,启动介质初始化,加载uboot 程序并执行。
将 uboot 程序从启动介质加载到内存中是 boot ROM 完成的,所以 boot ROM 就需要知道,从哪里拷贝程序,程序有多大,拷贝到哪里。这些信息都存储在启动介质起始的一片区域,boot ROM 从此区域获取到这些信息后,才进行后续的加载工作。因此,我们烧写到启动介质上的镜像文件,除了应用程序uboot.bin 本身,还需要包含一些额外的头信息,配合 boot ROM 的工作。这些额外的头信息,包括 IVT、Boot data、DCD(Device Configuration Data)。
所以一个完整的镜像主要由 IVT(Image Vector Table offset)、Boot Data、DCD(Device ConfigurationData)、User code and data(Application+CSF)几部分组成,如下图的左半部分。

 1 ) IVT (Image Vector Table )
IVT(Image Vector Table)是一个统一格式的 structure,其原型如下面结构体所示,从结构体定义我们得知,IVT 中记录了 Boot data、Application、DCD、CSF 以及 IVT 本身在内存中的位置信息:

 

 这些信息对 boot ROM 加载启动至关重要。IVT 大小固定为 32byte,其在 Bootable image 中的偏移位置也是固定的,例如对于 eMMC 版,IVT 在 eMMC 中的偏移地址为 1Kbyte=0X400 bytes。其他启动介质如下图:

 2 ) Boot data
Boot data 也是一个 structure,其组成如下图所示:

 

 

 BD(Boot data)中记录了 Bootable image 的起始地址与总长度。BD 大小固定为 16byte,BD 信息虽然记录在了 IVT 中,但其在 Bootable image 中的偏移位置并不是任意的,BD 是紧挨着 IVT 的。

3 ) DCD
CPU 上电之后,在进入 uboot 之前,需要对内存进行初始化,以便将 uboot 从启动介质引导到内存中。这些外设的一些初始化配置信息就存储在 DCD 中。下面这个表就是可以在 DCD 中可配置的一些寄存器地址范围,从表中看出,DCD 区域中不仅可以初始化内存,也可以对其他的一些外设或者其他功能进行初始化:

 4 ) User code and data
User code 就是我们的 app 即 uboot.bin 程序,data 指的是 CSF,certificates and signatures,跟安全启动相关的签证证书信息,目前没有用到,这里不再讲解。boot ROM 在完成一些初始化工作之后,将uboot.bin 加载到内存指定地址中,最终跳转到该地址执行 uboot.bin,此时 boot ROM 生命周期结束,开启 uboot 生命周期。

2.uboot启动阶段

uboot 启动可分为汇编语言执行和 C 语言执行两个阶段,两个阶段以_main 函数为分界。
uboot 第一阶段由_start (arch/arm/lib/vectors.S)进入,然后跳转到reset(arch/arm/cpu/armv7/start.S)
函数, reset 函数进行设置 CPU 运行模式,关闭中断等一系列 CPU 内部环境初始化后,跳转到_main 函数。
第二阶段通过_main 函数进入,_main 主要做了如下功能:
1. 设置 C 代码的运行环境,为调用 board_init_f 接口做准备
a.设置堆栈(C 代码的函数调用,堆栈是必须的)
b.调用 board_init_f 接口,从堆栈开始的地方,为 u-boot 中 global data 数据结构,分配空间
c.调用 board_init_f_init_reserve 接口,对 gd 进行初始化
2. 调用 board_init_f 函数,完成一些前期的初始化工作,例如:
a.设置 gd 结构体中各成员在内存中的地址,包括环境变量地址、uboot 重定位的地址。
b.串口初始化,打印板卡信息
3. 根据 board_init_f 指定的 uboot 重定位的地址,执行 u-boot 的 relocation 重定位操作
4. 清除 BSS 段
5. 调用 board_init_r 函数,执行后续的板级初始化操作,包括各种外设接口、中断、环境变量等。最后调用
run_main_loop()函数,进入倒计时等待,等待超时后执行 bootcmd 启动内核。
具体流程如下图:

猜你喜欢

转载自blog.csdn.net/mainmaster/article/details/121739484