Uboot启动流程
7. startup
- 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_init
, console_init_f
, dram_init
是用来配置硬件外设和相关数据结构 , 如 serial_init()
会将外设驱动和 gd 关联起来(gd->cur_serial_dev = dev;
),以后要使用串口就不需要直接调用具体的外设驱动了。 setup_dest_addr
、 setup_reloc
、 一起设置 uboot 代码在内存中的位置,为接下来将代码拷贝到内存做准备。
注:ppc 是在 jump_to_copy
将代码拷贝到内存的。
执行完 board_init_f
配置好相关数据结构、变量后, uboot 会调用 relocate_code
进行重定位代码,会把代码拷贝到 ddr 内存,然后调用 board_init_r
真正的初始化、启动硬件。board_init_r
和 board_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,这三部分)的处理上还是有一些细节差别的。