3. Agilex SoC 启动流程与引导链

Agilex SoC 的启动不是“上电即运行 Linux”这么简单,而是一条由多个引导阶段接力完成的可信链。每个阶段只做有限的事:初始化它必须初始化的硬件,然后把控制权连同“下一阶段在哪里、用什么参数启动”的信息交给下一个阶段。理解这条链的关键,是搞清楚每一棒交接时控制权如何转移、镜像如何被找到和校验、参数如何被传递。

整条链的顺序是:BootROM → U-Boot SPL → ATF(BL31)→ U-Boot(BL33)→ Linux 内核。其中 BootROM 固化在芯片内,不可修改;SPL 和 ATF 通常由 Altera 提供的源码或二进制集成;U-Boot 与内核由开发者自行构建和配置。下面这张图给出各阶段的加载关系与交接内容。

BootROM 片内固化 读 BOOT_PIN U-Boot SPL DDR / 时钟 / 引脚 加载 ATF + U-Boot ATF (BL31) EL3 安全监控 PSCI / 跳转 BL33 U-Boot (BL33) 命令行 / 环境变量 加载内核与 DTB Linux 内核启动 挂载 rootfs 交接 交接 交接 交接 启动介质 QSPI / SD / eMMC 镜像布局 SPL → ATF → U-Boot 内核与 DTB Image + .dtb + rootfs 参数传递 bootargs / DTB 关键交接内容 • BootROM → SPL:从启动介质读取 SPL 到片上 SRAM,校验后跳转 • SPL → ATF:SPL 初始化 DDR,将 ATF 与 U-Boot 载入 DDR,先跳 ATF • ATF → U-Boot:ATF 在 EL3 完成安全初始化,通过 PSCI 跳转 BL33 • U-Boot → Linux:加载 Image 与 DTB,设置 bootargs,跳转内核入口

BootROM:不可更改的第一棒

Agilex SoC 上电复位后,CPU 从片内 BootROM 的固定地址开始执行。BootROM 是芯片出厂固化的,开发者无法修改,它的职责非常克制:读取 BOOT_PIN 或熔丝配置确定启动介质(QSPI、SD 卡、eMMC 等),从该介质的约定偏移处读取 SPL 镜像,做基本校验后搬运到片上 SRAM 并跳转执行。由于片上 SRAM 容量有限(通常几百 KB),SPL 必须足够小,这也是为什么 SPL 只做 DDR 初始化等最必要的动作。

如果 BootROM 阶段失败,通常表现为串口无任何输出或输出极短后停止。此时应优先检查启动介质选择引脚、镜像是否烧写到正确偏移、以及 SPL 是否超出 SRAM 容量。

U-Boot SPL:把 DDR 带起来

SPL(Secondary Program Loader)运行在片上 SRAM 中,核心任务是把 DDR 初始化好,因为后续的 ATF、U-Boot 和内核都必须在 DDR 中运行。SPL 的 DDR 参数来自 Altera 提供的配置(如 board/altera/agilex/ 下的 DDR 初始化代码或 handoff 文件),这些参数与具体板卡的 DDR 颗粒、频率、时序强相关,不能随意套用。

DDR 可用后,SPL 从启动介质读取 ATF 和 U-Boot 镜像到 DDR 的指定地址,然后跳转到 ATF。在典型配置中,SPL 会依次加载 bl31.bin(ATF)和 u-boot.itb 或 u-boot.bin。以下是一个简化的 SPL 加载顺序示意(示例参数,非实测):

SPL 阶段(示例):
  DDR 初始化完成,容量 2GB
  加载 ATF 到 0x8000_0000 附近
  加载 U-Boot 到 0x8100_0000 附近
  跳转 ATF 入口

ATF(BL31):安全世界的守门人

ARM Trusted Firmware 在 Agilex SoC 上以 BL31 形式运行在 EL3(最高异常等级)。它负责建立安全监控环境,提供 PSCI(Power State Coordination Interface)服务,使 Linux 能够通过 SMC 指令请求 CPU 热插拔、系统复位等操作。ATF 完成初始化后,通过 bl31_main 跳转到 BL33,也就是 U-Boot。

ATF 与 U-Boot 的交接依赖一组“handoff”参数,包括入口地址、设备树地址等。如果 ATF 找不到 BL33 入口,启动会停在 ATF 阶段。排查时可观察串口是否打印 ATF 版本信息,以及是否出现跳转 BL33 的日志。

U-Boot(BL33):真正的引导管理器

U-Boot 运行在 EL2 或 EL1(非安全世界),是开发者最常打交道的阶段。它提供命令行、环境变量、网络启动、文件系统读写等能力。启动 Linux 时,U-Boot 需要完成三件事:把内核镜像(Image)和设备树(.dtb)加载到 DDR,设置 bootargs 内核命令行,然后跳转到内核入口。

典型的手动启动命令如下(示例):

# 从 SD 卡加载内核与 DTB
fatload mmc 0:1 0x82000000 Image
fatload mmc 0:1 0x83000000 socfpga_agilex.dtb
setenv bootargs "console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait"
booti 0x82000000 - 0x83000000

其中 booti 用于 64 位 ARM 内核镜像,参数依次为内核地址、initrd 地址(- 表示无 initrd)、DTB 地址。内核启动后,会解析 DTB 获取硬件信息,并根据 bootargs 中的 root= 挂载根文件系统。

参数传递:从 U-Boot 到内核

U-Boot 向内核传递信息有两条主要途径:一是 bootargs 环境变量,最终成为内核命令行;二是设备树 DTB,描述硬件拓扑。两者必须一致,例如 bootargs 中指定的串口设备要与 DTB 中 serial0 的地址匹配,否则会出现“内核启动但无控制台输出”的现象。

调试启动链时,建议逐阶段确认串口输出:BootROM 通常无输出或极短;SPL 会打印 DDR 容量与加载地址;ATF 打印版本与 BL33 跳转;U-Boot 打印版本与命令行;内核打印 Booting Linux 及设备初始化日志。哪一阶段没有输出,问题就定位在该阶段或其前一阶段。

常见故障与判断依据

启动失败时,可按以下顺序判断:若串口完全无输出,检查供电、时钟、启动引脚与 BootROM 介质选择;若 SPL 输出后停止,检查 DDR 参数与 ATF/U-Boot 镜像是否存在于介质正确偏移;若 ATF 输出后停止,检查 BL33 入口地址与 U-Boot 镜像格式;若 U-Boot 能进命令行但内核不启动,检查 bootargs、内核地址、DTB 地址与 booti 参数;若内核启动但无法挂载 rootfs,检查 root= 设备名与文件系统类型。