第三章 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启动流程大致如下:

  1. 从DDR起始地址开始执行(通常是0x42000000附近)
  2. 初始化串口、网卡、Flash控制器等外设
  3. 读取环境变量(env),决定启动参数
  4. 加载内核镜像(uImage或zImage)到DDR
  5. 加载设备树文件(.dtb)到DDR
  6. 跳转到内核入口

这里有个细节: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 启动流程总结

好了,我们把整个流程串起来捋一遍:

  1. 上电复位 → CPU从BootROM开始执行
  2. BootROM → 初始化基本硬件,加载Fastboot到SRAM
  3. Fastboot → 初始化DDR,加载Uboot到DDR
  4. Uboot → 初始化外设,加载内核和设备树到DDR
  5. 内核启动 → 解压内核,初始化驱动,挂载根文件系统
  6. 文件系统挂载 → 执行init进程,启动用户空间程序

每一步都有它存在的理由,少一环都不行。我记得刚接触海思平台时,总觉得Fastboot是多余的,后来发现没有它,BootROM那点SRAM根本装不下Uboot。说白了,这是芯片设计者在成本和性能之间做的权衡。

下一章我们会深入Uboot的定制和移植,到时候再聊怎么给Hi3559A写一个自己的启动脚本。今天就先到这里,你先把启动流程理清楚,后面的事情就好办了。