请稍侯

Uboot启动流程

7. startup

  1. armv7

CPU 上电自动进入默认的 reset 中断向量入口,开始执行 uboot 启动汇编(arch/arm/cpu/armv7/start.S)

/*************************************************************************
 *
 * Startup Code (reset vector)
 *
 * Do important init only if we don't start from memory!
 * Setup memory and board specific bits prior to relocation.
 * Relocate armboot to ram. Setup stack.
 *
 *************************************************************************/

    .globl  reset
    .globl  save_boot_params_ret

reset:
    /* Allow the board to save important registers */
    b   save_boot_params

接下来关中断进入 SVC 模式,设置自己的中断向量表地址(将地址写到 SCTLR 寄存器),初始化 CPU (根据实际不同处理器需求),然后进入 C 运行时代码(bl _main)。

_main 位于 arch/arm/lib/crt0.S ,此处代码是独立于硬件的,主要用来初始化硬件配置和为接下来的 C 语言提供运行环境(提供 stack 环境等),最终进入 uboot 的主循环。

 /*
  * entry point of crt0 sequence
  */

 ENTRY(_main)

...
 ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)     /*配置 stack*/ 
...
 bl  board_init_f_init_reserve           /*初始化 gd 结构体*/    
...
 bl  board_init_f                        /* 初始化外设和相关数据结构*/
...
 ldr r0, =__bss_start    /* this is auto-relocated! */
 ldr r1, =__bss_end      /* this is auto-relocated! */
 mov r2, #0x00000000     /* prepare zero to clear BSS */
...
 b   relocate_code      /* 将代码拷贝到 ddr*/
...
    /* 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 */
    ldr pc, =board_init_r   /* this is auto-relocated! */
...
 ENDPROC(_main)

注: 有的开发板的 uboot 会要求生成两个 bin 文件 : u-boot.bin 和 u-boot-spl.bin,此处先只讨论 u-boot.bin。

进入 board_init_f() 后会分步骤的初始化外设、将代码拷贝到 DDR 内存。

void board_init_f(ulong boot_flags)
{
...
    if (initcall_run_list(init_sequence_f))
        hang();       
...
}

initcall_run_list() 用来执行结构体数组 init_sequence_f 定义的一系列配置初始化函数,而 init_sequence_f 包含的初始化函数如下:

static init_fnc_t init_sequence_f[] = {
#ifdef CONFIG_SANDBOX
    setup_ram_buf,
#endif
    setup_mon_len,
#ifdef CONFIG_OF_CONTROL
    fdtdec_setup,
#endif
#ifdef CONFIG_TRACE
    trace_early_init,
#endif
    initf_malloc,
    initf_console_record,
#if defined(CONFIG_MPC85xx) || defined(CONFIG_MPC86xx)
    /* TODO: can this go into arch_cpu_init()? */
    probecpu,
#endif
#if defined(CONFIG_X86) && defined(CONFIG_HAVE_FSP)
    x86_fsp_init,
#endif
    arch_cpu_init,      /* basic arch cpu dependent setup */
    initf_dm,
    arch_cpu_init_dm,
    mark_bootstage,     /* need timer, go after init dm */
#if defined(CONFIG_BOARD_EARLY_INIT_F)
    board_early_init_f,
#endif
    /* TODO: can any of this go into arch_cpu_init()? */
#if defined(CONFIG_PPC) && !defined(CONFIG_8xx_CPUCLK_DEFAULT)
    get_clocks,     /* get CPU and bus clocks (etc.) */
#if defined(CONFIG_TQM8xxL) && !defined(CONFIG_TQM866M) \
        && !defined(CONFIG_TQM885D)
    adjust_sdram_tbs_8xx,
#endif
    /* TODO: can we rename this to timer_init()? */
    init_timebase,
#endif
#if defined(CONFIG_ARM) || defined(CONFIG_MIPS) || \
        defined(CONFIG_BLACKFIN) || defined(CONFIG_NDS32) || \
        defined(CONFIG_SPARC)
    timer_init,     /* initialize timer */
#endif
#ifdef CONFIG_SYS_ALLOC_DPRAM
#if !defined(CONFIG_CPM2)
    dpram_init,
#endif
#endif
#if defined(CONFIG_BOARD_POSTCLK_INIT)
    board_postclk_init,
#endif
#if defined(CONFIG_SYS_FSL_CLK) || defined(CONFIG_M68K)
    get_clocks,
#endif
    env_init,       /* initialize environment */
#if defined(CONFIG_8xx_CPUCLK_DEFAULT)
    /* get CPU and bus clocks according to the environment variable */
    get_clocks_866,
    /* adjust sdram refresh rate according to the new clock */
    sdram_adjust_866,
    init_timebase,
#endif
    init_baud_rate,     /* initialze baudrate settings */
    serial_init,        /* serial communications setup */
    console_init_f,     /* stage 1 init of console */
#ifdef CONFIG_SANDBOX
    sandbox_early_getopt_check,
#endif
#ifdef CONFIG_OF_CONTROL
    fdtdec_prepare_fdt,
#endif
    display_options,    /* say that we are here */
    display_text_info,  /* show debugging info if required */
#if defined(CONFIG_MPC8260)
    prt_8260_rsr,
    prt_8260_clks,
#endif /* CONFIG_MPC8260 */
#if defined(CONFIG_MPC83xx)
    prt_83xx_rsr,
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_M68K)
    checkcpu,
#endif
    print_cpuinfo,      /* display cpu info (and speed) */
#if defined(CONFIG_MPC5xxx)
    prt_mpc5xxx_clks,
#endif /* CONFIG_MPC5xxx */
#if defined(CONFIG_DISPLAY_BOARDINFO)
    show_board_info,
#endif
    INIT_FUNC_WATCHDOG_INIT
#if defined(CONFIG_MISC_INIT_F)
    misc_init_f,
#endif
    INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
    init_func_i2c,
#endif
#if defined(CONFIG_HARD_SPI)
    init_func_spi,
#endif
    announce_dram_init,
    /* TODO: unify all these dram functions? */
#if defined(CONFIG_ARM) || defined(CONFIG_X86) || defined(CONFIG_NDS32) || \
        defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32)
    dram_init,      /* configure available RAM banks */
#endif
#if defined(CONFIG_MIPS) || defined(CONFIG_PPC) || defined(CONFIG_M68K)
    init_func_ram,
#endif
#ifdef CONFIG_POST
    post_init_f,
#endif
    INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_SYS_DRAM_TEST)
    testdram,
#endif /* CONFIG_SYS_DRAM_TEST */
    INIT_FUNC_WATCHDOG_RESET

#ifdef CONFIG_POST
    init_post,
#endif
    INIT_FUNC_WATCHDOG_RESET
    /*
     * Now that we have DRAM mapped and working, we can
     * relocate the code and continue running from DRAM.
     *
     * Reserve memory at end of RAM for (top down in that order):
     *  - area that won't get touched by U-Boot and Linux (optional)
     *  - kernel log buffer
     *  - protected RAM
     *  - LCD framebuffer
     *  - monitor code
     *  - board info struct
     */
    setup_dest_addr,
#if defined(CONFIG_BLACKFIN)
    /* Blackfin u-boot monitor should be on top of the ram */
    reserve_uboot,
#endif
#if defined(CONFIG_SPARC)
    reserve_prom,
#endif
#if defined(CONFIG_LOGBUFFER) && !defined(CONFIG_ALT_LB_ADDR)
    reserve_logbuffer,
#endif
#ifdef CONFIG_PRAM
    reserve_pram,
#endif
    reserve_round_4k,
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \
        defined(CONFIG_ARM)
    reserve_mmu,
#endif
#ifdef CONFIG_DM_VIDEO
    reserve_video,
#else
# ifdef CONFIG_LCD
    reserve_lcd,
# endif
    /* TODO: Why the dependency on CONFIG_8xx? */
# if defined(CONFIG_VIDEO) && (!defined(CONFIG_PPC) || defined(CONFIG_8xx)) && \
        !defined(CONFIG_ARM) && !defined(CONFIG_X86) && \
        !defined(CONFIG_BLACKFIN) && !defined(CONFIG_M68K)
    reserve_legacy_video,
# endif
#endif /* CONFIG_DM_VIDEO */
    reserve_trace,
#if !defined(CONFIG_BLACKFIN)
    reserve_uboot,
#endif
#ifndef CONFIG_SPL_BUILD
    reserve_malloc,
    reserve_board,
#endif
    setup_machine,
    reserve_global_data,
    reserve_fdt,
    reserve_arch,
    reserve_stacks,
    setup_dram_config,
    show_dram_config,
#if defined(CONFIG_PPC) || defined(CONFIG_M68K) || defined(CONFIG_MIPS)
    setup_board_part1,
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_M68K)
    INIT_FUNC_WATCHDOG_RESET
    setup_board_part2,
#endif
    display_new_sp,
#ifdef CONFIG_SYS_EXTBDINFO
    setup_board_extra,
#endif
    INIT_FUNC_WATCHDOG_RESET
    reloc_fdt,
    setup_reloc,
#if defined(CONFIG_X86) || defined(CONFIG_ARC)
    copy_uboot_to_ram,
    clear_bss,
    do_elf_reloc_fixups,
#endif
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX)
    jump_to_copy,
#endif
    NULL,
};

init_sequence_f 因为要支持多种处理器、多种外设、多种配置,所以会很长,但是针对特定的开发板只需要其中的某几个函数即可,而且针对 armv7 需要重点关注的函数有以下几个:

serial_init,        /* serial communications setup */
console_init_f,     /* stage 1 init of console */
INIT_FUNC_WATCHDOG_INIT,
INIT_FUNC_WATCHDOG_RESET,
dram_init,      /* configure available RAM banks */
setup_dest_addr,
setup_reloc,

其中 serial_initconsole_init_fdram_init 是用来配置硬件外设和相关数据结构 , 如 serial_init() 会将外设驱动和 gd 关联起来(gd->cur_serial_dev = dev;),以后要使用串口就不需要直接调用具体的外设驱动了。 setup_dest_addrsetup_reloc 、 一起设置 uboot 代码在内存中的位置,为接下来将代码拷贝到内存做准备。

注:ppc 是在 jump_to_copy 将代码拷贝到内存的。

执行完 board_init_f 配置好相关数据结构、变量后, uboot 会调用 relocate_code 进行重定位代码,会把代码拷贝到 ddr 内存,然后调用 board_init_r 真正的初始化、启动硬件。board_init_rboard_init_f 结构类似,都是执行一连串的初始化函数:

void board_init_r(gd_t *new_gd, ulong dest_addr)
{
...
    if (initcall_run_list(init_sequence_r))
        hang();

    /* NOTREACHED - run_main_loop() does not return */
    hang();
}
 init_fnc_t init_sequence_r[] = {
     initr_trace,
     initr_reloc,
     /* TODO: could x86/PPC have this also perhaps? */
 #ifdef CONFIG_ARM
     initr_caches,
     /* Note: For Freescale LS2 SoCs, new MMU table is created in DDR.
      *   A temporary mapping of IFC high region is since removed,
      *   so environmental variables in NOR flash is not availble
      *   until board_init() is called below to remap IFC to high
      *   region.
      */
 #endif
     initr_reloc_global_data,
 #if defined(CONFIG_SYS_INIT_RAM_LOCK) && defined(CONFIG_E500)
     initr_unlock_ram_in_cache,
 #endif
     initr_barrier,
     initr_malloc,
     initr_console_record,
 #ifdef CONFIG_SYS_NONCACHED_MEMORY
     initr_noncached,
 #endif
     bootstage_relocate,
 #ifdef CONFIG_DM
     initr_dm,
 #endif
     initr_bootstage,
 #if defined(CONFIG_ARM) || defined(CONFIG_NDS32)
     board_init, /* Setup chipselects */
 #endif
     /*
      * TODO: printing of the clock inforamtion of the board is now
      * implemented as part of bdinfo command. Currently only support for
      * davinci SOC's is added. Remove this check once all the board
      * implement this.
      */
 #ifdef CONFIG_CLOCKS
     set_cpu_clk_info, /* Setup clock information */
 #endif
 #ifdef CONFIG_EFI_LOADER
     efi_memory_init,
 #endif
     stdio_init_tables,
     initr_serial,
     initr_announce,
     INIT_FUNC_WATCHDOG_RESET
 #ifdef CONFIG_NEEDS_MANUAL_RELOC
     initr_manual_reloc_cmdtable,
     initr_manual_reloc_cmdtable,
 #endif
 #if defined(CONFIG_PPC) || defined(CONFIG_M68K)
     initr_trap,
 #endif
 #ifdef CONFIG_ADDR_MAP
     initr_addr_map,
 #endif
 #if defined(CONFIG_BOARD_EARLY_INIT_R)
     board_early_init_r,
 #endif
     INIT_FUNC_WATCHDOG_RESET
 #ifdef CONFIG_LOGBUFFER
     initr_logbuffer,
 #endif
 #ifdef CONFIG_POST
     initr_post_backlog,
 #endif
     INIT_FUNC_WATCHDOG_RESET
 #ifdef CONFIG_SYS_DELAYED_ICACHE
     initr_icache_enable,
 #endif
 #if defined(CONFIG_PCI) && defined(CONFIG_SYS_EARLY_PCI_INIT)
     /*
      * Do early PCI configuration _before_ the flash gets initialised,
      * because PCU ressources are crucial for flash access on some boards.
      */
     initr_pci,
 #endif
 #ifdef CONFIG_WINBOND_83C553
     initr_w83c553f,
 #endif
 #ifdef CONFIG_ARCH_EARLY_INIT_R
     arch_early_init_r,
 #endif
     power_init_board,
 #ifndef CONFIG_SYS_NO_FLASH
     initr_flash,
 #endif
     INIT_FUNC_WATCHDOG_RESET
 #if defined(CONFIG_PPC) || defined(CONFIG_M68K) || defined(CONFIG_X86) || \
     defined(CONFIG_SPARC)
     /* initialize higher level parts of CPU like time base and timers */
     cpu_init_r,
 #endif
 #ifdef CONFIG_PPC
     initr_spi,
 #endif
 #ifdef CONFIG_CMD_NAND
     initr_nand,
 #endif
 #ifdef CONFIG_CMD_ONENAND
     initr_onenand,
 #endif
 #ifdef CONFIG_GENERIC_MMC
     initr_mmc,
 #endif
 #ifdef CONFIG_HAS_DATAFLASH
     initr_dataflash,
 #endif
     initr_env,
 #ifdef CONFIG_SYS_BOOTPARAMS_LEN
     initr_malloc_bootparams,
 #endif
     INIT_FUNC_WATCHDOG_RESET
     initr_secondary_cpu,
 #if defined(CONFIG_ID_EEPROM) || defined(CONFIG_SYS_I2C_MAC_OFFSET)
     mac_read_from_eeprom,
 #endif
     INIT_FUNC_WATCHDOG_RESET
 #if defined(CONFIG_PCI) && !defined(CONFIG_SYS_EARLY_PCI_INIT)
     /*
      * Do pci configuration
      */
     initr_pci,
 #endif
     stdio_add_devices,
     initr_jumptable,
 #ifdef CONFIG_API
     initr_api,
 #endif
     console_init_r,     /* fully init console as a device */
 #ifdef CONFIG_DISPLAY_BOARDINFO_LATE
     show_board_info,
 #endif
 #ifdef CONFIG_ARCH_MISC_INIT
     arch_misc_init,     /* miscellaneous arch-dependent init */
 #endif
 #ifdef CONFIG_MISC_INIT_R
     misc_init_r,        /* miscellaneous platform-dependent init */
 #endif
     INIT_FUNC_WATCHDOG_RESET
 #ifdef CONFIG_CMD_KGDB
     initr_kgdb,
 #endif
     interrupt_init,
 #if defined(CONFIG_ARM) || defined(CONFIG_AVR32)
     initr_enable_interrupts,
 #endif
 #if defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32) || defined(CONFIG_M68K)
     timer_init,     /* initialize timer */
 #endif
 #if defined(CONFIG_STATUS_LED)
     initr_status_led,
 #endif
     /* PPC has a udelay(20) here dating from 2002. Why? */
 #ifdef CONFIG_CMD_NET
     initr_ethaddr,
 #endif
 #ifdef CONFIG_BOARD_LATE_INIT
     board_late_init,
 #endif
 #if defined(CONFIG_CMD_AMBAPP)
     ambapp_init_reloc,
 #if defined(CONFIG_SYS_AMBAPP_PRINT_ON_STARTUP)
     initr_ambapp_print,
 #endif
 #endif
 #ifdef CONFIG_CMD_SCSI
     INIT_FUNC_WATCHDOG_RESET
     initr_scsi,
 #endif
 #ifdef CONFIG_CMD_DOC
     INIT_FUNC_WATCHDOG_RESET
     initr_doc,
 #endif
 #ifdef CONFIG_BITBANGMII
     initr_bbmii,
 #endif
 #ifdef CONFIG_CMD_NET
     INIT_FUNC_WATCHDOG_RESET
     initr_net,
 #endif
 #ifdef CONFIG_POST
     initr_post,
 #endif
 #if defined(CONFIG_CMD_PCMCIA) && !defined(CONFIG_CMD_IDE)
     initr_pcmcia,
 #endif
 #if defined(CONFIG_CMD_IDE)
     initr_ide,
 #endif
 #ifdef CONFIG_LAST_STAGE_INIT
     INIT_FUNC_WATCHDOG_RESET
     /*
      * Some parts can be only initialized if all others (like
      * Interrupts) are up and running (i.e. the PC-style ISA
      * keyboard).
      */
     last_stage_init,
 #endif
 #ifdef CONFIG_CMD_BEDBUG
     INIT_FUNC_WATCHDOG_RESET
     initr_bedbug,
 #endif
 #if defined(CONFIG_PRAM) || defined(CONFIG_LOGBUFFER)
     initr_mem,
 #endif
 #ifdef CONFIG_PS2KBD
     initr_kbd,
 #endif
 #if defined(CONFIG_SPARC)
     prom_init,
 #endif
     run_main_loop,
 };

init_sequence_r 也很长,道理同 init_sequence_f,这里需要关注的是 run_main_loop,进入该函数后,就一路向前进入 uboot 的 shell 和系统引导代码,不在返回汇编了。

static int run_main_loop(void)
{
...
    /* main_loop() can return to retry autoboot, if so just run it again */
    for (;;)
        main_loop();
    return 0;
}

进入 main_loop() 以后,uboot 要么进入 shell 要么直接引导 Linux。

/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
...
    s = bootdelay_process();
...
    autoboot_command(s);

    cli_loop();
    panic("No CLI available");
}

main_loop() 首先会等待一段时间检查用户是否有输入(bootdelay_process()),要进入 shell ,如果用户没有输入则直接执行默认的启动命令(autoboot_command),否则进入 shell (cli_loop),而 shell 就是个死循环,一直在等待用户输入命令。

注:不同架构处理器的启动代码逻辑(flash->cache->ddr,这三部分)的处理上还是有一些细节差别的。