RK3588多核启动全链路解析:从上电到SMP的每一步
你有没有遇到过这样的场景?系统上电后,串口只打印出主核的日志,其余七个核心“悄无声息”,像是从未存在过。或者更糟——内核卡在smp_init(),迟迟不往下走,而/proc/cpuinfo里永远只有CPU0在线。
如果你正在调试RK3588平台的启动流程,尤其是想搞清楚为什么从核没起来、PSCI调用失败、或者Boot ROM跳转异常,那么这篇文章就是为你准备的。
我们不讲空泛概念,也不堆砌术语。我们将以实战视角,沿着RK3588在arm64架构下的真实启动路径,一步步拆解从硬件复位到Linux SMP就绪的全过程。重点不是“有哪些阶段”,而是“每个阶段到底干了什么”、“谁在控制”、“出了问题怎么查”。
上电那一刻:八个核心同时醒来,但只有一个能说话
当RK3588上电或触发复位信号时,所有八个CPU核心(A76×4 + A55×4)都会被释放复位状态,并尝试从预设地址开始取指执行——这个地址通常是片内ROM的起始位置,比如0x0000_0000。
但这里有个关键机制:硬件仲裁逻辑会强制只允许一个核心(通常是CPU0)进入执行状态,其他核心则被置于等待或低功耗挂起状态。
✅为什么这么做?
如果多个核心同时运行初始化代码,就会导致外设冲突、内存竞争、时钟重复配置等问题。必须有一个“主控者”先建立秩序,再逐个唤醒队友。
这个唯一的幸运儿就是所谓的主核(Primary Core),它将肩负起加载后续引导程序、初始化系统资源、最终唤醒其他兄弟的重任。
第一棒:Boot ROM —— 硬件信任根,不可篡改的生命线
它是谁?它在哪?
Boot ROM 是固化在SoC内部掩膜ROM中的只读代码,也叫一级引导程序(BL1)。它是整个启动链的起点,也是唯一无法被用户修改的部分。
它的入口地址是芯片设计时硬编码的,例如0x0000_0000。一旦上电,CPU0就会从此处开始执行。
它做了什么?
虽然功能极简,但它承担着至关重要的任务:
- 检测启动模式引脚(GPIO)或eFUSE设置,确定从哪里加载下一阶段;
- 初始化基本时钟源和电源管理模块;
- 尝试从eMMC、SPI NAND、USB等介质读取第二阶段镜像(通常是MaskROM Loader或ATF);
- 可选地进行签名验证(支持Secure Boot);
- 成功后将其加载到SRAM或DRAM中,并跳转过去。
启动介质选择示例:
| GPIO配置 | 启动设备 |
|---|---|
| 0b00 | eMMC |
| 0b01 | SPI NAND |
| 0b10 | SD Card |
| 0b11 | USB Download |
这意味着你可以通过外部跳线改变启动方式,非常适用于烧录和调试。
关键特性总结
| 特性 | 说明 |
|---|---|
| 不可修改 | 固化于硅片,提供可信根(Root of Trust) |
| 最小化职责 | 不跑复杂驱动,只为快速加载下一阶段 |
| 安全校验支持 | 支持RSA/AES签名验证,防止恶意固件注入 |
⚠️ 注意:如果Boot ROM找不到有效镜像,通常会进入USB下载模式,等待PC端工具(如RKDevTool)刷机。这也是你在“砖机”时还能救回来的原因。
第二棒:ATF(Arm Trusted Firmware)—— 安全世界的总指挥
运行环境:EL3,最高特权等级
ATF作为BL2或BL31阶段运行,在Secure World下以Exception Level 3(EL3)执行,拥有对整个系统的完全控制权。
它的主要使命是搭建安全基础设施,并为非安全世界(Normal World)铺平道路。
核心职责一览
- 异常向量设置:建立完整的异常处理框架;
- GICv3初始化:配置中断控制器,使能IPI(处理器间中断);
- TrustZone划分:通过TZC/TZMA保护安全内存区域;
- PSCI服务注册:暴露标准接口用于核启停、休眠等操作;
- SMC路由配置:处理来自非安全世界的请求转发;
- 移交控制权:最终跳转至U-Boot(BL33)或直接到内核。
多核启动的关键:PSCI 接口
PSCI(Power State Coordination Interface)是Arm定义的标准电源管理接口,RK3588支持PSCI v1.1,允许动态启用/禁用CPU核心。
主核可以通过调用PSCI_CPU_ON来远程唤醒指定MPIDR的核心。
// 示例:主核唤醒CPU1 int power_up_secondary_core(uint64_t mpidr, uint64_t entry) { register uint64_t r0 asm("x0") = PSCI_CPU_ON; register uint64_t r1 asm("x1") = mpidr; // 如 0x80000001 register uint64_t r2 asm("x2") = entry; // 入口函数地址 register uint64_t r3 asm("x3") = 0; asm volatile("smc #0" : "+r"(r0) : "r"(r1), "r"(r2), "r"(r3) : "memory"); return r0; // 返回值表示成功与否 }🔍注意点:
mpidr必须与设备树中定义的一致;entry地址必须可访问且映射正确;- 若ATF未启用
CONFIG_PSCI或未注册cpu_on回调,则此调用将失败。
第三棒:U-Boot —— 主核的舞台,但从核只能旁观
身份定位:BL33,运行于Non-secure EL2 或 EL1
U-Boot在此阶段负责完成以下关键动作:
- 初始化DDR控制器(PHY训练、时序校准);
- 驱动存储设备(eMMC/SD/NAND)读取内核镜像和DTB;
- 解析设备树,传递硬件信息给内核;
- 设置启动参数(ATAGS或FDT);
- 最终跳转至内核入口。
但它有一个重要限制:U-Boot默认只在主核上运行,不对从核做任何主动操作。
但它也为多核做了哪些准备?
尽管不直接唤醒从核,U-Boot仍需为后续SMP做好基础工作:
- 确保DDR初始化成功:否则从核即使被唤醒也无法访问内存;
- 正确填充设备树:包括
/cpus节点下的拓扑结构、clock-frequency、enable-method 等; - 保留共享数据区:某些平台需要预留用于核同步的内存页;
- 关闭MMU前保存现场:避免跳转后上下文丢失。
设备树片段示例(dtsi):
cpus { #address-cells = <2>; #size-cells = <0>; cpu-map { cluster0 { core0 { cpu = <&CPU0>; }; core1 { cpu = <&CPU1>; }; }; }; CPU0: cpu@0 { device_type = "cpu"; compatible = "arm,cortex-a76"; reg = <0x0 0x0>; enable-method = "psci"; next-level-cache = <&L3_CACHE0>; }; CPU1: cpu@1 { device_type = "cpu"; compatible = "arm,cortex-a76"; reg = <0x0 0x1>; enable-method = "psci"; // 告诉内核用PSCI唤醒 }; };💡 提示:若此处
enable-method缺失或写错,内核将无法唤醒从核!
终点冲刺:Linux内核 SMP 启动机制详解
阶段一:主核启动(BSP)
主核从_start开始执行,经过一系列汇编初始化后进入start_kernel(),完成如下关键步骤:
- 内存子系统初始化(paging_init)
- 中断系统 setup(init_IRQ)
- 调度器初始化(sched_init)
- SMP框架注册
- 解析
/cpus节点,构建CPU topology map
然后进入smp_init(),正式开启多核上线流程。
阶段二:从核唤醒(APs)
smp_init()会遍历所有在设备树中标记为“可用”的CPU节点,调用:
firmware_ops->cpu_boot(cpu)这个函数指针通常指向psci_cpu_boot(),内部封装了前面提到的SMC调用。
一旦ATF收到该请求,便会为对应核心设置上下文并触发其从低功耗状态恢复执行。
从核启动入口:secondary_startup()
这是所有从核的统一入口点,位于arch/arm64/kernel/head.S:
ENTRY(secondary_startup) mrs x0, mpidr_el1 and x0, x0, #0xff // 提取Affinity Level 0 (core ID) mov x1, #PAGE_OFFSET orr x1, x1, #__secondary_data add x1, x1, x0, LSL #3 // 计算偏移:core_id * 8 ldur x2, [x1, #8] // 读取piggyback标志 cbz x2, 1f // 若未初始化,则走完整流程 b secondary_start_here // 已初始化则跳过setup 1: bl __cpu_setup // 架构级初始化 adr x0, secondary_start_here msr vbar_el1, x0 isb b __primary_switched ENDPROC(secondary_startup)这段代码的核心逻辑是:
- 获取自己的
MPIDR_EL1寄存器值; - 根据core ID查找专属数据结构;
- 判断是否已完成早期初始化;
- 若否,则执行必要的CPU setup(如TLB、cache);
- 切换页表,进入C语言环境;
- 最终调用
cpu_startup_entry(),进入idle loop等待调度。
📌 补充知识:
- MPIDR格式:
<Aff3.Aff2.Aff1.Aff0>,RK3588一般使用0x800000XX形式,其中XX为core编号;- Cache Coherency:依赖CCI-550或CMN总线实现ACE-Lite一致性协议;
- IPI通信:通过GICv3的SGI(Software Generated Interrupt)实现核间通知。
实际问题排查指南:那些年我们踩过的坑
❌ 故障现象1:从核无法启动,日志停留在Booting CPUs...
可能原因分析:
| 原因 | 检查方法 |
|---|---|
| ATF未正常加载 | 查看串口是否有ATF打印,确认其是否执行到el3_exit |
| PSCI服务未注册 | 检查ATF编译选项是否启用CONFIG_PSCI=1 |
设备树enable-method缺失 | 使用fdtdump查看DTB中/cpus/cpu@1是否有enable-method = "psci" |
| 入口地址无效 | 确保内核映射了正确的stext地址且可执行 |
🔧调试建议:
- 在ATF中添加INFO("PSCI: cpu_on called for 0x%llx\n", mpidr);日志;
- 使用JTAG连接DS-5或OpenOCD观察各核PC是否停滞;
- 检查__pa_symbol(__secondary_data)地址是否被正确映射。
❌ 故障现象2:启动卡死在early_printk,DDR似乎没工作
真相往往是:DDR训练失败!
U-Boot阶段的DDR初始化极为关键。若PHY训练参数错误、时序不准、电压不稳定,可能导致:
- 主核勉强运行(使用IRAM),但从核无法访问DRAM;
- 即使唤醒,也会因缺页崩溃;
- 内核根本加载不进来。
🔧解决思路:
- 检查U-Boot中
dram_init()相关代码; - 使用RK官方工具(如DDR Tool)生成最优训练结果;
- 确认
rk3588_ddr_*.dtsi中的timing节点是否匹配实际颗粒; - 启用
CONFIG_DEBUG_LL_UARTx查看底层串口输出。
❌ 故障现象3:多核温度不均,性能差异大
这通常不是启动问题,而是频率策略配置不当所致。
A76和A55属于不同Cluster,可能绑定不同的DVFS表。
🔧检查项:
/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor- 是否启用了
interactive或schedutil; - 是否有thermal throttling限制;
- 使用
cpupower frequency-info查看当前频点。
设计建议与最佳实践
✅ 内存一致性保障
- 启用SMP Cache Coherency(CCI/CMN);
- 避免手动flush cache unless necessary;
- 使用
dma-coherent属性标记DMA设备。
✅ 中断分发优化
- GIC Distributor配置全局中断;
- Redistributor分配per-CPU私有中断(PPI);
- 使用IPI进行核间通信(如reschedule、TLB flush);
✅ 电源域管理
- A76 Cluster 和 A55 Cluster 可独立上下电;
- 结合CPUIdle驱动实现深度睡眠;
- 注意Cluster级reset release顺序。
✅ 调试技巧推荐
| 方法 | 工具/命令 |
|---|---|
| 多核跟踪 | DS-5 Streamline, OpenOCD + GDB Multi-target |
| 寄存器查看 | read_mpidr()inline asm |
| 日志增强 | pr_info("CPU%d: %s\n", smp_processor_id(), __func__) |
| 死锁检测 | 启用CONFIG_DETECT_HUNG_TASK |
总结:掌握这条链,你就掌握了RK3588的灵魂
RK3588的多核启动不是一个简单的“加载→跳转”过程,而是一场精密协作的接力赛:
- Boot ROM是裁判员,决定谁能先出发;
- ATF是安全卫士兼调度官,掌控PSCI大门;
- U-Boot是主核专属助手,准备好战场却不参战;
- Linux Kernel是总指挥,通过设备树识别兵力,调用PSCI逐个唤醒战士;
- GICv3 + CCI + MPIDR是通信网、交通线和身份证系统,缺一不可。
当你下次面对“七核失踪案”时,请记住:
🔍先查设备树 → 再看PSCI → 然后盯DDR → 最后抓JTAG
每一个环节都环环相扣,任何一个疏漏都会让整个SMP体系瘫痪。
而这套机制,也正是现代arm64 SoC强大灵活性与高安全性背后的真正基石。
如果你正在开发定制化Bootloader、做安全启动加固、或是优化启动时间,理解这套全流程不仅是加分项,更是必备技能。
欢迎在评论区分享你的调试经历,我们一起破解更多嵌入式谜题。