说明:本文参考很多博主的文章,此笔记仅用于记录自己的学习过程
U-Boot启动内核的过程可以分为两个阶段,两个阶段的功能如下:
(1)第一阶段的功能
硬件设备初始化(设置SVC管理模式、关看门狗、关中断、设置时钟频率、RAM初始化、关MMU等)
加载U-Boot第二阶段代码到RAM空间
设置好栈
跳转到第二阶段代码入口
(2)第二阶段的功能
初始化本阶段使用的硬件设备
检测系统内存映射
将内核从Flash读取到RAM中
为内核设置启动参数
调用内核
分析 uboot 的启动流程,首先要找到“入口”。uboot 的链接脚本为arch/arm/cpu/u-boot.lds。编译uboot后,会在 uboot 根目录下生成 u-boot.lds文件。
一、u-boot.lds 分析
路径:/arch/arm/cpu/u-boot.lds
#include
//指定输出可执行文件是elf格式,32位ARM指令,小端
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm) //指定输出可执行文件的平台为ARM
ENTRY(_start) //入口函数 ,指定输出可执行文件的起始代码段为_start
SECTIONS
{
/* 指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置。必须使编译器知道这个地址,通常都是修改此处来完成 */
. = 0x00000000; //起始地址 从0x0位置开始
. = ALIGN(4); //代码以4字节对齐
.text : //文本段
{
*(.__image_copy_start) //变量__image_copy_start 映像文件复制起始地址
*(.vectors) //异常向量表 .vectors标记的代码段 (uboot 的起始地址)
CPUDIR/start.o (.text*)//启动函数,arch/arm/cpu/armv7/start.s 编译出来的代码放到中断向量表后面
*(.text*) //剩余的文本段
}
...... //安全相关
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } //ro数据段
. = ALIGN(4);
.data : { //RW数据段
*(.data*)
}
. = ALIGN(4);
. = .;
. = ALIGN(4);
.u_boot_list : { //存放uboot自定义命令
KEEP(*(SORT(.u_boot_list*)));
}
. = ALIGN(4);
.image_copy_end :
{
*(.__image_copy_end) //变量__image_copy_end
}
.rel_dyn_start :
{
*(.__rel_dyn_start)
}
.rel.dyn : {
*(.rel*)
}
.rel_dyn_end :
{
*(.__rel_dyn_end)
}
.end :
{
*(.__end)
}
_image_binary_end = .;
/*
* Deprecated: this MMU section is used by pxa at present but
* should not be used by new boards/CPUs.
*/
. = ALIGN(4096);
.mmutable : {
*(.mmutable)
}
/*
* Compiler-generated __bss_start and __bss_end, see arch/arm/lib/bss.c
* __bss_base and __bss_limit are for linker only (overlay ordering)
*/
.bss_start __rel_dyn_start (OVERLAY) : {
KEEP(*(.__bss_start));
__bss_base = .;
}
.bss __bss_base (OVERLAY) : {
*(.bss*)
. = ALIGN(4);
__bss_limit = .;
}
.bss_end __bss_limit (OVERLAY) : {
KEEP(*(.__bss_end));
}
.dynsym _image_binary_end : { *(.dynsym) }
.dynbss : { *(.dynbss) }
.dynstr : { *(.dynstr*) }
.dynamic : { *(.dynamic*) }
.plt : { *(.plt*) }
.interp : { *(.interp*) }
.gnu.hash : { *(.gnu.hash) }
.gnu : { *(.gnu*) }
.ARM.exidx : { *(.ARM.exidx*) }
.gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}
从代码可知:
ENTRY(_start):入口函数名,在arch/arm/lib/vectors.S文件中定义
__image_copy_start、__image_copy_end: 用于u-boot搬移本身image到指定的ddr地址处。uboot
拷贝的首地址0x17800000,在uboot.map文件中可查得,通过CONFIG_SYS_TEXT_BASE来设置。
__rel_dyn_start、__rel_dyn_end用于重定位代码
__bss_start、__bss_end是bss段的开始、结束地址
二、_start
路径:arch/arm/lib/vectors.S
...
.macro ARM_VECTORS //中断向量表的宏定义
#ifdef CONFIG_ARCH_K3
ldr pc, _reset
#else
b reset //中断向量表,跳转到reset
#endif
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
.endm
...
_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
ARM_VECTORS //中断向量表的宏
#endif /* !defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) */
从代码可知:_start 调用了 ARM_VECTORS,最终调用 b reset,跳转到reset。
三、reset
路径:arch/arm/cpu/arm920t/start.S
arch/arm/cpu/arm920t/start.S
/*
*************************************************************************
*
- Startup Code (called from the ARM reset exception vector)
- 3. do important init only if we don't start from memory!
- relocate armboot to ram
- setup stack
- jump to second stage
- *************************************************************************
*/
.globl reset
reset:
/*
* 设置CPU进入管理模式
*/
mrs r0, cpsr @将cpsr寄存器的内容传送到r0寄存器
bic r0, r0, #0x1f @工作模式位清零
orr r0, r0, #0xd3 @设置为SVC 管理模式
msr cpsr, r0 @将r0的值赋给cpsr
#ifdef CONFIG_S3C24X0
/* turn off the watchdog */
# if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interrupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#else
/* 关看门狗 */
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
# endif
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
/* 关中断 */
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0] //关闭所有中断
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
/* 设置时钟频率, FCLK:HCLK:PCLK = 1:2:4, 而FCLK默认为120Mhz */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif /* CONFIG_S3C24X0 */
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit //关闭mmu,并初始化各个bank
#endif
bl _main
/*------------------------------------------------------------------------------*/
.globl c_runtime_cpu_setup
c_runtime_cpu_setup:
mov pc, lr
以上代码主要功能:
1、设置SVC模式、关看门狗,中断、设置PLL;
2、跳转 cpu_init_crit 函数;
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches
*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 1 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0
#ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
mov ip, lr
bl lowlevel_init
mov lr, ip
#endif
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
3、跳转_main函数
四、lowlevel_init
.globl lowlevel_init
lowlevel_init:
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
ldr r0, =SMRDATA
ldr r1, =CONFIG_SYS_TEXT_BASE
sub r0, r0, r1
ldr r1, =BWSCON /* Bus Width Status Controller */
add r2, r0, #13*4
0:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne 0b
/* everything is fine now */
mov pc, lr
.ltorg
/* the literal pools origin */
SMRDATA:
.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
.word 0x32
.word 0x30
.word 0x30
五、_main
路径: arch/arm/lib/crt0.S
ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
/*
这里首先为调用board_init_f准备一个临时堆栈,CONFIG_SYS_INIT_SP_ADDR 这个宏就是cpu片上内存的高地址(片上内存的 大小减去GD_SIZE)。然后将堆栈初始的地址保存在r9,所以r9就是gd的起始地址,后面需要靠r9访问gd的成员。然后将r0赋值成0,r0就是要调用的board_init_f函数的第一个参数!
CONFIG_SYS_INIT_SP_ADDR = IRAM大小 - sizeof(GD)
*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =(CONFIG_SPL_STACK)
#else
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
sub sp, sp, #GD_SIZE /* allocate one GD above SP */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
mov r9, sp /* GD is above SP */
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.
*/
/*
*这段代码的主要功能就是将uboot搬移到内存的高地址去执行,为kernel腾出低端空间,防止kernel解压覆盖uboot。
• adr lr, here
• ldr r0, [r9, #GD_RELOC_OFF]
• add lr, lr, r0
• 功能就是,将relocate后的here标号的地址保存到lr寄存器,这样等到relocate完成后,就可以直接跳到relocate后的here标号去执行了。
*relocate_code函数的原理及流程,是 uboot 的重要代码,下面详解!
*/
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
b relocate_code //重定位
here:
/*
*relocate完成后,uboot的代码被搬到了内存的顶部,所以必须重新设置异常向量表的
*地址,c_runtime_cpu_setup这个函数的主要功能就是重新设置异常向量表的地址。
*/
bl relocate_vectors
/* Set up final (full) environment */
bl c_runtime_cpu_setup /* we still call old routine here */
/*
*清空bss段。
*/
/*
*在relocate的过程中,并没有去搬移bss段。bss段是auto-relocated的!为什么?
*可以自己思考一下,又或许看完我后面介绍的relocate的原理后你会明白!
*/
ldr r0, =__bss_start /* this is auto-relocated! */
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 */
strlo r2, [r0] /* clear 32-bit BSS word */
addlo r0, r0, #4 /* move to next */
blo clbss_l
/*
*这两行代码无视之,点灯什么的,和这里要讲的uboot的原理及过程没有半毛钱关系。
*/
bl coloured_LED_init
bl red_led_on
/*
*将relocate后的gd的地址保存到r1,然后调用board_init_r函数,进入uboot的新天地!
*/
/* 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! */
/* we should not return here. */
#endif
ENDPROC(_main)
以上代码主要功能:
1、board_init_f(0) ,路径:common/board_f.c
第一阶段启动
函数主要功能:
初始化一系列外设,比如串口、定时器等。
初始化 gd 的各个成员变量, uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM最后面的内存区域中。这么做的目的是给 Linux 腾出空间,防止 Linux kernel 覆盖掉 uboot,将 DRAM前面的区域完整的空出来。在拷贝之前肯定要给 uboot 各部分分配好内存位置和大小,比如 gd 应该存放到哪个位置, malloc内存池应该存放到哪个位置等等。这些信息都保存在 gd 的成员变量中,因此要对 gd 的这些成员变量做初始化。
void board_init_f(ulong boot_flags)
{
gd->flags = boot_flags;
gd->have_console = 0;
// 设置global_data里面的一些标志位
if (initcall_run_list(init_sequence_f))
hang();
// 调用initcall_run_list依次执行init_sequence_f函数数组里面的函数
// 一旦init_sequence_f的函数出错,会导致initcall_run_list返回不为0,而从卡掉
}
init_sequence_f中定义了一系列初始化函数
static init_fnc_t init_sequence_f[] = {
setup_mon_len,
// 计算整个镜像的长度gd->mon_len, 此处为__bss_end -_start,也就是整个代码的长度。
initf_malloc,
// early malloc的内存池的设定。 初始化 gd 中跟 malloc 有关的成员变量,比如 malloc_limit内存池大小
initf_console_record,// console的log的缓存
arch_cpu_init,
// cpu的一些特殊的初始化
initf_dm,
arch_cpu_init_dm,
mark_bootstage, /* need timer, go after init dm */
/* TODO: can any of this go into arch_cpu_init()? */
env_init, // 环境变量的初始化,设置 gd 的成员变量 env_addr,也就是环境变量的保存地址
init_baud_rate, // 波特率的初始化
serial_init, // 串口的初始化
console_init_f, // console的初始化
print_cpuinfo, // 打印CPU的信息
init_func_i2c,
init_func_spi, // i2c和spi的初始化
dram_init, /* configure available RAM banks */
// ddr的初始化,最重要的是ddr ram size的设置!!!!gd->ram_size
// 如果说uboot是在ROM、flash中运行的话,那么这里就必须要对DDR进行初始化
setup_dest_addr, //设置目的地址, 设置gd->ram_size(ram大小), gd->ram_top(ram最高位置), gd->relocaddr(重定位后的最高地址)
reserve_round_4k, //对 gd->relocaddr 做 4KB 对 齐
reserve_trace, //留出跟踪调试的内存
setup_machine, //设置机器 ID, linux 启动的时候会和这个机器 ID 匹配(无效函数,新版本使用设备树)
reserve_global_data, //保留出 gd_t 的内存区域, gd_t 结构体大小为 248B
reserve_fdt, //留出设备树相关的内存区域
reserve_arch,
reserve_stacks, //留出栈空间
// ==以上部分是对relocate区域的规划,具体参考《[uboot] (番外篇)uboot relocation介绍》
setup_dram_config, //设置 gd->bd->bi_dram[0].start 和gd->bd->bi_dram[0].size,告诉 linux 内核 DRAM 的起始地址和大小
show_dram_config, //用于显示 DRAM 的配置
display_new_sp, //显示新的 sp 位置,也就是 gd->start_addr_sp
reloc_fdt, //用于重定位 fdt,没有用到
setup_reloc, //设置 gd 的其他一些成员变量,供后面重定位的时候使用
// relocation之后gd一些成员的设置
NULL,
};
2、relocate_code,路径:arch/arm/lib/relocate.S
3、relocate_vectors
函数 relocate_vectors用于重定位向量表
4、board_init_r,路径: common/board_r.c
第二阶段启动。
前面,board_init_f 函数会调用一系列的函数来初始化外设和 gd 的成员变量。但是 board_init_f 并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数 board_init_r 来完成的。
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
if (initcall_run_list(init_sequence_r))
hang();
// 调用initcall_run_list依次执行init_sequence_r函数数组里面的函数,initcall_run_list这里不深究
// 一旦init_sequence_r的函数出错,会导致initcall_run_list返回不为0,而从卡掉
/* NOTREACHED - run_main_loop() does not return */
hang();
// uboot要求在这个函数里面终止一切工作,或者进入死循环,一旦试图返回,则直接hang。
}
init_sequence_r
init_fnc_t init_sequence_r[] = {
initr_trace, // trace调试跟踪相关的初始化
initr_reloc, // 设置 gd->flags,标记重定位完成
initr_reloc_global_data,
// relocate(重定位)之后,gd中一些的成员的重新设置
initr_malloc, // malloc内存池的设置
initr_console_record, //初始化控制台相关的内容
bootstage_relocate, //启动状态重定位
initr_bootstage, //初始化 bootstage
#if defined(CONFIG_ARM) || defined(CONFIG_NDS32)
board_init, /* Setup chipselects */
// 板级自己需要的特殊的初始化函数,如board/samsung/tiny210/board.c中定义了board_init这个函数
#endif
stdio_init_tables, //stdio 相关初始化
initr_serial, // 串口初始化
initr_announce, // 打印uboot运行位置的log
initr_logbuffer, // logbuffer的初始化
power_init_board,
#ifdef CONFIG_CMD_NAND
initr_nand, // 如果使用nand flash,那么这里需要对nand进行初始化
#endif
#ifdef CONFIG_GENERIC_MMC
initr_mmc, // 如果使用emmc,那么这里需要对nand进行初始化
#endif
initr_env, // 初始化环境变量
initr_secondary_cpu, //初始化其他 CPU 核,单核没用
stdio_add_devices, //各种输入输出设备的初始化,如 LCD driver, I.MX6ULL使用 drv_video_init 函数初始化 LCD。
initr_jumptable, //初始化跳转表
console_init_r, //控制台初始化,初始化完成以后此函数会调用stdio_print_current_devices函数来打印出当前的控制台设备
interrupt_init, // 初始化中断
#if defined(CONFIG_ARM) || defined(CONFIG_AVR32)
initr_enable_interrupts, // 使能中断
#endif
run_main_loop, // 进入一个死循环,在死循环里面处理终端命令。
};
最终,uboot运行到了run_main_loop,并且在run_main_loop进入命令行状态,等待终端输入命令以及对命令进行处理。
1)run_main_loop函数
static int run_main_loop(void)
{
/* initialize uboot log */
init_write_log();
/* main_loop() can return to retry autoboot, if so just run it again */
for (;;)
main_loop();
return 0;
}
2)main_loop(),路径:common/main.c
main_loop()
>>>s = bootdelay_process();
>>>s = getenv("bootcmd"); //处理 bootcmd 启动命令
>>>from GD
>>>autoboot_command(s)
>>>run_command_list(s, -1, 0);
>>>cli_simple_run_command_list()
while (*next) {
>>>cli_simple_run_command()
>>>cmd_process()
>>>cmd_call()
>>>result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
cmd_tbl_t/U_BOOT_CMD -----> do_cboot()
}
>>>cli_loop(); //进入命令行模式
>>> cli_simple_loop()
>>>run_command_repeatable()
>>>cli_simple_run_command()
>>>cmd_process()
到此,uboot运行到main_loop函数。