3. Boot流程移植:ATF启动流程、UEFI移植要点、设备树适配

好,咱们进入第三章。这一章讲的是系统上电后,CPU从复位向量到操作系统启动之间的那段“无人区”。说白了,就是Boot流程怎么移植到你的板子上。

ARMv9的启动链,比之前复杂了不少。我个人习惯把整个流程拆成三段:ATF(安全固件)拉起核心、UEFI(或者uboot)做硬件初始化、最后设备树把硬件描述交给内核。这三段,哪一段出问题,板子都起不来。

3.1 ATF启动流程:从EL3到EL2的接力

ATF是ARMv8/v9体系里最底层的固件。它运行在EL3,是系统的“上帝模式”。我刚开始接触ATF时,总觉得它就是个简单的bootloader,后来踩了坑才明白——它负责的事情远比你想象的多。

核心职责:

  • 初始化安全世界(Secure World)的异常向量表
  • 配置GIC(中断控制器)的路由策略
  • 设置PSCI(电源状态协调接口)的CPU热插拔、挂起/恢复
  • 最后,跳转到非安全世界的Bootloader(如UEFI)

ATF的启动流程,我习惯用这张图来理解:

ATF 启动流程与层级跳转 BL1 (ROM Code) 固化在芯片ROM中 BL2 (Trusted Boot) 验证并加载BL31/BL32 BL31 (Runtime) SMC处理、PSCI服务 BL32 (TEE) 安全操作系统(如OP-TEE) BL33 (UEFI) 非安全世界Bootloader ⚠ 移植要点: • BL1通常由芯片厂商提供,你基本动不了 • BL2需要适配你的Flash布局和签名方案 • BL31的PSCI实现必须与你的GIC版本匹配

嗯,这里要注意:BL1是固化在芯片ROM里的,你改不了。真正需要你动手的,是从BL2开始。我曾经在一个项目里,因为BL2的FIP(Firmware Image Package)打包顺序搞错了,导致BL31加载失败,板子一直卡在EL3出不来。排查了两天才发现——就是FIP里镜像的偏移地址写反了。

3.2 UEFI移植要点:EDK2的板级适配

UEFI在ARMv9平台上,通常用EDK2(TianoCore)来实现。它运行在EL2,负责给操作系统提供一个标准的启动环境。

移植UEFI,说白了就是告诉EDK2:你的板子长什么样、内存多大、外设挂在哪里。我个人觉得,最核心的移植工作就三块:

移植模块 关键文件/目录 常见坑点
Platform Description Platform/// DSC/FDF文件中的Flash布局与实际硬件不符
Memory Map MemoryInitPei 或 MemMap DRAM的地址范围和大小写错,导致UEFI认不全内存
Peripheral Library Library/ 下的 GicLib, TimerLib GIC版本不匹配(GICv2 vs GICv3)导致中断不响应

我的经验: 移植UEFI时,先别急着改代码。先把板子的 .dsc 和 .fdf 文件读透。这两个文件定义了整个固件的编译和打包规则。我见过太多人一上来就改C代码,结果编译出来的固件根本烧不进去——因为FDF里的分区大小设错了。

举个例子,EDK2里定义Flash布局的FDF文件,长这样:

[FD.Board_Flash]
BaseAddress   = 0x08000000  # SPI Flash基地址
Size          = 0x00400000  # 4MB
ErasePolarity = 1

# UEFI固件区域
0x00000000 | 0x00100000
  FILE = Platform/MyBoard/Fd/BOOT.fd

# 变量存储区域
0x00100000 | 0x00040000
  gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageVariableBase

# FIP区域(ATF镜像)
0x00140000 | 0x000C0000
  FILE = Platform/MyBoard/Fd/FIP.bin

你看,每个区域的偏移和大小都是硬编码的。如果你的板子Flash是8MB,但这里写的是4MB,那后面的FIP区域就会被截断。我曾经就因为没改这个Size,导致ATF的BL31镜像只加载了一半,系统启动到一半就挂了。

3.3 设备树(DTS)适配:把硬件“说”给内核听

设备树,说白了就是一份硬件描述文件。内核通过它知道:CPU有几个核、内存多大、外设挂在哪条总线上、中断号是多少。

ARMv9的设备树适配,和之前版本最大的区别在于:

  • 多级中断控制器:GICv3/v4的ITS(中断翻译服务)需要在DTS里显式描述
  • PPI(私有外设中断):每个CPU核的私有中断,必须绑定到对应的CPU节点
  • 电源管理:PSCI的节点必须与ATF的BL31实现一致

我拿一个典型的ARMv9设备树片段来说明:

/dts-v1/;

/ {
    model = "My ARMv9 Board";
    compatible = "myvendor,armv9-board";

    #address-cells = <2>;
    #size-cells = <2>;

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        cpu@0 {
            device_type = "cpu";
            compatible = "arm,cortex-x2";
            reg = <0x0>;
            enable-method = "psci";
        };

        cpu@1 {
            device_type = "cpu";
            compatible = "arm,cortex-x2";
            reg = <0x1>;
            enable-method = "psci";
        };
    };

    psci {
        compatible = "arm,psci-1.0";
        method = "smc";
    };

    gic: interrupt-controller@2f000000 {
        compatible = "arm,gic-v3";
        reg = <0x0 0x2f000000 0x0 0x10000>,  // GICD
              <0x0 0x2f100000 0x0 0x200000>,  // GICR
              <0x0 0x2f300000 0x0 0x10000>,   // ITS
              <0x0 0x2f400000 0x0 0x10000>;   // GICC
        interrupts = <1 9 4>;
        #interrupt-cells = <3>;
        #address-cells = <2>;
        #size-cells = <2>;
        ranges = <0x0 0x0 0x0 0x2f000000 0x0 0x500000>;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x0 0x80000000 0x0 0x80000000>;  // 2GB
    };
};

注意: 设备树里的 reg 地址,必须和你的硬件设计完全一致。我遇到过最离谱的bug,是硬件工程师改了DDR的片选地址,但没通知我。结果设备树里写的还是旧地址,内核一启动就panic,因为访问的内存区域根本不存在。

适配设备树时,我建议你按这个顺序来:

  1. 先配CPU和内存:这是内核启动的基石,配不对连log都看不到
  2. 再配中断控制器:GIC的节点错了,所有外设的中断都收不到
  3. 然后配串口:串口能出log,后面调试就方便了
  4. 最后配其他外设:网卡、USB、PCIe等,一个一个来

嗯,这里有个小技巧:编译设备树时,用 dtc -I dts -O dtb -o board.dtb board.dts 生成dtb。然后用 dtc -I dtb -O dts board.dtb 反编译回来,看看有没有丢失信息。我经常用这招来检查设备树有没有被编译器优化掉关键节点。

3.4 启动链的调试思路

移植过程中,板子起不来是常态。我的调试思路是这样的:

现象 可能原因 排查手段
上电后无任何输出 ATF的BL1/BL2未正确加载 检查Flash烧录地址、FIP打包顺序
ATF有log,但UEFI没起来 BL33的入口地址或格式不对 确认FDF中BL33的BaseAddress与编译一致
UEFI有log,内核启动panic 设备树中内存或中断描述错误 用 devicetree.dump 检查内核收到的dtb
内核启动后无外设 设备树中外设节点遗漏或地址错误 对照原理图逐项核对 reg 和 interrupts

说白了,Boot流程移植就是一场“对答案”的过程——你的代码描述,必须和硬件设计完全一致。多一个0、少一个地址,板子都不买账。

本章小结:

  • ATF的BL1不可改,重点在BL2的FIP打包和BL31的PSCI适配
  • UEFI移植的核心是DSC/FDF文件中的Flash布局和内存映射
  • 设备树是硬件与内核的“翻译官”,地址和中断号必须一字不差
  • 调试时,从ATF log开始,逐级确认每一层的启动状态