3、系统启动流程:BootROM、SPL、U-Boot、内核启动、根文件系统挂载

各位同学,今天我们来聊聊T507上电后,到底发生了什么。

很多人做嵌入式开发好几年,一提到启动流程就只知道「上电就跑Linux」。但实际上一颗SoC从复位到进入用户界面,中间要经历好几个阶段。我当年第一次调T507的时候,就被这个启动链给绕晕过——BootROM、SPL、U-Boot、内核、根文件系统,每个阶段都有自己的使命。

说白了,这就是一个「小马拉大车」的过程。芯片刚上电时,内部资源极其有限,只能先运行一段极小的代码,然后逐步加载更大的程序,最终把整个Linux系统跑起来。

3.1 BootROM:芯片的第一口奶

T507上电后,CPU会从复位向量地址开始执行。这个地址指向的是芯片内部固化的BootROM,大约64KB大小。这段代码是出厂就烧死的,你改不了,我也不建议你去改。

BootROM的任务很简单:初始化最基本的硬件,然后根据启动引脚的电平状态,去指定的存储介质里找下一段代码

关键点:T507支持的启动介质包括:

  • NAND Flash
  • eMMC / SD卡
  • SPI NOR Flash
  • USB(用于烧录模式)

我记得第一次调板子时,死活起不来。查了半天,发现是启动引脚的电阻焊错了,BootROM一直在尝试从空的SPI Flash里读数据。嗯,硬件上的坑,往往是最隐蔽的。

我的习惯:画原理图时,我会把启动配置引脚用排针引出来,调试阶段直接跳线选择。这样就不用反复改电阻了。

3.2 SPL:二级程序加载器

BootROM加载的第一段代码就是SPL(Secondary Program Loader)。为什么需要SPL?因为BootROM太小了,它只能做最基础的事情——比如初始化个串口、读个NAND的前几页。而U-Boot本身又太大了,动辄几百KB,BootROM根本装不下。

所以中间加了个SPL,它的大小通常被限制在32KB以内。SPL的任务是:初始化DRAM,然后把完整的U-Boot从存储介质加载到内存里

我曾经踩过的坑:SPL的代码必须放在存储介质的固定偏移位置。比如eMMC的话,通常是偏移1MB处。如果你用dd命令写镜像时算错了偏移量,板子就会卡在BootROM阶段,串口上连个打印都看不到。

T507的SPL源码在U-Boot的arch/arm/mach-sunxi/目录下。你编译U-Boot时,会先编译出SPL,再编译出完整的U-Boot。我个人习惯在调试阶段打开SPL的串口调试信息:

# 在U-Boot源码目录下
make menuconfig
# 进入以下选项:
# Boot media --> 选择你的启动介质
# SPL --> 开启 DEBUG 和 串口输出

这样SPL在初始化DRAM时,会打印出详细的时序参数。如果内存有问题,这里就能看出来。

3.3 U-Boot:真正的引导加载器

SPL把U-Boot加载到DRAM后,控制权就交给了U-Boot。U-Boot是个完整的引导加载器,它提供了命令行、环境变量、网络启动等功能。

在T507上,U-Boot主要做这几件事:

  1. 初始化更多外设——比如以太网、USB、显示控制器
  2. 读取设备树(DTB)——告诉内核硬件是怎么连接的
  3. 加载内核镜像——从存储介质或网络加载zImage/Image
  4. 传递启动参数——比如根文件系统在哪、控制台用哪个串口

你想想看,如果没有U-Boot,每次改个内核参数都要重新编译内核,那得多痛苦。U-Boot的环境变量就是用来干这个的:

# 典型的T507 U-Boot环境变量
setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait'
setenv bootcmd 'ext4load mmc 0:1 0x42000000 /boot/zImage; ext4load mmc 0:1 0x43000000 /boot/sun50i-t507.dtb; bootz 0x42000000 - 0x43000000'
saveenv
boot

这里有个细节:bootz命令用于启动压缩的zImage,而bootm用于启动uImage。T507官方SDK默认用zImage,我建议你也别改,省得踩坑。

避坑指南:U-Boot的设备树地址(0x43000000)不能和内核加载地址(0x42000000)重叠。我曾经把DTB地址设成了0x42008000,结果内核启动到一半就崩了——因为解压后的内核把设备树给覆盖了。

3.4 内核启动:从汇编到C的世界

U-Boot跳转到内核入口后,内核就开始接管系统了。这个过程大致分三步:

阶段 主要工作 关键代码
汇编阶段 设置页表、使能MMU、切换异常等级 arch/arm64/kernel/head.S
C语言阶段 start_kernel() 初始化核心子系统 init/main.c
驱动加载 根据设备树匹配并初始化驱动 drivers/of/platform.c

在T507上,内核启动时最常出问题的是设备树匹配。全志的芯片有很多变种,T507和T5、T7的设备树不能混用。我见过有人拿着T5的设备树去启动T507,结果GPU、显示接口全都不工作。

内核启动完成后,会打印出类似这样的信息:

[    1.234567] VFS: Mounted root (ext4 filesystem) on device 179:2.
[    1.240123] devtmpfs: mounted
[    1.243456] Freeing unused kernel memory: 1024K
[    1.248901] Run /sbin/init as init process

看到Run /sbin/init这行,说明内核已经准备把控制权交给用户空间了。

3.5 根文件系统挂载:用户空间的起点

内核启动的最后一步,就是挂载根文件系统。根文件系统里放着init进程、库文件、配置文件、应用程序。没有它,内核就是个光杆司令。

T507上常见的根文件系统类型:

  • ext4:最常用,性能好,支持日志
  • squashfs:只读压缩文件系统,适合固件升级场景
  • initramfs:内存文件系统,用于早期初始化或调试

挂载方式由内核参数root=指定。比如:

root=/dev/mmcblk0p2   # eMMC第二个分区
root=/dev/nfs          # 网络根文件系统(调试时超好用)

我的调试技巧:开发阶段我强烈建议用NFS根文件系统。这样你改完代码,在PC上编译,目标板重启就直接用新文件系统了,不用反复烧写eMMC。命令如下:

setenv bootargs 'console=ttyS0,115200 root=/dev/nfs nfsroot=192.168.1.100:/nfsroot ip=192.168.1.10'

根文件系统挂载成功后,内核会执行/sbin/init(通常是BusyBox或systemd)。至此,整个启动流程结束,系统进入了用户空间。

我曾经遇到的经典问题:根文件系统挂载失败,内核直接panic。排查步骤:

  1. 检查U-Boot的bootargsroot=参数是否正确
  2. 确认存储介质的分区表——用fdisk -l看看分区类型
  3. 如果用的是ext4,确认内核编译时开启了CONFIG_EXT4_FS
  4. 串口打印里搜VFS: Cannot open root device,它会告诉你具体原因

好了,这一章的内容就到这里。从BootROM到根文件系统,每一步都有它的设计逻辑。你把这些流程理清楚了,以后遇到启动问题,就能快速定位是哪个环节出了岔子。下一章我们聊聊设备树,那可是T507硬件描述的核心。