uboot启动流程——MIPS
Bootloader 是在操作系统运行之前执行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射表,从而建立适当的系统软硬件环境,为最终调用操作系统内核做好准备。
uboot引导系统启动, UBoot包含两个阶段的启动,一个是SPL启动,一个是正常的启动我们称为第二阶段Uboot。当然,我们也可以选择使用SPL和不使用,主要根据CPU中的SRAM(或者cache,bootram阶段需要初始化完成)的大小,如果不能放下uboot大小,则必须先使用SPL启动,进行DDR的初始化,以获取更大的可以空间。
Version: u-boot-201307
1 | +----------------+-----------------------------------+ |
在编译的过程中,这两个阶段通过CONFIG_SPL_BUILD
宏将编译分离。拥有不同的配置,所以许多地方的宏是和SPL的不一样。而且链接的文件也不一致。
- SPL:
1
./arch/mips/cpu/xburst/x1000/u-boot-spl.lds
- uboot:
1
/arch/mips/cpu/u-boot.lds
目的
流程:
SPL
1 | u-boot-spl.lds |
u-boot-spl.lds
1 |
1 | MEMORY { .sram : ORIGIN = CONFIG_SPL_TEXT_BASE,\ |
在bootram将SPL搬到静态ram中后,执行SPL的代码将从_start
开始。
start.S
1 | #define RESERVED_FOR_SC(x) .space 1536, x |
- 设置spl的空间布局,加载识别区域,SC填充区域等
- 选择MMU类型
- 通过SR,使能异常向量和配置中断屏蔽位
- 配置一个特殊的中断异常入口(0x200)
- 初始化cache
- 跳转
board_init_f
soc.c
数据结构
bd_t
保存板子参数
1 | typedef struct bd_info { |
file: arch/mips/include/asm/u-boot.h
gd_t
全局的系统初始化参数
1 | typedef struct global_data { |
file: include/asm-generic/global_data.h
board_init_f
1 | void board_init_f(ulong dummy) |
file: arch/mips/cpu/xburst/x1000/soc.c
- 初始化GPIO
- 使能串口时钟,初始化串口
- 初始化timer
- 初始化时钟,配置CPU,DDR和外设的时钟大小
- 初始化看门狗
- 初始化DDR
- 清除BSS段
ginfo
1 |
|
系统信息的结构体 (gd 是指 Global Data, bd 是指 Board info Data) 应该存放于在 DRAM 控制器未初始化之前就能使用的空间中,比如TCSM中。
为什么要清除BSS段?
1 | /* Clear the BSS */ |
可执行程序包括BSS段、代码段、数据段。BSS(Block Started by Symbol)通常指用来存放程序中未初始化的全局变量和静态变量的一块内存区域,特点是可读可写,在程序执行之前BSS段会自动清0。所以,未初始化的全局变量在程序执行之前已经成0
bss段起源于unix中。变量分两种,全局变量
和局部变量
。局部变量是保留在栈中的,根据C语言规定,如果对局部变量不进行初始化,初始值是不确定的,在栈中位置也不固定。全局变量有专门的数据段存储,且初始化值为0,且位置是固定的。综上,数据分为俩种,位置固定(全局,数据段)
,位置不固定(局部-栈里)
。
其实,数据段里的这么多全局变量都初始化为0存在目标文件中是没有必要的,增大了存储空间使用
。所以就把数据段里边数据,也即未初始化全局变量存放到了BSS段里边. 并未占有真正的空间。当有目标文件被载入的时候,清除bss段,将全局变量清0
, 其实也是在为bss段分配空间.
board_init_r
1 | void board_init_r(gd_t *dummy1, ulong dummy2) |
file: common/spl/spl.c
- 从存储介质(sd/emmc)读取uboot,并跳转到uboot执行
- 在SPL运行完后,已可以直接加载kernel或相应的BIN文件执行
执行C代码所必需的条件或者环境?
1 | la sp, STACK_TOP // sp |
- 禁止看门狗,防止CPU不断的重启
- 设置堆栈
SPL执行阶段其栈空间的位置?
1 | TCSM |
CPU上电后,在bootrom中执行时,由于其是固化的代码段(只读)。因此在上电初期将Data段,BSS段以及栈指定到TCSM中(一个静态RAM,CPU上电即可以使用)。bootrom中一些外围设备如sd boot的SD控制器等初始化完成后,在SD卡中将SPL加载到TCSM中,bootrom的PC跳入SPL进行执行,此时依然使用bootrom的栈空间。
uboot
1 | u-boot.lds |
u-boot.lds
1 | OUTPUT_ARCH(mips) |
start.S
1 |
1 | .set noreorder |
- 重新设置栈指针
0x80400000
, - 跳转
board_init_f
CONFIG_SYS_SDRAM_BASE = 0x8000 0000
,是 MIPS 虚拟寻址空间中kseg0
段的起始地址(参考《 See MIPS Run 》),它经过 CPU TLB 翻译后是 DRAM 内存的起始物理地址。
为什么不直接跳转,而使用jr
这样就可以知道代码的位置,而不是标号值。
board.c
uboot内存布局:
1 |
1 | +-------------------+ <-+ 0x9000 0000 |
board_init_f
1 | void board_init_f(ulong bootflag) |
- 调用init_sequence 函数队列,对板子进行一些初始化
1 | /* |
- 为uboot在DRAM中执行准备条件
relocate_code
重定位,U-boot运行后将自己的代码段,数据段,BSS 段等搬到DRAM 中的另一个位置继续运行.
目的:
- 为kernel腾出内存的低端空间,防止kernel解压覆盖uboot。
- 对于由静态存储器(spiflash nandflash)启动,这个relocation是必须的,将代码搬到DRAM中运行
1 | relocate_code(addr_sp, id, addr); |
id: 之前在 U-boot 的 1M 空间中分配的 GD 结构体的地址
addr: U-boot 重新定位到 DRAM 之后的代码起始地址
1 | /* |
- 移动gp指针
- 复制代码到RAM中
- 刷新一下cache
- 跳到RAM代码当中去(in_ram),in_ram的主要工作是:更新GOT;清空BSS段;最后跳到
board_init_r
。
疑问
- 如何对函数进行寻址调用
- 如何对全局变量进行寻址操作(读写)
- 对于全局指针变量中存储的其他变量或函数地址在relocation之后如何操作
uboot GOT
GOTs(global offset tables):是uboot能跳转到不同空间运行的原理.
一个完整可运行的bin文件,link时指定的链接地址,load时的加载地址,运行时的运行地址,这3个地址应该是一致的。但是relocation
后运行地址不同于加载地址,特别是链接地址,uboot任何进行函数跳转???
compiler在cc时加入-fpic
或-fpie
选项,会在目标文件中生成GOT(global offset table),将本文件中需要relocate的值存放在GOT中,函数尾部的Label来存储GOT的offset以及其中变量的offset,变量寻址首先根据尾部Label相对寻址找到GOT地址,以及变量地址在GOT中的位置,从而确定变量地址,这样对于目标文件统一修改GOT中的值,就修改了变量地址的offset,完成了relocation。
ld时加入-pie选项,就会将GOT并入到rel.dyn
段中,uboot在relocate_code中统一根据rel.dyn段修改需要relocation的数值
划分RAM
1 | +------------------+ |
board_init_r
This is the next part if the initialization sequence: we are now running from RAM and have a “normal” C environment, i. e. global data can be written, BSS has been cleared, the stack size in not that critical any more, etc.
此时已在DRAM中运行。
1 | void board_init_r(gd_t *id, ulong dest_addr) |
- 初始化串口
- 初始化系统内存分配函数
- 如果使用MMC存储介质,初始化MMC设备
- 初始化环境变量的指针,将 env_ptr 指针及其指向的地址初始化,用来存放环境变量结构体,然后将 flash 中的环境变量拷贝到内存中。
- 初始化sdio设备
- 初始化网络设备
- 进去命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作
main_loop
1 | void main_loop(void) |
file:common/main.c
do_bootm
将内核解压缩,然后调用do_bootm_linux引导内核
do_bootm_linux
启动内核
参考
- uboot的relocation原理详细分析