更好的阅读体验请见:硬核26000字分析uboot启动过程
汇编阶段
最先执行的是汇编文件start.S,这个文件跟架构有关,例如芯片架构是arm926ejs,那路径就在arch/arm/cpu/start.S
。
.globl reset
reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
** bl _main**
bl命令是先跳转然后返回到跳转前的地方。
没有定义**CONFIG_SKIP_LOWLEVEL_INIT
**宏,所以执行cpu_init_crit
,再跳转到_main。
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush D cache before disabling it
*/
mov r0, #0
flush_dcache:
mrc p15, 0, r15, c7, c10, 3
bne flush_dcache
mcr p15, 0, r0, c8, c7, 0 /* invalidate TLB */
mcr p15, 0, r0, c7, c5, 0 /* invalidate I Cache */
/*
* disable MMU and D cache
* enable I cache if CONFIG_SYS_ICACHE_OFF is not defined
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00000300 /* clear bits 9:8 (---- --RS) */
bic r0, r0, #0x00000087 /* clear bits 7, 2:0 (B--- -CAM) */
#ifdef CONFIG_SYS_EXCEPTION_VECTORS_HIGH
orr r0, r0, #0x00002000 /* set bit 13 (--V- ----) */
#else
bic r0, r0, #0x00002000 /* clear bit 13 (--V- ----) */
#endif
orr r0, r0, #0x00000002 /* set bit 1 (A) Align */
#ifndef CONFIG_SYS_ICACHE_OFF
orr r0, r0, #0x00001000 /* set bit 12 (I) I-Cache */
#endif
mcr p15, 0, r0, c1, c0, 0
/*
* Go setup Memory and board specific bits prior to relocation.
*/
mov ip, lr /* perserve link reg across call */
bl lowlevel_init /* go setup pll,mux,memory */
mov lr, ip /* restore link */
mov pc, lr /* back to my caller */
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
cpu_init_crit
主要是对内存的一些初始化,初学者可以不用关注,即便是工作了也很少会涉及到这里,因此看不懂也没关系,直接跳过,只需要了解这个过程。
跳转到_main
函数,那么_main
函数在哪?
在uboot的顶层目录,使用grep -nr "_main"
搜索一下。
可以看到有很多,根据实际情况,我们的芯片是32位的,所以定义应该是在
arch/arm/lib/crt0.S
,然后我们分析下*arch/arm/lib/crt0.S
*这个文件,以下带序号的是我增加的注释。
ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
***1. 设置栈指针***
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =(CONFIG_SPL_STACK)
#else
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
**2. sp低3位清0,目的是8字节对齐**
#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
**3. 跳转执行board_init_f_alloc_reserve**
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
**4. 跳转执行board_init_f**
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.
*/
**5. 设置gd这个结构体的起始地址**
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 */
**6. 代码重定位**
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! */
**7. 清BSS段**
#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 */
**8. 调用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! */
#endif
/* we should not return here. */
#endif
ENDPROC(_main)
里面细节可以不用纠结,但是里面步骤可以记住,面试通常会问到。如果对汇编感兴趣,也可以先了解下这几条Arm汇编指令再去理解。
- 设置栈指针
- sp低3位清0,目的是8字节对齐
- 跳转执行**
board_init_f_alloc_reserve
** - 跳转执行**
board_init_f
** - 设置gd这个结构体的起始地址
- 代码重定位
- 清BSS段
- 调用**
board_init_r
**
GD_START_ADDR_SP
这个宏定义是通过计算偏移值得到的,DEFINE(GD_START_ADDR_SP, offsetof(struct global_data, start_addr_sp))
,定义在*lib/asm-offsets.c
*。
board_init_f_alloc_reserve
ulong board_init_f_alloc_reserve(ulong top)
{
/* Reserve early malloc arena */
#if defined(CONFIG_SYS_MALLOC_F)
top -= CONFIG_SYS_MALLOC_F_LEN;
#endif
/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
top = rounddown(top-sizeof(struct global_data), 16);
return top;
}
这个函数是给预留一部分内存给全局变量gd结构体,并进行16字节对齐。返回的是已分配空间的地址。
board_init_f
学习uboot要知道的最重要的两个函数就是board_init_f
和board_init_r
。
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;
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
}
**CONFIG_SYS_GENERIC_GLOBAL_DATA
**没有定义,跳过。
然后对gd->flags
和gd->have_console
赋值。
gd
是指向struct global_data
的指针,这个地址通常用宏**DECLARE_GLOBAL_DATA_PTR
**获取。
所以gd
这个结构体的地址在汇编文件*arch/arm/lib/crt0.S
*里已经确定了,r9寄存器的值就是gd结构体的地址。
**DECLARE_GLOBAL_DATA_PTR
宏定义在arch/arm/include/asm/global_data.h
#ifdef CONFIG_ARM64
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("x18")
#else
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
#endif
#endif
内存布局关系可以参考网上一张图。
然后执行initcall_run_list
函数,跑init_sequence_f
这个函数指针数组里的函数,这里面的函数都是成功返回0,任一函数返回非0值就会失败,然后卡住,需要复位,具体看hang();
的实现。
initcall_run_list
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;
}
init_sequence_f
里面的函数较多,都是各种初始化,这里不去一一分析,大概知道里面会初始化串口、CPU、内存相关等内容就行。
board_init_r
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
#ifdef CONFIG_NEEDS_MANUAL_RELOC
int i;
#endif
#ifdef CONFIG_AVR32
mmu_init_r(dest_addr);
#endif
#if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
gd = new_gd;
#endif
#ifdef CONFIG_NEEDS_MANUAL_RELOC
for (i = 0; i < ARRAY_SIZE(init_sequence_r); i++)
init_sequence_r[i] += gd->reloc_off;
#endif
if (**initcall_run_list**(init_sequence_r))
hang();
/* NOTREACHED - run_main_loop() does not return */
hang();
}
board_init_r
跟board_init_f
其实很类似,都是执行一个数组里的函数,board_init_r
执行init_sequence_r
这个数组里的函数,也是各种初始化函数,前面的初始化函数我们不一一分析,工作中用到再去看,数组的最后是执行run_main_loop
函数。
static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOX
sandbox_main_loop_init();
#endif
/* main_loop() can return to retry autoboot, if so just run it again */
for (;;)
**main_loop**();
return 0;
}
可以看到这是一个死循环里的函数,因为uboot不可能一直往下跑。
main_loop
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 */
1. 初始化shell环境,我们可以在uboot里执行相关的命令,跟在linux上执行命令类似
cli_init();
run_preboot_environment_command();
#if defined(CONFIG_UPDATE_TFTP)
update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */
2. 进入倒计时处理
s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);
autoboot_command(s);
cli_loop();
}
main_loop
函数主要是初始化shell环境,然后进入一个倒计时处理,用过都知道uboot启动内核前会有个倒计时,如果我们在这个倒计时前中断,就可以停在uboot设置环境变量里,就可以做修改环境变量、升级、更新镜像、用相关命令调试等。
bootdelay_process
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 */
**1. 从环境变量里读取倒计时多少秒**
s = getenv("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
#if !defined(CONFIG_FSL_FASTBOOT) && defined(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");**
#if !defined(CONFIG_FSL_FASTBOOT) && defined(is_boot_from_usb)
if (is_boot_from_usb()) {
s = getenv("bootcmd_mfg");
printf("Run bootcmd_mfg: %s\n", s);
}
#endif
**2. 把bootdelay复值给全局变量stored_bootdelay **
process_fdt_options(gd->fdt_blob);
stored_bootdelay = bootdelay;
**3. 返回字符串s,此时字符串s的内容就是bootcmd的内容,见57行,从环境变量获取bootcmd内容**
return s;
}
autoboot_command
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 */
}
倒计时在第5行完成,假设倒计时结束前终止了,则直接返回,执行cli_loop
函数,这就是那个shell环境,我们可以在里面输入命令,设置环境变量等。
如果倒计时完了没有终止,则会执行run_command_list
,s
是bootcmd
的内容。
run_command_list
int run_command_list(const char *cmd, int len, int flag)
{
int need_buff = 1;
char *buff = (char *)cmd; /* cast away const */
int rcode = 0;
if (len == -1) {
len = strlen(cmd);
#ifdef CONFIG_SYS_HUSH_PARSER
/* hush will never change our string */
need_buff = 0;
#else
/* the built-in parser will change our string if it sees \n */
need_buff = strchr(cmd, '\n') != NULL;
#endif
}
if (need_buff) {
buff = malloc(len + 1);
if (!buff)
return 1;
memcpy(buff, cmd, len);
buff[len] = '\0';
}
#ifdef CONFIG_SYS_HUSH_PARSER
rcode = parse_string_outer(buff, FLAG_PARSE_SEMICOLON);
#else
/*
* This function will overwrite any \n it sees with a \0, which
* is why it can't work with a const char *. Here we are making
* using of internal knowledge of this function, to avoid always
* doing a malloc() which is actually required only in a case that
* is pretty rare.
*/
rcode = **cli_simple_run_command_list**(buff, flag);
#endif
if (need_buff)
free(buff);
return rcode;
}
第4行把常量转为非常量buff指针,传给cli_simple_run_command_list
。
cli_simple_run_command_list
int cli_simple_run_command_list(char *cmd, int flag)
{
char *line, *next;
int rcode = 0;
/*
* Break into individual lines, and execute each line; terminate on
* error.
*/
next = cmd;
line = cmd;
while (*next) {
if (*next == '\n') {
*next = '\0';
/* run only non-empty commands */
if (*line) {
debug("** exec: \"%s\"\n", line);
if (cli_simple_run_command(line, 0) < 0) {
rcode = 1;
break;
}
}
line = next + 1;
}
++next;
}
if (rcode == 0 && *line)
rcode = (**cli_simple_run_command**(line, 0) < 0);
return rcode;
}
这个函数的逻辑就是拆分bootcmd
,去执行单个命令。
例如一条bootcmd的命令是run mfgtool_args;bootz ${loadaddr} ${initrd_addr} ${fdt_addr};
那它会在cli_simple_run_command
函数里面解析,遇到';'就会分割;
然后执行cmd_process
函数执行,先执行run mfgtool_args
,然后再执行bootz ${loadaddr} ${initrd_addr} ${fdt_addr};
cmd_process
enum command_ret_t cmd_process(int flag, int argc, char * const argv[],
int *repeatable, ulong *ticks)
{
enum command_ret_t rc = CMD_RET_SUCCESS;
cmd_tbl_t *cmdtp;
/* Look up command in command table */
cmdtp = find_cmd(argv[0]);
if (cmdtp == NULL) {
printf("Unknown command '%s' - try 'help'\n", argv[0]);
return 1;
}
/* found - check max args */
if (argc > cmdtp->maxargs)
rc = CMD_RET_USAGE;
#if defined(CONFIG_CMD_BOOTD)
/* avoid "bootd" recursion */
else if (cmdtp->cmd == do_bootd) {
if (flag & CMD_FLAG_BOOTD) {
puts("'bootd' recursion detected\n");
rc = CMD_RET_FAILURE;
} else {
flag |= CMD_FLAG_BOOTD;
}
}
#endif
/* If OK so far, then do the command */
if (!rc) {
if (ticks)
*ticks = get_timer(0);
**rc = cmd_call(cmdtp, flag, argc, argv);**
if (ticks)
*ticks = get_timer(*ticks);
*repeatable &= cmdtp->repeatable;
}
if (rc == CMD_RET_USAGE)
rc = cmd_usage(cmdtp);
return rc;
}
第8行,先通过find_cmd
函数找到cmdtp
,cmdtp
是指向结构体的指针,里面包含了cmd和对应的回调函数。
struct cmd_tbl_s {
char *name; /* Command Name */
int maxargs; /* maximum number of arguments */
int repeatable; /* autorepeat allowed? */
/* Implementation function */
int (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
char *usage; /* Usage message (short) */
#ifdef CONFIG_SYS_LONGHELP
char *help; /* Help message (long) */
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* do auto completion on the arguments */
int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
那么通过argv[0]
参数找到的是谁呢?
前面说到,cmd_process
是执行;隔开的命令,此时传进来的是run mfgtool_args
,那么argv[0]
就是run
,argv[1]
就是mfgtool_args
。
所以,现在是通过run
这个命令找到其对应的struct cmd_tbl_s
,对我们来说,进到cmd目录,使用grep -nr "run" *.c
,找到其定义在nvedit.c,1143行。
找到cmdtp
之后,34行执行cmd_call
。
cmd_call
static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int result;
result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
if (result)
debug("Command failed, result=%d\n", result);
return result;
}
run
对应的cmdtp->cmd
就是do_run
。所以这里就是执行do_run
函数。
后面执行的bootz ${loadaddr} ${initrd_addr} ${fdt_addr};
命令同样如此,通过bootz找到对应的cmdtp
,对应执行的是do_booz
函数,这里往下就是启动内核的。
do_bootz
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int ret;
/* Consume 'bootz' */
argc--; argv++;
if (**bootz_start**(cmdtp, flag, argc, argv, &images))
return 1;
/*
* We are doing the BOOTM_STATE_LOADOS state ourselves, so must
* disable interrupts ourselves
*/
bootm_disable_interrupts();
images.os.os = IH_OS_LINUX;
ret = do_bootm_states(cmdtp, flag, argc, argv,
**BOOTM_STATE_OS_PREP **| **BOOTM_STATE_OS_FAKE_GO **|
**BOOTM_STATE_OS_GO**,
&images, 1);
return ret;
}
执行bootz_start
。
bootz_start
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[], bootm_headers_t *images)
{
int ret;
ulong zi_start, zi_end;
1. 这里进入do_bootm_states,只是执行了bootm_start和找boot_fn
ret = **do_bootm_states**(cmdtp, flag, argc, argv, BOOTM_STATE_START,
images, 1);
/* Setup Linux kernel zImage entry point */
if (!argc) {
images->ep = load_addr;
debug("* kernel: default image load address = 0x%08lx\n",
load_addr);
} else {
images->ep = simple_strtoul(argv[0], NULL, 16);
debug("* kernel: cmdline image address = 0x%08lx\n",
images->ep);
}
**2. 设置内核镜像的起始地址和结束地址**
**ret = bootz_setup(images->ep, &zi_start, &zi_end);**
if (ret != 0)
return 1;
lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);
/*
* Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not
* have a header that provide this informaiton.
*/
if (bootm_find_images(flag, argc, argv))
return 1;
#ifdef CONFIG_SECURE_BOOT
extern uint32_t authenticate_image(
uint32_t ddr_start, uint32_t image_size);
if (authenticate_image(images->ep, zi_end - zi_start) == 0) {
printf("Authenticate zImage Fail, Please check\n");
return 1;
}
#endif
return 0;
}
函数内容很多,关注重点,最开始就执行**do_bootm_states
**。
do_bootm_states
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
int states, bootm_headers_t *images, int boot_progress)
{
boot_os_fn *boot_fn;
ulong iflag = 0;
int ret = 0, need_boot_fn;
images->state |= states;
/*
* Work through the states and see how far we get. We stop on
* any error.
*/
**1. 入参states传了BOOTM_STATE_START,所以会执行16行**
if (states & BOOTM_STATE_START)
ret = bootm_start(cmdtp, flag, argc, argv);
if (!ret && (states & BOOTM_STATE_FINDOS))
ret = bootm_find_os(cmdtp, flag, argc, argv);
if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
ret = bootm_find_other(cmdtp, flag, argc, argv);
argc = 0; /* consume the args */
}
/* Load the OS */
if (!ret && (states & BOOTM_STATE_LOADOS)) {
ulong load_end;
iflag = bootm_disable_interrupts();
ret = bootm_load_os(images, &load_end, 0);
if (ret == 0)
lmb_reserve(&images->lmb, images->os.load,
(load_end - images->os.load));
else if (ret && ret != BOOTM_ERR_OVERLAP)
goto err;
else if (ret == BOOTM_ERR_OVERLAP)
ret = 0;
#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
if (images->os.os == IH_OS_LINUX)
fixup_silent_linux();
#endif
}
/* Relocate the ramdisk */
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
if (!ret && (states & BOOTM_STATE_RAMDISK)) {
ulong rd_len = images->rd_end - images->rd_start;
ret = boot_ramdisk_high(&images->lmb, images->rd_start,
rd_len, &images->initrd_start, &images->initrd_end);
if (!ret) {
setenv_hex("initrd_start", images->initrd_start);
setenv_hex("initrd_end", images->initrd_end);
}
}
#endif
#if defined(CONFIG_OF_LIBFDT) && defined(CONFIG_LMB)
if (!ret && (states & BOOTM_STATE_FDT)) {
boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);
ret = boot_relocate_fdt(&images->lmb, &images->ft_addr,
&images->ft_len);
}
#endif
/* From now on, we need the OS boot function */
if (ret)
return ret;
**2. 找boot_fn函数**
** boot_fn = bootm_os_get_boot_func(images->os.os);**
need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |
BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
if (boot_fn == NULL && need_boot_fn) {
if (iflag)
enable_interrupts();
printf("ERROR: booting os '%s' (%d) is not supported\n",
genimg_get_os_name(images->os.os), images->os.os);
bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
return 1;
}
/* Call various other states that are not generally used */
if (!ret && (states & BOOTM_STATE_OS_CMDLINE))
ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);
if (!ret && (states & BOOTM_STATE_OS_BD_T))
ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);
** 3.第二次执行该函数的时候,states传入了BOOTM_STATE_OS_PREP,所以会执行90行**
if (!ret && (states & **BOOTM_STATE_OS_PREP**))
ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
#ifdef CONFIG_TRACE
/* Pretend to run the OS, then run a user command */
if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
char *cmd_list = getenv("fakegocmd");
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
images, boot_fn);
if (!ret && cmd_list)
ret = run_command_list(cmd_list, -1, flag);
}
#endif
/* Check for unsupported subcommand. */
if (ret) {
puts("subcommand not supported\n");
return ret;
}
**4**.** 第二次执行该函数的时候,执行boot_selected_os**
/* Now run the OS! We hope this doesn't return */
if (!ret && (states & **BOOTM_STATE_OS_GO**))
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
images, boot_fn);
/* Deal with any fallout */
err:
if (iflag)
enable_interrupts();
if (ret == BOOTM_ERR_UNIMPLEMENTED)
bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL);
else if (ret == BOOTM_ERR_RESET)
do_reset(cmdtp, flag, argc, argv);
return ret;
}
这个函数内容很多,其实就是做了两个工作:
- 执行了16行的
bootm_start
- 找到
boot_fn
函数,就是do_bootm_linux
执行完后,最终会回到do_bootz
函数,再重新执行do_bootm_states
,不一样的是,此时states传入的宏有**BOOTM_STATE_OS_PREP
、BOOTM_STATE_OS_FAKE_GO
和BOOTM_STATE_OS_GO
**。
- 执行
boot_fn
函数 - 执行
boot_selected_os
函数
boot_selected_os
int boot_selected_os(int argc, char * const argv[], int state,
bootm_headers_t *images, boot_os_fn *boot_fn)
{
arch_preboot_os();
boot_fn(state, argc, argv, images);
/* Stand-alone may return when 'autostart' is 'no' */
if (images->os.type == IH_TYPE_STANDALONE ||
state == BOOTM_STATE_OS_FAKE_GO) /* We expect to return */
return 0;
bootstage_error(BOOTSTAGE_ID_BOOT_OS_RETURNED);
#ifdef DEBUG
puts("\n## Control returned to monitor - resetting...\n");
#endif
return BOOTM_ERR_RESET;
}
这里调用boot_fn
,执行do_bootm_linux
,也是第二次执行了。
每次执行的目的是不一样的,根据status选择。
do_bootm_linux
int do_bootm_linux(int flag, int argc, char * const argv[],
bootm_headers_t *images)
{
/* No need for those on ARM */
if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
return -1;
if (flag & BOOTM_STATE_OS_PREP) {
boot_prep_linux(images);
return 0;
}
if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
**boot_jump_linux**(images, flag);
return 0;
}
boot_prep_linux(images);
boot_jump_linux(images, flag);
return 0;
}
boot_jump_linux
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
#ifdef CONFIG_ARM64
void (*kernel_entry)(void *fdt_addr, void *res0, void *res1,
void *res2);
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1,
void *res2))images->ep;
debug("## Transferring control to Linux (at address %lx)...\n",
(ulong) kernel_entry);
bootstage_mark(BOOTSTAGE_ID_RUN_OS);
announce_and_cleanup(fake);
if (!fake) {
do_nonsec_virt_switch();
kernel_entry(images->ft_addr, NULL, NULL, NULL);
}
#else
unsigned long machid = gd->bd->bi_arch_number;
char *s;
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2;
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
1. 设置内核入口地址
kernel_entry = (void (*)(int, int, uint))images->ep;
s = getenv("machid");
if (s) {
if (strict_strtoul(s, 16, &machid) < 0) {
debug("strict_strtoul failed!\n");
return;
}
printf("Using machid 0x%lx from environment\n", machid);
}
debug("## Transferring control to Linux (at address %08lx)" \
"...\n", (ulong) kernel_entry);
bootstage_mark(BOOTSTAGE_ID_RUN_OS);
**2. 这个也很重要,会卸载一些驱动,释放内存等。**
**announce_and_cleanup(fake);**
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
r2 = (unsigned long)images->ft_addr;
else
r2 = gd->bd->bi_boot_params;
if (!fake) {
#ifdef CONFIG_ARMV7_NONSEC
if (armv7_boot_nonsec()) {
armv7_init_nonsec();
secure_ram_addr(_do_nonsec_entry)(kernel_entry,
0, machid, r2);
} else
#endif
3. 跳转到内核镜像的地址执行
**kernel_entry(0, machid, r2);**
}
#endif
}
到此uboot的正常启动流程就结束了。
里面还有很多细节,工作中根据实际情况去分析就行。uboot的启动流程面试中经常会问到,因此熟悉它是非常必要的。
你有什么想问的?欢迎评论区留言或滴滴我。
ps:我是哆哆,一枚二本机械狗,从华为外包逆袭到芯片原厂公司,目前从业于半导体行业,在一家芯片原厂公司任职Linux固件工程师,喜欢阅读内核源码,深入操作系统的世界,除此之外,星主还喜欢个人成长,秉承终身学习,终身成长,坚持运动。
星主从最开始玩51单片机,到stm32,然后玩Linux,逼自己看过很多书籍,自学的过程走过很多弯路,最后也如愿以偿。所以,我想把我的经验分享给朋友们。同时,打造一个嵌入式圈子,欢迎所有嵌入式行业的朋友进来。期待你的关注!
标签:uboot,int,ret,init,26000,endif,images,CONFIG,硬核 From: https://blog.51cto.com/u_15267951/6131315