ddr_sp , r4 = gd, r5= addr
Relocate_code:
(1) 使能I/D cache
(2) 使用新的stack:r1=addr_sp。将gd(global data)保存在 r9中,将relocate code地址保存在r10中
mr r1, r3 /* Set new stack pointer */
mr r9, r4 /* Save copy of Init Data pointer */
mr r10, r5 /* Save copy of Destination Address */
(3) 重定位GOT
(4) Relocate code
(5) Flush D cache
(6) 跳转到in_ram代码中,r10中保存着RAM中代码起始位置,加上in_ram的相对位置即为in_ram在ram中的位置:
addi r0, r10, in_ram - _start + _START_OFFSET
mtlr r0
blr /* NEVER RETURNS! */
in_ram:
(1) 重定位GOT2
(2) Clear bss
(3) 将gd以及代码在内存中的起始位置作为参数,调用board_init_r
mr r3, r9 /* Init Data pointer */
mr r4, r10 /* Destination Address */
bl board_init_r
下面再次进入board.c 文件
board_init_r:
这部分代码初始化一些列外设,这里只提几个感兴趣的地方。
(1) 建立Command table。U-boot支持N多命令,通过一个cmdtbl来管理,这个cmdtbl是在运行时被负值的
for (cmdtp = &__u_boot_cmd_start; cmdtp != &__u_boot_cmd_end; cmdtp++) {……}
u-boot的命令都被定义在.u_boot.cmd段,并在链接时确定期位置:在__u_boot_cmd_start和__u_boot_cmd_end之间。
(2) Trap的安装是通过调用start.s中的代码完成的。其实就是将原有的trap重定位到dest_addr,也就是RAM中的起始位置。
(3) 茫茫多设备的初始化 …………
(4) 进入main_loop()等待用户输入命令,如bootm命令。
三、 Bootm代码分析
目前大多数kernel的booting都由bootm来完成,可以说这个函数是连接U-boot和kernel的桥梁,bootm命令主要通过do_bootm()(/common/cmd_bootm.c)函数和do_bootm_linux()(/lib_ppc/bootm.c)函数来实现。后者是与架构相关的。Powerpc kernel booting有两种方式:1、使用of——open firmware;2、使用dt——device tree。现在高版本的u-boot基本都支持dt启动方式。在制作uImage时会在Image head中加入一段信息,告诉u-boot使用的启动方式。这里只讨论dt启动方式。
其实整个启动过程很简单,do_bootm命令接收命令行参数,根据命令行参数对内核的资源进行有效性校验,然后将所有信息存入image结构。并把这个结构传递给bootm.c。bootm.c 从image中找到内核、ramdisk、cmdline以及FDT,将cmdline保存在固定的位置,然后跳转到kernel所在的地址,并把FDT和kernel本身的地址作为参数传递过去,kernel会根据cmdline中的信息选择启动方式。
1、当用户敲入bootm kernel_addr – fdt_addr时,u-boot调用do_bootm函数。
2、Local memory block initiate。初始化本地存储块(可能是用户kernel内存管理)
3、boot_get_kernel(),这个函数找到kernel image,验证其完整性,并定位内核数据。将信息存放在images中。
fit_parse_conf;取出load_addr,这个地址在uImage编译时确定。
genimg_get_image;如果image在flash上,则拷贝到load_addr内存中。
image_get_type;判断image head的类型,显然kernel应该是IH_TYPE_KERNEL类型的。
image_get_kernel;根据image head中的信息对image进行校验。
image_get_data;取得kernel数据指针。
image_get_data_size;取得kernel大小。
返回image地址
4、判断压缩格式,并解压缩内核
5、调用do_bootm_linux()函数启动内核。
下面分析do_bootm_linux()函数。这个函数比较重要,我们将详细分析之。
(1) 首先为内核命令行参数分配空间,这个空间必须在内核可以访问的高地址,并且不会被其他数据干扰。取出sp值:int sp; asm( "mr %0,1": "=r"(sp) : );// sp=r1。即栈。
(2) sp -= 1024; 留出1k空间,保证访问安全
(3) lmb_reserve(lmb, sp, (CFG_SDRAM_BASE + get_effective_memsize() - sp)); //把这sp以上的空间加入到lmb保护起来。
(4) boot_get_fdt :获取fdt,这段代码较长,我没有细看。主要就是从image中获取tdt的地址和大小。
(5) boot_get_cmdline:获取命令行参数,在lmb中申请一段空间,然后把uboot中bootargs的内容复制到里面。
(6) boot_get_kbd:获取board info,也就是从gd(global data)中将bd复制到lmb中。
(7) 找到内核起始位置
(8) 找ramdisk。如果有,首先判断是否有效,是否是initrd,然后拷贝到RAM中的rd_load地址,这个地址在image 结构中有定义。
(9) boot_relocate_fdt :将fdt定位到lmb中
(10) 启动内核,将fdt参数和内核本身参数传递给内核。