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

电子说

1.4w人已加入

描述

在嵌入式系统中,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

u-boot

一、重定位的核心目的:为何需要“搬家”?

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. 获取链接脚本定义的地址符号    extern ulong __image_copy_start, __image_copy_end;    extern ulong __bss_start, __bss_end;    ulong dst = CONFIG_SYS_TEXT_BASE;  // 重定位目标地址(DDR)    ulong src = (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.cvoid board_init_f(ulong boot_flags) {    ulong sp = CONFIG_SYS_INIT_SP_ADDR;  // DDR 中的栈地址    sp -= sizeof(struct global_data);   // 预留全局数据结构空间    gd = (struct global_data *)sp;    memset(gd, 0, sizeof(struct global_data));    // 初始化栈指针    asm volatile("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 初始化参数,确保重定位地址与硬件配置一致。

审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分