(3)U-Boot 启动流程详解
- lowlevel_init 函数详解
函数 lowlevel_init 在文件 arch/arm/cpu/armv7/lowlevel_init.S 中定义
#include <asm-offsets.h>#include <config.h>#include <linux/linkage.h>ENTRY(lowlevel_init) /* * Setup a temporary stack. Global data is not available yet. */ ldr sp, =CONFIG_SYS_INIT_SP_ADDR /* 宏定义在include/configs/mx6ullevk.h sp 指向 0X91FF00,这属于 IMX6UL/IMX6ULL 的外部 ram*/ bic sp, sp, #7 /* 8-byte alignment for ABI compliance */#ifdef CONFIG_SPL_DM mov r9, #0#else /* * Set up global data for boards that still need it. This will be * removed soon. */#ifdef CONFIG_SPL_BUILD ldr r9, =gdata#else sub sp, sp, #GD_SIZE /* GD_SIZE = 248 ,定义在 generic-asm-offsets.h*/ bic sp, sp, #7 /*八字节对齐*/ mov r9, sp /*SP = 0X0091FF00-248=0X0091FE08*/#endif#endif /* * Save the old lr(passed in ip) and the current lr to stack */ push {ip, lr} /* * Call the very early init function. This should do only the * absolute bare minimum to get started. It should not: * * - set up DRAM * - use global_data * - clear BSS * - try to start a console * * For boards with SPL this should be empty since SPL can do all of * this init in the SPL board_init_f() function which is called * immediately after this. */ bl s_init /*跳转到 s_init ,实现初始化*/ pop {ip, pc} /*弹出堆栈,并将lr的值返回给PC*/ENDPROC(lowlevel_init)
- s_init 函数详解
上一节的初始化跳转到 s_init ,该函数在定义在 arch/arm/cpu/armv7/mx6/soc.c 的808行。
void s_init(void){ struct anatop_regs *anatop = (struct anatop_regs *)ANATOP_BASE_ADDR; struct mxc_ccm_reg *ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR; u32 mask480; u32 mask528; u32 reg, periph1, periph2; if (is_cpu_type(MXC_CPU_MX6SX) || is_cpu_type(MXC_CPU_MX6UL) || is_cpu_type(MXC_CPU_MX6ULL) || is_cpu_type(MXC_CPU_MX6SLL)) return; //判断到CPU类型是 MXC_CPU_MX6ULL ,返回 /*省略。。。*/}
函数返回到 cpu_init_crit ,而后又返回到 save_boot_params_ret, 继续执行 _main 函数。
- _main 函数详解
_main 函数在文件 arch/arm/lib/crt0.S 中
/* * entry point of crt0 sequence */ENTRY(_main)/* * Set up initial C runtime environment and call board_init_f(0). */#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) ldr sp, =(CONFIG_SPL_STACK)#else ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) //设置 sp = 0X0091FF00#endif#if defined(CONFIG_CPU_V7M) /* 未定义 v7M forbids using SP as BIC destination */ mov r3, sp bic r3, r3, #7 mov sp, r3#else bic sp, sp, #7 /* 8-byte alignment for ABI compliance */#endif mov r0, sp bl board_init_f_alloc_reserve /*此函数有一个参数,参数为 r0 中的值, 留出晚期的 malloc 内存区域和 gd 内存区域,该函数返回 top=0X0091FA00。该函数定义在 common/init/board_init.c*/ mov sp, r0 /*将 board_init_f_alloc_reserve 返回值 0X0091FA00 放入堆栈指针*/ /* set up gd here, outside any C code */ mov r9, r0 /* r9 寄存器寄存着全局变量 gd 的地址*/ bl board_init_f_init_reserve /*此函数用于初始化 gd, 同时设置 gd->malloc_base=0X0091FB00,这个也就是 early malloc 的起始地址。 该函数定义在 common/init/board_init.c -68*/ mov r0, #0 bl board_init_f /*初始化 DDR,定时器,实现代码拷贝等等, 定义在common/board_f.c -1037*/#if ! defined(CONFIG_SPL_BUILD)/* * Set up intermediate environment (new sp and gd) and call * relocate_code(addr_moni). Trick here is that we'll return * 'here' but relocated. *//*从新设置环境(sp 和 gd)、获取 gd->start_addr_sp 的值赋给 sp,在函数 board_init_f中会初始化 gd 的所有成员变量,其中 gd->start_addr_sp=0X9EF44E90, 所以这里相当于设置 sp=gd->start_addr_sp=0X9EF44E90。 0X9EF44E90 是 DDR 中的地址,阐明新的 sp 和 gd 将会寄存到 DDR 中,而不是外部的 RAM 了。 GD_START_ADDR_SP=64,*/ ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */#if defined(CONFIG_CPU_V7M) /* 未定义 v7M forbids using SP as BIC destination */ mov r3, sp bic r3, r3, #7 mov sp, r3#else bic sp, sp, #7 /* 8-byte alignment for ABI compliance */#endif ldr r9, [r9, #GD_BD] /* r9 = gd->bd GD_BD=0,计算出新的 gd 的地位,并赋给 r9*/ sub r9, r9, #GD_SIZE /* new GD is below bd 新的 gd 在 bd 上面,所以 r9 减去 gd 的大小就是新的 gd 的地位,获取到新的 gd 的地位当前赋值给 r9。*/ adr lr, here /*设置 lr 寄存器为 here,这样前面执行其余函数返回的时候就返回到了第 122 行的 here 地位处*/ ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off GD_RELOC_OFF=68*/ /*lr 寄存器的值加上 r0 寄存器的值,从新赋值给 lr 寄存器。因为接下来要重定位代码,也就是把代码拷贝到新的中央去(当初的 uboot 寄存的起始地址为 0X87800000,上面要将 uboot 拷贝到 DDR 最初面的地址空间出,将 0X87800000 开始的内存空进去),其中就包含here,因而 lr 中的 here 要应用重定位后的地位。*/ add lr, lr, r0#if defined(CONFIG_CPU_V7M) orr lr, #1 /* As required by Thumb-only */#endif ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr 读取 gd->relocaddr 的值赋给 r0 寄存器,此时 r0 寄存器就保留着 uboot 要拷贝的目标地址,为 0X9FF47000。 GD_RELOCADDR=48*/ b relocate_code /*此函数负责将 uboot 拷贝到新的中央去,此函数定义在文件 arch/arm/lib/relocate.S -67*/here:/* * now relocate vectors */ bl relocate_vectors /*对中断向量表做重定位, arch/arm/lib/relocate.S -16*//* Set up final (full) environment */ bl c_runtime_cpu_setup /* we still call old routine here arch/arm/cpu/armv7/start.S -77*/#endif#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)# ifdef CONFIG_SPL_BUILD /* Use a DRAM stack for the rest of SPL, if requested */ bl spl_relocate_stack_gd cmp r0, #0 movne sp, r0 movne r9, r0# endif ldr r0, =__bss_start /* this is auto-relocated! */#ifdef CONFIG_USE_ARCH_MEMSET ldr r3, =__bss_end /* this is auto-relocated! */ mov r1, #0x00000000 /* prepare zero to clear BSS */ subs r2, r3, r0 /* r2 = memset len */ bl memset#else ldr r1, =__bss_end /* this is auto-relocated! */ mov r2, #0x00000000 /* prepare zero to clear BSS */clbss_l:cmp r0, r1 /* while not at end of BSS */#if defined(CONFIG_CPU_V7M) itt lo#endif strlo r2, [r0] /* clear 32-bit BSS word */ addlo r0, r0, #4 /* move to next */ blo clbss_l#endif /*147-159,革除BSS段*/#if ! defined(CONFIG_SPL_BUILD) bl coloured_LED_init bl red_led_on#endif /* call board_init_r(gd_t *id, ulong dest_addr) */ mov r0, r9 /* gd_t 设置 board_init_r 第一个参数*/ ldr r1, [r9, #GD_RELOCADDR] /* dest_addr 设置 board_init_r 第二个参数*/ /* call board_init_r */#if defined(CONFIG_SYS_THUMB_BUILD) ldr lr, =board_init_r /* this is auto-relocated! */ bx lr#else ldr pc, =board_init_r /* this is auto-relocated! common/board_r.c -997*/#endif /* we should not return here. */#endifENDPROC(_main)
- board_init_f 函数详解
board_init_f 函数次要有两个工作:
①、初始化一系列外设,比方串口、定时器,或者打印一些音讯等。
②、初始化 gd 的各个成员变量, uboot 会将本人重定位到 DRAM 最初面的地址区域,也就是将本人拷贝到 DRAM 最初面的内存区域中。这么做的目标是给 Linux 腾出空间,避免 Linux kernel 笼罩掉 uboot,将 DRAM 后面的区域残缺的空进去。在拷贝之前必定要给 uboot 各局部调配好内存地位和大小,比方 gd 应该寄存到哪个地位, malloc 内存池应该寄存到哪个地位等等。这些信息都保留在 gd 的成员变量中,因而要对 gd 的这些成员变量做初始化。最终造成一个残缺的内存“调配图”,在前面重定位 uboot 的时候就会用到这个内存“调配图”。
void board_init_f(ulong boot_flags){#ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA /*未定义*/ /* * For some archtectures, global data is initialized and used before * calling this function. The data should be preserved. For others, * CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined and use the stack * here to host global data until relocation. */ gd_t data; gd = &data; /* * Clear global data before it is accessed at debug print * in initcall_run_list. Otherwise the debug print probably * get the wrong vaule of gd->have_console. */ zero_global_data();#endif gd->flags = boot_flags; gd->have_console = 0; /*initcall_run_list() 函数负责顺次调用 init_sequence_f 中的初始化函数,这个函数我只能说是稍微了解,要让我写,我是写不出的*/ if (initcall_run_list(init_sequence_f)) hang();#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \ !defined(CONFIG_EFI_APP) /* NOTREACHED - jump_to_copy() does not return */ hang();#endif /* Light up LED1 */ imx6_light_up_led1();}
用于初始化调用函数的原型如下,
int initcall_run_list(const init_fnc_t init_sequence[]){ const init_fnc_t *init_fnc_ptr; for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { unsigned long reloc_ofs = 0; int ret; if (gd->flags & GD_FLG_RELOC) reloc_ofs = gd->reloc_off;#ifdef CONFIG_EFI_APP reloc_ofs = (unsigned long)image_base;#endif debug("initcall: %p", (char *)*init_fnc_ptr - reloc_ofs); if (gd->flags & GD_FLG_RELOC) debug(" (relocated to %p)\n", (char *)*init_fnc_ptr); else debug("\n"); ret = (*init_fnc_ptr)(); if (ret) { printf("initcall sequence %p failed at call %p (err=%d)\n", init_sequence, (char *)*init_fnc_ptr - reloc_ofs, ret); return -1; } } return 0;}
被调用的函数列表中有太多行,很多宏定义决定了某些函数是否调用,把这个去除后的函数如下:
static init_fnc_t init_sequence_f[] = { setup_mon_len, /*设置 gd 的 mon_len 成员变量*/ initf_malloc, /*初始化malloc内存池大小和malloc指针*/ initf_console_record, /*未定义,返回0*/ arch_cpu_init, /* basic arch cpu dependent setup */ initf_dm, /*驱动模型的一些初始化, 未定义*/ arch_cpu_init_dm, /*未定义*/ mark_bootstage, /* need timer, go after init dm 做标记board_init_f*/ board_early_init_f, /*I.MX6ULL 用来初始化串口的 IO 配置*/ timer_init, /* initialize timer */ board_postclk_init, /* I.MX6ULL to Set VDDSOC to 1.175V*/ get_clocks, env_init, /* initialize environment */ init_baud_rate, /* initialze baudrate settings */ serial_init, /* serial communications setup */ console_init_f, /* stage 1 init of console 初始控制台,设置仅一个控制台*/ display_options, /* say that we are here 输入版本信息 UBoot日期和工夫*/ display_text_info, /* show debugging info if required 若开启debug, 会输入 text_base、 bss_start、 bss_end*/ print_cpuinfo, /* display cpu info (and speed) */ show_board_info, INIT_FUNC_WATCHDOG_INIT /*空函数*/ INIT_FUNC_WATCHDOG_RESET /*空函数*/ init_func_i2c, announce_dram_init, dram_init, /* configure available RAM banks 设置 gd->ram_size */ post_init_f, /*上电复位自检*/ testdram, /*空函数*/ setup_dest_addr, /*设置目标地址,设置 gd->ram_size,gd->ram_top,gd->relocaddr这三个的值*/ reserve_round_4k, /*reserve_round_4k 函 数 用 于 对 gd->relocaddr 做 4KB 对 齐 , 因 为gd->relocaddr=0XA0000000,曾经是 4K 对齐了,所以调整后不变。*/ reserve_mmu, /*留出 MMU 的 TLB 表的地位,调配 MMU 的 TLB 表内存当前会对 gd->relocaddr 做 64K 字节对齐*/ reserve_trace, /*留出跟踪调试的内存, I.MX6ULL 没有用到*/ reserve_uboot, /*留出重定位后的 uboot 所占用的内存区域, uboot 所占用大小由gd->mon_len 所指定,留出 uboot 的空间当前还要对 gd->relocaddr 做 4K 字节对齐,并且从新设置 gd->start_addr_sp*/ reserve_malloc, reserve_board, setup_machine, /*老版本应用,新设施没有用*/ reserve_global_data, /*保留出 gd_t 的内存区域*/ reserve_fdt, /*留出设施树相干的内存区域*/ reserve_arch, /*空函数*/ reserve_stacks, /*留出栈空间,16字节对齐*/ setup_dram_config, /*设置DRAM,通知linux DRAM 的起始地址和大小*/ show_dram_config, /*显示DRAM信息*/ display_new_sp, /*开启debug后,显示 gd->start_addr_sp*/ reloc_fdt, /*重定位 fdt,没有用到*/ setup_reloc, /*设置 gd 的其余一些成员变量,供前面重定位的时候应用,并且将以前的 gd 拷贝到 gd->new_gd 处。*/}