首页 > 其他分享 >硬核26000字分析uboot启动过程

硬核26000字分析uboot启动过程

时间:2023-03-19 19:01:25浏览次数:54  
标签:uboot int ret init 26000 endif images CONFIG 硬核

更好的阅读体验请见:​​硬核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"​​搜索一下。

硬核26000字分析uboot启动过程_#u-boot

可以看到有很多,根据实际情况,我们的芯片是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汇编指令再去理解。

  1. 设置栈指针
  2. sp低3位清0,目的是8字节对齐
  3. 跳转执行**​​board_init_f_alloc_reserve​​**
  4. 跳转执行**​​board_init_f​​**
  5. 设置gd这个结构体的起始地址
  6. 代码重定位
  7. 清BSS段
  8. 调用**​​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

内存布局关系可以参考网上一张图。

硬核26000字分析uboot启动过程_#u-boot_02

然后执行​​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行。

硬核26000字分析uboot启动过程_#u-boot_03

硬核26000字分析uboot启动过程_#u-boot_04

找到​​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;
}

这个函数内容很多,其实就是做了两个工作:

  1. 执行了16行的​​bootm_start​
  2. 找到​​boot_fn​​函数,就是​​do_bootm_linux​

执行完后,最终会回到​​do_bootz​​函数,再重新执行​​do_bootm_states​​,不一样的是,此时states传入的宏有**​​BOOTM_STATE_OS_PREP​​BOOTM_STATE_OS_FAKE_GO​​BOOTM_STATE_OS_GO​​**。

  1. 执行​​boot_fn​​函数
  2. 执行​​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

相关文章

  • Uboot命令
    ?:  查看所有支持命令 pri:  查看uboot这个软件的环境变量,变量名=变量值 setenv:设置环境变量  setenvabc100200:设置添加一个变量值“100200”是个字......
  • Uboot编译步骤和分析汇编代码
    编译uboot  生成一系列的配置文件:makes5p_goni_config  执行make  centos下要安装依赖的文件包:yum-yinstallgccgcc-c++autoconfpcrepcre-develma......
  • i.mx6ull移植uboot(mx6ull_alientek_emmc.h)
    #defineis_mx6ull_9x9_evk()CONFIG_IS_ENABLED(TARGET_MX6ULL_9X9_EVK)#ifdefCONFIG_TARGET_MX6ULL_9X9_EVK#definePHYS_SDRAM_SIZESZ_256M#defineCO......
  • uboot 下的指令
    一、i2c指令i2c--help: i2c指令帮助i2cbus:获取i2c总线信息i2cdev:查看当前i2c设备i2cdev0:将i2c0作为当前设备i2cmd0x6A0x06.10x01:0x6A-->设备地址,0x06.1-->......
  • 通过uboot传参设置mtd分区流程源码分析
    因为公司同事反映他使用的开板无法将根目录下的ip_work目mounth成功,由于本人当时没有去现场查看问题,只是象征性的询问内核是否创建了/dev/mtdblock5设备节点,因为该开发板默......
  • 硬核工厂!钢厂远程监管,三维组态监控 HMI
    钢铁行业作为我国的支柱产业,也是我国能源消耗的重点行业之一,随着国家节能减排政策的推进,有效实施能源管控是企业提高能源绩效、降低能源成本和提高核心竞争力的重要途径。通......
  • uboot 传递参数给 kernel 内核
    uboot通过环境变量 bootargs传递参数给kernel内核,bootargs存储在设备树的chosen节点中,则kernel内核是通过读取设备树的chosen节点中的bootargs属性获取uboot......
  • [硬核] Bootstrap Blazor Table 综合演示例子
    知识点:1.导入导出2.分页功能3.增删改查4.批量删除5.批量编辑(审核)6.列排序与列搜索7.顶部搜索实现所有列搜索8.高级搜索实现多条件搜索9.顶部与刷新与视图列10......
  • qemu使用uboot通过网络加载 linux kernel
    qemu使用uboot通过网络加载linuxkernel。参考文章:https://www.zhaixue.cc/qemu/qemu-u-boot.htmlhttps://zhuanlan.zhihu.com/p/547338158 1#!/bin/sh2......
  • 痞子衡嵌入式:MCUBootUtility v4.0发布,开始支持MCX啦
    --痞子衡维护的NXP-MCUBootUtility工具距离上一个大版本(v3.5.0)发布过去9个月了,这一次痞子衡为大家带来了版本升级v4.0.0,这个版本主要有两个重要更新需要跟大家......