目录
链接脚本u-boot.lds详解
U-Boot启动流程详解
reset函数源码详解
lowlevel_init函数详解
s_init函数详解
链接脚本u-boot.lds详解
要分析uboot的启动流程,首先要找到“入口”,找到第一行程序在哪里。程序的链接是由链接脚本来决定的,所以通过链接脚本可以找到程序的入口。如果没有编译过uboot的话链接脚本为arch/arm/cpu/u-boot.lds。但是这个不是最终使用的链接脚本,最终的链接脚本是在这个链接脚本的基础上生成的。编译一下 uboot,编译完成以后就会在 uboot 根目录下生成 u-boot.lds文件,如图所示:
打开u-boot.lds内容如下:
第3行为代码当前入口点:_start, _start在文件arch/arm/lib/vectors.S中有定义,如图所示:
从图中的代码可以看出, start后面就是中断向量表,从图中的".section ".vectors","ax"可以得到,此代码存放在.vectors段里面。
使用如下命令在uboot中查找“_image_copy_start”:
grep -nR"_image_copy_start"
打开u-boot.map,找到如图所示位置:
u-boot.map是uboot的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址,从图中932行可以看到_image_copy_start为0X87800000,而.text的起始地址也是0X87800000。
继续回到u-boot.lds示例代码中,第11行是vectors段,vectors段保存中断向量表,从图_start文件示例代码中我们知道了vectors.S的代码是存在vectors段中的。从图u-boot.map文件示例代码可以看出,vectors段的起始地址也是0X87800000,说明整个uboot的起始地址就是0X87800000。
第12行将arch/arm/cpu/armv7/start.s编译出来的代码放到中断向量表后面。
第13行为text段,其他的代码段就放到这里。
在u-boot.lds中有一些跟地址有关的“变量”需要我们注意一下,后面分析u-boot源码的时候会用到,这些变量要最终编译完成才能确定的!!!比如我编译完成以后这些“变量”的值如表所示:
表中的“变量”值可以在u-boot.map文件中查找,表中除了_image_copy_start,以外,其他的变量值每次编译的时候可能会变化,如果修改了uboot代码、修改了uboot配置、选用不同的优化等级等等都会影响到这些值。所以,一切以实际值为准!
U-Boot启动流程详解
reset函数源码详解
u-boot.lds中我们已经知道了入口点是arch/arm/lib/vectors.S文件中的_start,代码如下:
第35行就是reset 函数。
第37行从reset函数跳转到save_bootparams函数,而save_boot_params函数同样定义在start.S里面,定义如下:
第43行,读取寄存器cpsr中的值,并保存到r0寄存器中。
第44行,将寄存器r0中的值与Ox1F进行与运算,结果保存到r1寄存器中,目的就是提取cpsr的bit0-bit4这5位,这5位为M4 M3 M2 M1 M0, M[4:0]这五位用来设置处理器的工作模式,如表所示:
第45行,判断r1寄存器的值是否等于 0X1A(0b11010),也就是判断当前处理器模式是否处于 Hyp模式。
第46 行,如果r1和Ox1A不相等,也就是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处于SVC模式,并且关闭FIQ和IRQ这两个中断。
继续执行执行下面的代码:
第56行,如果没有定义CONFIG_OMAP44XX和CONFIG_SPL_BUILD的话条件成立,此处条件成立。
第58行读取CP15中c1寄存器的值到r0寄存器中,这里是读取SCTLR 寄存器的值。
第59行,CR V在arch/arm/include/asm/system.h中有如下所示定义:
#define CR_V(1 << 13)/* Vectors relocated to 0xffff0000 */
因此这一行的目的就是清除SCTLR寄存器中的bit13, SCTLR寄存器结构如图所示:
从图可以看出,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行就是设置向量表重定位的。
代码继续往下执行:
第68行如果没有定义CONFIG_SKIP_LOWLEVEL_INIT的话条件成立。我们没有定义CONFIG_SKIP_LOWLEVEL_INIT,因此条件成立,执行下面的语句。
示例代码中的内容比较简单,就是分别调用函数cpu_init_cp15、 cpu_init_crit和_main。
函数cpu_init_ep15用来设置CP15相关的内容,比如关闭MMU啥的,此函数同样在start.S文件中定义的,代码如下:
函数cpu_init_cp15都是一些和CP15有关的内容,我们不用关心,有兴趣的可以详细的看一下。
函数cpu init crit也在是定义在start.S文件中,函数内容如下:
可以看出函数cpu_init_crit内部仅仅是调用了函数lowlevel_init,接下来就是详细的分析一下lowlevel_init和_main这两个函数。
lowlevel_init函数详解
函数lowlevel_init在文件arch/arm/cpu/armv7/lowlevel_init.S中定义,内容如下:
第22行设置sp指向CONFIG_SYS_INIT_SP_ADDR,CONFIG_SYS_INIT_SP_ADDR在include/configs/mx6ullevk.h文件中,在 mx6ullevk.h中有如下所示定义:
示例代码中的IRAM-BASE-ADDR和IRAM-SIZE在文
件.arch/arm/include/asm/arch-mx6/imx-regs.h中有定义,如下所示,其实就是IMX6UL/IM6ULL内部ocram的首地址和大小。
如果408行的条件成立的话IRAM_SIZE=0X40000,当定义了CONFIG_MX6SX、CONFIG_MX6U、CONFIG_MX6SLL和CONFIG_MX6SL中的任意一个的话条件就不成立,在.config中定义了CONFIG MX6UL,所以条件不成立,因此IRAM SIZE=0X20000-128KB,
结合示例代码,可以得到如下值:
CONFIG_SYS_INIT_RAM_ADDR = IRAM_BASE_ADDR = 0x00900000.
CONFIG_SYS_INIT_RAM_SIZE= 0x00020000 = 128KB
还需要知道GENERATED_GBL_DATA_SIZE的值,在文件include/generated/generic-asm-offsets.h中有定义,如下:
GENERATED_GBL_DATA_SIZE-256, GENERATED_GBL_DATA_SIZE的含义为(sizeof(struct global_data) + 15) & ~15。
综上所述,CONFIG_SYS_INIT_SP_ADDR值如下:
CONFIG_SYS_INIT_SP_OFFSET= 0x00020000 - 256 = 0x1FF00。
CONFIG_SYS_INIT_SP_ADDR = 0x00900000 + 0X1FF00 = 0x0091FF00,
结果如下图所示:
此时sp指向0X91FF00,这属于IMX6UL/IMX6ULL的内部ram。
继续回到文件lowlevel_init.S,第23行对sp指针做8字节对齐处理!
第34行, sp指针减去GD_SIZE, GDSIZE同样在generic-asm-offsets.h中定了,大小为248,见示例代码generic-asm-offsets.h第11行。
第35行对sp做8字节对齐,此时sp的地址为0x0091FF00-248-0X0091FE08,此时sp位置如图所示:
第36行将sp地址保存在r9寄存器中。
第42行将ip和Ir压栈
第57行调用函数s_init,得,又来了一个函数。
第58行将第36行入栈的ip和Ir进行出栈,并将Ir赋给pc。
s_init函数详解
知道lowlevel_init函数后面会调用s_init函数, s_init函数定义在文件arch/arm/cpu/army7/mx6/soc.c中,如下所示:
在第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,函数调用路径如图所示:
从图可知,接下来要执行的是save_boot_params_ret中的_main函数,下一章分析_main函数。