深入S5P6818 U-Boot启动流程:从reset向量到main_loop的代码级解析
在嵌入式系统开发中,Bootloader作为硬件上电后运行的第一段程序,承担着初始化硬件、加载操作系统内核的重要职责。对于基于S5P6818处理器的开发板而言,U-Boot作为最常用的开源Bootloader,其启动流程的深入理解是开发者进行系统移植和优化的基础。本文将带您深入S5P6818的U-Boot启动过程,从reset向量开始,逐步解析到main_loop的完整执行路径。
1. 异常向量表与reset处理
当S5P6818处理器上电或复位时,会从特定的地址开始执行指令。这个地址通常指向U-Boot的异常向量表,它是ARM架构规定的硬件行为。在U-Boot的启动代码start.S中,我们可以看到异常向量表的定义:
.globl _stext _stext: b reset /* 复位向量 */ 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这段代码定义了ARM处理器的七种异常处理入口。其中,复位异常(reset)是最重要的,它通过b reset指令跳转到reset标签处开始U-Boot的真正启动流程。
reset处理的第一步是设置处理器模式。ARM处理器有多种运行模式,U-Boot需要在特权模式(Supervisor Mode,SVC)下运行:
reset: /* 设置CPU为SVC32模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 清除模式位 */ orr r0, r0, #0xd3 /* 设置SVC模式并禁用中断 */ msr cpsr,r0提示:SVC模式是ARM处理器的特权模式之一,允许执行所有指令和访问所有资源,适合Bootloader的运行环境。
2. 关键硬件初始化
在设置完处理器模式后,U-Boot需要初始化关键的硬件模块,为后续操作做好准备。这包括:
关闭看门狗定时器:防止在初始化过程中看门狗超时导致系统复位
/* 禁用看门狗 */ ldr r0, =0xC0019000 /* S5P6818看门狗寄存器地址 */ mov r1, #0 str r1, [r0]禁用MMU和Cache:在初始化阶段,MMU和Cache可能会干扰对物理内存的直接访问
bl cpu_init_cp15 /* 调用函数禁用MMU和Cache */时钟和内存控制器初始化:通过
cpu_init_crit函数初始化PLL和内存控制器bl cpu_init_crit /* 初始化PLL、时钟和内存 */
cpu_init_crit函数最终会跳转到lowlevel_init,这是与具体硬件平台相关的低级初始化代码。对于S5P6818,这部分代码通常包括:
- 设置锁相环(PLL)配置,确定CPU和总线时钟频率
- 初始化内存控制器,配置DRAM时序参数
- 设置系统时钟分频器
3. U-Boot的自搬移(Relocation)过程
U-Boot启动时通常从非易失性存储器(如NOR Flash或eMMC)开始执行,但为了获得更好的性能,需要将自身代码搬移到RAM中运行。这个过程称为"自搬移"或"重定位"(Relocation)。
在S5P6818的U-Boot中,自搬移过程由relocate_to_text代码段实现:
relocate_to_text: adr r0, _stext /* r0 <- 当前代码位置 */ ldr r1, TEXT_BASE /* r1 <- 目标地址(RAM中的地址) */ cmp r0, r1 /* 比较当前地址和目标地址 */ beq clear_bss /* 如果已经在RAM中,跳过搬移 */ ldr r2, _bss_start_ofs add r2, r0, r2 /* r2 <- 源代码结束地址 */ copy_loop_text: ldmia r0!, {r3-r10} /* 从源地址[r0]加载8个寄存器 */ stmia r1!, {r3-r10} /* 存储到目标地址[r1] */ cmp r0, r2 /* 检查是否到达源结束地址 */ ble copy_loop_text ldr r1, TEXT_BASE /* 跳转到RAM中的新地址 */ mov pc, r1这段代码的核心是通过ldmia和stmia指令批量加载和存储数据,高效地完成代码搬移。搬移完成后,程序会跳转到RAM中的新地址继续执行。
4. C运行环境准备
在完成自搬移后,U-Boot需要为后续的C代码执行准备环境,主要包括:
清除BSS段:BSS段用于存放未初始化的全局变量,需要清零
clear_bss: ldr r0, _bss_start_ofs ldr r1, _bss_end_ofs ldr r4, TEXT_BASE add r0, r0, r4 /* BSS段起始地址 */ add r1, r1, r4 /* BSS段结束地址 */ mov r2, #0x00000000 /* 清零值 */ clbss_l: str r2, [r0] /* 清零循环 */ add r0, r0, #4 cmp r0, r1 bne clbss_l设置栈指针:C函数调用需要栈空间
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) bic sp, sp, #7 /* 8字节对齐 */ sub sp, #GD_SIZE /* 为全局数据结构分配空间 */ bic sp, sp, #7 mov r9, sp /* 全局数据结构指针 */ mov r0, #0初始化全局数据结构:U-Boot使用全局数据结构
gd_t来维护系统状态typedef struct global_data { bd_t *bd; unsigned long flags; unsigned long baudrate; // ...其他字段 } gd_t;
5. 板级初始化和main_loop
在准备好C运行环境后,U-Boot会依次调用两个重要的板级初始化函数:
board_init_f:在搬移前的初始化,主要完成:
- 串口初始化
- 内存大小检测
- 环境变量初始化
- 为relocation计算参数
board_init_r:在relocation后的初始化,主要完成:
- 设备树初始化(如果使用)
- 设备驱动初始化
- 环境变量加载
- 命令行接口准备
最后,U-Boot进入main_loop函数,这是U-Boot的最终阶段:
void main_loop(void) { const char *s; s = bootdelay_process(); /* 处理启动延迟 */ if (cli_process_fdt(&s)) /* 检查是否进入命令行 */ cli_secure_boot_cmd(s); autoboot_command(s); /* 自动启动流程 */ }在main_loop中,U-Boot会:
- 检查是否有用户输入中断自动启动过程
- 如果没有中断,则加载并启动内核
- 如果被中断,则进入命令行界面等待用户输入
6. 链接脚本与代码布局
理解U-Boot启动流程还需要了解其内存布局,这由链接脚本u-boot.lds决定。典型的S5P6818 U-Boot链接脚本包含以下关键部分:
.text : { *(.__image_copy_start) arch/arm/cpu/slsiap/s5p6818/start.o (.text*) arch/arm/cpu/slsiap/s5p6818/vectors.o (.text*) *(.text*) } .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } .data : { *(.data*) } . = .; .u_boot_list : { KEEP(*(SORT(.u_boot_list*))); } . = ALIGN(4); .__image_copy_end = .; .bss_start (NOLOAD) : { KEEP(*(.__bss_start)); *(.bss*) *(COMMON) . = ALIGN(4); KEEP(*(.__bss_end)); }这个链接脚本定义了代码段(.text)、只读数据段(.rodata)、可写数据段(.data)和BSS段的布局,确保U-Boot的各个部分被正确加载到内存中。
7. 调试与问题排查
在实际开发中,理解U-Boot启动流程对于调试至关重要。以下是一些常见问题及排查方法:
U-Boot无法启动:
- 检查复位向量是否正确跳转
- 验证处理器模式是否设置为SVC
- 确认看门狗是否被正确禁用
Relocation失败:
- 检查
TEXT_BASE定义是否正确 - 验证源地址和目标地址计算
- 确认内存控制器初始化是否正确
- 检查
BSS段清零问题:
- 检查
_bss_start和_bss_end定义 - 确认清零操作是否覆盖整个BSS段
- 检查
栈设置问题:
- 验证
CONFIG_SYS_INIT_SP_ADDR是否在有效RAM范围内 - 确认栈指针是否正确对齐
- 验证
对于S5P6818平台,可以通过串口输出调试信息来跟踪启动过程。在关键代码位置添加打印语句,可以帮助定位问题所在阶段。