天天看点

Uboot启动分析--__main分析(1)

arch/arm/lib/crt0_64.S

上一节start.S主要做了以下工作:

reset初始化,save_boot_params(nxp定义)保存参数到大小256byte的sram中

设置cpu状态:小端模式,MMU 、 i/d cache 都关闭,段定义;

判断当前的异常等级并将中断向量的地址写到各个EL3/2/1对应的VBAR寄存器中;

打开EA、FIQ、IRQ、NS四种中断;

打开SIMD和FP浮点运算功能;

配置cntfrq_el0 系统时钟计数器的频率。

最后主核跳转_main,从核等待主核唤醒。

_main函数主要完成的工作:

先设置用于调用board_init_f()函数的初始环境,该环境仅仅是提供了堆栈和存储位置GD(‘global data’)结构,两者都是位于可以使用的RAM(SRAM,locked cache…)中,在调用board_init_f()函数前,GD应该被清0;

调用board_init_f()函数,该函数的功能为从system RAM(DRAM,DDR…)中执行准备硬件,当system RAM还不能够使用的时,必须要使用目前的GD存储传递到后续阶段的所有数据,这些数据包括重定位的目标,将来的堆栈和GD的位置;

设置中间环境,其中堆栈和GD是由board_init_f()函数在system RAM中进行分配的,但此时的bss和初始化的非常量仍然不能使用;

对于正常的uboot引导(非SPL),调用relocate_code()函数,该函数的功能将uboot从当前的位置重新转移到由board_init_f()函数计算的目标位置;

对于SPL,board_init_f()函数仅仅是返回(crt0),没有代码的重定位;

设置用于调用board_init_r()函数的最终环境,该环境将有bss段(初始化为0),初始化非常量数据(初始化为预期值),并入栈到system RAM中,GD保留了board_init_f()函数设置的值;

为了使uboot正常运行(非SPL),某些CPU还有一些关于内存的工作要做,调用c_runtime_cpu_setup()函数;

调用board_init_r()函数。

首先加载spl的栈地址,将其保存到x0中,对其进行16byte对齐后指向sp指针。在board_init_f_alloc_reserve之前保存sp指针栈到x0,board_init_f_alloc_reserve执行后需要还原sp栈指针。

从“top”(这里指sp指针)地址分配保留空间用作“全局变量”,返回分配空间的“top”地址(返回sp指针地址)

GD 在 16 字节边界上向下对齐。早期的 malloc area 未对齐,因此它遵循堆栈我们正在构建的架构的对齐约束。GD 是最后分配的,所以这个函数的返回值是保留区的底部和GD的地址,都应该调用上下文需要它。

board_init_f_alloc_reserve预留了早期malloc的空间和global_data结构体空间,大小为SYS_MALLOC_F_LEN=0x10000(65536bytes),board_init_f_init_reserve现在可以开始初始化gd结构了。board_init_f_init_stack_protection_addr为gd->start_addr_sp设置了spl的栈指针,然后sp向上对齐16字节。将早期malloc空间的基地址保存到gd->malloc_base;board_init_f_init_stack_protection只有在设置完spl的栈指针才可以进行memset。

bootrom是固化在芯片内部的一块rom,初始化各种接口,并从中读取内容加载到片内SRAM中。因为存储设备接口相对简单,大部分不需要适配即可存取。但是DDR等需要修改代码进行适配。

所以就需要spl,spl被加载到片内SRAM中,片内SRAM不需要初始化即可运行,但是容量有限。spl运行起来后进行必要的初始化后,初始化DDR,并将uboot从存储设备中读到DDR中。

uboot运行在DDR中,则不受空间大小限制,可以进行复杂的操作。支持包括不同文件系统、脚本执行、多种操作系统加载等等操作。其中主要的工作是从存储设备中读取kernel,解析后跳转到kernel执行。

准备完sp栈和早期malloc空间后,清空x0寄存器,调用board_init_f(定义在board/freescale/imx8mp_evk/spl.c)。这里i.mx系列对board_init_f函数进行了覆盖,实现了自己的启动步骤。

uboot重定位,将uboot从cpu内部flash拷贝到ram中。从image_copy_start开始拷贝,截止到image_copy_end。x1寄存器熟先存储着内部ram中uboot的image_copy_start,而x2寄存器存储的是image_copy_end。拷贝实现的关键指令ldp和stp。拷贝路径是x1->x11->x0,直至到x2中存储的image_copy_end末尾。

当x0中的数据长度和x1中的数据长度相等时,停止拷贝重定位完成,调用relocate_done。switch_el宏获取异常登记并跳转到对应标签处。这部分在start.S中已经分析过了。不管是异常EL3、EL2、EL1,都会走到标签0。关闭i-cache,加入指令屏障。

对于重定位后链接地址与运行地址不一致的解决办法就是使用位置无关码,在uboot编译使用ld链接的时候使用参数"-pie"可生成与位置无关的可执行程序,使用该参数后,会生成一个.rel.dyn段,uboot则是靠该段去修复重定位后产生的问题的,在uboot的反汇编文件中,有.rel.dyn段代码。

重定位各异常等级的vbar,即重定位中断向量表。

uboot已经在relocate_code中被拷贝到ram中了,还需要重定位spl 的gd栈。这个函数功能就是计算栈的位置,使用memcpy将gd结构拷贝给一个新的gd结构,并返回新的gd栈指针。

在board_init_r之前必须要清bss段,不清零的话在首次使用全局变量的时候会发生一些错误。

/common/board_r.c

init_sequence_r中存储着一系列的初始化函数,initcall_run_list确保了各系统初始化的顺序运行。根据CONFIG_XX来使能相应的驱动。最后run_main_loop进入循环。

其中有一些是需要厂商覆盖的,比如board_init 、board_late_init。

这一篇的重点内容:uboot是如何从sdram拷贝到ram中的?