第三章 Hi3559A启动流程:从BootROM到文件系统挂载
说实话,搞嵌入式开发这么多年,我见过不少新手一上来就急着写应用代码,结果板子起不来,连打印信息都看不到。其实,搞懂启动流程,就像摸清了芯片的脾气。Hi3559A的启动,说白了就是一场精心设计的接力赛——从片内ROM到DDR,再到内核和文件系统,每一棒都不能掉链子。
3.1 BootROM:芯片的第一口呼吸
芯片上电后,CPU会从哪个地址开始执行?答案是片内的BootROM。这个ROM是出厂固化的,你改不了,也擦不掉。它的任务很简单:初始化最基本的硬件,然后根据管脚电平或eFuse配置,决定从哪个介质加载下一级代码。
我个人习惯把BootROM比作「芯片的BIOS」。它做的事情包括:
- 初始化CPU时钟和PLL
- 配置基本的DDR控制器(但此时DDR还没完全可用)
- 检测启动介质选择管脚(比如从SPI Flash还是eMMC启动)
- 从选定的介质读取Fastboot镜像到片内SRAM
关键点:BootROM阶段,DDR通常还没初始化完毕。所以Fastboot镜像必须足够小,能塞进片内SRAM(Hi3559A的SRAM大约192KB)。我见过有人把Fastboot编译得太大,结果BootROM加载失败,板子直接变砖——嗯,这坑我踩过。
3.2 Fastboot:短小精悍的搬运工
Fastboot是海思平台特有的一个中间阶段。它不像Uboot那么功能丰富,但胜在体积小、启动快。它的核心使命只有一个:初始化DDR,然后把Uboot从存储介质搬到DDR里。
你可能会问:「为什么不让BootROM直接加载Uboot?」原因很简单——Uboot太大了,片内SRAM装不下。Fastboot就像个瘦小的快递员,先把大件货物(Uboot)从仓库(Flash)搬到宽敞的客厅(DDR),然后功成身退。
Fastboot的代码通常放在SPI Flash的固定偏移位置。我记得在Hi3559A上,这个偏移是0x0或者0x40000,具体看你的分区表怎么定。
避坑指南:我曾经因为Fastboot的时钟配置不对,导致DDR初始化失败,Uboot加载后跑飞。排查了整整两天,最后发现是PLL参数写错了。所以,如果你遇到板子反复重启,先检查Fastboot阶段的DDR初始化日志。
3.3 Uboot启动:真正的引导指挥官
Uboot被加载到DDR后,才算是真正进入了我们熟悉的引导阶段。Hi3559A的Uboot和标准Uboot大同小异,但有几个海思特有的点需要注意。
Uboot启动流程大致如下:
- 从DDR起始地址开始执行(通常是0x42000000附近)
- 初始化串口、网卡、Flash控制器等外设
- 读取环境变量(env),决定启动参数
- 加载内核镜像(uImage或zImage)到DDR
- 加载设备树文件(.dtb)到DDR
- 跳转到内核入口
这里有个细节:Hi3559A的Uboot默认会从SPI Flash的特定分区读取环境变量。如果你改了启动参数但没保存,下次重启又变回默认值——我刚开始就老犯这毛病,后来养成习惯,每次改完env都记得执行 saveenv。
# 典型的Uboot启动参数示例
setenv bootargs 'mem=512M console=ttyAMA0,115200 root=/dev/mmcblk0p2 rw rootfstype=ext4'
setenv bootcmd 'sf read 0x42000000 0x100000 0x800000; bootm 0x42000000'
saveenv
boot
注意:Uboot阶段最容易出问题的是DDR时序。Hi3559A支持DDR3和DDR4,但不同颗粒的时序参数差异很大。我曾经换了一批DDR颗粒,结果Uboot启动到一半就卡死,最后发现是tRFC参数没调对。建议量产前务必做DDR压力测试。
3.4 内核启动:从汇编到C的世界
Uboot跳转到内核后,内核会先执行一段汇编代码(head.S),完成页表初始化、中断向量表设置等底层工作。然后才跳到C语言的入口函数 start_kernel。
Hi3559A的内核启动有几个关键点:
- 设备树(DTB)必须正确传递,否则外设驱动加载会失败
- 内核启动参数中的
mem要和你实际DDR大小匹配 - 海思的媒体驱动(如VI、VPSS、VENC)在内核启动后才会加载
我个人习惯在调试阶段开启内核的早期打印:
# 在bootargs中添加 earlyprintk
setenv bootargs '... earlyprintk clk_ignore_unused ...'
为什么要加 clk_ignore_unused?因为海思的时钟树比较复杂,内核在启动后期会关闭未使用的时钟,但有些外设驱动还没加载,一关就出问题。加上这个参数,可以避免很多莫名其妙的死机。
经验之谈:内核启动时,如果卡在 Uncompressing Linux... 这一步,多半是DDR问题或者内核镜像损坏。如果卡在 Starting kernel ... 之后,那就要检查设备树和bootargs了。我遇到过最诡异的一次,是设备树里GPIO的pinmux配置和实际硬件对不上,导致内核在初始化GPIO时直接panic。
3.5 文件系统挂载:最后的临门一脚
内核启动完成后,会尝试挂载根文件系统。Hi3559A常用的文件系统类型有:
| 类型 | 存储介质 | 特点 |
|---|---|---|
| initramfs | 内存 | 启动快,但掉电丢失,适合调试 |
| ext4 | eMMC/SD卡 | 稳定,支持读写,量产常用 |
| squashfs | SPI Flash | 只读,压缩率高,适合固件升级 |
| NFS | 网络 | 开发调试方便,但依赖网络环境 |
文件系统挂载失败,通常表现为内核启动后卡在 VFS: Cannot open root device 或者进入 Kernel panic - not syncing。这时候别慌,先检查:
- bootargs里的
root=参数是否正确(设备节点名别写错) - 文件系统镜像是否损坏(用
fsck检查一下) - 内核是否编译了对应的文件系统驱动(比如ext4驱动)
小技巧:我习惯在开发阶段先用NFS挂载文件系统,这样修改代码后不用反复烧写Flash。等调试稳定了,再做成squashfs或ext4镜像烧进去。你想想看,每次改个脚本都要重新烧写整个文件系统,多浪费时间。
3.6 启动流程总结
好了,我们把整个流程串起来捋一遍:
- 上电复位 → CPU从BootROM开始执行
- BootROM → 初始化基本硬件,加载Fastboot到SRAM
- Fastboot → 初始化DDR,加载Uboot到DDR
- Uboot → 初始化外设,加载内核和设备树到DDR
- 内核启动 → 解压内核,初始化驱动,挂载根文件系统
- 文件系统挂载 → 执行init进程,启动用户空间程序
每一步都有它存在的理由,少一环都不行。我记得刚接触海思平台时,总觉得Fastboot是多余的,后来发现没有它,BootROM那点SRAM根本装不下Uboot。说白了,这是芯片设计者在成本和性能之间做的权衡。
下一章我们会深入Uboot的定制和移植,到时候再聊怎么给Hi3559A写一个自己的启动脚本。今天就先到这里,你先把启动流程理清楚,后面的事情就好办了。