U-Boot启动流程详解

参考:U-Boot顶层目录链接脚本文件(u-boot.lds)介绍
作者:一只青木呀
发布时间: 2020-10-23 13:52:23
网址:https://blog.csdn.net/weixin_45309916/article/details/109240625

目录

  • 链接脚本 u-boot.lds 详解
    • 1、u-boot.lds文件
    • 2、arch/arm/lib/vectors.S 文件
    • 3、u-boot.map(地址映射文件)
    • 4、链接文件分析
    • 总结
  • U-Boot 启动流程详解
    • reset 函数源码详解
    • lowlevel_init 函数详解
    • s_init 函数详解
    • _main 函数详解
    • board_init_f 函数详解(初始化外设、uboot重定位给Linux腾空间)
    • relocate_code 函数详解
    • relocate_vectors 函数详解
    • board_init_r 函数详解
    • run_main_loop 函数详解
    • cli_loop 函数详解
    • cmd_process 函数详解
  • bootz 启动 Linux 内核过程
    • images 全局变量
    • do_bootz 函数(bootz启动内核命令的执行函数)
    • bootz_start 函数
    • do_bootm_states 函数
    • bootm_os_get_boot_func 函数
    • do_bootm_linux 函数

上一章我们详细的分析了 uboot 的顶层 Makefile,理清了 uboot 的编译流程。本章我们来详细的分析一下 uboot 的启动流程,理清 uboot 是如何启动的。通过对 uboot 启动流程的梳理,我们就可以掌握 ①外设是在哪里被初始化的,这样当我们需要修改这些外设驱动的时候就会心里有数。另外,通过分析 uboot 的启动流程可以 ②了解 Linux 内核是如何被启动的

链接脚本 u-boot.lds 详解

要分析 uboot 的启动流程,首先要找到“入口”,找到第一行程序在哪里。程序的链接是由链接脚本(链接脚本是编译生成的)来决定的,所以通过链接脚本可以找到程序的入口。如果没有编译过 uboot 的话链接脚本为 arch/arm/cpu/u-boot.lds。但是这个不是最终使用的链接脚本,最终的链接脚本是在这个链接脚本的基础上生成的。编译一下 uboot,编译完成以后就会在 uboot 根目录下生成 u-boot.lds
文件,如下图所示:
U-Boot启动流程详解_第1张图片
只有编译 u-boot 以后才会在根目录下出现 u-boot.lds 文件!

1、u-boot.lds文件

打开 u-boot.lds,内容如下:
U-Boot启动流程详解_第2张图片
U-Boot启动流程详解_第3张图片
第 3 行为代码当前入口点: _start, _start 在文件 arch/arm/lib/vectors.S 中有定义,如图下所示:

2、arch/arm/lib/vectors.S 文件

U-Boot启动流程详解_第4张图片
从上图可以看出, _start 后面就是中断向量表,从图中的“.section “.vectors”, "ax”可以得到,此代码存放在.vectors 段里面。

3、u-boot.map(地址映射文件)

使用如下命令在uboot 中查找“__image_copy_start”:(上图第一节第十行代码处)

grep -nR "__image_copy_start"

搜索结果如图32.1.3 所示:

U-Boot启动流程详解_第5张图片

打开 u-boot.map(地址映射文件):
U-Boot启动流程详解_第6张图片
u-boot.map 是 uboot 的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址,从上图932 行可以看到 __image_copy_start 为 0X87800000,而.text 的起始地址也是0X87800000

4、链接文件分析

.text(代码段) 描述
*(.__image_copy_start) uboot 拷贝的首地址

在链接文件中第 10 行*(._image_copystart) 在映射文件中可以看到地址为 0X87800000,而.text 的起始地址也是0X87800000。

在链接文件中第 11 行是 vectors 段, vectors 段保存中断向量表(裸机部分讲过),从u-boot.lds文件我们知道了 vectors.S 的代码是存在 vectors 段中的。从地址映射文件中, vectors 段的起始地址也是 0X87800000,说明整个 uboot 的起始地址就是 0X87800000,这也是为什么我们裸机例程的链接起始地址选择0X87800000 了,目的就是为了和uboot 一致。

在链接文件中第 12 行将 arch/arm/cpu/armv7/start.s 编译出来的.o代码放到中断向量表后面(参照上图u-boot.map的代码)。

在链接文件中第 13 行为 text 段,其他的代码段就放到这里

在链接文件中第 16 行 .rodata只读数据段(一般存放常量)

在链接文件中第 18 行,数据段 (一般存放已初始化的全局和静态变量)

在链接文件中第 24 行 ,.u_boot_list段

在链接文件中第 28 行, .image_copy_end:uboot 拷贝的结束地址

在链接文件中第 32 行,.rel_dyn_start:.rel.dyn 段起始地址

在链接文件中第 39 行,.rel_dyn_end:.rel_dyn段结束地址

在链接文件中第 52 行,.bss_start:.bss 段起始地址(静态数据区,一般存放未初始化的全局和静态变量)

在链接文件中第 61 行,.bss_end:.bss段结束

总结

在u-boot.lds 中有一些跟地址有关的“变量”需要我们注意一下,后面分析u-boot 源码的时候会用到,这些变量要最终编译完成才能确定的!!!比如我编译完成以后这些“变量”的值如表所示:

变量 数值 描述
*(.vectors) 0x87800000 中断向量表
arch/arm/cpu/armv7/start.o 0x87800300 strrt.c
__image_copy_start 0x87800000 uboot 拷贝的首地址
__image_copy_end 0x8785dd54 uboot 拷贝的结束地址
__rel_dyn_start 0x8785dd54 .rel.dyn 段起始地址
__rel_dyn_end 0x878668f4 .rel.dyn 段结束地址
_image_binary_end 0x878668f4 镜像结束地址
__bss_start 0x8785dd54 .bss 段起始地址
__bss_end 0x878a8e74 .bss 段结束地址

上表中的“变量”值可以在 u-boot.map 文件中查找,上表中除了__image_copy_start以外,其他的变量值每次编译的时候可能会变化,如果修改了 uboot 代码、修改了 uboot 配置、选用不同的优化等级等等都会影响到这些值。所以,一切以实际值为准。

U-Boot 启动流程详解

reset 函数源码详解

从u-boot.lds 中我们已经知道了入口点是arch/arm/lib/vectors.S 文件中的_start,代码如下:

U-Boot启动流程详解_第7张图片
第48 行_start 开始的是中断向量表,其中54~61 行就是中断向量表,和我们裸机例程里面一样。54 行跳转到reset 函数里面,reset 函数在arch/arm/cpu/armv7/start.S 里面,代码如下:
U-Boot启动流程详解_第8张图片
第35 行就是reset 函数。
第37 行从reset 函数跳转到了save_boot_params 函数,而save_boot_params 函数同样定义在start.S 里面,定义如下:

U-Boot启动流程详解_第9张图片
save_boot_params 函数也是只有一句跳转语句,跳转到save_boot_params_ret 函数(为啥不直接跳转到呢,搞得这么麻烦),
save_boot_params_ret 函数代码如下:

U-Boot启动流程详解_第10张图片

第43 行,读取寄存器cpsr(程序状态寄存器,前面架构部分讲过) 中的值,并保存到r0 寄存器中。

第44 行,将寄存器r0 中的值与0X1F 进行与运算,结果保存到r1 寄存器中,目的就是提取cpsr 的bit0~bit4 这5 位,这5 位为M4 M3 M2 M1 M0,M[4:0]这五位用来设置处理器的工作模式,如表32.2.1.1 所示:

U-Boot启动流程详解_第11张图片

第45 行,判断r1 寄存器的值是否等于0X1A(0b11010),也就是判断当前处理器模式是否处于Hyp 模式。

第46 行,如果r1 和0X1A 不相等,也就是CPU 不处于Hyp 模式的话就将r0 寄存器的bit0~5 进行清零,其实就是清除模式位。

第47 行,如果处理器不处于Hyp 模式的话就将r0 的寄存器的值与0x13 进行或运算,0x13=0b10011,也就是设置处理器进入SVC 模式。

第48 行,r0 寄存器的值再与0xC0 进行或运算,那么r0 寄存器此时的值就是0xD3,cpsr的I 为和F 位分别控制IRQ 和FIQ 这两个中断的开关,设置为1 就关闭了FIQ 和IRQ!

第49 行,将r0 寄存器写回到cpsr 寄存器中。完成设置CPU 处于SVC32 模式,并且关闭FIQ 和IRQ 这两个中断。

继续执行执行下面的代码:

U-Boot启动流程详解_第12张图片
第56 行,如果没有定义CONFIG_OMAP44XX 和CONFIG_SPL_BUILD 的话条件成立,此处条件成立。

第58 行读取CP15 中c1 寄存器的值到r0 寄存器中,根据17.1.4 小节可知,这里是读取SCTLR 寄存器的值。

第59 行,CR_V 在arch/arm/include/asm/system.h 中有如下所示定义:

#define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */

因此这一行的目的就是清除SCTLR 寄存器中的bit13,SCTLR 寄存器结构如图32.2.1.1 所示:

U-Boot启动流程详解_第13张图片

从图32.2.1.1 可以看出,bit13 为V 位,此位是向量表控制位,当为0 的时候向量表基地址为0X00000000,软件可以重定位向量表。为1 的时候向量表基地址为0XFFFF0000,软件不能重定位向量表。这里将V 清零目的就是为了接下来的向量表重定位,这个我们在第十七章有过详细的介绍了。

第60 行将r0 寄存器的值重写写入到寄存器SCTLR 中。

第63 行设置r0 寄存器的值为_start,_start 就是整个uboot 的入口地址,其值为0X87800000,相当于uboot 的起始地址,因此0x87800000 也是向量表的起始地址。

第64 行将r0 寄存器的值(向量表值)写入到CP15 的c12 寄存器中,也就是VBAR 寄存器。因此第58~64 行就是设置向量表重定位的。

代码继续往下执行:

U-Boot启动流程详解_第14张图片
第68 行如果没有定义CONFIG_SKIP_LOWLEVEL_INIT 的话条件成立。我们没有定义CONFIG_SKIP_LOWLEVEL_INIT,因此条件成立,执行下面的语句。

示例代码32.2.1.6 中的内容比较简单,就是分别调用函数cpu_init_cp15、cpu_init_crit 和_main。

函数cpu_init_cp15 用来设置CP15 相关的内容,比如关闭MMU 啥的,此函数同样在start.S文件中定义的,代码如下:

U-Boot启动流程详解_第15张图片
函数cpu_init_cp15 都是一些和CP15 有关的内容,我们不用关心,有兴趣的可以详细的看一下。

函数cpu_init_crit 也在是定义在start.S 文件中,函数内容如下:

U-Boot启动流程详解_第16张图片
可以看出函数cpu_init_crit 内部仅仅是调用了函数lowlevel_init,接下来就是详细的分析一下lowlevel_init 和_main 这两个函数。

lowlevel_init 函数详解

函数lowlevel_init 在文件arch/arm/cpu/armv7/lowlevel_init.S 中定义,内容如下:
U-Boot启动流程详解_第17张图片
在这里插入图片描述

第22 行设置sp 指向CONFIG_SYS_INIT_SP_ADDR,CONFIG_SYS_INIT_SP_ADDR 在include/configs/mx6ullevk.h 文件中,在mx6ullevk.h 中有如下所示定义:

U-Boot启动流程详解_第18张图片
示例代码32.2.2.2 中的IRAM_BASE_ADDR 和IRAM_SIZE 在文件
arch/arm/include/asm/arch-mx6/imx-regs.h 中有定义,如下所示,其实就是IMX6UL/IM6ULL 内部ocram 的首地址和大小

U-Boot启动流程详解_第19张图片
U-Boot启动流程详解_第20张图片

U-Boot启动流程详解_第21张图片
U-Boot启动流程详解_第22张图片
U-Boot启动流程详解_第23张图片
U-Boot启动流程详解_第24张图片

s_init 函数详解

在上一小节中,我们知道lowlevel_init 函数后面会调用s_init 函数,s_init 函数定义在文件arch/arm/cpu/armv7/mx6/soc.c 中,如下所示:
U-Boot启动流程详解_第25张图片
在第816 行会判断当前CPU 类型,如果CPU 为MX6SX、MX6UL、MX6ULL 或MX6SLL中的任意一种,那么就会直接返回,相当于s_init 函数什么都没做。所以对于I.MX6UL/I.MX6ULL 来说,s_init 就是个空函数。从s_init 函数退出以后进入函数lowlevel_init,但是lowlevel_init 函数也执行完成了,返回到了函数cpu_init_crit,函数cpu_init_crit 也执行完成了,最终返回到save_boot_params_ret,函数调用路径如图32.2.3.1 所示:

U-Boot启动流程详解_第26张图片

从图32.2.3.1 可知,接下来要执行的是save_boot_params_ret 中的_main 函数,接下来分析_main 函数。

_main 函数详解

第93 行,调用board_init_f 函数,此函数定义在文件common/board_f.c 中!主要用来初始化DDR,定时器,完成代码拷贝等等,此函数我们后面在详细的分析。

第103 行,重新设置环境(sp 和gd)、获取gd->start_addr_sp 的值赋给sp,在函数board_init_f中会初始化gd 的所有成员变量,其中gd->start_addr_sp=0X9EF44E90,所以这里相当于设置
sp=gd->start_addr_sp=0X9EF44E90。0X9EF44E90 是DDR 中的地址,说明新的sp 和gd 将会存放到DDR 中,而不是内部的RAM 了。GD_START_ADDR_SP=64,参考示例代码32.2.2.4。

这个就是_main 函数的运行流程,在_main 函数里面调用了board_init_f、relocate_code、relocate_vectors 和board_init_r 这4 个函数,接下来依次看一下这4 个函数都是干啥的。

board_init_f 函数详解(初始化外设、uboot重定位给Linux腾空间)

_main 中会调用board_init_f 函数,board_init_f 函数主要有两个工作:

  • ①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。
  • ②、初始化gd 的各个成员变量,uboot 会将自己重定位到DRAM 最后面的地址区域,也就是将自己拷贝到DRAM 最后面的内存区域中。这么做的目的是给Linux 腾出空间,防止Linux kernel 覆盖掉uboot,将DRAM 前面的区域完整的空出来。在拷贝之前肯定要给uboot 各部分分配好内存位置和大小,比如gd 应该存放到哪个位置,malloc 内存池应该存放到哪个位置等等。这些信息都保存在gd 的成员变量中,因此要对gd 的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位uboot 的时候就会用到这个内存“分配图”。

。。代码未贴出:

第9 行,board_early_init_f 函数,板子相关的早期的一些初始化设置,I.MX6ULL 用来初始化串口的IO 配置

第10 行,timer_init,初始化定时器,Cortex-A7 内核有一个定时器,这里初始化的就是Cortex-A 内核的那个定时器。通过这个定时器来为uboot 提供时间。就跟Cortex-M 内核Systick 定时器一样。关于Cortex-A 内部定时器的详细内容,请参考文档《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》的“Chapter B8 The Generic Timer”章节。

第11 行,board_postclk_init,对于I.MX6ULL 来说是设置VDDSOC 电压。

第12 行,get_clocks 函数用于获取一些时钟值,I.MX6ULL 获取的是sdhc_clk 时钟,也就是SD 卡外设的时钟。

第13 行,env_init 函数是和环境变量有关的,设置gd 的成员变量env_addr,也就是环境变量的保存地址。

第14 行,init_baud_rate 函数用于初始化波特率,根据环境变量baudrate 来初始化gd->baudrate。

第15 行,serial_init,初始化串口。

第16 行,console_init_f,设置gd->have_console 为1,表示有个控制台,此函数也将前面暂存在缓冲区中的数据通过控制台打印出来。

第17 行、display_options,通过串口输出一些信息,如图32.2.5.1 所示:

在这里插入图片描述

第19 行,print_cpuinfo 函数用于打印CPU 信息,结果如图32.2.5.3 所示:

在这里插入图片描述
第20 行,show_board_info 函数用于打印板子信息,会调用checkboard 函数,结果如图32.2.5.4 所示:
U-Boot启动流程详解_第27张图片
第23 行,init_func_i2c 函数用于初始化I2C,初始化完成以后会输出如图32.2.5.5 所示信息:
U-Boot启动流程详解_第28张图片


第44 行,setup_dest_addr 函数,设置目的地址,设置gd->ram_size,gd->ram_top,gd->relocaddr这三个的值。接下来我们会遇到很多跟数值有关的设置,如果直接看代码分析的话就太费时间了,我可以修改uboot 代码,直接将这些值通过串口打印出来,比如这里我们修改文件common/board_f.c,因为setup_dest_addr 函数定义在文件common/board_f.c 中,在setup_dest_addr函数输入如图32.2.5.6 所示内容:

U-Boot启动流程详解_第29张图片
设置好以后重新编译uboot,然后烧写到SD 卡中,选择SD 卡启动,重启开发板,打开SecureCRT,uboot 会输出如图32.2.5.7 所示信息:

在这里插入图片描述
从图32.2.5.7 可以看出:

gd->ram_size = 0X20000000 //ram 大小为0X20000000=512MB
gd->ram_top = 0XA0000000 //ram 最高地址为0X80000000+0X20000000=0XA0000000
gd->relocaddr = 0XA0000000 //重定位后最高地址为0XA0000000

第45 行,reserve_round_4k 函数用于对gd->relocaddr 做4KB 对齐,因为
gd->relocaddr=0XA0000000,已经是4K 对齐了,所以调整后不变。

第46 行,reserve_mmu,留出MMU 的TLB 表的位置,分配MMU 的TLB 表内存以后会对gd->relocaddr 做64K 字节对齐。完成以后gd->arch.tlb_size、gd->arch.tlb_addr 和gd->relocaddr如图32.2.5.8 所示:

在这里插入图片描述
从图32.2.5.8 可以看出:

gd->arch.tlb_size= 0X4000 //MMU 的TLB 表大小
gd->arch.tlb_addr=0X9FFF0000 //MMU 的TLB 表起始地址,64KB 对齐以后
gd->relocaddr=0X9FFF0000 //relocaddr 地址

第47 行,reserve_trace 函数,留出跟踪调试的内存,I.MX6ULL 没有用到!

第48 行,reserve_uboot,留出重定位后的uboot 所占用的内存区域,uboot 所占用大小由gd->mon_len 所指定,留出uboot 的空间以后还要对gd->relocaddr 做4K 字节对齐,并且重新设置gd->start_addr_sp,结果如图32.2.5.9 所示:

在这里插入图片描述

。。。

在这里插入图片描述
从图32.2.5.16 可以看出,uboot 重定位后的偏移为0X18747000,重定位后的新地址为0X9FF4700,新的gd 首地址为0X9EF44EB8,最终的sp 为0X9EF44E90。

至此,board_init_f 函数就执行完成了,最终的内存分配如图32.2.5.16 所示:
U-Boot启动流程详解_第30张图片

relocate_code 函数详解

relocate_vectors 函数详解

board_init_r 函数详解

run_main_loop 函数详解

cli_loop 函数详解

cmd_process 函数详解

bootz 启动 Linux 内核过程

uboot启动Linux使用bootz命令,①bootz命令是如何启动Linux内核?②uboot的生命是怎么终止的?③Linux又是怎么启动的呢?

images 全局变量

不管是bootz命令 还是bootm 命令,在启动Linux 内核的时候都会用到一个重要的全局变量:images,images 在文件cmd/bootm.c 中有如下定义:

在这里插入图片描述
images 是bootm_headers_t 类型的全局变量,bootm_headers_t 是个boot 头结构体,在文件include/image.h 中的定义如下(删除了一些条件编译代码):
U-Boot启动流程详解_第31张图片
U-Boot启动流程详解_第32张图片

第335 行的os 成员变量是image_info_t 类型的,为系统镜像信息。
第352~362 行这11 个宏定义表示BOOT 的不同阶段。

接下来看一下结构体image_info_t,也就是系统镜像信息结构体,此结构体在文件include/image.h 中的定义如下:
U-Boot启动流程详解_第33张图片
全局变量images 会在bootz 命令的执行中频繁使用到,相当于Linux 内核启动的“灵魂”

U-Boot启动流程详解_第34张图片

U-Boot启动流程详解_第35张图片
下面详细介绍各个函数:

do_bootz 函数(bootz启动内核命令的执行函数)

bootz 命令的执行函数为do_bootz,在文件cmd/bootm.c 中有如下定义:

bootz_start 函数

do_bootm_states 函数

bootm_os_get_boot_func 函数

do_bootm_linux 函数

你可能感兴趣的:(正点IMX6ULL系统移植,linux)