3.1、_main
ENTRY(_main)
#if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK)
ldr r0, =(CONFIG_TPL_STACK) /* TPL(三级引导)使用独立栈 */
#elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr r0, =(CONFIG_SPL_STACK) /* SPL(二级引导)使用独立栈 */
#else
ldr r0, =(CONFIG_SYS_INIT_SP_ADDR) /* 主 U-Boot 使用默认栈 */
#endif
bic r0, r0, #7 /* 栈地址 8 字节对齐(ARM ABI 要求) */
mov sp, r0 /* 设置栈指针 */
bl board_init_f_alloc_reserve /* 在栈顶预留 gd 和早期 malloc 空间 */
mov sp, r0 /* 调整栈指针到预留区域顶部 */
mov r9, r0 /* r9 = gd(全局数据指针) */
bl board_init_f_init_reserve /* 初始化 gd 结构 */
#if defined(CONFIG_SPL_EARLY_BSS)
SPL_CLEAR_BSS
#endif
mov r0, #0
bl board_init_f /* 执行板级早期初始化 */
#if ! defined(CONFIG_SPL_BUILD)
ldr r0, [r9, #GD_START_ADDR_SP] /* 获取新栈地址(gd->start_addr_sp) */
bic r0, r0, #7 /* 8 字节对齐 */
mov sp, r0 /* 切换到新栈 */
ldr r9, [r9, #GD_BD] /* r9 = gd->bd(板级信息结构体) */
sub r9, r9, #GD_SIZE /* 新 gd 位于 bd 下方 */
adr lr, here /* 保存当前返回地址(here 标签) */
ldr r0, [r9, #GD_RELOC_OFF] /* 获取重定位偏移量 */
add lr, lr, r0 /* 计算重定位后的返回地址 */
#if defined(CONFIG_CPU_V7M)
orr lr, #1 /* As required by Thumb-only */
#endif
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
b relocate_code /* 跳转到重定位代码 */
here:
bl relocate_vectors
bl c_runtime_cpu_setup /* 执行 CPU 相关的后期初始化(如缓存、MMU 配置) */
#endif
#if !defined(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK)
#if !defined(CONFIG_SPL_EARLY_BSS)
SPL_CLEAR_BSS /* 确保未初始化的全局变量(.bss 段)全部清零 */
#endif
# ifdef CONFIG_SPL_BUILD
/* Use a DRAM stack for the rest of SPL, if requested */
bl spl_relocate_stack_gd
cmp r0, #0
movne sp, r0
movne r9, r0
# endif
#if ! defined(CONFIG_SPL_BUILD)
bl coloured_LED_init
bl red_led_on
#endif
mov r0, r9 /* 参数1: gd(全局数据指针) */
ldr r1, [r9, #GD_RELOCADDR] /* 参数2: 重定位地址 */
#if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)
ldr lr, =board_init_r /* Thumb 模式跳转 */
bx lr
#else
ldr pc, =board_init_r /* ARM 模式跳转 */
#endif
ENDPROC(_main)
由于IMX6ULL没有定义CONFIG_SPL_BUILD和CONFIG_TPL_STACK,即没有定义SPL和TPL两个阶段,因此该段函数有几个关键点:
ldr r0, =(CONFIG_SYS_INIT_SP_ADDR) | 加载主 U-Boot 的默认栈地址到 r0 |
bic r0, r0, #7 | 确保栈地址 8 字节对齐(ARM ABI 要求) |
mov sp, r0 | 设置栈指针,准备运行 C 代码 |
bl board_init_f_alloc_reserve | 在栈顶预留 gd 和早期 malloc 空间 |
bl board_init_f_init_reserve | 初始化 gd 结构 |
bl board_init_f | 执行板级早期初始化 |
b relocate_code | 重定位代码 |
bl relocate_vectors | 重定位向量表 |
ldr pc, =board_init_r | 进行板级初始化 |
3.2、board_init_f
void board_init_f(ulong boot_flags)
{
gd->flags = boot_flags;
gd->have_console = 0;
if (initcall_run_list(init_sequence_f))
hang();
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
!defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64) && \
!defined(CONFIG_ARC)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif
}
这是一个关于 U-Boot 启动过程中 init_sequence_f 函数指针数组的详细说明。init_sequence_f 是 U-Boot 早期初始化阶段的核心执行流程,按顺序调用一系列初始化函数,逐步完成硬件和软件环境的搭建。运行在 原始位置(Flash/ROM),尚未重定位到 RAM,主要任务如下:
(1)CPU 和基础外设初始化(初始化 CPU、时钟、串口、定时器等基础硬件)→ arch_cpu_init、timer_init、serial_init
(2)内存初始化 → dram_init、testdram
(3)环境变量和调试信息 → env_init、print_cpuinfo
(4)内存保留U-Boot、malloc、设备树等) → reserve_uboot、reserve_malloc
(5)重定位准备(计算目标地址,保留空间) → setup_dest_addr、setup_reloc
3.3、relocate_code
relocate_code 函数主要用于 将 U-Boot 自身代码从 Flash/ROM 复制到 RAM(重定位),并修复重定位后的符号地址(如全局变量、函数指针)
ENTRY(relocate_code)
ldr r1, =__image_copy_start /* r1 = 源代码起始地址(Flash 中的 U-Boot) */
subs r4, r0, r1 /* r4 = 重定位偏移量(目标地址 - 源地址) */
beq relocate_done /* 如果偏移量为0(无需重定位),直接跳过 */
ldr r2, =__image_copy_end /* r2 = 源代码结束地址 */
copy_loop:
ldmia r1!, {r10-r11} /* 从源地址 [r1] 加载2个寄存器(64位) */
stmia r0!, {r10-r11} /* 存储到目标地址 [r0] */
cmp r1, r2 /* 检查是否到达源结束地址 [r2] */
blo copy_loop /* 如果未结束,继续循环 */
ldr r2, =__rel_dyn_start /* r2 = 重定位表起始地址 */
ldr r3, =__rel_dyn_end /* r3 = 重定位表结束地址 */
fixloop:
ldmia r2!, {r0-r1} /* r0 = 需修复的地址,r1 = 修复类型 */
and r1, r1, #0xff /* 提取低8位(类型字段) */
cmp r1, #R_ARM_RELATIVE /* 检查是否是相对重定位 */
bne fixnext /* 如果不是,跳过 */
/* 相对重定位修复:原值 + 偏移量 */
add r0, r0, r4 /* r0 = 需修复的地址(RAM 中的新地址) */
ldr r1, [r0] /* 读取原值 */
add r1, r1, r4 /* 新值 = 原值 + 重定位偏移量 */
str r1, [r0] /* 写回修复后的值 */
fixnext:
cmp r2, r3 /* 检查是否处理完所有重定位项 */
blo fixloop /* 如果未结束,继续循环 */
relocate_done:
#ifdef __XSCALE__
/* XScale 架构需无效化指令缓存和排空写缓冲区 */
mcr p15, 0, r0, c7, c7, 0 /* 无效化指令缓存 */
mcr p15, 0, r0, c7, c10, 4 /* 排空写缓冲区 */
#endif
/* 返回调用者 */
#ifdef __ARM_ARCH_4__
mov pc, lr /* ARMv4 不支持 bx lr,直接 mov pc, lr */
#else
bx lr /* ARMv5+ 使用 bx lr 返回 */
#endif
ENDPROC(relocate_code)
uboot的relocate动作就是指UBOOT的重定向动作,也就是将uboot自身镜像拷贝到DDR上的另外一个位置的动作。(对于IMX6ULL来讲,在BOOTROM阶段就已经把整个UBOOT拷贝到了DDR,因此是从DDR一个位置,拷贝到DDR的另一个位置,有些芯片是BOOTROM,将SPL拷贝到SRAM,SPL进行初级初始化后将UBOOT从SD等外设拷贝到DDR)
3.4、board_init_r
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
/*
* Set up the new global data pointer. So far only x86 does this
* here.
* TODO(sjg@chromium.org): Consider doing this for all archs, or
* dropping the new_gd parameter.
*/
#if CONFIG_IS_ENABLED(X86_64)
arch_setup_gd(new_gd);
#endif
#ifdef CONFIG_NEEDS_MANUAL_RELOC
int i;
#endif
#if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
gd = new_gd;
#endif
gd->flags &= ~GD_FLG_LOG_READY;
#ifdef CONFIG_NEEDS_MANUAL_RELOC
for (i = 0; i < ARRAY_SIZE(init_sequence_r); i++)
init_sequence_r[i] += gd->reloc_off;
#endif
if (initcall_run_list(init_sequence_r))
hang();
/* NOTREACHED - run_main_loop() does not return */
hang();
}
board_init_r()
函数是 U-Boot 第二阶段初始化的入口函数,主要职责是:
- 设置全局数据结构指针
- 处理需要手动重定位的函数
- 按顺序执行
init_sequence_r
中的所有初始化函数 - 最后进入
run_main_loop
主循环(通常不会返回)
主要可以分为以下几类:
(1)基础系统初始化
initr_trace
: 初始化跟踪系统initr_reloc
: 重定位处理initr_caches
: 缓存初始化(ARM架构)initr_malloc
: 初始化内存分配器log_init
: 初始化日志系统initr_console_record
: 控制台记录初始化
(2)硬件相关初始化
board_init
: 板级初始化(设置芯片选择等)initr_serial
: 串口初始化initr_watchdog
: 看门狗初始化power_init_board
: 电源初始化initr_flash
: Flash 初始化initr_nand
: NAND 初始化initr_mmc
: MMC/SD 卡初始化
(3)驱动和总线初始化
initr_dm
: 设备模型初始化initr_pci
: PCI 总线初始化initr_scsi
: SCSI 初始化initr_ide
: IDE 初始化
(4)网络相关
initr_ethaddr
: 以太网地址初始化initr_net
: 网络初始化
(5)环境变量和配置
initr_env
: 环境变量初始化mac_read_from_eeprom
: 从EEPROM读取MAC地址
(6)后期初始化
board_late_init
: 板级后期初始化last_stage_init
: 最后阶段初始化
(7)最终阶段
run_main_loop
: 进入主循环(处理命令等)
static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOX
sandbox_main_loop_init();
#endif
/* main_loop() can return to retry autoboot, if so just run it again */
for (;;)
main_loop();
return 0;
}
U-Boot 启动后会显示一个 3 秒的等待提示,在此期间如果用户按下回车键,系统将停留在 U-Boot 命令行界面;若倒计时结束仍无操作,则会自动执行预设命令启动 Linux 内核。这一交互逻辑是由 run_main_loop() 函数实现的