Rockchip U-Boot启动流程详解:从BootRom到main_loop,代码级剖析(以RK3399为例)

张开发
2026/4/15 14:13:20 15 分钟阅读

分享文章

Rockchip U-Boot启动流程详解:从BootRom到main_loop,代码级剖析(以RK3399为例)
Rockchip U-Boot启动流程深度解析从BootRom到main_loop的完整代码路径当我们谈论嵌入式系统开发时U-Boot作为最广泛使用的开源引导加载程序之一其重要性不言而喻。特别是在Rockchip平台上理解U-Boot的完整启动流程对于系统定制、性能优化和故障排查至关重要。本文将深入剖析RK3399平台上U-Boot从BootRom到main_loop的完整执行路径为开发者提供一份详尽的代码级指南。1. Rockchip启动架构概述Rockchip平台采用分阶段启动的设计理念这种分层架构既保证了启动的安全性又提供了足够的灵活性。RK3399作为Rockchip的高端处理器代表其启动流程具有典型性BootRom阶段固化在芯片内部的不可修改代码负责最基本的硬件初始化和加载下一阶段程序TPL/SPL阶段Tiny Program Loader和Secondary Program Loader分别运行在SRAM和DDR中U-Boot Proper阶段完整的U-Boot环境提供丰富的功能和命令行接口Kernel加载阶段最终引导Linux内核启动// Rockchip典型的启动链示例 BOOTROM TPL SPL TRUST U-BOOT KERNEL这种分阶段设计的主要优势在于安全性每个阶段只拥有完成下一阶段加载所需的最小权限可靠性前级加载程序可以验证后级程序的完整性和合法性灵活性开发者可以在不同阶段介入实现定制需求2. BootRom阶段芯片启动的第一行代码RK3399的BootRom是启动流程的起点它固化在芯片内部无法被修改。当芯片上电或复位时Cortex-A53的Core0会从0xFFFF0000地址开始执行BootRom代码。BootRom的主要职责包括时钟初始化设置CPU、总线和外设的基本时钟存储介质检测识别启动设备eMMC、SPI Flash、SD卡等安全验证检查下一阶段代码的签名如果启用安全启动加载TPL将Tiny Program Loader加载到SRAM中执行// BootRom启动后的典型执行流程 _start: nop b reset // 跳转到reset处理程序 reset: // 检查加载器标签 ldr x0, __loader_tag ldr w1, [x0] ldr x0, LoaderTagCheck ldr w2, [x0] cmp w1, w2 b.eq checkok ret // 返回到maskrom或miniloader checkok: // 设置异常向量表 adr x0, vectors switch_el x1, 3f, 2f, 1fBootRom阶段的一个重要概念是重映射控制由SGRF_PMU_CON0[15]寄存器位控制。当remap设置为0时0xFFFF0000地址被映射到BootRom设置为1时则映射到INTMEM0。3. TPL/SPL阶段关键的低级初始化3.1 TPL (Tiny Program Loader)TPL是运行在SRAM中的最小加载程序其主要任务包括DDR控制器初始化为下一阶段准备内存环境时钟树配置设置PLL和分频器提供系统所需的各种时钟基本外设初始化如串口调试输出加载SPL从存储设备读取SPL到DDR中// TPL中的典型DDR初始化流程 void ddr_init(void) { // 1. 复位DDR控制器 writel(0x1, DDRC_CTRL_REG); udelay(100); // 2. 配置PHY参数 configure_ddr_phy(); // 3. 设置时序参数 set_timing_parameters(); // 4. 校准阻抗 calibrate_impedance(); // 5. 启动DDR控制器 writel(0x2, DDRC_CTRL_REG); }3.2 SPL (Secondary Program Loader)SPL运行在已初始化的DDR内存中具有更大的代码空间和更丰富的功能更完整的外设初始化包括存储设备、USB、网络等加载U-Boot Proper和Trust固件从存储设备读取完整U-Boot安全验证检查加载镜像的签名和完整性环境变量初始化建立基本的运行环境SPL阶段的一个重要文件是lowlevel_init.S它处理从BootRom到SPL的过渡ENTRY(lowlevel_init) mov x29, lr // 保存返回地址 ldr x0, secondary_boot_func bl secondary_switch_to_el2 #ifdef CONFIG_ARMV8_SWITCH_TO_EL1 bl secondary_switch_to_el1 #endif4. U-Boot Proper阶段完整的引导环境当SPL完成其工作后会将控制权转交给U-Boot Proper。这是开发者最常接触的阶段也是功能最丰富的部分。4.1 执行入口与初始化流程U-Boot Proper的执行从_start符号开始最终会调用两个关键函数board_init_f完成基本的硬件初始化和内存配置board_init_r初始化设备驱动和高级功能// U-Boot Proper的典型初始化序列 void board_init_f(ulong boot_flags) { gd-flags boot_flags; gd-have_console 0; if (initcall_run_list(init_sequence_f)) hang(); } void board_init_r(gd_t *new_gd, ulong dest_addr) { if (initcall_run_list(init_sequence_r)) hang(); }4.2 关键初始化步骤U-Boot Proper的初始化过程非常复杂涉及数百个函数调用。以下是一些关键步骤初始化阶段主要功能相关文件板级早期初始化GPIO、时钟、电源等基本配置board/rockchip/*.c设备模型初始化创建设备树、绑定驱动common/board_f.c内存初始化DDR配置、内存区域划分arch/arm/mach-rockchip/*.c设备驱动初始化存储、网络、显示等外设drivers/*环境变量初始化加载和解析环境变量common/env_*.c命令行初始化准备命令行接口common/cli.c4.3 内存重定位U-Boot在启动过程中会执行一次重要的内存重定位操作这主要涉及计算重定位地址确定U-Boot在内存中的最终位置复制代码和数据将自身复制到新的内存区域修复地址引用更新所有与位置相关的指针和引用跳转到新位置继续执行重定位后的代码// 内存重定位的关键代码片段 void relocate_code(ulong addr_sp, gd_t *new_gd, ulong addr) { // 计算重定位偏移量 ulong reloc_off addr - CONFIG_SYS_TEXT_BASE; // 复制.text段 memcpy((void *)addr, (void *)CONFIG_SYS_TEXT_BASE, __bss_start - __text_start); // 复制.data段 memcpy((void *)(addr (__data_start - __text_start)), (void *)__data_start, __data_end - __data_start); // 修复GD指针 new_gd (gd_t *)((ulong)new_gd reloc_off); // 跳转到重定位后的代码 asm volatile(mov sp, %0\n mov x0, %1\n mov x1, %2\n b relocate_vectors : : r(addr_sp), r(new_gd), r(addr)); }5. ARMv8异常等级切换RK3399基于ARMv8架构使用异常等级(EL)来管理不同特权级别的代码执行。U-Boot在启动过程中会经历多次异常等级切换EL3安全监控模式由BootRom运行EL2虚拟化模式通常由SPL使用EL1操作系统模式U-Boot Proper主要运行在此等级EL0用户模式U-Boot不使用// 异常等级切换示例代码 switch_el x1, 3f, 2f, 1f 3: // EL3处理 msr vbar_el3, x0 orr x0, x0, #0xf // SCR_EL3.NS|IRQ|FIQ|EA msr scr_el3, x0 b 0f 2: // EL2处理 msr vbar_el2, x0 mov x0, #0x33ff msr cptr_el2, x0 // 启用FP/SIMD b 0f 1: // EL1处理 msr vbar_el1, x0 mov x0, #3 20 msr cpacr_el1, x0 // 启用FP/SIMD 0: bl lowlevel_init6. main_loopU-Boot的交互核心当所有初始化完成后U-Boot会进入main_loop函数这是U-Boot的交互核心void main_loop(void) { bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, main_loop); // 初始化调制解调器 modem_init(); // 设置版本变量 #ifdef CONFIG_VERSION_VARIABLE setenv(ver, version_string); #endif // 初始化命令行接口 cli_init(); // 执行预启动命令 run_preboot_environment_command(); // 处理TFTP更新 #if defined(CONFIG_UPDATE_TFTP) update_tftp(0UL); #endif // 处理启动延迟 s bootdelay_process(); // 安全启动处理 if (cli_process_fdt(s)) cli_secure_boot_cmd(s); // 自动启动或进入命令行 autoboot_command(s); cli_loop(); }main_loop的主要功能包括环境变量处理加载和解析启动参数自动启动流程根据配置自动加载内核命令行接口提供交互式命令环境超时处理实现启动延迟和中断功能7. Rockchip特有的功能扩展Rockchip在标准U-Boot基础上添加了许多平台特有功能这些功能通常位于board/rockchip/common/目录下Fastboot支持实现快速刷机功能Rockusb协议专有的USB下载模式显示初始化处理Rockchip的显示子系统电源管理包括充电和低电量处理存储设备检测特殊的启动模式判断// Rockchip特有的重启类型判断 enum fbt_reboot_type board_fbt_get_reboot_type(void) { enum fbt_reboot_type frt FASTBOOT_REBOOT_UNKNOWN; uint32_t loader_flag IReadLoaderFlag(); int reboot_mode loader_flag ? (loader_flag 0xFF) : BOOT_NORMAL; // 反馈重启模式到内核 ISetLoaderFlag(SYS_KERNRL_REBOOT_FLAG | reboot_mode); if ((loader_flag 0xFFFFFF00) SYS_LOADER_REBOOT_FLAG) { switch (reboot_mode) { case BOOT_NORMAL: frt FASTBOOT_REBOOT_NORMAL; break; case BOOT_LOADER: do_rockusb(NULL, 0, 0, NULL); break; case BOOT_FASTBOOT: frt FASTBOOT_REBOOT_FASTBOOT; break; // 其他模式处理... } } return frt; }8. 调试与问题排查理解U-Boot启动流程对于调试系统启动问题至关重要。以下是一些常用的调试技巧串口输出确保串口初始化尽早完成并保持输出日志LED指示灯在关键阶段切换LED状态可视化执行流程内存检测在DDR初始化后立即进行内存测试启动暂停在关键点插入while(1);暂停执行版本信息检查各组件版本是否匹配// 在代码中添加调试信息的示例 #define DEBUG #ifdef DEBUG printf([DEBUG] %s: Reached checkpoint %d\n, __func__, checkpoint_id); led_toggle(LED_DEBUG); #endif对于RK3399平台还需要特别注意DDR初始化参数不同板型可能需要不同的DDR配置电源管理确保所有电源域正确上电时钟配置验证各模块时钟频率是否符合预期启动设备识别确认系统从正确的设备启动通过深入理解Rockchip U-Boot的完整启动流程开发者可以更有效地定制引导加载程序解决启动问题并优化系统启动时间。无论是添加新的硬件支持、实现安全启动功能还是调试复杂的启动故障这份代码级的流程分析都将成为宝贵的参考资料。

更多文章