u-boot启动流程
u-boot移植框架
- board,不用说了,板级,uboot使用dts后,这块代码应尽量简化
- machine, SOC级,主要是一些外设
- ARCH, 如arm(包含armv7和armv8)
- CPU, 如armv8
各框架启动时的关系:
总体流程图
以 uboot armv7 imx6 32位芯片 启动流程为例
u-boot:启动详细的代码调用流程
u-boot.lds:(arch/arm/cpu/u-boot.lds)
|-->_start:(arch/arm/lib/vectors.S)
|-->reset(arch/arm/cpu/armv7/start.S)
|-->save_boot_params(arch/arm/cpu/armv7/start.S) /*将引导参数保存到内存中*/
|-->save_boot_params_ret(arch/arm/cpu/armv7/start.S)
|-->cpu_init_cp15(arch/arm/cpu/armv7/start.S) /*初始化*/
|-->cpu_init_crit(arch/arm/cpu/armv7/start.S)
|-->lowlevel_init(arch/arm/cpu/armv7/lowlevel_init.S)
|-->_main(arch/arm/lib/crt0.S)
|-->board_init_f_alloc_reserve(common/init/board_init.c) /*为u-boot的gd结构体分配空间*/
|-->board_init_f_init_reserve(common/init/board_init.c) /*将gd结构体清零*/
|-->board_init_f(common/board_f.c)
|-->initcall_run_list(include/initcall.h) /*初始化序列函数*/
|-->init_sequence_f[](common/board_f.c) /* 初始化序列函数数组 */
|-->board_early_init_f(board/freescale/mx6ull_toto/mx6ull_toto.c)/*初始化串口的IO配置*/
|-->timer_init(arch/arm/imx-common/timer.c) /*初始化内核定时器,为uboot提供时钟节拍*/
|-->init_baud_rate(common/board_f.c) /*初始化波特率*/
|-->serial_init(drivers/serial/serial.c) /*初始化串口通信设置*/
|-->console_init_f(common/console.c) /*初始化控制台*/
|-->...
|-->relocate_code(arch/arm/lib/relocate.S) /*主要完成镜像拷贝和重定位*/
|-->relocate_vectors(arch/arm/lib/relocate.S)/*重定位向量表*/
|-->board_init_r(common/board_r.c)/*板级初始化*/
|-->initcall_run_list(include/initcall.h)/*初始化序列函数*/
|-->init_sequence_r[](common/board_f.c)/*序列函数*/
|-->initr_reloc(common/board_r.c) /*设置 gd->flags,标记重定位完成*/
|-->serial_initialize(drivers/serial/serial-uclass.c)/*初始化串口*/
|-->serial_init(drivers/serial/serial-uclass.c) /*初始化串口*/
|-->initr_mmc(common/board_r.c) /*初始化emmc*/
|-->mmc_initialize(drivers/mmc/mmc.c)
|-->mmc_do_preinit(drivers/mmc/mmc.c)
|-->mmc_start_init(drivers/mmc/mmc.c)
|-->console_init_r(common/console.c) /*初始化控制台*/
|-->interrupt_init(arch/arm/lib/interrupts.c) /*初始化中断*/
|-->initr_net(common/board_r.c) /*初始化网络设备*/
|-->...
|-->run_main_loop(common/board_r.c)/*主循环,处理命令*/
|-->main_loop(common/main.c)
|-->bootdelay_process(common/autoboot.c) /*读取环境变量bootdelay和bootcmd的内容*/
|-->autoboot_command(common/autoboot.c) /*倒计时按下执行,没有操作执行bootcmd的参数*/
|-->abortboot(common/autoboot.c)
|-->printf("Hit any key to stop autoboot: %2d ", bootdelay);
/*到这里就是我们看到uboot延时3s启动内核的地方*/
|-->cli_loop(common/cli.c) /*倒计时按下space键,执行用户输入命令*/
ARCH级上电过程
上电后的ARCH级初始化过程在 arch/arm/cpu/armv7/start.S 文件里
总结:
①初始化中断向量表
②进入SVC模式,关FIQ,IRQ
③设置中断向量表偏移VBAR
④初始化CP15,关MMU,ICACHE等
⑤sram内存布局,设置SP指针
初始化中断向量表
由于imx6dl芯片属于armv7架构,在arch/arm/cpu/目录下,通过分析链接脚本u-boot.lds代码段.text可知:可执行程序的入口是_start
start 位于 arch/arm/lib/vectors.S 文件中:
_start:
.globl _reset
.globl _undefined_instruction
.globl _software_interrupt
.globl _prefetch_abort
.globl _data_abort
.globl _not_used
.globl _irq
.globl _fiq
从函数入口_start可以看到,入口的指令存放的就是中断向量表,0地址偏移存放reset, 0x4地址存放_undefined_instruction,0x8地址存放_software_interrupt… 即一上电就执行偏移量为0地址的reset中断_
CPU上电最开始进入_start,进而进入reset函数,该函数定义在arch/arm/cpu/armv7/start.S 里面。
reset:
/* Allow the board to save important registers */
b save_boot_params
进一步进入save_boot_params ->save_boot_params_ret。
WEAK(save_boot_params)
...
b save_boot_params_ret @ back to my caller
ENDPROC(save_boot_params)
函数调用关系如下:reset->save_boot_params->save_boot_params_ret。
进入SVC模式,关闭FIQ 和 IRQ中断
save_boot_params_ret:
.....
/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
*/
mrs r0, cpsr @读取cpsr寄存器
and r1, r0, #0x1f @ 对r0和0x1a进行与运算,目的是取出cpu模式。
teq r1, #0x1a @ 比较 r1 和 0X1a 的值
bicne r0, r0, #0x1f @ 如果 r1 和 0X1A 不相等,也就是 CPU 不处于 Hyp 模式的话就将 r0 寄存器的低5位清零,清除模式位
orrne r0, r0, #0x13 @如果处理器不处于 Hyp 模式的话就将 r0 的寄存器的值与 0x13 进行或运算, 0x13=0b10011,也就是设置处理器进入 SVC 模式
orr r0, r0, #0xc0 @ 0xC0或运算,那么 r0 寄存器此时的值就是 0xD3,cpsr 的 I 为和 F 位分别控制 IRQ 和 FIQ 这两个中断的开关,设置为 1 就关闭了 FIQ 和 IRQ。
msr cpsr,r0 @ r0写入cpsr
设置中断向量表偏移VBAR
该过程把CP15 SCTLR Register读出来,对bit13清0,再写SCTLR Register 进去。
#if !CONFIG_IS_ENABLED(SYS_NO_VECTOR_TABLE)
/*
* cp15配置,设置中断向量表偏移VBAR
*/
/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Register
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register
#ifdef CONFIG_HAS_VBAR
/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif
#endif
如下图是SCTLR 寄存器:bit13 为 V 位,此位是向量表控制位,当为 0 的时候向量表基地址 为 0X00000000,软件可以重定位向量表。为 1 的时候向量表基地址为 0XFFFF0000,软件不能 重定位向量表。
这里将 V 清零,目的就是为了接下来的向量表重定位。
关MMU,ICACHE
cpu_init_cp15就是初始化协处理器寄存器CP15, 比如关闭MMU, ICACHE等。都是一些CP15协寄存器操作指令。
/* the mask ROM code should have PLL and others stable */
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT)
#ifdef CONFIG_CPU_V7A
bl cpu_init_cp15 /* cp15配置,关MMU,ICACHE */
#endif
sram内存布局
cpu_init_crit 实际是进入 lowlevel_init , lowlevel_init 在 arch/arm/cpu/armv7/lowlevel_init.S 文件里
代码看出即 在内部sram中设置SP指针, 有SPL默认配置 和 soc厂家自带配置 两种方式去设置SP指针,为c语言的运行配置环境。
arch/arm/cpu/armv7/start.S文件
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT_ONLY)
bl cpu_init_crit /* lowlevel_init() 配置 */
#endif
...
ENTRY(cpu_init_crit)
/*
* Jump to board specific initialization...
* The Mask ROM will have already initialized
* basic memory. Go here to bump up clock rate and handle
* wake up conditions.
*/
b lowlevel_init @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)
...
#endif
bl _main /* 进入板级初始化 */
/*------------------------------------------------------------------------------*/
arch/arm/cpu/armv7/lowlevel_init.S 文件
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =CONFIG_SPL_STACK
#else
ldr sp, =SYS_INIT_SP_ADDR
#endif
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DM
mov r9, #0
#else
/*
* Set up global data for boards that still need it. This will be
* removed soon.
*/
#ifdef CONFIG_SPL_BUILD
ldr r9, =gdata
#else
sub sp, sp, #GD_SIZE
bic sp, sp, #7
mov r9, sp
#endif
#endif
板级上电过程
板级初始化过程在 arch/arm/lib/crt0.S 文件里
堆栈、GD、early malloc空间的分配
ulong board_init_f_alloc_reserve(ulong top)
{
/* Reserve early malloc arena */
#ifndef CFG_MALLOC_F_ADDR
#if CONFIG_IS_ENABLED(SYS_MALLOC_F)
top -= CONFIG_VAL(SYS_MALLOC_F_LEN);
#endif
#endif
/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
top = rounddown(top-sizeof(struct global_data), 16);
return top;
}
board_init_f_alloc_reserve(指定malloc/gd指针),该函数主要是留出早期的 malloc 内存区域和 gd 内存区域,其中CONFIG_SYS_MALLOC_F_LEN=0X400( 在 文 件 include/generated/autoconf.h 中定义, CONFIG_SYS_MALLOC_F也同样定义成1) , sizeof(struct global_data)=248(GD_SIZE 值)。rounddown是一个向下对齐的函数。
堆栈、GD、early malloc空间的初始化
board_init_f_init_reserve该函数主要 初始化gd,gd内存清0
芯片级外设(uart,timer,console等)初始化和DRAM划分
board_init_f 此函数定义在文件 common/board_f.c,该函数非常重要,主要功能如下:
①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。
②、初始化 gd 的各个成员变量,uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。这么做的目的是给 Linux 腾出空间,防止 Linux kernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。在拷贝之前肯定要给 uboot 各部分 分配好内存位置和大小,比如 gd 应该存放到哪个位置,malloc 内存池应该存放到哪个位置等等。这些信息都保存在 gd 的成员变量中,因此要对 gd 的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位 uboot 的时候就会用到这个内存“分配图”。
代码重定位
relocate_code 代码段重定位, rel.dyn重定位
向量表重定位
relocate_vectors 对中断向量表进行重定位
进行板级(flash,网卡,i2c,sdio等)的外设初始化
前面board_init_f初始化了芯片级外设(uart,Timer,console等)并且进行了DRAM划分,为代码重定位做准备。接下来board_init_r进行板级的外设初始化。
init_sequence_r 定义在文件 common/board_r.c,负责初始化各个硬件外设,比如串口、flash、网卡、i2c、sdio等等
预启动linux过程
void main_loop(void)
{
const char *s;
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop"); //调用 bootstage_mark_name 函数,打印出启动进度。
if (IS_ENABLED(CONFIG_VERSION_VARIABLE))
env_set("ver", version_string); /* set version variable */ //设置版本号环境变量
cli_init(); //命令行初始化
if (IS_ENABLED(CONFIG_USE_PREBOOT))
run_preboot_environment_command(); //preboot处理
if (IS_ENABLED(CONFIG_UPDATE_TFTP))
update_tftp(0UL, NULL, NULL); //tftp处理
if (IS_ENABLED(CONFIG_EFI_CAPSULE_ON_DISK_EARLY)) {
/* efi_init_early() already called */
if (efi_init_obj_list() == EFI_SUCCESS)
efi_launch_capsules();
}
process_button_cmds();
s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);
autoboot_command(s); //自动启动linux
/* if standard boot if enabled, assume that it will be able to boot */
if (IS_ENABLED(CONFIG_BOOTSTD_PROG)) {
int ret;
ret = bootstd_prog_boot();
printf("Standard boot failed (err=%dE)\n", ret);
panic("Failed to boot");
}
cli_loop(); //若停在uboot,循环处理命令
panic("No CLI available");
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com