前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >26000字剖析uboot启动过程

26000字剖析uboot启动过程

原创
作者头像
哆哆jarvis
发布2023-03-24 19:49:22
1.7K0
发布2023-03-24 19:49:22
举报
文章被收录于专栏:嵌入式进阶之路

更好的阅读体验请见: uboot启动流程分析

汇编阶段

最先执行的是汇编文件start.S,这个文件跟架构有关,例如芯片架构是arm926ejs,那路径就在*arch/arm/cpu/start.S*。

代码语言:javascript
复制
  .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函数。

代码语言:javascript
复制
#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*这个文件,以下带序号的是我增加的注释。

代码语言:javascript
复制
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

代码语言:javascript
复制
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_fboard_init_r

代码语言:javascript
复制
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->flagsgd->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

代码语言:javascript
复制
#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

代码语言:javascript
复制
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

代码语言:javascript
复制
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_rboard_init_f其实很类似,都是执行一个数组里的函数,board_init_r执行init_sequence_r这个数组里的函数,也是各种初始化函数,前面的初始化函数我们不一一分析,工作中用到再去看,数组的最后是执行run_main_loop函数。

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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_listsbootcmd的内容。

run_command_list

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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函数找到cmdtpcmdtp是指向结构体的指针,里面包含了cmd和对应的回调函数。

代码语言:javascript
复制
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]就是runargv[1]就是mfgtool_args

所以,现在是通过run这个命令找到其对应的struct cmd_tbl_s,对我们来说,进到cmd目录,使用grep -nr "run" *.c,找到其定义在nvedit.c,1143行。

找到cmdtp之后,34行执行cmd_call

cmd_call

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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_PREPBOOTM_STATE_OS_FAKE_GOBOOTM_STATE_OS_GO**。

  1. 执行boot_fn函数
  2. 执行boot_selected_os函数

boot_selected_os

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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的启动流程面试中经常会问到,因此熟悉它是非常必要的。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 更好的阅读体验请见: uboot启动流程分析
  • 汇编阶段
  • board_init_f_alloc_reserve
  • board_init_f
    • initcall_run_list
    • board_init_r
      • main_loop
        • bootdelay_process
          • autoboot_command
            • run_command_list
              • cli_simple_run_command_list
                • cmd_process
                  • cmd_call
                    • do_bootz
                      • bootz_start
                        • do_bootm_states
                          • boot_selected_os
                            • do_bootm_linux
                              • boot_jump_linux
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档