当前位置: 首页 > news >正文

Rockchip RK3588启动流程深度剖析:arm64架构下固件初始化完整指南

Rockchip RK3588 启动流程深度剖析:从上电到内核加载的完整实战指南

你有没有遇到过这样的场景?
RK3588 板子通电后串口毫无输出,或者 Linux 内核卡在 “Booting kernel” 之后;更糟的是,多核 CPU 只有一个主核上线,其余全部“失联”。这些问题往往不是驱动写错了,也不是设备树配错了——它们根植于系统启动最底层的固件初始化阶段。

本文不讲空泛理论,也不堆砌术语。我们将以一名嵌入式系统工程师的真实视角,带你逐级拆解 RK3588 在 arm64 架构下的完整启动链路,从 Mask ROM 开始,穿过 BL2、BL31、PSCI,直到 U-Boot 加载 Linux 内核为止。目标只有一个:让你真正看懂每一步发生了什么,以及出问题时该往哪里查。


上电之后的第一声“心跳”:Mask ROM 与 SPL 如何唤醒沉睡的 SoC

当 RK3588 芯片接通电源,第一段执行的代码并不是你烧录进 Flash 的 U-Boot,而是固化在芯片内部的一段只读代码——Mask ROM

这段代码是硬件厂商预置的“生命线”,无法修改,也无需编译。它的任务非常明确:

  1. 检测启动模式(通过 GPIO 配置决定优先从 SPI Flash、eMMC 还是 USB 启动);
  2. 从选定介质中读取最小引导程序(SPL),加载到片上 SRAM;
  3. 跳转执行 SPL。

为什么需要 SPL?

因为 DDR 尚未初始化!此时整个系统的可用内存只有几 KB 的 SRAM。SPL 的作用就是完成最基础的硬件初始化,尤其是DDR 初始化

// 示例:U-Boot SPL 中的关键调用流程 board_init_f() → dram_init() → rk3588_dram_init()

这一步极其关键。如果 DDR 训练失败(比如时序参数不匹配或硬件设计缺陷),后续所有操作都会崩塌。这也是为什么很多“黑屏无输出”的问题,其实早在 SPL 阶段就已经注定了。

一旦 DDR 成功初始化,SPL 就会将主引导镜像(通常是 U-Boot proper 或 ATF 的 BL2)复制到内存,并跳转过去。至此,真正的“软件控制权”才正式移交。


引导链的核心枢纽:ATF 是如何组织各阶段固件协同工作的?

到了这里,我们进入Arm Trusted Firmware(ATF)的世界。它是现代 arm64 平台启动架构的事实标准,尤其在支持 TrustZone 和安全启动的 SoC 上不可或缺。

ATF 的四个角色:BL2、BL31、BL32、BL33 分别是谁?

固件阶段名称运行等级角色定位
BL2第二阶段引导器EL3初始化平台、加载后续组件
BL31运行时服务核心EL3提供 PSCI、SMC 等运行时服务
BL32安全世界 OS(可选)EL1/EL0(Secure)OP-TEE / Trusty 实例
BL33非安全世界终点EL2/EL1(Normal)U-Boot 或 Linux 内核

你可以把 ATF 想象成一个“调度中心”:它不直接干活,但确保每个模块按顺序登场、各就各位。

典型启动路径长什么样?

在 ROC-RK3588S-PC 这类开发板上,典型的加载顺序如下:

Mask ROM ↓ U-Boot SPL(DDR init) ↓ BL2(ATF Stage 2) ↓ BL31 ← 加载并准备运行时服务 BL32 ← 可选,用于运行可信应用 ↓ BL33(U-Boot 或 Kernel) ↓ Linux 内核开始执行

注意:BL31 必须在 BL33 之前运行,因为它要为操作系统提供PSCI 接口异常路由机制。没有它,Linux 根本无法管理 CPU 休眠或多核唤醒。


BL31:藏在 EL3 的“幕后指挥官”

如果你问:“谁掌控着 arm64 系统的最高权限?”答案只有一个:运行在 EL3 的 BL31

什么是 EL3?

arm64 架构定义了四个异常等级(Exception Level):

  • EL3:最高特权级,仅用于安全监控和固件服务;
  • EL2:虚拟化层(Hypervisor),常用于 KVM;
  • EL1:操作系统内核;
  • EL0:用户程序。

而 BL31 是唯一长期驻留在 EL3 的软件实体。它就像系统的“安保总长”,负责处理以下三件大事:

  1. SMC(Secure Monitor Call)拦截与分发
  2. PSCI 电源管理接口注册
  3. TrustZone 安全边界设置

BL31 到底做了些什么?

让我们看看 RK3588 上 BL31 的入口函数:

// 文件:plat/rockchip/rk3588/aarch64/rk3588_entrypoint.S ENTRY(rk3588_primary_cpu_boot_flow) bl rk3588_config_setup bl plat_rockchip_rk3588_pmu_init bl bl31_early_platform_setup bl bl31_plat_arch_setup bl bl31_init_exception_stack bl bl31_main b . END(rk3588_primary_cpu_boot_flow)

这段汇编代码看似简单,实则完成了平台级初始化。其中最关键的是bl31_main(),它会:

  • 设置 EL3 异常向量表;
  • 注册 SMC 处理函数;
  • 初始化 PSCI 框架;
  • 准备跳转至下一阶段(BL33)。
关键点:el3_exit()不是一次普通的跳转

当 BL31 完成初始化后,不会“返回”,而是调用el3_exit(),这是一个特殊的指令序列,用于:

  • 清除当前安全上下文;
  • 设置下一级运行环境(如 EL2 NS);
  • 执行eret返回到指定地址。

这个过程类似于“交钥匙”——BL31 把系统控制权交给 U-Boot,同时确保其运行在正确的异常等级和安全状态中。


PSCI:让 Linux 内核优雅地控制每一颗 CPU 核心

你想过吗?当你在 Linux 中执行echo 0 > /sys/devices/system/cpu/cpu1/online,关闭某个 CPU 核心时,是谁在背后真正执行这条命令?

答案是:PSCI(Power State Coordination Interface)

PSCI 是什么?

PSCI 是 Arm 定义的标准接口,允许操作系统通过 SMC/HVC 指令请求底层固件进行电源状态切换。它屏蔽了硬件差异,使得同一份内核代码可以在不同 SoC 上运行。

常见调用包括:

接口功能
PSCI_CPU_ON唤醒指定 CPU 核心
PSCI_CPU_OFF关闭当前核心
PSCI_SYSTEM_SUSPEND系统挂起
PSCI_SYSTEM_RESET整体复位

Linux 内核如何使用 PSCI?

在内核初始化阶段,会尝试探测是否存在 PSCI 支持:

// arch/arm64/kernel/psci.c static int __init psci_init(void) { if (!firmware_has_feature(FW_FEATURE_PSCI)) return -EOPNOTSUPP; psci_ver = invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0); cpu_ops[cpu]->cpu_on = psci_cpu_enable; }

这些调用最终通过smc #0指令陷入 EL3,由 BL31 的 PSCI 处理器响应:

// ATF: services/std_svc/psci/psci_handlers.c static int psci_cpu_on_handler(u_register_t target_cpu, u_register_t entry_point, u_register_t context_id) { psci_set_cpu_entry_point(target_cpu, entry_point); return psci_cpu_wake(target_cpu); // 触发核间中断 IPI }

也就是说,Linux 自己并不能唤醒从核,它只是发了个请求,真正的动作是由 BL31 完成的。


实战排错:从核无法启动?别急,先看这几个地方!

你在调试 RK3588 多核启动时是否见过这个日志?

CPU1: failed to come online CPU2: failed to come online CPU3: failed to come online

别慌,这不是硬件坏了,大概率是你漏掉了几个关键配置。

排查清单

✅ 1. 设备树中是否启用 PSCI?

必须在每个 CPU 节点中声明:

cpu-map { cluster0 { core0 { cpu = <&cpu0>; }; core1 { cpu = <&cpu1>; }; ... }; }; &cpu0 { device_type = "cpu"; compatible = "arm,cortex-a76"; enable-method = "psci"; }; &cpu1 { device_type = "cpu"; compatible = "arm,cortex-a55"; enable-method = "psci"; // 必须有! };

如果没有enable-method = "psci",内核根本不会尝试调用 PSCI 去唤醒其他核。

✅ 2. BL31 是否正确注册了psci_cpu_on

检查 ATF 编译日志或插入打印语句,确认psci_cpu_on_handler已被绑定。

可以在psci_cpu_on_handler开头加一句:

INFO("PSCI: CPU_ON request for CPU%lu, entry=0x%lx\n", target_cpu, entry_point);

然后观察串口是否有输出。若无,则说明回调未注册或被覆盖。

✅ 3. 从核启动地址是否可达?

A76/A55 从核上电后并不会自动执行代码,而是等待一个“核间中断”(IPI)来触发跳转。这个跳转地址由 PSCI 设置。

确保:
- 地址位于已映射的物理内存区域;
- DDR 初始化覆盖了该地址范围;
- 没有 MMU 映射冲突。

✅ 4. 主核是否提前释放了从核?

在 BL31 初始化过程中,主核执行完bl31_main()后会调用runtime_svc_init(),注册各种服务。此时其他核心应处于“holding pen”状态,等待PSCI_CPU_ON请求。

如果主核在初始化完成前就试图唤醒从核(例如误用了sev指令),会导致不可预测行为。


安全启动与 TEE 集成:如何构建可信执行环境?

除了基本启动流程,越来越多项目要求实现安全启动(Secure Boot)可信执行环境(TEE),例如支付、DRM、生物识别等场景。

ATF 如何支撑 TEE?

在 ATF 架构中,BL32 就是用来运行 TEE OS 的,比如OP-TEE

典型流程如下:

BL2 → 加载 BL31 和 BL32(OP-TEE) → 设置 BL32 运行于 Secure World (EL1) → 跳转至 BL31 BL31 → 初始化 SMC 路由 → 当 NS World 发起 SMC 调用时,跳转至 OP-TEE 处理

这意味着,你的普通应用程序可以通过tee_client_open_session()等 API,在安全环境中执行加密、签名、密钥存储等敏感操作。

如何开启 Secure Boot?

Rockchip 提供了基于 eFUSE 的安全机制:

  1. 使用 RSA-2048 对 BL2、BL31、U-Boot 等镜像签名;
  2. 在 BL2 阶段验证签名(需启用CONFIG_ROCKCHIP_SECURE_BOOT);
  3. 烧录公钥哈希至 eFUSE,并锁定;
  4. 启用secure-boot-enablebit,强制每次启动校验。

一旦启用,任何未经签名的固件都无法运行,极大提升了抗攻击能力。

⚠️ 注意:eFUSE 一旦烧录不可逆,请务必在测试阶段充分验证!


调试技巧与最佳实践:少走弯路的工程经验

1. 日志是你的朋友

默认情况下 ATF 输出的日志很少。要深入排查问题,建议开启详细日志:

make PLAT=rk3588 LOG_LEVEL=50 DEBUG=1

LOG_LEVEL=50对应LOG_LEVEL_INFO,能看到大部分关键事件;若需更细粒度,可用60(DEBUG)。

2. JTAG + OpenOCD 单步调试 EL3 代码

对于复杂问题(如异常跳转失败、栈溢出),建议使用 JTAG 调试探针连接 OpenOCD,配合 GDB 实现:

  • 断点设置在rk3588_primary_cpu_boot_flow
  • 查看寄存器状态(特别是SPSR_EL3,ELR_EL3);
  • 跟踪eret后跳转的目标地址是否正确。

3. GPIO 打桩法:低成本定位卡死位置

如果没有专业调试工具,可以用最原始的方法:在关键函数前后翻转 GPIO:

gpio_direction_output(123, 1); mdelay(100); gpio_direction_output(123, 0);

然后用示波器观察电平变化,就能知道程序到底执行到了哪一步。

4. 内存布局规划要早做

建议在项目初期就明确内存划分:

区域大小用途
OCRAM (Secure)64KBBL31 + SPD
Secure DDR2MBOP-TEE 内存池
Shared Buffer1MBNS/S 数据交换
U-Boot Malloc32MB驱动动态分配
Kernel Image≥32MB解压空间 + 运行时

避免后期因内存冲突导致诡异问题。


结语:理解启动流程,才能真正掌控系统

RK3588 作为一款高性能国产 SoC,其启动机制融合了 arm64 架构的先进设计理念:分层引导、异常等级隔离、安全与非安全世界分离、标准化接口抽象。

掌握这套机制的意义远不止于“让板子跑起来”。它能帮助你:

  • 快速定位低层级崩溃(如 EL3 异常、从核挂起);
  • 移植 OP-TEE 构建安全应用生态;
  • 优化启动时间(Boot Time Optimization);
  • 实现符合国密标准的安全启动链;
  • 为未来支持 RME(Realm Management Extension)等新特性打下基础。

技术演进从未停止。随着 SMMU、RME、Memory Tagging 等新技术的引入,arm64 的可信执行环境正在变得更强大、更精细。而今天你对 BL31 和 PSCI 的每一次深入理解,都是通往下一代嵌入式系统的基石。

如果你正在调试 RK3588 的启动问题,欢迎在评论区留言交流具体现象,我们一起分析解决。

http://www.jsqmd.com/news/157817/

相关文章:

  • 基于BCD编码的CD4511驱动机制全面讲解
  • Vue3低代码设计器EpicDesigner:从零开始快速搭建可视化页面
  • 抖音下载器完整指南:免费获取高清无水印视频
  • 学会接受别人的离开:并不是你做错了什么,而是他不再需要你了
  • Dynamic-datasource性能调优完整指南:从基础配置到高级优化
  • PyTorch-CUDA-v2.6镜像支持TorchSnapshot模型快照功能
  • ECDICT开源英汉词典数据库:构建专业词典应用的终极指南
  • 抖音高清无水印视频下载器完整使用指南
  • OBS实时字幕插件配置全攻略:打造无障碍直播体验
  • 消息守护者:重新定义数字沟通的完整性
  • 如何快速实现Emby弹幕功能:dd-danmaku完整使用指南
  • SKkeeper:Blender形状键保护终极指南
  • Twinkle Tray:让Windows多显示器亮度控制变得简单又智能
  • TVBoxOSC电视盒子应用终极使用教程
  • Dynamic-datasource高效配置实战:连接池优化与性能提升技巧
  • 为什么这款开源媒体播放器成为Windows用户的新宠?
  • 开源直播工具完整指南:解锁B站专业推流新体验
  • 5分钟掌握图像智能修复:Inpaint-Anything完整指南
  • 告别Markdown预览烦恼:这款神器让你写作效率翻倍
  • AppleRa1n激活锁绕过终极指南:从入门到精通
  • DINOv2实战指南:从零构建视觉AI应用
  • 3个核心技巧让Elasticsearch数据查询效率翻倍:es-client完全使用手册
  • 如何3分钟掌握WindowResizer:突破窗口限制的终极指南
  • MZmine 3质谱数据分析终极指南:从安装到实战的完整解决方案
  • ECDICT英中词典数据库:开发者必备的免费词汇宝库终极指南
  • 免费音乐标签编辑器终极指南:简单上手,完美整理您的音乐库
  • PyTorch-CUDA-v2.6镜像在智能写作助手训练中的应用
  • SpringBoot多数据源终极指南:dynamic-datasource快速配置与实战
  • 蓝奏云直链解析终极指南:3分钟实现文件下载自由
  • Auto-Unlocker日志系统深度解析:多策略架构的设计哲学