uboot启动之第一次运行C函数到uboot重定位
接上一篇博文《uboot启动流程之上电启动到第一次准备好C语言运行环境》,本文从board_init_f()开始。
board_init_f定义在uboot/common/board_f.c中(CONFIG_SYS_GENERIC_BOARD=y)。
这里全局变量gd定义在uboot/arch/arm/include/asm/global_data.h中,在29行间接包含(uboot/include/common.h ->uboot/arch/arm/include/asm/global_data.h):
uboot/common/board_f.c
uboot/include/common.h
这个#include中的asm在uboot编译工程中讲过,是一个软连接,连接到uboot/arch/arm/include/asm/
我们去这个uboot/arch/arm/include/asm/global_data.h中看看全局变量gd的定义:
uboot/arch/arm/include/asm/global_data.h
从定义看,gd是一个全局的寄存器变量,占用的寄存器位r8, gd即r8。
前面分析过,r8存储了的数据为地址(0x80000000+16K – GD_SIZE), 即全局变量gd指向(0x80000000+16K – GD_SIZE)。
在行1020-1022在栈上定义一个gd_t变量,并且让gd指向这个地址。
这里为什么要在栈上定义一个gd_t,然后再让gd指向它?这不是改变了gd的指向了吗?
实际上,并没有改变gd指向,gd还是指向r8保存的栈地址。因为arm使用的是空递减栈,即sp指向的是下一次使用的内存地址。在crt0.S中,我们让sp预留了gd_t的内存,即这个预留出的内存就是这里定义的data局部变量。
1025行设置gd->flags为0(r0寄存器值在C函数board_init_f被调用前清零了)。
1027行通过initcall_run_list循环调用init_sequence_f的函数列表。
uboot/lib/initcall.c
init_sequence_f定义了很多函数list:
uboot/common/board_f.c
zero_global_data:将gd指向的内存清零。
mark_bootstage: 设置bootstage为board_init_f。
env_init: 设置环境变量保存的地址。在uboot/common/Makefile中定义如下:
CONFIG_ENV_IS_IN_SPI在uboot/include/autoconf.mk中定义:
所以, env_init为定义在uboot/common/env_spi.c中的env_init, 如下:
default_environment[]定义在uboot/include/env_default.h, 定义了默认的环境变量的值。
所有的默认boot环境变量配置都在文件uboot/include/common.cfg中定义,比如:
bootcmd=bootfmh
bootdelay=0
uboot/include/env_default.h
init_baud_rate: 配置串口波特率为默认值115200
CONFIG_BAUDRATE定义在uboot/include/common.cfg中:
这个CONFIG_SPX_FEATURE_BAUDRATE_CONSOLE_TTY在工程配置文件*.PRJ定义:
这个是特定厂商的配置文件,不做进一步介绍。总之,这个*.PRJ定义的东西,会替换
common.h中对应的宏。
serial_init:初始化一个串口,为uboot cmd交互提供交互控制台。
定义在uboot/drivers/serial/serial.c
serial_init直接调用default_serial_console串口设备start()函数。
在ast2500evb板子中,使用的UART为ns16550, 且CONFIG_SYS_NS16550_SERIAL=y
故, 这里这个default_serial_console定义在uboot/drivers/serial/serial_ns16550.c,
且配置的CONFIG_CONS_INDEX为5.
所以,serial_init就是调用了eserial5_device->start(), 即eserial5_init().
100行中serial_ports[port-1]为CONFIG_SYS_NS16550_COM5(0x1E784000):
serial_ns16550.c -> uboot/include/common.h -> uboot/include/config.h
-> uboot/include/ configs/ast2500evb.h -> uboot/include/configs/ast.cfg
calc_divisor()用于计算每传输一个码元需要的时钟滴答数,计算方式如下:
CONFIG_SYS_NS16550_CLK定义:
uboot/include/configs/ast.cfg
uboot/arch/arm/cpu/astcommon/ast_clk.c.
计算出clock_divisor后,调用NS16550_init()处理:
uboot/drivers/serial/ns16550.c
NS16550_init就是对UART port的寄存器进行配置。
console_init_f:
这个时候还没有std,所以,前面的UART串口就用作console。
如果配置了CONFIG_PRE_CONSOLE_BUFFER=y, 那么
console_init_f就是向UART口输出打印。
uboot/common/console.c
这个putc就是向UART口发送一个字符。
如果没有配置CONFIG_PRE_CONSOLE_BUFFER=y,
那这个print_pre_console_buffer()就是空,什么都不做。
init_func_i2c:
初始化i2c总线。
对于ast2500evb, i2c_init什么也不做,见uboot/arch/arm/cpu/astcommon/ast_i2c.c
uboot/arch/arm/cpu/astcommon/Makefile
uboot/include/autoconf.mk
dram_init:
这里dram初始化就是sdram的信息记录,定义如下:
uboot/board/ast2500evb/ast2500evb.c
sdram寄存器的配置和初始化在*.S中就做过了
(不然你怎么能跑到C语言环境,堆栈指针可是指向这个)
这里保存了sdram的基址CONFIG_SYS_SDRAM_BASE(2G),
大小为CONFIG_SYS_SDRAM_SYS_USED(480M)。
setup_dest_addr:
这里主要是设置uboot relocate到sdram中的地址为sdram的最大地址: 2G+480M.
reserve_mmu:
reserve_mmu主要是在sdram顶端预留TLB的内存(16k).
reserve_uboot:
reserve_uboot主要就是设置uboot monitor relocate的内存地址(整个uboot image size)
大小为bss_end - _start, 见setup_mon_len()
uboot/arch/arm/cpu/arm1176/start.S
reserve_malloc:
reserve malloc区间内存,大小为TOTAL_MALLOC_LEN, 即64k+64k
reserve_board:
reserve gd->bd内存。
reserve_global_data:
在sdram中reserve全局变量内存struct global_data *gd.
uboot/include/common.h
这里将新的gd放在TLB, uboot monitor下面。
reserve_fdt:
reserve FDT(flat device tree)内存。
reserve_stacks:
reserve 中断IRQ使用的堆栈。
IRQ使用的堆栈大小为CONFIG_STACKSIZE_IRQ + CONFIG_STACKSIZE_FIQ, 即4k + 4k.
setup_dram_config:
将sdram的基址,以及大小,保存到gd->bd中。
setup_baud_rate:
保存波特率配置到gd->bd.
setup_board_extra:
保存额外信息到gd->bd, 比如处理器时钟,plb和pci总线频率等。
reloc_fdt:
将FDT信息拷贝到新的FDT内存地址处。
setup_reloc:
计算uboot被relocated后的地址和原始地址之间的偏移offset.
至此,board_init_f的所有工作完成。下面我们总结一下,relocate之后的内存布局:
在uboot/arch/arm/lib/crt0.S, 我们使用了”bl”来跳转到board_init_f,当board_init_f执行完后,程序回到了该跳转语句的下一条指令:
上面的代码很直观:
100行,改变sp到gd->start_addr_sp地址处,即上图中的sp域高地址处。
102-103行,r8为gd新地址处,即上图中的gd域。
105行设置返回地址为”here”。
106-107行改变lr为uboot relocate之后返回地址(当前here地址+uboot relocate offset)。
108行设置uboot relocate到的新地址。
109行跳转到relocate_code处执行.
我们先来看relocate_code怎么做的(注意,此时的sp已经指向sdram内存了,见上图的sp).
uboot/arch/arm/lib/relocate.S
40行设置uboot image当前的start地址。
41行计算relocate addr和当前uboot image的start地址的偏移。
43行设置uboot image当前的end地址。
46-49行从r1(uboot image 当前start地址)处将数据加载到r10-r11, 然后再将r10-r11寄存器的数据存到r0(uboot新地址)处,即完成一次uboot image数据的一次搬移;之后重复这个动作,直到uboot image的__image_copy_start到__image_copy_end段的数据全部被搬移到新地址。
从50行一直到69行在做的就是将uboot image中的”Label”段fix成relocate之后的地址。有关这一段的描写,参考https://blog.****.net/skyflying2012/article/details/37660265。
下图是uboot.lds描述的uboot段:
搬运完之后,来到relocate_done。