Uboot启动流程简要概括
Uboot可以看作是一个比较复杂的综合裸机程序,学习之余对其启动流程进行一个简要的概括,理清自己的思路。
详细内容参考STM32MP1嵌入式Linux驱动开发指南
Uboot程序入口 _start
根据链接脚本 u-boot.lds找到代码入口:_start
该函数定义在:arch/arm/lib/vectors.S
开始是中断向量表,然后跳转到reset函数
reset函数概要
定义在arch/arm/cpu/armv7/start.S
分别调用函数:
- save_boot_params
该函数中又调用了save_boot_params_ret函数,设置处理器进入SVC模式,关闭 FIQ 和 IRQ这两个中断,向量表重定位。 - cpu_init_cp15
用来设置CP15 相关的内容,比如关闭MMU - cpu_init_crit
其中只是调用了lowlevel_init函数。 - _main
下面详解
_main函数
该函数定义在文件 arch/arm/lib/crt0.S 中
首先设置sp指针指向0XC0100000
调用board_init_f_alloc_reserve预留出早期malloc区和gd内存区
初始化(清零)gd指针
调用board_init_f , 该函数定义在common/board_f.c中,主要完成外设初始化和uboot重定位
调用relocate_code函数进行uboot重定位
调用relocate_vectors对中断向量表重定位
调用函数 c_runtime_cpu_setup, board_init_f并没有初始化所有的外设,还需要做一些后续工作,这 些后续工作就是由函数 board_init_r 来完成的
board_init_f函数概要
函数定义在文件 common/board_f.c
该函数中重点:通过函数 initcall_run_list 来运行初始化序列 init_sequence_f里面的一些 列函数,init_sequence_f 里面包含了一系列的初始化函数
/* 初始化序列函数数组 */
static init_fnc_t init_sequence_f[] = {
setup_mon_len, /* 设置gd的mon_len成员,表示uboot代码的长度 */
initf_malloc, /* 初始化gd中与malloc相关的成员 */
initf_console_record,
/* 初始化arm架构相关的东西 */
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, /* 与板子的早期外设初始化,imx6ul用来初始化串口 */
#endif
#if defined(CONFIG_ARM) || defined(CONFIG_MIPS) || \
defined(CONFIG_BLACKFIN) || defined(CONFIG_NDS32) || \
defined(CONFIG_SPARC)
timer_init, /* initialize timer */ /* 初始化Cortex-A7中的定时器 */
#endif
#if defined(CONFIG_SYS_FSL_CLK) || defined(CONFIG_M68K)
get_clocks, /* 获取时钟值 */
#endif
/* 和环境变量有关,设置gd的env_addr成员 */
env_init, /* initialize environment */
init_baud_rate, /* initialze baudrate settings */ /* 初始化串口波特率 */
serial_init, /* serial communications setup */ /* 初始化串口 */
console_init_f, /* stage 1 init of console */ /* 设置console标志位 */
display_options, /* say that we are here */ /* 显示uboot版本和编译时间字符串 */
display_text_info, /* show debugging info if required */
/* 显示cpu的相关信息 */
print_cpuinfo, /* display cpu info (and speed) */
#if defined(CONFIG_DISPLAY_BOARDINFO)
show_board_info, /* 显示board的相关信息 */
#endif
INIT_FUNC_WATCHDOG_INIT
INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
init_func_i2c, /* 初始化i2c接口(实际初始化的是SPD_BUS) */
#endif
#if defined(CONFIG_HARD_SPI)
init_func_spi, /* i.mx6ul并没有初始化SPI接口 */
#endif
announce_dram_init, /* 输出"DRAM:"字符串 */
#if defined(CONFIG_ARM) || defined(CONFIG_X86) || defined(CONFIG_NDS32) || \
defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32)
dram_init, /* configure available RAM banks */ /* 获取DDR的大小 */
#endif
/*
* 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,/* 设置目的地址(gd->ram_size,gd->ram_top,gd->relocaddr) */
reserve_round_4k, /* 对gd->relocaddr做4K对齐 */
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \
defined(CONFIG_ARM)
reserve_mmu, /* 留出mmu的TLB表位置 */
#endif
reserve_trace, /* 留出ddr调试追踪的内存 */
#if !defined(CONFIG_BLACKFIN)
reserve_uboot, /* 留出重定位uboot占用的位置 */
#endif
#ifndef CONFIG_SPL_BUILD
reserve_malloc, /* 留出malloc的内存位置和ENV的内存大小 */
reserve_board, /* 留出bd所占用的内存大小(80字节) */
#endif
setup_machine,
reserve_global_data, /* 留出gd_t结构的内存大小(248字节) */
reserve_fdt, /* 留出设备树的内存大小 */
reserve_arch, /* 空函数 */
reserve_stacks, /* 留出栈空间(16字节)并做16字节对齐 */
setup_dram_config, /* 设置DRAM的信息 */
show_dram_config, /* 显示DRAM的位置 */
display_new_sp, /* 显示新的sp位置 */
INIT_FUNC_WATCHDOG_RESET
reloc_fdt, /* 重定位fdt(没有用) */
setup_reloc, /* 设置gd结构体的一些其他成员 */
NULL,
};
代码段参考链接:https://www.cnblogs.com/Cqlismy/p/12006287.html
最终内存分配图如下:
relocate_code 函数概要
relocate_code 函数是用于代码拷贝的,此函数定义在文件 arch/arm/lib/relocate.S 中
ENTRY(relocate_code)
ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */ //r1保存源image开始地址 __image_copy_start=0XC0100000.
//r0 = gd->relocaddr,r4 = r0-r1 = 0XF5C46000 - 0XC0100000 = 0X35B46000, r4保存的是偏移量
subs r4, r0, r1 /* r4 <- relocation offset */ //r4 = gd->reloc_off,保存偏移地址
beq relocate_done /* skip relocation */ //如果r0和r1相等,则不需要uboot重定位
ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */ //r2保存源image结束地址, __image_copy_end = 0XC01ACD98.
copy_loop:
ldmia r1!, {r10-r11} /* copy from source address [r1] */ //拷贝uboot代码到r10和r1, 拷贝结束后更新r1.
stmia r0!, {r10-r11} /* copy to target address [r0] */ //将uboot代码写到目的地址, 结束后更新r0.
cmp r1, r2 /* until source end address [r2] */ //判断uboot是否拷贝完成
blo copy_loop //循环拷贝
/*
* fix .rel.dyn relocations //修复(重定位).rel.dyn段
*/
ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
and r1, r1, #0xff
cmp r1, #23 /* relative fixup? */
bne fixnext
/* relative fix: increase location by offset */
add r0, r0, r4
ldr r1, [r0]
add r1, r1, r4
str r1, [r0]
fixnext:
cmp r2, r3
blo fixloop
relocate_done:
ENDPROC(relocate_code)
参考链接:https://www.cnblogs.com/Cqlismy/p/12152400.html
编译链接 uboot 的时候会使用到 “-pie”选项,使用“-pie”选项以后会生成一个.rel.dyn 段,uboot 就是靠这个.rel.dyn 来解决重定位问题。
relocate_vectors 函数概要
函数 relocate_vectors 用于重定位向量表,此函数定义在文件 relocate.S 中
board_init_r 函数
board_init_r 函数定义在文件 common/board_r.c 中
board_init_f 并没有初始化所有的外设,还需要做一些后续工作,这 些后续工作就是由函数 board_init_r 来完成的
程序中调用 initcall_run_list 函数来执行初始化序列 init_sequence_r
init_fnc_t init_sequence_r[] = {
initr_trace, /* 初始化buffer跟踪相关 */
initr_reloc, /* 标记重定位完成 */
#ifdef CONFIG_ARM
initr_caches, /* 使能caches */
#endif
initr_reloc_global_data, /* 初始化gd的一些成员变量 */
initr_barrier,
initr_malloc, /* 初始化malloc区域 */
initr_console_record, /* 初始化控制台相关内容 */
bootstage_relocate, /* 启动状态重定位 */
initr_bootstage, /* 初始化bootstage */
#if defined(CONFIG_ARM) || defined(CONFIG_NDS32)
board_init, /* Setup chipselects */
#endif
stdio_init_tables, /* stdio相关初始化 */
initr_serial, /* serial接口初始化 */
initr_announce,
INIT_FUNC_WATCHDOG_RESET
power_init_board,
#ifndef CONFIG_SYS_NO_FLASH
initr_flash,
#endif
INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_CMD_NAND
initr_nand, /* 初始化nand flash */
#endif
#ifdef CONFIG_GENERIC_MMC initr_mmc, #endif
initr_env, /* 环境变量初始化 */
INIT_FUNC_WATCHDOG_RESET
initr_secondary_cpu,
stdio_add_devices,
initr_jumptable,
console_init_r, /* fully init console as a device */
interrupt_init,
#if defined(CONFIG_ARM) || defined(CONFIG_AVR32)
initr_enable_interrupts,
#endif
#ifdef CONFIG_CMD_NET
initr_ethaddr, /* 获取mac地址 */
#endif
#ifdef CONFIG_BOARD_LATE_INIT
board_late_init, /* 板子后期外设初始化 */
#endif
#ifdef CONFIG_CMD_NET
INIT_FUNC_WATCHDOG_RESET
initr_net, /* 初始化板子的网络设备 */
#endif
run_main_loop,
};
初始化完成后进入run_main_loop函数,该函数为处理命令的主循环。