news 2026/5/20 21:42:13

从SPL到内核:i.MX6ULL平台U-Boot启动流程与关键函数深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从SPL到内核:i.MX6ULL平台U-Boot启动流程与关键函数深度解析

1. i.MX6ULL平台U-Boot启动全景图

当一块i.MX6ULL开发板通电瞬间,处理器内部固化的Boot ROM会率先接管控制权。这个藏在芯片内部的"引路人"会根据BOOT引脚电平判断启动介质(如SD卡、eMMC等),然后将存储设备中的SPL(Secondary Program Loader)搬运到内部SRAM运行。这个不足100KB的微型引导程序,正是整个启动链条的第一个关键环节。

为什么需要SPL这个"二传手"?原因在于i.MX6ULL的内部SRAM仅有128KB,而完整版U-Boot通常超过400KB。SPL就像个精干的先遣队,只携带最必要的装备(时钟/DDR初始化代码)为后续大部队开辟道路。我曾用示波器测量过这个阶段的耗时:从电源稳定到SPL开始执行,整个过程通常在200ms内完成。

2. SPL阶段关键函数解剖

2.1 _start:一切的开端

在arch/arm/lib/vectors.S中定义的_start符号,是处理器跳出Boot ROM后遇到的第一个路标。这个看似简单的汇编代码段,实则暗藏玄机:

_start: 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函数。有趣的是,在早期调试时,我曾故意修改这里的指令顺序,结果系统直接卡死在启动阶段——这说明Boot ROM会严格校验向量表的完整性。

2.2 reset:处理器的重生仪式

reset函数就像给处理器做"全身检查":

  1. 设置SVC模式并关闭中断(避免初始化过程被打断)
  2. 调用cpu_init_cp15初始化协处理器
  3. 执行cpu_init_crit进行关键硬件初始化

特别值得注意的是MMU的关闭操作:

mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002000 // 清除bit13(V) bic r0, r0, #0x00000007 // 清除bit2:0(CAM)

这段代码通过CP15协处理器直接操作控制寄存器,关闭地址转换和缓存。我在早期移植时曾遗漏这个步骤,导致后续内存访问全部错乱。

2.3 lowlevel_init:板级定制的舞台

这个函数是硬件厂商的"自留地",主要完成:

  • 初始化关键时钟(如ARM内核PLL)
  • 配置DDR控制器时序参数
  • 设置GPIO复用状态

以DDR初始化为例,i.MX6ULL需要精确配置如下参数:

struct mx6ul_iomux_ddr_regs { uint32_t dram_dqm[4]; uint32_t dram_ras; uint32_t dram_cas; // 共40多个寄存器... };

这些值必须严格匹配具体板子的走线长度和存储器芯片规格。我曾遇到过因一个参数错误导致内存带宽下降50%的案例。

3. U-Boot主体阶段深度游

3.1 _main:C语言的奠基者

当SPL完成基础建设后,会通过_main函数将控制权移交给完整版U-Boot。这个过渡过程堪称精妙:

  1. 设置临时栈指针(CONFIG_SYS_INIT_SP_ADDR)
  2. 分配并初始化全局数据结构gd(global_data)
  3. 调用board_init_f进行前期初始化

内存布局在这个阶段会发生重大变化。通过实际调试输出可以看到:

Before relocate: Text base: 0x87800000 Stack top: 0x00910000 After relocate: Text base: 0x9ff47000 Stack top: 0x9ff46000

3.2 代码重定位的魔法

relocate_code函数实现的核心功能,可以用这个搬运过程描述:

while (r1 < r2) { *(uint32_t *)r0 = *(uint32_t *)r1; r0 += 4; r1 += 4; }

但真正的难点在于位置无关码(PIC)处理。U-Boot通过.rel.dyn段动态修正所有绝对地址引用。例如:

原始指令:ldr r3, [pc, #12] ; 加载0x878042c8处值 重定位后:ldr r3, [pc, #12] ; 加载(0x878042c8 + offset)处值

3.3 board_init_r:豪华装修阶段

这个函数就像房子的精装修工程:

  1. 初始化串口、网卡等外设
  2. 扫描存储设备分区
  3. 加载环境变量
  4. 准备Linux启动参数

特别值得一提的是环境变量的处理流程:

env_init → env_relocate → env_load

如果检测到env分区损坏,会自动恢复默认值。这个机制让我在量产时避免了大量返修。

4. 内核引导的临门一脚

4.1 do_bootz:内核搬运工

bootz命令的执行包含这些关键步骤:

  1. 验证zImage头部签名
  2. 解析设备树结构
  3. 设置ARM寄存器传参:
    • r0 = 0
    • r1 = 机器ID(对于设备树可忽略)
    • r2 = 设备树物理地址

实际调试时,可以通过这些命令观察准备状态:

=> bdinfo // 查看板级信息 => fdt addr 83000000 // 定位设备树 => fdt print / // 查看设备树结构

4.2 内核交接的最后一米

kernel_entry的调用堪称电子世界的"时空穿越":

void (*kernel_entry)(int zero, int arch, uint params); kernel_entry = (void *)images->ep; kernel_entry(0, machid, r2);

这个瞬间,处理器会:

  1. 关闭所有中断
  2. 清空流水线
  3. 跳转到内核入口地址
  4. 永远不会再返回

我在调试这个阶段时,曾用JTAG捕获到精确的跳转时序:从执行kernel_entry到内核第一个printk输出,通常只需要3-5ms。

通过示波器可以观察到这个过程中电源纹波的变化:当内核开始调度后,CPU电流会出现明显的波动特征,这与U-Boot阶段的平稳电流形成鲜明对比。这种物理层面的变化,正是软件世界层层递进的最佳印证。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/20 21:41:32

电力场景配电站开关状态检测数据集VOC+YOLO格式1156张2类别有增强

注意数据集中大约1/3是原图&#xff0c;剩余按照1:2增强生成的图片注意查看图片预览数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件)图片数量(jpg文件个数)&#xff1a;1156标注…

作者头像 李华
网站建设 2026/5/20 21:39:35

云原生数据库实战:TiDB与CockroachDB对比选型与落地实践

云原生数据库实战&#xff1a;TiDB与CockroachDB对比选型与落地实践 大家好&#xff0c;我是迪哥。云原生数据库是云原生时代的核心组件&#xff0c;从 TiDB 到 CockroachDB&#xff0c;从分布式到多活&#xff0c;我们经历了多种方案的演进。今天就聊聊云原生数据库的选型和落…

作者头像 李华
网站建设 2026/5/20 21:36:06

用STM32CubeMX的TIM6实现精准1秒定时:HAL库与LL库代码对比与选择建议

STM32CubeMX中TIM6实现1秒定时的HAL与LL库深度对比 在嵌入式开发中&#xff0c;定时器是最基础也最常用的外设之一。对于STM32开发者来说&#xff0c;如何选择适合自己项目的驱动库层——是更抽象的HAL库还是更接近硬件的LL库&#xff0c;往往是一个令人纠结的问题。本文将以TI…

作者头像 李华
网站建设 2026/5/20 21:29:50

喜马拉雅音频下载终极指南:免费解锁付费内容的桌面神器

喜马拉雅音频下载终极指南&#xff1a;免费解锁付费内容的桌面神器 【免费下载链接】xmly-downloader-qt5 喜马拉雅FM专辑下载器. 支持VIP与付费专辑. 使用GoQt5编写(Not Qt Binding). 项目地址: https://gitcode.com/gh_mirrors/xm/xmly-downloader-qt5 你是否曾因网络…

作者头像 李华