2. 硬件初始化
说实话,很多做系统开发的工程师,对CPU上电那一刻到底发生了什么,往往是一知半解。大家更关心的是U-Boot怎么跑起来,Linux内核怎么解压。但我要说,真正决定系统能否稳定启动的,恰恰是上电后那几百微秒的硬件初始化过程。
我自己刚入行那会儿,也踩过这个坑。有一次调试一块新板子,死活起不来,JTAG连不上,示波器量电源轨也没问题。折腾了两天,最后发现是SoC内部的POR(Power-On Reset)时序不满足要求,ROM Code根本没开始执行。嗯,从那以后,我对上电流程就格外上心了。
2.1 上电复位:一切从POR开始
你按下电源键,或者板子刚插上电,第一个动作是什么?
不是CPU开始跑代码,而是电源管理单元(PMU)开始工作。PMU会检测各个电源域是否达到稳定电压。ARM64 SoC内部通常有多个电源域:
- VDD_CORE:CPU核心供电,一般0.8V~1.2V
- VDD_IO:IO接口供电,1.8V或3.3V
- VDD_MEM:内存控制器供电
- VDD_PLL:锁相环模拟供电
这些电源域的上电顺序是有严格要求的。我见过一个项目,因为硬件工程师把VDD_CORE和VDD_IO的使能信号接反了,导致每次上电CPU都进入一种奇怪的锁死状态。所以,上电时序不是软件能解决的,必须在硬件设计阶段就确认好。
2.2 复位向量:CPU的第一口“奶”
当POR释放,时钟稳定后,CPU核心会从复位向量(Reset Vector)开始取指。ARM64架构下,复位向量地址通常是固定的:
- 对于大多数Cortex-A系列,复位向量在0x00000000或0xFFFF0000
- 但实际SoC设计中,这个地址会被映射到片内的ROM区域
为什么是ROM?因为此时DDR内存还没初始化,NAND Flash或eMMC也读不了。唯一能保证“上电就能用”的存储介质,就是SoC内部集成的Boot ROM。
我个人习惯把Boot ROM比作CPU的“出厂固件”。它体积很小,通常只有几十KB到几百KB,但功能极其关键。它负责:
- 最基本的CPU异常向量表设置
- 堆栈指针(SP)的初始化
- 跳转到ROM Code的主入口函数
这里有个细节要注意:ARM64在复位后默认处于EL3异常级别。这是最高权限级别,相当于整个系统的“上帝模式”。为什么?因为后续的ATF(ARM Trusted Firmware)需要在这个级别下完成安全初始化。
2.3 ROM Code:SoC内部的“迷你引导程序”
ROM Code是固化在芯片内部的程序,用户无法修改。它的主要任务是:找到并加载下一级启动代码。
你可能会问:“ROM Code怎么知道从哪里加载?”
答案是:启动设备选择。SoC内部有一套硬件逻辑,根据芯片外部引脚的电平状态(Boot Mode Pins),决定从哪个设备读取启动代码。常见的启动设备包括:
| 启动模式 | 典型设备 | 特点 |
|---|---|---|
| eMMC/SD卡 | 内置存储 | 速度快,容量大 |
| NAND Flash | 原始闪存 | 需要ECC校验 |
| SPI NOR Flash | 串行Flash | 容量小,但启动简单 |
| UART/USB | 串口/USB下载 | 用于调试和工厂烧录 |
ROM Code会按照预设的优先级,依次尝试从这些设备读取数据。读取到的数据通常是Boot Image,它包含一个头部和实际的代码段。头部里记录了:
- 镜像大小
- 加载地址
- 校验和或签名
- 入口函数地址
2.4 时钟与PLL初始化:让CPU跑起来
ROM Code执行过程中,有一项关键任务:配置系统时钟。
刚上电时,CPU运行在一个非常低的频率上,通常是外部晶振的频率(比如24MHz或32.768KHz)。这个频率连看门狗都喂不饱,更别说跑复杂的代码了。ROM Code需要:
- 使能内部振荡器(OSC)
- 配置锁相环(PLL),将低频时钟倍频到目标频率
- 选择系统主时钟源(从晶振切换到PLL输出)
- 分配时钟给各个总线(AXI、AHB、APB)
我曾在调试一个网络处理器时,发现ROM Code阶段时钟配置有误,导致DDR控制器无法锁定PLL。结果就是,系统能启动到ROM Code,但一加载U-Boot就崩溃。最后查了芯片的勘误表,才发现是某个PLL的锁定时间不够,需要在ROM Code里加一个延时循环。
说白了,时钟树配置是SoC启动中最容易出问题的环节之一。因为不同芯片的PLL参数、分频系数、锁定时间都不一样,而ROM Code又是固定的,所以芯片原厂必须在流片前就把这些参数验证好。
2.5 安全启动与信任根
现代ARM64 SoC,尤其是面向服务器或汽车电子的芯片,都支持安全启动(Secure Boot)。ROM Code在这里扮演了信任根(Root of Trust)的角色。
流程是这样的:
- ROM Code内部固化了芯片原厂的公钥哈希
- Boot Image的头部包含数字签名
- ROM Code用内置公钥验证签名的合法性
- 验证通过,才允许执行Boot Image中的代码
如果签名验证失败,ROM Code会拒绝加载。这意味着,任何未经签名的第三方固件都无法在芯片上运行。这对防止恶意固件注入非常有效,但也给开发者带来了麻烦——你必须在开发阶段关闭安全启动,或者使用芯片厂商提供的签名工具。
2.6 从ROM Code到下一级:跳转前的最后准备
ROM Code完成所有初始化后,会做几件“善后”工作:
- 关闭自身占用的中断和异常
- 设置好堆栈指针(SP_EL3)
- 将Boot Image从加载设备拷贝到SRAM或DDR中(如果DDR已初始化)
- 跳转到Boot Image的入口地址
跳转时,CPU会保持EL3异常级别。这意味着,下一级代码(通常是ATF或U-Boot SPL)仍然拥有最高权限,可以继续配置系统资源。
这里有一个容易忽略的点:ROM Code在跳转前,会把一些关键信息传递给下一级代码。比如:
- 启动设备类型
- 时钟频率配置
- DDR大小和时序参数
- 芯片版本号
这些信息通常通过寄存器或共享内存传递。下一级代码必须按照芯片手册约定的格式来解析。我见过有人写U-Boot SPL时,忽略了ROM Code传递的DDR参数,自己重新初始化了一遍内存控制器,结果导致系统不稳定。其实,ROM Code已经帮你做好了最基础的硬件配置,没必要重复造轮子。
2.7 一张图看懂SoC内部启动流程
说了这么多,不如一张流程图来得直观。下面是我画的SoC内部启动流程,涵盖了从CPU上电到ROM Code执行完毕的全过程:
这张图把整个流程串起来了。你可以看到,ROM Code阶段其实做了大量工作,远不止“读个Flash”那么简单。时钟、安全、设备检测,每一步都关系到后续能否正常启动。
好了,关于SoC内部启动流程,我就讲到这里。记住一句话:上电后的几百微秒,决定了系统未来几小时甚至几年的稳定性。下次调试启动问题,不妨先从ROM Code入手,看看它有没有正确执行。