贵州省网站建设_网站建设公司_展示型网站_seo优化
2026/1/19 16:07:56 网站建设 项目流程

RK3588启动时aarch64 CPU初始化超详细版说明


从一个“卡死”的CPU说起

你有没有遇到过这样的情况:新做的RK3588板子上电后,串口毫无输出,JTAG连上去发现PC(程序计数器)停在第一条指令不动?或者更诡异的是,代码看似执行了,却在某个eret指令后直接消失不见,再也无法响应调试?

这类问题往往不源于外设驱动或操作系统配置,而是藏得更深——出在CPU最底层的初始化阶段。尤其是对于像RK3588这样基于ARMv8-A架构、支持完整aarch64和TrustZone机制的高端SoC来说,哪怕是一个寄存器位设置错误,都可能导致系统永远无法跳出EL3(Exception Level 3),甚至引发不可恢复的异常循环。

本文将带你深入到RK3588芯片加电后的第一毫秒,逐行解析其aarch64 CPU核心是如何从复位向量一步步建立起运行环境,并最终把控制权交给U-Boot或Linux内核的全过程。我们不讲套话,不堆术语,只聚焦真实开发中会碰到的问题与解法。


aarch64启动的核心:异常等级模型

要理解RK3588的启动流程,必须先搞清楚ARMv8-A的异常等级(Exception Level, EL)模型。这是整个aarch64世界的安全与权限基石。

四层“防护塔”:EL0 到 EL3

异常等级名称权限级别典型角色
EL0用户态最低应用程序
EL1内核态Linux Kernel
EL2虚拟化监控更高Hypervisor / VMM
EL3安全监控最高ATF (BL31), Secure Monitor

当你按下电源键,RK3588的每一个大核(A76/A55)都会从EL3开始执行。为什么是EL3?因为它拥有最高特权,可以访问所有硬件资源,包括安全控制器、加密引擎、内存保护单元等,是构建可信执行环境(TEE)的第一道防线。

✅ 关键点:所有初始化操作都在EL3完成,然后通过eret降级跳转至EL2或EL1。这个过程不可逆,除非发生异常重新进入更高EL。


启动起点:Mask ROM 与 复位向量

RK3588一上电,CPU从物理地址0x0000_0000取指执行。这个地方不是Flash也不是DDR,而是芯片内部固化的Mask ROM—— 一段由瑞芯微写死的只读代码。

这段代码虽然你看不到源码,但它的行为是确定的:

  1. 初始化PLL,稳定主频;
  2. 检测启动介质(eMMC、SPI NOR/NAND、USB烧录模式);
  3. 加载第一阶段引导程序(MiniLoader 或 SPL)到SRAM;
  4. 跳转执行SPL。

此时CPU仍处于aarch64 + EL3状态,尚未启用MMU、Cache,也没有堆栈。也就是说,接下来你要写的每一条汇编指令,都必须极其小心——任何一次非法内存访问都会让CPU陷入Data Abort异常,而如果你还没设置好异常向量表,那就会彻底“死机”。


EL3初始化五大关键步骤

当控制权交到你的固件(如ATF中的BL31)手中时,第一步就是建立基本运行环境。以下是必须按顺序完成的五个核心动作。


1. 设置栈指针 SP_EL3

mov x0, #0x40000000 // 假设片上SRAM起始地址为0x40000000 add sp, x0, #0x10000 // 栈顶 = 起始 + 64KB

⚠️注意:这里的sp默认指的是当前异常等级下的栈指针,即SP_EL3。你不能假设SP_EL0或其他等级的栈已经准备好。

如果后续需要切换到其他EL(比如EL2),你还得显式设置对应等级的SP,例如使用:

msr sp_el1, x1 msr sp_el2, x2

否则一旦发生中断或异常返回,CPU将因找不到正确的栈而崩溃。


2. 关闭中断:DAIF屏蔽

msr daifset, #0xf // 屏蔽 IRQ, FIQ, SError, Debug

DAIF 是PSTATE中的四个标志位:
-D: Debug 异常掩码
-A: Asynchronous Abort (SError) 掩码
-I: IRQ 中断掩码
-F: FIQ 快速中断掩码

在初始化期间关闭所有中断,是为了防止外部事件干扰关键配置(比如正在改SCTLR时来了个IRQ)。否则可能造成状态混乱或竞态条件。

完成后可通过msr daifclr, #0xf重新开启。


3. 配置 SCTLR_EL3:CPU行为总控开关

mrs x0, sctlr_el3 // 读出现有值 orr x0, x0, #(1 << 2) // A=1: 使能对齐检查 bic x0, x0, #(1 << 4) // C=0: 禁用数据缓存 D-cache bic x0, x0, #(1 << 12) // I=0: 禁用指令缓存 I-cache bic x0, x0, #(1 << 0) // M=0: MMU关闭 msr sctlr_el3, x0

📌关键位域说明

Bit字段功能
0MMMU使能。未建页表前务必保持为0
2A对齐检查。访问未对齐地址会触发Alignment Fault
4C数据缓存使能
12I指令缓存使能

💡 实践建议:
- 在建立有效的页表之前,绝对不要开启M=1,否则会触发Address Size Fault;
- Cache可在DDR初始化后逐步打开,但需配合dccivac/icivau等维护指令保证一致性。


4. 设置 VBAR_EL3:异常向量入口

ldr x0, =vector_table_el3 msr vbar_el3, x0

VBAR_EL3指向当前EL3的异常向量表基址。该表结构固定,共16项,每项间隔128字节,覆盖如下类型:

vector_table_el3: b reset_handler // Reset b undefined_handler // Undefined instruction b svc_handler // Supervisor Call (SVC) b prefetch_abort_handler // Prefetch Abort b data_abort_handler // Data Abort b . // Reserved b irq_handler // IRQ b fiq_handler // FIQ

要求
- 向量表必须8字节对齐
- 每个处理函数应以eret返回;
- 若未设置VBAR就发生异常,CPU会跳转到未知位置,极难调试。

你可以利用它来做早期日志输出,比如在data_abort_handler里打印出错地址和寄存器状态。


5. 配置 SCR_EL3:安全世界切换控制器

mrs x0, scr_el3 orr x0, x0, #(1 << 0) // NS=1 → 进入 Non-Secure World orr x0, x0, #(1 << 10) // RW=1 → 下一级运行在 aarch64 msr scr_el3, x0

SCR_EL3 是决定安全状态迁移的关键寄存器:

名称作用
0NS1=非安全世界,0=安全世界
10RW1=下一级运行aarch64,0=aarch32
8ST是否允许安全定时器
7IRQ是否允许非安全IRQ陷至EL3

🎯 典型用途:
当你在BL31(ATF)中准备跳转到U-Boot(BL33)时,就需要先把NS设为1,表示“我要去非安全世界了”。否则即使跳过去了,也会因为安全属性不匹配而被拦截。


如何跳出去?ERET 实现异常等级降级

完成了EL3的初始化后,下一步是“移交政权”——跳转到更低特权等级,通常是EL2(用于Hypervisor)或 EL1(用于Linux kernel)

这一步靠的是eret指令,但它不是普通的跳转,而是“异常返回”,依赖两个特殊寄存器:

  • ELR_EL3:保存目标执行地址;
  • SPSR_EL3:保存目标状态的PSTATE。
mov x0, #0x80000000 // U-Boot入口地址 msr elr_el3, x0 // 设置跳转目标 mrs x1, spsr_el3 orr x1, x1, #(0x3 << 0) // M[3:0] = 0b11 → 目标为 EL2 aarch64 // orr x1, x1, #(0x1 << 0) // 若目标为 EL1 aarch64,则用 0b01 msr spsr_el3, x1 eret // 执行!切换EL并跳转

🧠工作原理
-eret触发后,CPU会从ELR读取地址作为PC;
- 从SPSR恢复PSTATE,包括目标EL、运行状态(aarch64/aarch32)、中断屏蔽状态;
- 安全状态由SCR_NS决定;
- 最终进入目标EL,开始执行新世界的代码。

⚠️ 常见坑点:
- SPSR.M 设置错误 → 进入非法状态 → CPU lockup;
- ELR指向不可执行区域 → 取指失败 → Prefetch Abort;
- 未关闭MMU但无页表 → 访问任意地址都会触发Translation Fault。


整体启动流程图解

[Power On] ↓ CPU 复位 → PC = 0x00000000 ↓ Mask ROM 执行(ROM Code) ↓ 加载 MiniLoader/SPL 至 SRAM ↓ DRAM 初始化(DDR PHY训练、控制器配置) ↓ 跳转至 DRAM 中的 BL31 (ATF) ↓ BL31 在 EL3 执行: ├── 设置 SP_EL3 ├── 关闭 DAIF ├── 配置 SCTLR_EL3(禁MMU/CACHE) ├── 设置 VBAR_EL3 ├── 配置 SCR_EL3(切换NS/RW) └── 准备 ELR/SPSR → eret 跳转 ↓ 进入 BL33 (U-Boot) at EL2 (aarch64) ↓ U-Boot 初始化外设、加载 Kernel ↓ Kernel 入口 (_start) at EL1 ↓ 开启MMU、创建页表、接管系统

🔍 提示:多核系统中,只有主核(CPU0)会走完整流程,其余核通常停留在WFE状态,等待PSCI调用唤醒。


调试实战:三个经典“踩坑”场景

❌ 问题1:CPU卡死在第一条指令

现象:JTAG连接后发现PC没动,串口无输出。

排查思路
1. 是否晶振未起振?用示波器测32.768kHz和24MHz;
2. Flash读取失败?检查SPI CLK/DATA线路阻抗;
3. 代码未正确烧录?尝试USB Loader模式重刷;
4. 栈指针未设?导致后续push出错。

🔧解决方案
- 添加GPIO翻转代码(如LED闪烁),定位执行进度;
- 使用DS-5OpenOCD+GDB单步跟踪;
- 在汇编开头加nop循环,确认是否能进main。


❌ 问题2:ERET之后没有反应

现象:log显示“即将跳转”,但之后一切静默。

原因分析
- SPSR.M 设置错误(误设为aarch32);
- ELR指向的地址没有映射或不可执行;
- 目标EL的栈未设置,异常返回失败;
- MMU已开但页表为空。

🔧调试技巧
- 在跳转前打印elr_el3,spsr_el3,scr_el3
- 使用静态链接确保目标函数地址可知;
- 在U-Boot入口处加uart_putc('U')测试是否到达。


❌ 问题3:频繁触发 Data Abort

现象:刚一访问内存就崩,异常向量跳进data_abort_handler。

常见诱因
- 访问NULL指针或越界地址;
- Cache未维护导致脏数据;
- 外设MMIO地址未通过IOMMU映射;
- 页表Entry配置错误(如AP权限不对)。

🔧 解决方案:
- 在异常处理函数中 dump 所有寄存器(尤其是FAR_EL3, ESR_EL3);
- 使用mrs x0, far_el3获取出错虚拟地址;
- 使用mrs x1, esr_el3查看异常原因(bit[31:26]编码);
- 插入dsb sy; isb同步流水线后再访问内存。


工程最佳实践建议

为了写出稳定可靠的初始化代码,推荐以下做法:

  1. 最小化初期依赖
    不要在SP设置前调用函数,避免隐式push/pop。

  2. 严格对齐
    代码、栈、页表均按16字节对齐,减少Alignment Fault风险。

  3. 尽早启用串口输出
    在SPL阶段就初始化UART0,输出“[EL3] Start”便于远程调试。

  4. 防御性编程
    每个MSR/MRS操作加注释,说明修改意图和影响范围。

  5. 查阅TRM文档
    RK3588的技术参考手册(Technical Reference Manual)中有大量Errata和初始化序列细节,务必比对。

  6. 安全优先
    在跳转至非安全世界前,完成TZMA/TZC-400配置,划分安全内存区。


写在最后:掌握底层,才能掌控全局

RK3588作为国产高端SoC的代表,其复杂度远超传统MCU。但正是这种复杂性,赋予了它在AI边缘计算、工业视觉、智能座舱等领域的强大生命力。

而这一切的前提,是你能否稳稳地走过那最初的几百条汇编指令。那些看似枯燥的msrmrseret,实则是通往系统大门的钥匙。

当你下次面对一块“不开机”的开发板时,请记住:

问题不在别处,就在第一条指令之后的第17行。

如果你在移植U-Boot、定制ATF或调试Secure Boot过程中遇到具体问题,欢迎留言交流。我们可以一起剖析反汇编、解读ESR、追踪ELR,直到点亮那一行“Welcome to U-Boot”。

毕竟,真正的嵌入式工程师,都是从看懂reset_handler开始的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询