在嵌入式系统中,U-Boot作为引导加载程序,其启动流程的核心环节之一就是重定位(Relocation)。对于RK3506这类基于ARM Cortex-A架构的芯片,重定位的本质是将U-Boot代码从初始加载地址(通常是片内ROM或Flash)复制到运行效率更高的片外RAM,再切换执行环境到RAM中运行。
本文将结合U-Boot源码中ARM Cortex核心的启动代码,拆解RK3506平台U-Boot重定位的实现逻辑、关键步骤与底层原理。
路径:u-boot/arch/arm/cpu/armv7/start.S

一、重定位的核心目的:为何需要“搬家”?
RK3506的U-Boot启动初期,代码通常从片内BootROM或SPI Flash加载到片内SRAM(小容量高速内存)执行。但SRAM容量有限(通常仅几十KB),无法容纳完整的U-Boot镜像(包含驱动、命令、文件系统等模块),也无法满足后续加载Linux内核的需求。
重定位的核心目标:
1.释放存储空间:将U-Boot完整镜像从Flash/SRAM迁移到大容量片外DDR内存;
2.提升执行效率:DDR内存带宽更高、容量更大,支持U-Boot运行复杂逻辑(如设备初始化、内核加载);
3.预留内存空间:为Linux内核、设备树等后续加载的镜像预留连续内存区域。
二、重定位的前提:启动初期的关键初始化
在执行重定位前,U-Boot必须完成一系列底层初始化,为“搬家”做好准备。结合本文提供的ARM Cortex启动代码,这些准备工作主要集中在reset入口函数中:
1.模式与中断初始化
reset: b save_boot_paramssave_boot_params_ret: /* 切换到 SVC32 模式,禁用 FIQ/IRQ */ mrs r0, cpsr and r1, r0,#0x1f @ 掩码模式位 teq r1,#0x1a @ 检查是否为 HYP 模式 bicne r0, r0,#0x1f @ 清除模式位 orrne r0, r0,#0x13 @ 设置为 SVC 模式(管理模式) orr r0, r0,#0xc0 @ 禁用 FIQ (0x80) 和 IRQ (0x40) msr cpsr, r0
•切换到ARM特权模式(SVC32):确保U-Boot拥有访问系统寄存器、修改内存配置的权限;
•禁用中断:避免初始化过程中被外部中断打断,导致系统异常。
2.向量表初始化
/* 设置 VBAR 寄存器,指向 U-Boot 向量表 */ldr r0, =_startmcr p15,0, r0, c12, c0,0 @ 写入VBAR(向量表基地址寄存器)
•ARMv7架构通过VBAR寄存器指定异常向量表地址;
•重定位前,向量表位于初始加载地址(如SRAM),后续重定位后需确保向量表地址同步更新(由链接脚本配合处理)。
3.缓存与MMU初始化(cpu_init_cp15)
重定位前需禁用MMU(内存管理单元)和缓存,避免地址映射干扰内存复制:
ENTRY(cpu_init_cp15) /* invalidate L1 I/D 缓存、TLB */ mov r0,#0 mcr p15,0, r0, c8, c7,0 @ invalidate TLBs mcr p15,0, r0, c7, c5,0 @ invalidate I-cache /* 禁用 MMU、缓存相关配置 */ mrc p15,0, r0, c1, c0,0 bic r0, r0,#0x00002000 @ 清除 V 位(向量表地址偏移) bic r0, r0,#0x00000007 @ 清除 CAM 位(缓存相关) orr r0, r0,#0x00000800 @ 启用 BTB(分支预测)#ifdef CONFIG_SYS_ICACHE_OFF bic r0, r0,#0x00001000 @ 禁用 I-cache#else orr r0, r0,#0x00001000 @ 启用 I-cache(重定位后生效)#endif mcr p15,0, r0, c1, c0,0
•初始化CP15寄存器(ARM系统控制寄存器),清空缓存和TLB(地址转换缓存);
•禁用MMU:此时CPU访问的是物理地址,确保内存复制过程中地址无映射偏差;
•按需启用I-cache(指令缓存):重定位后代码在DDR中运行时,缓存可提升执行效率。
4.板级底层初始化(cpu_init_crit)
ENTRY(cpu_init_crit) b lowlevel_init @ 跳转到板级初始化ENDPROC(cpu_init_crit)
•lowlevel_init是RK3506平台的板级初始化函数(由瑞芯微适配);
•核心任务:初始化DDR内存控制器、配置PLL时钟(提升DDR带宽)、初始化SPI Flash等外设;
•关键:只有完成DDR初始化,U-Boot才能将自身复制到DDR中,这是重定位的硬件基础。
三、重定位的核心实现:_main函数的“搬家”逻辑
当底层初始化(尤其是DDR初始化)完成后,代码通过bl _main跳转到U-Boot核心初始化流程,重定位的核心逻辑就在_main函数中(位于common/main.c)。
结合RK3506的平台特性,_main函数中重定位的关键步骤如下:
1.确定重定位地址(链接脚本定义)
U-Boot的重定位目标地址由链接脚本(如arch/arm/cpu/armv7/rk3506/u-boot.lds)定义,核心符号:
•_start:U-Boot初始加载地址(SRAM或Flash地址);
•__image_copy_start:镜像复制起始地址(初始加载地址的代码段起始);
•__image_copy_end:镜像复制结束地址;
•__bss_start/__bss_end:BSS段起始/结束地址(重定位后需清零);
•CONFIG_SYS_TEXT_BASE:重定位目标地址(RK3506通常配置为DDR起始地址,如0x80000000)。
2.内存复制:从初始地址到DDR
重定位的核心操作是逐字节复制U-Boot镜像到DDR目标地址,代码逻辑简化如下:
// 简化自 common/main.cvoid_main(void) { // 1. 获取链接脚本定义的地址符号 externulong__image_copy_start, __image_copy_end; externulong__bss_start, __bss_end; ulongdst = CONFIG_SYS_TEXT_BASE; // 重定位目标地址(DDR) ulongsrc = (ulong)&__image_copy_start; // 源地址(SRAM/Flash) // 2. 只有当源地址 != 目标地址时,才需要复制(避免自身覆盖) if(src != dst) { memcpy((void*)dst, (void*)src, &__image_copy_end - &__image_copy_start); } // 3. 清零 BSS 段(未初始化全局变量) memset((void*)&__bss_start,0, &__bss_end - &__bss_start); // 4. 跳转到 DDR 中的 U-Boot 继续执行 board_init_f_r_trampoline(dst);}
•复制范围:从__image_copy_start到__image_copy_end,包含代码段(.text)、数据段(.data)等已初始化部分;
•避免自身覆盖:若源地址与目标地址重叠(如部分SRAM与DDR地址重叠),U-Boot会先复制不重叠部分,再处理重叠区域,防止复制过程中覆盖未复制的代码;
•BSS段清零:BSS段存储未初始化全局变量,C语言标准要求其初始值为0,因此重定位后需手动清零。
3.跳转至DDR执行:地址切换
复制完成后,通过board_init_f_r_trampoline函数跳转到DDR中的U-Boot代码继续执行。此时CPU执行的指令已从DDR读取,重定位完成。
4.栈指针更新
重定位后,栈指针(SP)也需更新到DDR中的安全地址(避免使用SRAM栈导致溢出),由board_init_f函数初始化:
// 简化自 common/board_f.cvoidboard_init_f(ulongboot_flags){ ulongsp = CONFIG_SYS_INIT_SP_ADDR; // DDR 中的栈地址 sp -=sizeof(structglobal_data); // 预留全局数据结构空间 gd = (structglobal_data *)sp; memset(gd,0,sizeof(structglobal_data)); // 初始化栈指针 asmvolatile("mov sp, %0": :"r"(sp) : "memory"); // 后续初始化:设备树加载、命令初始化、内核引导等}
•CONFIG_SYS_INIT_SP_ADDR:RK3506配置为DDR中的一段连续地址,确保栈空间足够;
•global_data:U-Boot全局数据结构,存储系统状态(如内存布局、设备信息),重定位后需在DDR中重新初始化。
四、重定位后的关键处理
1.向量表同步更新
重定位后,向量表地址需同步更新到DDR中的新地址,避免异常处理时跳转到旧地址(SRAM/Flash)。由于之前已通过VBAR寄存器设置向量表基地址为_start,而_start在重定位后指向DDR地址,因此无需额外修改(链接脚本确保_start对应DDR目标地址)。
2.缓存重新配置
重定位完成后,U-Boot会重新启用I-cache/D-cache(若配置),提升执行效率。此时MMU仍处于禁用状态(直到Linux内核启动时启用),CPU直接访问DDR物理地址。
3.避免重定位后的地址错误
•所有全局变量、函数指针均使用位置无关代码(PIC)编译,确保重定位后地址正确映射;
•链接脚本通过TEXT_BASE强制指定目标地址,确保复制后的镜像在DDR中地址对齐。
五、RK3506重定位的特殊注意事项
1.DDR初始化优先级:RK3506的DDR控制器初始化是重定位的前提,需通过lowlevel_init配置DDR时序、电压,确保DDR稳定工作;
2.Flash访问兼容性:若初始加载地址为SPI Flash(如0x10000000),复制时需通过RK3506的SPI控制器驱动读取Flash数据,再写入DDR;
3.内存布局优化:RK3506的DDR起始地址通常为0x80000000,U-Boot重定位后,会在DDR中预留后续加载Linux内核(如0x80200000)和设备树(如0x80100000)的空间,避免地址冲突。
六、总结:重定位的完整流程
RK3506 U-Boot重定位的核心是“初始化硬件→复制镜像→切换执行环境”,完整流程可概括为:
1.复位入口(reset):切换SVC模式、禁用中断、初始化向量表;
2.底层初始化:初始化CP15寄存器(缓存/ MMU)、板级硬件(DDR/PLL);
3.确定地址:通过链接脚本获取源地址、目标地址(DDR);
4.镜像复制:memcpy复制代码段/数据段到DDR,清零BSS段;
5.切换执行:更新栈指针,跳转到DDR中的U-Boot继续执行;
6.后续初始化:加载设备树、初始化外设、引导Linux内核。
重定位是U-Boot从“小容量初始环境”到“大容量运行环境”的关键一步,理解其原理不仅能帮助排查启动故障(如DDR初始化失败导致重定位失败),也能为定制化U-Boot(如调整内存布局、优化启动速度)提供基础。
对于RK3506开发者,建议结合链接脚本和lowlevel_init代码,重点关注CONFIG_SYS_TEXT_BASE和DDR初始化参数,确保重定位地址与硬件配置一致。
审核编辑 黄宇
-
u-boot
+关注
关注
0文章
123浏览量
39464 -
RK3506
+关注
关注
0文章
82浏览量
716
发布评论请先 登录
2025瑞芯微开发者大会万象奥科展出RK3506邮票孔核心板
明远智睿 RK3506 核心板:工业物联网领域的性能 “小巨人”
RK3506开发板Linux开发板极致性价比之选
明远智睿RK3506:嵌入式领域新标杆
【米尔RK3506国产开发板评测试用】开箱体体验
瑞芯微RK3506 vs NXP i.MX6ULL
米尔瑞芯微多核异构低功耗RK3506核心板重磅发布
低成本解决方案,RK3506的应用场景分析!
RK3506各型号间有什么差异?

深入理解 RK3506 U-Boot 重定位:从代码到原理
评论