资讯详情

资讯详情

芯片上电启动链解析:ROM code、SPL、U-Boot与DDR训练

简介芯片从复位到内核启动的完整链路常因阶段多、模式杂而难以串联这份面向嵌入式底层开发初学者及驱动、系统移植人员的文档围绕上电复位、ROM Code、SPL、U-Boot 到 Kernel 的顺序展开重点解释各阶段在 SRAM 与 DDR 之间的加载关系及职责边界。压缩包共 1 个 docx 文件约 1.41MB篇幅集中、便于通读适合作为 boot 流程的入门梳理或调试时的速查材料。文档结合 SD 卡启动、eMMC、UART/USB 下载以及 ptset 测试模式等场景说明 ROM Code 初始化与启动介质判断、SPL 先搬运约 70KB 程序再加载剩余内容、U-Boot 装载内核等关键环节。已有 923 人学习读者可据此建立四阶段衔接的整体框架理解不同 boot 方式的适用差异并为后续排查启动卡死、介质选择与镜像加载问题提供清晰线索。1. 上电那一刻为什么第一条指令不可能待在 DDR 里芯片启动 boot 这件事很多人第一反应是「从 flash 里把程序读出来跑」。真正麻烦的地方在于上电瞬间没有任何一块大容量存储器是可信可用的。DDR 靠电容存数据掉电即失而且必须先经过一轮训练校准才能稳定读写eMMC、SD 卡要被控制器访问控制器又依赖时钟和引脚配置。整条链的第一条指令只能待在掉电不失、复位即可读、容量极小的一块存储上——片内 ROM。这条「先在小地方活下来再把自己搬到大地方」的链条直接决定了后面每一级为什么存在、为什么长成那样。2. ROM code固化在硅片里的第一行代码能做什么ROM code 是整条启动链上唯一一段流片之后就谁也改不了的代码。它被掩膜在片内 ROM 里容量通常只有几十 KB 到一百多 KB优化等级拉满也塞不下太多东西。这个「不可修改 极小容量」的双重约束反过来解释了 SPL、U-Boot 为什么会被拆出来——并非设计者偏爱分层真正的原因是 ROM code 承担不了这么多职责。它只能做那些「不做就永远起不来」的最小动作做完立刻把控制权交出去。2.1 复位向量与 handler 模式CPU 被强制听话的第一秒复位信号释放的瞬间CPU 不读任何外部存储器它从一个由芯片设计固定死的地址取指这个地址在物理上映射到 BootROM。同时异常向量表也被放在那里。所以哪怕你想在第一条指令上做点什么花样都没机会——硬件已经把路线定好。进入 ROM code 之后CPU 运行在一个特权模式原文里称为 handler 模式实际就是复位后落到的那个高特权态MMU 关着cache 关着地址就是物理地址一切访问直达。这时的内存模型非常简单能访问的只有片内 SRAM 和少数几个外设寄存器。栈指针会被立即设到 SRAM 顶部往下的一小块区域BSS 清零然后才敢进 C 语言写的主流程。我一般会这么理解这一步的定位它把一颗刚从模拟电路里醒过来的芯片变成一个能执行顺序逻辑的处理器。这里有个容易被忽略的细节——看门狗。ROM code 往往会先把看门狗喂着或者干脆关掉因为后面 DDR 训练动辄几十毫秒如果看门狗默认开着且超时很短芯片会在训练途中被复位表现就是「上电后毫无反应」这类玄学现象。2.2 系统初始化与 boot 介质判定strap、eFuse 和一个小状态机CPU 初始化完成后ROM code 会判断要不要进测试模式原文的 ptset。这个模式是给产线用的进去之后就不再走正常启动路径转而执行内置的测试固件做引脚连通性、存储器件探测之类的检查。不进测试模式才继续做系统初始化时钟树、引脚复用、以及「要被用来 boot 的那些模块」的配置。原文里点到的键盘、UART 初始化配置就是这一类——注意它只配置「这次启动可能用到」的模块而不是全部外设容量和时间都不允许它铺开。接下来是 boot 介质的选择这是 ROM code 里最像状态机的一段。选择依据通常有两个来源一是外部 strap 引脚的电平复位释放前后几个时钟周期内被锁存进寄存器硬件上靠上拉下拉电阻决定相当于一个拨码开关二是 eFuse 里一次性烧写的配置位优先级一般高于 strap。两者组合出一张「候选介质列表」比如 SD → eMMC → SPI NOR → UART 下载。判定不是「选中一个就完事」而是「按顺序试」。对每个候选介质初始化控制器、读第 0 个块、校验镜像头部的 magic 和长度失败就换下一个。这个设计看着朴素实际是整条链鲁棒性的来源一张卡坏了、一个片外 flash 空片芯片仍然能从别的通道起来。2.3 从存储介质到 SRAM搬运逻辑与头部校验选好介质之后真正的搬运才开始。ROM code 不认识文件系统不认识分区表它只认镜像开头固定偏移处的一小段头部。头部里至少有magic 标识、镜像长度、加载地址、入口地址很多方案还会加 CRC 和签名。/* ROM code 主流程骨架入口用汇编写主体用 C 写这是常见的组织方式 */ void bootrom_main(void) { cpu_init(); /* 关 MMU/cache设栈到 SRAM 顶部清 BSS喂狗 */ if (read_strap_mode() MODE_TEST) { run_production_test(); /* ptset产线测试固件走完就不再回正常启动 */ return; } sys_init(); /* 时钟树、pinmux、以及 boot 通道所需外设的最小配置 */ for (int i 0; i boot_dev_count; i) { dev_t dev boot_dev_order[i]; /* 由 eFuse 与 strap 共同决定的优先级表 */ if (dev_init(dev) ! 0) continue; /* 控制器都起不来直接换下一个 */ boot_hdr_t hdr; if (read_boot_header(dev, hdr) ! 0) continue; /* 读不到第 0 块空片或介质未插好 */ if (verify_header(hdr) ! 0) continue; /* magic / CRC / 可选签名任一不过就换 */ if (hdr.load_size SRAM_USABLE) continue; /* 装不下就别试避免踩烂栈区 */ load_image(dev, hdr.load_addr, hdr.load_size); /* 搬到片内 SRAM 指定地址 */ jump_to(hdr.entry); /* 跳过去正常情况下不再返回 */ } enter_download_mode(); /* 所有介质都失败落到 UART/USB等主机喂镜像 */ }几个参数值得单独说清楚。boot_dev_order不是代码里写死的数组而是每次上电根据 eFuse 和 strap 现算出来的这也是同一颗芯片能做出「卡启动」「eMMC 启动」不同产品形态的原因。hdr.load_size是镜像头自己声明的长度ROM code 会拿它跟SRAM_USABLE比对——这个上限通常是 SRAM 总容量减去 ROM code 自己的栈、堆、BSS 之后的余量原文提到的 70KB 就属于这一档的数字。hdr.entry是下一级程序的入口跳过去之后 ROM code 的使命就结束了它不会回来也不负责善后。enter_download_mode()是整条链里唯一的「后悔药」也是很多开发者对 boot 流程最该建立的一个认知只要芯片还能进下载模式就永远有救。UART 和 USB 下载协议被实现在 ROM code 里主机侧工具通过它把镜像推下去镜像先落到 SRAM 里跑起来再由它去把 eMMC 或 flash 写回来。换句话说下载模式本身就是一个「借 SRAM 当舞台」的最小启动路径只不过舞台上的演员是主机送进来的。3. SPL70KB 的窄门为什么决定了启动链的形状SPL 全称 Secondary Program Loader在某些方案里也被叫成 first stage bootloader 或者 MLO。名字不重要重要的是它的处境运行在片内 SRAM 里抬头就是 70KB 左右的天花板。这个天花板并非人为设的限制它由 SRAM 物理容量减去 ROM code 的运行期占用算出改不了。整个启动链之所以是四级而不是两级根子就在这道窄门上。3.1 AON SRAM 的容量账先搬 70KB 的意义先算一笔账。假设片内 always-on SRAM 总共 128KBROM code 自己在运行期要占掉一部分栈和堆镜像头部要占掉几百字节SPL 自己的 BSS、栈、以及给 DDR 训练预留的临时缓冲又要占一块能留给「代码 只读数据」的空间大概就是六七十 KB。这个数字要跟 U-Boot proper 的体量对比一个带命令行、带 USB 栈、带文件系统、带网络协议的 U-Boot编译出来几百 KB 起步压缩了也常常超过一百 KB。塞不进 SRAM一点办法都没有。所以 SPL 存在的意义非常直白它是那个「小到能塞进 SRAM、又大到足以把 DDR 拉起来」的最小程序。它不解析文件系统不支持命令行不做任何交互只干三件事——把时钟拉到工作频率、把 DDR 训练好、把真正的大程序从存储介质搬进 DDR。有人会问能不能让 ROM code 直接初始化 DDR技术上可行但 ROM code 是不可修改的DDR 参数跟板级走线、颗粒型号强相关把它固化进硅片等于把芯片锁死在一种内存配置上商业上完全不可接受。这条约束正是分级的根本动机可变的东西必须放在可修改的软件里。3.2 SPL 的最小职责集时钟、DDR 训练、下一级搬运DDR 训练是 SPL 里最容易出问题的一段。DDR 的读写时序随颗粒、PCB 走线长度、电压、温度漂移出厂参数只能给一个大致的窗口必须上电时实测校准。常见的训练项包括写 leveling、读 gate training、读 leveling、以及参考电压扫描。每一步都在找一个「能稳定读写」的时间点找到之后写进控制器寄存器。正因为这一步依赖实测SPL 里通常会带一张由厂商工具生成的参数表作为训练的起点。参数表不对训练要么失败要么勉强通过然后在高温下随机翻车——这类问题用玄学形容不算夸张。/* SPL 主体跑在 SRAM 里目标是让 DDR 可用然后把 U-Boot proper 搬进去 */ void spl_main(void) { board_early_init(); /* pinmux、串口为了能看到打印、PMIC 电压调整 */ clock_init(); /* PLL 倍频把 DDR 和 CPU 拉到目标频率 */ if (ddr_init(ddr_params) ! 0) { spl_panic(ddr training failed); /* 训练失败必须停住不能带病往下跳 */ } uart_puts(ddr ready\n); /* 从 ROM code 通过寄存器传下来的启动介质信息里找到 U-Boot 所在位置 */ load_addr UBOOT_LOAD_ADDR; /* 必须与 U-Boot 的链接地址一致 */ if (load_image_from_bootdev(load_addr, img) ! 0) { spl_panic(load uboot failed); /* 读失败时可以切备份镜像再试一次 */ } if (verify_image(img) ! 0) { spl_panic(uboot verify failed); /* CRC 或签名不过同样别硬跳 */ } clean_dcache(); disable_mmu(); /* 跳转前把地址翻译关掉约定一致 */ jump_to_uboot(UBOOT_ENTRY, boot_info); /* 把启动介质等信息作为参数递过去 */ }ddr_params是那张参数表通常以 const 数组形式编进 SPL。UBOOT_LOAD_ADDR是最容易被忽视的参数SPL 把镜像搬到哪U-Boot 就必须按哪个地址链接两者差一个字节都会在跳转后跑飞现象是串口吐一串乱码然后死掉。verify_image里的校验如果有安全启动要求此时验的是签名而不只是 CRC。boot_info是 SPL 传给下一级的小结构体告诉 U-Boot「你是从哪块介质起来的」省得它自己再猜一遍。3.3 冗余与回退把「唯一副本」变成两份SPL 阶段还有一个不显眼但很关键的设计镜像往往不止一份。常见做法是在存储介质上放两份 SPL 或两份 U-Boot头部带版本号和序号SPL 读第一份校验不过就切第二份。OTA 升级时先写备份区、校验通过再切换标志位这样升级到一半掉电也不会变砖。这套机制的代价是占用存储空间和增加一次校验耗时收益是把「一次写坏就报废」的概率压到接近零。对于量产设备这个交换非常划算。4. U-Boot 与 Kernel控制权怎么从 DDR 交到用户空间到了 U-Boot 这一级环境终于宽松了几百 KB 甚至几 MB 的空间完整的命令行可以读文件系统可以走网络可以交互。也正因为宽松这一级的职责从「活下去」变成了「决定怎么活」——从哪启动、加载哪个内核、传什么参数都在这里定。4.1 SPL 跳到 U-Boot proper地址、cache 与跳转约定交接这一步有几个隐式约定任何一个不满足都会导致「SPL 打印正常跳过去就没了」。第一是地址一致前面说过。第二是 cache 状态SPL 从介质读数据到 DDR 时可能开了 cache跳转前必须 clean 并 invalidate否则 U-Boot 可能读到脏数据。第三是 MMU 状态多数方案在跳转前保持 MMU 关闭让 U-Boot 自己从头建立映射。第四是参数传递启动介质、内存大小这类信息通过寄存器或一块预留内存递过去。U-Boot 起来之后会做一次「重定位」把自己从当前加载地址整体搬到 DDR 的高端然后跳到新位置继续跑。这一步的目的是腾出低端连续内存给内核和文件系统用。重定位前它还在低地址运行重定位后再跳一次这一跳是对链接脚本、内存布局理解是否扎实的考试。4.2 boot 模式矩阵本地启动与主机下载两条路原文里提到这套 boot 过程覆盖了 USB、UART 下载模式SD、eMMC boot 模式以及 ptset 测试模式。把这些当成一张矩阵看比逐条背诵清楚得多。模式谁来发起镜像落点典型用途SD / eMMC bootROM code 主动读卡先 SRAM后 DDR正常量产启动UART 下载主机侧工具发起握手先 SRAM可再写入介质空片首次烧录、救砖USB 下载主机侧工具发起枚举同上速率更高大批量产线烧录ptset 测试由 strap 或 eFuse 决定不进正常流程产线功能自检从这张表能看出一条清晰的分界前三行都是「把镜像弄进内存然后执行」区别只在传输通道第四行干脆不进启动链属于另一条岔路。理解这一点之后很多启动日志里看似矛盾的打印就有了解释——它们本来就不是同一条路径上该出现的东西。下载模式的价值不只是烧录。它把「芯片还能不能对话」和「存储介质里有没有可引导镜像」这两件事解耦了。前者是硅片层面的能力后者是软件层面的状态只要前者成立后者坏了也能修。4.3 kernel 加载与传参最后一条跳转指令U-Boot 的最后工作是把内核镜像、设备树、可选的 initramfs 从存储或网络读进 DDR然后按架构约定的方式跳进去。以 AArch64 为例跳转时 x0 通常放设备树的物理地址其余参数清零MMU 和 cache 保持关闭由内核自己建立页表。# U-Boot 命令行里最朴素的一次手工启动用来验证各段地址是否自洽 setenv bootargs consolettyS0,115200 root/dev/mmcblk0p2 rw load mmc 0:1 0x40080000 Image # 内核镜像搬到 DDR 的加载地址 load mmc 0:1 0x48000000 board.dtb # 设备树必须与 Image 不重叠 booti 0x40080000 - 0x48000000 # 起内核最后一项是 dtb 地址三个地址必须互不重叠且内核的加载地址要满足它对物理内存起始偏移的要求有的架构要求镜像不能跨越某个对齐边界。bootargs里的 root 参数决定了内核拿到控制权之后去哪挂根文件系统写错了的表现是内核打印跑到一半开始报「找不到根文件系统」然后 panic——这个现象常被误判成内核问题实际是参数问题。内核进场后先解压如果是压缩镜像、建立页表、初始化内存管理、探测设备、挂载根文件系统最后启动第一个用户态进程。到这一步boot 链才算真正结束从一个只有几十 KB 的 ROM 程序一路接力到一个能跑应用的操作系统。5. 避坑boot 卡在半路怎么用日志给启动链切片启动失败最怕的是「什么都没发生」。四个阶段各自有没有跑过、跑到哪一步停的必须靠可观测量来判断猜测没用。5.1 用串口打印和 GPIO 翻转给启动链切片第一手证据是串口。ROM code 阶段通常没有打印或者只有极短的字符SPL 开始才有稳定输出所以「串口一片安静」和「SPL 打印到 DDR ready 就停」是两种完全不同性质的故障定位方向也完全不同。没有串口输出时退一步用 GPIO在 SPL 的开头、DDR 训练前后、跳转前各翻转一个引脚用示波器或者逻辑分析仪看波形走到哪一步。这招土但在没有任何软件的裸片面前是唯一可靠的手段。血泪经验是一定在硬件设计阶段就留出至少一个可观测的测试点和一路调试串口后期再想加就没机会了。每一级的「最后一句打印」就是天然的切片点。把日志按阶段归类故障点会自己浮出来。5.2 五条踩坑记录现象上电后串口完全无输出电流也很小。原因多半卡在 ROM code 之前或之中。常见的三种是电源时序不对某路电压没按时起来、strap 引脚被误拉导致选了个不存在的启动介质、以及介质本身没插好或空片。解决先量各路电源的上电顺序和复位释放时刻再核对 strap 电平最后格式化一张已知可启动的卡插上去试。如果这些都正常进下载模式握手——能握手说明芯片活着问题在介质和头部。现象SPL 打印到一半停住最后一行往往是 DDR 相关。原因DDR 训练失败。参数表与实际颗粒不匹配、供电电压偏低、走线过长导致时序余量不足都会表现成一样的结果。温度也会影响常温能过、高温过不去是典型症状。解决先把频率降一档验证是不是余量问题再换保守参数表最后才怀疑硬件。训练日志里通常有每一步的窗口大小窗口窄得离谱就是硬件问题。现象SPL 正常结束跳转后串口吐乱码或直接静默。原因加载地址与链接地址不一致或者跳转前 cache、MMU 状态没处理好。解决核对 SPL 里的加载地址与 U-Boot 链接脚本里的起始地址是否完全一致确认跳转前做了 cache clean 和 MMU 关闭确认栈指针没有指向会被覆盖的区域。现象改了配置之后镜像变大ROM code 阶段就失败或者 SPL 跑起来行为异常。原因SPL 体积超过了 SRAM 可用空间。头部声明的长度超出上限时ROM code 会直接放弃这个介质即使勉强塞进去也可能踩到栈区导致随机崩溃。解决裁剪 SPL 功能、打开压缩、把非必需驱动挪到 U-Boot 里然后看一眼 map 文件确认实际占用。现象小批量试产正常量产批次偶发不启动。原因时序余量或电压余量不够个体差异被放大也可能是 eFuse 配置烧错而它一次性不可逆没有后悔药。解决试产阶段做高低温循环和多次上下电统计失败率eFuse 烧写必须先在少量样片上验证量产工装保留下载模式入口作为兜底。6. 设计取舍四级启动链是在为谁让步把这四段摆在一起看会发现整条链的复杂度分布不是随意的它精确对应了「哪些东西可变、哪些不可变」。阶段存储位置可修改性主要约束ROM code片内 ROM流片后不可改容量、无外部依赖SPL片内 SRAM可升级70KB 左右的天花板U-BootDDR可升级、可交互功能与体积平衡KernelDDR可升级硬件抽象与通用性ROM code 承担的是「不可变」的部分最基础的初始化、介质判定、下载协议。这些东西一旦定死就不能动所以必须做得极少。SPL 承担的是「跟着板子变」的部分DDR 参数、时钟配置这些必须可改所以放到软件里。U-Boot 承担「跟着产品形态变」的部分从哪启动、加载什么。内核承担「跟应用变」的部分。代价也很明显。四级接力意味着四次上下文切换、四次校验、四份镜像要维护任何一级的文件头和地址约定不一致排查成本都远高于单体程序。为了对冲这个代价才有了下载模式这条后路、双份镜像的冗余、以及各级的自校验。换句话说这套设计的鲁棒性并不来自某一级的强壮而来自「每一级都假定上一级可能出错并准备好替代路径」。验证这套假设最直接的办法是做一次「破坏性演练」故意把第二份镜像写坏、故意让 DDR 参数偏一点、故意在升级中途断电然后看设备能不能自己回到可用状态。做完一轮你对这份启动流程的理解会比读十遍文档扎实。从那以后我每次碰新的启动链都会先把「哪一级的失败能被谁兜住」列成一张表再动手缺一条就补一条。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →