ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RK3576启动流程详解:从BootROM到Linux根文件系统

RK3576启动流程详解:从BootROM到Linux根文件系统 前阵子调一块RK3576核心板上电之后串口纹丝不动。这种问题在嵌入式启动链路里实在是太常见了你以为的“开机”其实是BootROM、SPL、U-Boot、内核、根文件系统五个角色在几百毫秒内依次交接棒。如果你手里的板子也卡在RK3576启动阶段这篇就把整条链路拆开讲清楚——从芯片内部固化的BootROM到SPL从U-Boot proper再到Linux根文件系统挂载每一步做什么、为什么需要它、卡住时怎么定位都会聊到。内容适合正在跑RK3576或同系平台板卡、以及刚接触瑞芯微引导流程的朋友。1. 启动链路全貌为什么一次“开机”要拆成这么多棒很多人第一次接触RK3576启动流程时都会被绕晕明明叫U-Boot前面为什么还有个SPLSPL前面怎么还有个BootROMLinux内核明明是自己能起来的为什么还得靠U-Boot拉一把这事得从RK3576这类新一代ARM64 SoC的实际处境说起。上电瞬间CPU的DDR控制器完全处于未初始化状态片内SRAM又只有那么一点芯片内部ROM里固化的代码既要足够小、又要在任何异常情况下都能把系统拉起来所以它只能做“最小的事”。于是整个启动流程被拆成了明确的接力棒结构。1.1 五个角色各自的职责和交接条件我把RK3576从上电到进系统的链路按角色分个表方便你先建立全局感启动阶段运行位置主要职责交接条件BootROM片内ROM/SRAM初始化最小系统时钟、探测启动介质、加载并校验SPL把SPL完整搬进SRAM并通过校验SPLSecondary Program LoaderSRAM初始化DDR和关键时钟、加载ATF/U-Boot properDDR可用BL31和U-Boot镜像加载到DDRATFARM可信固件EL3提供PSCI电源管理、安全服务拉起U-BootU-Boot proper入口地址有效U-Boot properDDR、EL2初始化完整驱动、读取环境变量、加载内核/设备树内核镜像和DTB就位bootargs正确Linux内核DDR、EL1/EL0解压启动、初始化驱动、挂载根文件系统rootfs可访问init进程顺利执行这个表格基本就是RK3576启动链路的“总目录”。后面每一节其实就是把表格里的一行展开来细讲。1.2 为什么不能像单片机那样一步到位如果你之前只玩过MCU开发可能会想单片机从Flash读代码直接跑不也挺好吗为什么RK3576非要绕这么多弯核心原因是成本和灵活性。RK3576这类SoC的外部存储介质太多了——eMMC、SD卡、SPI NOR、SPI NAND、USB下载模式——每种介质的控制器初始化方式都不一样。如果把这些驱动全部固化在BootROM里BootROM体积会爆炸而且一旦出厂后发现有bug芯片只能报废重做。所以芯片厂把BootROM压到最小把真正复杂的存储、DDR、电源管理初始化全部后移到可升级的SPL/U-Boot里。换个生活化的说法BootROM像酒店房间里的“安全逃生图”它固定贴在墙上、内容非常精简只告诉你出口在哪真正的入住体验——怎么调空调、怎么连Wi-Fi得你自己去摸索。SPL和U-Boot就是那本可以随时更新、内容详细的服务手册。1.3 ARM64双世界架构对启动链的约束另一个常人容易忽略的原因是ARM64的特权级设计。RK3576是64位处理器运行在EL3、EL2、EL1、EL0四个异常等级下。U-Boot一般跑在EL2内核跑在EL1安全固件跑在EL3。U-Boot想跳到内核必须先通过ATF在EL3层完成安全状态切换否则直接越级跳转会触发异常。很多初次调试RK3576的朋友发现自己的U-Boot起不来排到最后居然不是U-Boot本身的问题而是BL31没有正确加载。这就是因为ATF在RK平台启动链里不是可选项而是必选项——它既是PSCI电源管理的提供方也是安全世界和普通世界之间的门卫。2. BootROM出厂就焊死在硅片上的第一棒BootROM是最容易被低估的一段。因为绝大多数开发者根本看不到它运行也没法给它打日志。但恰恰是这段看不到的代码决定了整条启动链路能不能开张。2.1 BootROM到底做了哪几件事RK3576上电后BootROM要完成的动作非常有限但极其关键。首先是基础时钟芯片刚上电时外部晶振或内部振荡器处于不稳定状态BootROM要先让PLL和核心时钟跑到一个基础频率保证CPU本身能正常取指。然后是启动介质探测BootROM根据芯片引脚状态、eFuse配置或者OTP里的启动优先级设定去eMMC、SD卡、SPI NOR等介质上扫描看哪个介质上有可用的启动镜像。值得注意的是BootROM阶段基本不会有串口输出。很多朋友上电后串口一片空白下意识以为是串口接线坏了其实很可能是SPL根本没有被BootROM成功加载。因为BootROM连串口外设都懒得初始化它只负责把SPL找出来、搬进SRAM、做完校验然后跳转。要验证BootROM是否工作正常最直接的办法是看芯片能不能进入maskrom或loader模式而不是指望它在普通串口上打印点什么。2.2 启动介质探测顺序与maskrom模式瑞芯微平台的启动介质探测顺序典型思路是从固定优先级的存储开始eMMC、SD卡、SPI NOR、USB下载模式。具体优先级在不同芯片上略有差异很多时候可以通过拨码开关或OTP配置来调整。如果所有介质里都找不到有效镜像BootROM就会进入一个“等救援”的状态这就是我们常说的maskrom模式。maskrom模式对开发者来说是个宝贝。RK平台能在量产阶段救回刷成砖的板子靠的就是这个模式。通常你只需要把板子的OTG口接到电脑使用瑞芯微官方烧录工具工具就能识别到BootROM里的USB描述符。因为BootROM内置了极小的一版USB设备驱动即使eMMC里的引导代码全毁了也能通过USB把新的SPL/U-Boot烧回去。这提醒了我们一件重要的事调试RK3576时不要轻言“变砖”。只要芯片还能进maskrom启动链路的人为修复空间就还在。2.3 安全启动的第一道锁如果产品开了Secure BootBootROM的职责还会再多一项——验签。芯片出厂后厂商会把公钥哈希烧进eFuse或OTP中BootROM在加载SPL时会先校验SPL镜像的签名。签名不对就直接拒绝执行。这个机制对量产设备意义重大它可以防止别人从外部存储介质里塞一个恶意引导程序确保只有经过签名的镜像才能在板子上运行。但副作用也很明显——调试阶段如果开了Secure Boot烧录的SPL没有对应私钥签名板子就只能永远卡在BootROM里连日志都看不到。所以我的习惯是刚拿到RK3576开发板时先确认Secure Boot处于关闭状态先调通功能最后做量产固件时再统一签名使能。3. SPL谁把DDR从完全黑暗里点亮如果BootROM是第一棒那SPL就是第二棒而且这一棒任务极其纯粹点亮DDR、搬运U-Boot。它不做文件系统不解析复杂分区不提供交互界面只做两件事——初始化内存和加载下一级镜像。3.1 BootROM为什么不敢自己初始化DDR外部DDR尤其是LPDDR4/LPDDR5的初始化本质是一次复杂的“硬件训练”。DDR颗粒的时序参数、电压摆幅、读写延迟、甚至板子走线长度都会影响到训练结果。不同核心板厂家用的DDR颗粒不同、PCB布局不同训练参数也千差万别。BootROM是固化在芯片里的它不可能预知每一块客户板子的DDR情况。所以芯片厂做了一个很聪明的设计BootROM只负责把很小的一段SPL代码从介质搬到SRAM而SPL里包含了板级DDR初始化代码和参数。这样每块板子都可以定制自己的DDR配置BootROM则保持“零知识”状态。这也是为什么同样一颗RK3576芯片不同厂家的核心板SPL镜像不能互刷——DDR初始化参数很可能不兼容。3.2 SPL在RK3576存储介质里的形态瑞芯微平台的SPL一般不是裸的二进制直接烧到介质开头的它外面还包了一层“idblock”结构。idblock里包含头部信息、驱动参数、SPL主体和校验数据整体再烧写到eMMC的boot分区或SD卡的固定偏移位置。BootROM去找镜像时会先读idblock头做基础校验然后才把SPL主体搬进SRAM。这里有一个很容易踩的坑如果直接把SPL裸镜像烧到eMMC开头BootROM是认不出来的。量产烧录时一定要用官方打包脚本或工具生成idblock格式的镜像然后再烧。我见过有同事图省事把编译出来的SPL二进制直接dd到mmcblk0boot0结果上电后完全没反应——排查了半天最后发现就是缺了idblock封装。3.3 为什么SPL之后还插了ATF这一层SPL在完成DDR初始化后接下来要做的是加载ATF和U-Boot proper。有朋友会问SPL把DDR点亮了为什么不直接加载Linux内核非要再经过U-Boot proper和ATF答案和启动策略灵活性有关。SPL为了体积做了大量裁减它没有完整文件系统驱动、没有网络栈、没有交互式命令行。如果把加载内核的任务也塞给SPLSPL会变得和U-Boot一样庞大SRAM根本装不下。而ATF则是因为ARM64安全架构绕不开U-Boot要进入内核跑在EL1必须由EL3的BL31完成低级别安全状态迁移顺便把PSCI服务建立起来之后内核才能用它来控制CPU启动和电源域。RK3576这条链路实际上是SPL把BL31加载到安全内存区再把U-Boot proper加载到DDR普通内存区最后SPL跳到BL31BL31初始化完安全世界后再跳到U-Boot。这中间只要有一个镜像的加载地址不对整个启动就会在沉默中失败这也是SPL阶段最难调试的原因之一。4. U-Boot proper真正能敲命令的“开机菜单”到了U-Boot proper这个阶段板子才算“活了过来”。串口有输出命令能交互环境变量能保存你可以像控制一台小电脑一样控制RK3576的启动行为。4.1 从最小驱动集合到完整驱动模型SPL为了塞进SRAM用的驱动是最简版一个串口一个存储控制器一个DDR初始化完事。U-Boot proper就不一样了它会加载完整的设备驱动模型DM把PMIC、以太网、USB、MMC、I2C等驱动全部枚举出来。这也是为什么U-Boot的启动日志要比SPL长出一大截——它确实做了更多的事。从调试角度说SPL阶段如果坏了你能获得的反馈非常有限但U-Boot proper阶段坏了至少串口会打印一堆信息还能进命令行手动排查。所以遇到启动问题我第一件事永远是判断问题出在SPL之前还是之后。判断依据很简单能看到U-Boot的版本号字符串和命令行提示符SPL就已经成功了。4.2 bootcmd里的秘密加载地址和启动方式U-Boot真正干活的地方是bootcmd环境变量。RK3576的SDK默认启动命令通常长这样setenv bootargs consolettyS0,1500000 root/dev/mmcblk0p5 rootwait rw; load mmc 1:4 $kernel_addr_r Image; load mmc 1:4 $fdt_addr_r rk3576-evb.dtb; booti $kernel_addr_r - $fdt_addr_r;这里的三个关键点要注意。第一个是$kernel_addr_r和$fdt_addr_r这两个加载地址不能随便乱设必须避开U-Boot自身占用的内存区域和ATF占用的安全内存区否则内核镜像或设备树可能会被U-Boot自身覆盖启动时出现各种诡异问题。第二个是booti而不是bootmbooti用于引导ARM64的Image格式内核bootm一般配合FIT镜像或uImage使用。第三个是分区号mmc 1:4这个数字对应的是mmc设备号和分区号一旦分区表变动这里就必须要同步改。RK3576这类RK平台常用的镜像加载方式不止裸ImageDTB这一种。另一种更现代更省心的方式是FIT Image——把U-Boot、ATF、DTB、内核甚至ramdisk打包成一个itb文件U-Boot用bootm统一加载校验。FIT的好处是镜像之间的一一对应关系由打包工具保证不容易出现“Image更新了但DTB还是旧版”这种低级事故。4.3 环境变量、分区表和kernel cmdline的联动很多RK3576板卡启动到一半就panic十有八九是root参数和分区表对不上。瑞芯微平台的分区有两种主流做法老式的parameter文本分区和新式的GPT分区。无论哪种U-Boot的环境变量里都会存一套固定的启动命令这套命令里的mmc设备号和分区号必须和实际烧录的分区表一致。我自己的习惯是每次改动分区表后第一件事不是重新编译内核而是进U-Boot命令行敲printenv bootcmd和printenv bootargs确认当前环境变量里的分区号是否和分区表对得上。如果发现是旧环境变量残留直接用setenv修正并saveenv保存。别小看这一步我见过太多项目因为改了产品分区规划却没有同步更新U-Boot环境变量导致量产板子启动后找不到rootfs最后只能拆机重烧。4.4 量产阶段U-Boot的瘦身与加固开发阶段U-Boot怎么方便怎么来但量产阶段就完全是另一套思路。量产U-Boot通常会做这几件事把bootdelay设成0让板子上电后不等待输入直接启动把默认串口打印去掉或降级减小启动时间和泄密面关闭不必要的命令行命令减少攻击面条件允许就开启FIT镜像签名校验。这里我特别提醒一下量产固件不要直接拿开发版的U-Boot环境变量区去烧录。开发过程中反复saveenv会把一些临时调试用的变量存进去比如某个错误的root路径、某个带调试信息的bootargs。量产镜像应该用干净的默认环境变量或者干脆在U-Boot配置里开启“环境变量只读”避免现场误改。5. 内核到根文件系统启动链路的最后一公里U-Boot执行booti之后启动接力棒就交到了Linux内核手里。很多人的调试经验到这一步就中断了——以为内核一旦解压跑起来系统就一定能进到shell。实际上“内核起来了”和“根文件系统挂载成功”之间还隔着一条非常容易翻车的河。5.1 内核接管后的第一串日志意味着什么当串口里出现Starting kernel ...之后U-Boot就完成了它的使命。紧接着内核会打印大段的启动日志早期的一行通常是[ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x412fd0b0]从这一刻起串口输出的内容就不再受U-Boot控制而是由内核的earlycon或标准console驱动接管。如果U-Boot跳转后串口完全黑屏别急着怀疑内核编译有问题——先确认bootargs里的console参数是否正确。比如RK3576的调试串口通常是ttyS0波特率1500000如果这里写成了ttyS1或错误波特率内核日志就会全部输出到空气里看起来就像“内核没启动”。5.2 root参数根文件系统的精确门牌号内核启动过程中会按照bootargs里的root参数去找根文件系统。最常见的写法是root/dev/mmcblk0p5 rootwait rwmmcblk0p5的意思是第一个MMC设备通常是eMMC的第5个分区。这里分两个层面看rootwait必不可少因为内核在SD/eMMC驱动还没完全挂载起来时分区设备节点可能还没生成如果不加rootwait内核会直接报“VFS: Unable to mount root fs”而分区号p5则必须和实际分区表里的rootfs分区位置一致。在调试阶段我更推荐用PARTUUID替代裸的设备节点名写法类似rootPARTUUID01234567-89ab-cdef-0123-456789abcdef rootwait rw原因很简单mmc设备节点的编号在某些场景下会漂移。比如你同时接了eMMC和SD卡内核探测顺序变了mmcblk0和mmcblk1可能互换用裸设备名就会导致根文件系统挂错盘。PARTUUID是分区表里写入的全局唯一标识只要分区表不变它就不会漂移。5.3 initramfs方案内存里先跑一套“临时系统”开发板阶段还有个非常实用的招儿先别直接挂eMMC/SD里的rootfs而是让内核加载initramfs在内存里先跑一个迷你的用户态环境。这样做的优势很明显——不用等存储驱动调稳先验证内核本身是否正常再用这个临时环境去检查真正的rootfs分区。制作一个简单的initramfs并不复杂。准备好一个目录放一个静态编译的busybox写一个简单的init脚本然后用cpio打成initramfs.img在U-Boot里通过load和booti传给内核即可。我曾在RK3576板子上用这种办法排查过rootfs损坏的问题先在initramfs里mount /dev/mmcblk0p5 /mnt发现分区根本挂不上用fsck检查才知道是eMMC的某个块坏了。要是没有initramfs这个中间层内核会直接在挂载阶段panic你甚至没机会去看rootfs到底出了什么问题。5.4 内核panic常见日志与定位路径根文件系统挂载失败的典型日志如下[ 1.234567] VFS: Unable to mount root fs on unknown-block(179,5) [ 1.234567] Kernel panic - not syncing: VFS: Unable to mount root fs看到这行日志时优先级最高的排查顺序是这样的先看root参数是否指向了正确的分区号再看分区表里这个分区的fstype是不是内核里真的编进去了比如用ext4就得确认CONFIG_EXT4_FSy用erofs则要看CONFIG_EROFS_FS最后再看存储驱动是否正常工作比如eMMC的控制器是否已经输出mmc0: new high speed SDXC card之类的枚举信息。一个有经验的工程师会通过这个panic日志立刻判断出内核本身没问题问题出在“内核和rootfs之间的连接”。把排查范围从整条启动链路缩小到bootargs和分区表两个点问题往往很快就水落石出。6. 把整条链路串起来调试的实战姿势文章写到这里链路中的每个环节都拆开讲过了。但实际的调试过程不是看书而是面对一块毫无反应或半路panic的板子手上只有一根串口线和一台电脑。我把这些年调试RK平台启动的经验浓缩成一套可复用的排查方法。6.1 串口日志阶段划分法串口日志是RK3576启动调试的第一现场。拿到日志后不要逐字读先按阶段划分法快速定位问题区间日志特征当前阶段问题侧重点完全没有输出BootROM/SPL之前存储介质里的引导镜像缺失、idblock损坏、maskrom状态U-Boot SPL握手信息后无U-Boot proper版本信息ATF/SPL跳转BL31加载失败、U-Boot入口地址错误、SRAM/DDR地址冲突出现U-Boot命令行提示符但无Starting kernelU-Boot启动策略bootcmd错误、镜像加载失败、bootdelay过长Starting kernel后无内核日志内核早期启动console参数错误、内核镜像损坏、DTB不匹配内核日志正常但VFS panicrootfs挂载root参数错误、分区表不符、文件系统驱动缺失这套划分法在绝大多数情况下都能把问题锁定在一个阶段内剩下的就是到对应阶段去查细节。6.2 “完全没有日志”时先别怀疑芯片坏了RK3576板子完全无日志时最常见的三个原因按概率排序是SPL/idblock没有正确烧进eMMC或SD卡板子进入了maskrom模式而不是正常启动模式串口连接的TX/RX接反或波特率不对。这三个原因都不涉及芯片本身损坏。所以正确的排查姿势是先确认能否通过OTG口让工具识别到设备。如果能识别到说明BootROM在跑芯片活着问题在SPL或存储介质如果识别不到再查电源、时钟、复位等最小系统电路。把精力花在“确认BootROM是否活”这件事上比反复更换芯片和焊板子高效得多。6.3 让U-Boot在出错时“停一下”而不是直接重启很多RK3576的开发板在启动失败后会自动重启导致你根本来不及看日志。U-Boot提供了一个非常实用的机制启动失败时可以进入命令交互界面等待开发者输入。你可以在U-Boot配置里打开CONFIG_BOOTSTD相关的自动交互或者在bootcmd前面临时加一个sleep 3给自己留出几秒钟手动中断的时间。一旦进入到U-Boot命令行整个调试就从“盲人摸象”变成了“定向排查”。你可以用mmc list看当前扫描到了哪些MMC设备用part list mmc 1看分区表是否正确用ls mmc 1:4检查内核镜像是否真实存在甚至用md直接查看内存地址上的数据是否和镜像内容一致。这些命令组合起来能覆盖从存储介质到内存加载的全链路检查。6.4 我在实际项目里踩过的两个典型坑第一个坑是SPL和U-Boot版本不匹配。RK3576的SDK里SPL和U-Boot proper通常是从同一个U-Boot源代码树编译出来的正常情况下不存在兼容问题。但我有一次为了用一个新功能单独编了一个新U-Boot却没有同步重新编SPL旧SPL加载新U-Boot后U-Boot还没打印版本号就卡死了。排查了很久才发现是新旧镜像之间的ABI变了SPL传下去的参数新U-Boot完全不认。从那以后我给自己定了条规矩SPL和U-Boot proper必须来自同一次编译不允许混搭。第二个坑是改分区表后忘了同步root参数。这个前面提到过但值得再强调一次。当时我为了给OTA增加一个缓存分区把所有分区编号往后顺延了一位重新烧录后板子启动到VFS panic。查了半天才发现U-Boot环境变量里的root/dev/mmcblk0p5对应的还是旧分区表rootfs实际已经挪到了p6。修正环境变量后一切正常。这个坑其实特别好避开——改分区表的同时把bootargs里的分区号同步修订并在量产脚本里固化下来。RK3576的启动链路虽然长但每一级都有明确的职责和清晰的交接边界。把这条链路理解透了以后不管换什么SoC平台启动调试的思路都是相通的先定位当前日志处于哪个阶段再看这一阶段的交接条件是否满足最后针对性地检查加载地址、环境变量、分区表和签名配置。这套思路帮我在不少板子上稳住了心态也希望它能帮你少走几趟弯路。
返回列表