3. 启动流程与BootROM:ARMv9标准启动流程、BootROM设计原理、启动模式选择

ARMv9的启动流程,说白了就是芯片上电后,从“什么都不会”到“跑起操作系统”的完整过程。我做了这么多年固件,见过太多启动阶段翻车的案例——有时候不是代码写错了,而是压根没走到该走的路径上。

这一章,我们就把ARMv9的启动流程掰开揉碎,看看BootROM到底在干什么,以及怎么选择启动介质。

3.1 ARMv9标准启动流程

ARMv9的启动流程,可以分成几个清晰的阶段。每个阶段都有明确的责任边界。

阶段 执行位置 主要任务 典型代码量
BootROM 芯片内部ROM 初始化最小系统,加载下一级Bootloader 4KB - 32KB
BL1 (Primary Bootloader) SRAM或片上SRAM 初始化DDR,验证BL2签名 8KB - 64KB
BL2 (Secondary Bootloader) DDR 加载ATF、OP-TEE、U-Boot等 64KB - 256KB
BL31 (ATF) DDR 提供运行时服务,管理异常等级切换 128KB - 512KB
BL32 (TEE) DDR 安全操作系统,处理安全敏感操作 256KB - 2MB
BL33 (U-Boot/EDK2) DDR 加载操作系统内核 512KB - 4MB

你可能会问:为什么需要这么多阶段?直接一个Bootloader搞定不行吗?

嗯,这里有个关键点——安全。ARMv9强制要求安全启动链,每个阶段都要验证下一阶段的签名。如果只有一个Bootloader,那验证逻辑就太复杂了,而且一旦出问题,整个系统都起不来。

我在项目中遇到过,某个芯片厂商把BL1和BL2合并了,结果每次升级都要重新烧写整个Bootloader,风险极高。后来还是老老实实拆开了。

3.2 BootROM设计原理

BootROM是芯片上电后执行的第一段代码。它固化在芯片内部的ROM里,用户改不了。所以BootROM的设计必须极其谨慎——一旦流片回来发现Bug,那只能通过芯片版本迭代来修复。

3.2.1 BootROM的核心职责

  • 最小系统初始化:设置CPU异常向量表、初始化栈指针、配置系统时钟。
  • 启动介质检测:按照预设的优先级,轮询eMMC、NOR Flash、UART等介质。
  • 加载并验证BL1:从检测到的介质中读取BL1镜像,验证其签名或哈希值。
  • 跳转到BL1:验证通过后,跳转到BL1的入口地址。

关键设计原则:BootROM必须足够小、足够快、足够可靠。它不需要处理复杂逻辑,但必须保证在任何异常情况下都能给出明确的错误指示。

3.2.2 BootROM的典型工作流程

下面这张图,是我自己画的一个BootROM工作流程。你看完就能明白,为什么BootROM的代码量通常只有几KB。

BootROM 工作流程图 上电复位 最小系统初始化 启动介质检测 eMMC 启动 NOR Flash 启动 UART 启动 加载并验证 BL1 跳转到 BL1 说明:BootROM 按优先级检测启动介质,一旦检测成功立即加载 BL1,不再继续轮询其他介质。

3.2.3 BootROM的异常处理

BootROM如果遇到错误,比如介质损坏、签名验证失败,它会怎么做?

我个人习惯的做法是:让BootROM进入一个死循环,同时通过GPIO或UART输出错误码。这样调试时,用示波器量一下GPIO电平,或者接上串口看输出,就能知道问题出在哪。

小技巧:在BootROM中预留一个“恢复模式”入口。比如检测到某个GPIO被拉低,就强制进入UART下载模式。这样即使eMMC里的Bootloader坏了,也能通过串口救回来。

3.3 启动模式选择

ARMv9芯片通常支持多种启动介质。选择哪种,取决于你的产品需求。

3.3.1 eMMC启动

eMMC是目前最主流的启动介质。它速度快、容量大、支持分区管理。

  • 优点:读写速度快,支持硬件分区(Boot Partition、User Partition),支持安全擦除。
  • 缺点:需要初始化MMC控制器,BootROM代码量稍大。
  • 典型场景:手机、平板、工业控制板。

我记得有一次,客户反馈说设备偶尔启动不起来。排查了半天,发现是eMMC的Boot Partition里写入了错误的数据。后来我们在BootROM里加了CRC校验,问题就解决了。

3.3.2 NOR Flash启动

NOR Flash支持XIP(eXecute In Place),也就是说,代码可以直接在Flash里运行,不需要拷贝到RAM里。

  • 优点:支持XIP,启动延迟极低,可靠性高。
  • 缺点:容量小(通常64MB以下),写入速度慢。
  • 典型场景:路由器、物联网网关、车载ECU。

注意:NOR Flash的XIP模式虽然快,但访问速度远低于DDR。如果你的Bootloader代码量较大,建议还是先拷贝到SRAM里再执行。

3.3.3 UART启动

UART启动主要用于调试和工厂烧录。BootROM通过UART接收数据,直接写入SRAM或DDR。

  • 优点:不需要额外的硬件,一根串口线就能搞定。
  • 缺点:速度极慢(通常115200bps),不适合量产。
  • 典型场景:开发调试、小批量烧录、紧急恢复。

我曾经在产线上遇到过一个问题:UART启动时,因为串口线太长,信号衰减导致数据错位。后来我们换成了带屏蔽的串口线,问题就解决了。嗯,有时候硬件问题比软件问题更隐蔽。

3.4 启动模式选择对比

特性 eMMC NOR Flash UART
启动速度 快(~100MB/s) 极快(XIP模式) 极慢(~11KB/s)
容量 大(4GB - 256GB) 小(1MB - 64MB) 无限制(取决于上位机)
可靠性 中等(有磨损均衡) 高(无磨损问题) 低(依赖外部设备)
成本 中等 高 极低
适用阶段 量产 量产 开发/调试

3.5 实战:配置启动模式

在实际项目中,启动模式的选择通常通过芯片的Boot Pin来配置。比如,某款ARMv9芯片的Boot Pin配置如下:

// Boot Pin 配置示例
// BOOT_MODE[2:0] 选择启动介质
// 000: eMMC Boot Partition 1
// 001: eMMC Boot Partition 2
// 010: NOR Flash
// 011: UART
// 100: 保留
// 101: 保留
// 110: 保留
// 111: 恢复模式

#define BOOT_MODE_REG  (*(volatile uint32_t *)0x10000000)
#define BOOT_MODE_MASK 0x07

uint32_t boot_mode = BOOT_MODE_REG & BOOT_MODE_MASK;

switch (boot_mode) {
    case 0x00:
        // eMMC Boot Partition 1
        emmc_boot(0);
        break;
    case 0x01:
        // eMMC Boot Partition 2
        emmc_boot(1);
        break;
    case 0x02:
        // NOR Flash
        nor_flash_boot();
        break;
    case 0x03:
        // UART
        uart_boot();
        break;
    case 0x07:
        // 恢复模式
        recovery_mode();
        break;
    default:
        // 默认尝试 eMMC
        emmc_boot(0);
        break;
}

避坑指南:我曾经遇到过,某个芯片的Boot Pin在复位瞬间有毛刺,导致BootROM读到了错误的模式。解决方案是在BootROM里加一个去抖逻辑——连续读三次,取多数值。

3.6 小结

ARMv9的启动流程,核心就是BootROM。它虽然小,但责任重大。理解了BootROM的设计原理,你就能明白为什么芯片上电后能自动找到Bootloader,以及为什么有时候启动会失败。

启动模式的选择,说白了就是根据产品需求,在速度、容量、成本之间做权衡。没有最好的方案,只有最合适的方案。