u-boot第三篇,该介绍uboot在imx6q芯片上的启动流程了;网上介绍uboot启动流程的文章很多,因此我这里只记录代码的执行流程,不详细分析代码的细节。
u-boot第一阶段
中断向量
中断向量在arch/arm/lib/vector.S中被定义,imx6去是ARMV7版本。
第一句汇编代码,就是复位向量,跳转到reset出执行代码
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
reset复位向量代码
reset代码在arch/arm/cpu/armv7/start.S中被定义
.globl reset
.globl save_boot_params_ret
reset:
/* Allow the board to save important registers */
b save_boot_params
save_boot_params_ret:
/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
*/
mrs r0, cpsr
and r1, r0, #0x1f @ mask mode bits
teq r1, #0x1a @ test for HYP mode
bicne r0, r0, #0x1f @ clear all mode bits
orrne r0, r0, #0x13 @ set SVC mode
orr r0, r0, #0xc0 @ disable FIQ and IRQ
msr cpsr,r0
/*
* Setup vector:
* (OMAP4 spl TEXT_BASE is not 32 byte aligned.
* Continue to use ROM code vector only in OMAP4 spl)
*/
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Register
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register
/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
#endif
bl _main
ENTRY(cpu_init_crit)
/*
* Jump to board specific initialization...
* The Mask ROM will have already initialized
* basic memory. Go here to bump up clock rate and handle
* wake up conditions.
*/
b lowlevel_init @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)
- 在start.S中,reset代码首先进入SVC模式、关闭中断,然后执行cpu_init_cp15、cpu_init_crit;
- cpu_init_cp15:在start.S中定义
- cpu_init_crit: 在arch\arm\cpu\armv7\ lowlevel_init.S中定义
- cpu_init_crit的执行,会返回到start.S中执行bl _main,此处也就是第二阶段_main在arch\arm\lib\crt0.S中定义
uboot第二阶段
- 在_main汇编代码中首先初始化SP栈指针,做8字节对齐,此时的栈位于内部RAM空间0x00900000处
- 然后为gd全局变量保留256字节 + CONFIG_SYS_MALLOC_F 宏定义的的空间,由函数board_init_f_alloc_reserve完成
- 由函数board_init_f_init_reserve完成board_init_f函数需要使用的CONFIG_SYS_MALLOC_F空间准备
- 在board_init_f函数中调用initcall_run_list(init_sequence_f),完成初始化序列函数的执行
- board_init_f函数返回后,调整gd全局变量,然后调用relocate_code与relocate_vectors进行代码重定位与中断向量重定位
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)
#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
mov sp, r0
/* set up gd here, outside any C code */
mov r9, r0
bl board_init_f_init_reserve
mov r0, #0
bl board_init_f
#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.
*/
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 */
sub r9, r9, #GD_SIZE /* new GD is below bd */
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
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 */
b relocate_code
here:
/*
* now relocate vectors
*/
bl relocate_vectors
/* Set up final (full) environment */
bl c_runtime_cpu_setup /* we still call old routine here */
#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
#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 */
ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
/* 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! */
#endif
/* we should not return here. */
#endif
ENDPROC(_main)
代码与中断向量重定位
上一篇关于uboot重定位的想法有错,实际还是发生了重定位,虽然uboot被imx6q内的ROMBOOT代码读到的链接地址0x17800000,但是还是发生的重定位,并且定位到8ff23000地址,
uboot代码增加debug提示信息后如下:
Reserving 819k for U-Boot at: 8ff23000
......
Relocation Offset is: 78723000
Relocating to 8ff23000, new gd at 8ef20eb8, sp at 8ef20e90
为什么要这样呢,我估计是为了远离Linux的加载地址0x12000000。
至于是怎么完成重定位过程的,这里我就不再详细描述,网上有很多介绍的文章:uboot代码重定位
这里我需要描述的是,uboot的重定位地址是怎么来的
- 在setup_mon_len函数中设置:gd->mon_len = (ulong)&__bss_end - (ulong)_start = 0x000CCFD8
- 在dram_init函数中设置:gd->ram_size = get_ram_size((void *)CONFIG_SYS_SDRAM_BASE, CONFIG_MAX_RAM_BANK_SIZE) = 2G;
- 在setup_dest_addr函数中设置重定位初始地址:gd->relocaddr = gd->ram_top = 0x90000000;
- 在reserve_mmu函数中保留TLB,预留64K空间,实际使用16K空间,此时gd->relocaddr = 0x8FFF0000
- 在reserve_uboot函数中设置:gd->relocaddr -= gd->mon_len = 0x8FFF0000-0x000CCFD8 = 0x8FF23028;并进行4K对齐,最后gd->relocaddr = 0x8FF23000
- 在setup_reloc函数完成代码重定位前的参数计算:gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE = 0x78723000
代码重定位过程
在执行board_init_f函数中调用initcall_run_list(init_sequence_f)的最后一个函数时,函数指针为NULL,返回值为0,这时继续回到_main
汇编代码。
在执行relocate_code函数之前我们先来关注_main
汇编代码以下5句
- 第一句:转呗here返回地址,此时here的值仍处于重定位之前的地址段
- 第二句:获取重定位的地址偏移量
- 第三句:将重定位的地址偏移量加到
lr
,函数执行返回地址寄存器,此时lr
中存储的here
地址变为重定位后的地址段 - 第四句:准备执行relocate_code函数需要的参数;即重定位地址放到
r0
寄存器 - 第五句:执行代码重定位,当此函数返回时,使用
lr
寄存器,因此返回到重定位后的地址段here
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0
...
ldr r0, [r9, #GD_RELOCADDR]
b relocate_code
here:
bl relocate_vectors
在完成重定位过程后,继续bss
段的清零工作,然后执行board_init_r函数,此函数不在返回。
board_init_r函数
board_init_r函数目录:\u-boot-2016.03\common\board_r.c。
在board_init_r函数中调用initcall_run_list(init_sequence_r),进行初始化序列,在序列的最后一个函数是run_main_loop,进行死循环阶段。
run_main_loop函数调用\u-boot-2016.03-r0\common\main.c的main_loop函数
其中在init_sequence_r序列函数中的第二个函数initr_reloc,用于向重定位后的代码中全局变量gd->flag
设置gd->flags |= GD_FLG_RELOC | GD_FLG_FULL_MALLOC_INIT
标志位,用于指示现处于重定位地址段执行uboot函数,这样在每次循环执行函数前都会提示重定位信息;例如:initcall: 1781287c (relocated to 8ff3587c)
init_sequence_r序列中的函数主要是板级 的初始化函数,与具体的硬件相关;至于这些函数的介绍也许会另写一篇文章介绍,这里我们主要关注是如何启动Linux内核的
启动Linux内核
- 在main_loop函数中会调用bootdelay_process函数;
- 此函数获取启动linux内核之前的延时
boot_delay
与启动Linux内核的环境变量bootcmd
- 然后执行autoboot_command函数,此函数会在
boot_delay
延时之后,执行上一步获取的bootcmd
环境变量 bootcmd="run findfdt;mmc dev ${mmcdev};if mmc rescan; then if run loadbootscript; then run bootscript; else if run loadimage; then run mmcboot; else run netboot; fi; fi; else run netboot; fi"
bootcmd
环境变量是一个shell脚本,需要执行一系列命令,由run_command_list函数完成。
void main_loop(void)
{
const char *s;
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
#ifndef CONFIG_SYS_GENERIC_BOARD
puts("Warning: Your board does not use generic board. Please read\n");
puts("doc/README.generic-board and take action. Boards not\n");
puts("upgraded by the late 2014 may break or be removed.\n");
#endif
#ifdef CONFIG_VERSION_VARIABLE
setenv("ver", version_string); /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */
cli_init();
run_preboot_environment_command();
#if defined(CONFIG_UPDATE_TFTP)
update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */
s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);
autoboot_command(s);
cli_loop();
}
const char *bootdelay_process(void)
{
char *s;
int bootdelay;
#ifdef CONFIG_BOOTCOUNT_LIMIT
unsigned long bootcount = 0;
unsigned long bootlimit = 0;
#endif /* CONFIG_BOOTCOUNT_LIMIT */
#ifdef CONFIG_BOOTCOUNT_LIMIT
bootcount = bootcount_load();
bootcount++;
bootcount_store(bootcount);
setenv_ulong("bootcount", bootcount);
bootlimit = getenv_ulong("bootlimit", 10, 0);
#endif /* CONFIG_BOOTCOUNT_LIMIT */
s = getenv("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
#ifdef is_boot_from_usb
if (is_boot_from_usb()) {
disconnect_from_pc();
printf("Boot from USB for mfgtools\n");
bootdelay = 0;
set_default_env("Use default environment for \
mfgtools\n");
} else {
printf("Normal Boot\n");
}
#endif
#ifdef CONFIG_OF_CONTROL
bootdelay = fdtdec_get_config_int(gd->fdt_blob, "bootdelay",
bootdelay);
#endif
debug("### main_loop entered: bootdelay=%d\n\n", bootdelay);
#if defined(CONFIG_MENU_SHOW)
bootdelay = menu_show(bootdelay);
#endif
bootretry_init_cmd_timeout();
#ifdef CONFIG_POST
if (gd->flags & GD_FLG_POSTFAIL) {
s = getenv("failbootcmd");
} else
#endif /* CONFIG_POST */
#ifdef CONFIG_BOOTCOUNT_LIMIT
if (bootlimit && (bootcount > bootlimit)) {
printf("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
(unsigned)bootlimit);
s = getenv("altbootcmd");
} else
#endif /* CONFIG_BOOTCOUNT_LIMIT */
s = getenv("bootcmd");
#ifdef is_boot_from_usb
if (is_boot_from_usb()) {
s = getenv("bootcmd_mfg");
printf("Run bootcmd_mfg: %s\n", s);
}
#endif
process_fdt_options(gd->fdt_blob);
stored_bootdelay = bootdelay;
return s;
}
void autoboot_command(const char *s)
{
debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
int prev = disable_ctrlc(1); /* disable Control C checking */
#endif
run_command_list(s, -1, 0);
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
disable_ctrlc(prev); /* restore Control C checking */
#endif
}
#ifdef CONFIG_MENUKEY
if (menukey == CONFIG_MENUKEY) {
s = getenv("menucmd");
if (s)
run_command_list(s, -1, 0);
}
#endif /* CONFIG_MENUKEY */
}
关于uboot命令实现与执行的特点需要另写一篇文章来记录,网上也有很多关于uboot命令实现文章,本片文章就先记录到这里吧。