SMC 调用遵循 ARM SMC Calling Convention (SMCCC) 规范(文档编号:ARM DEN 0028)。

这是一套标准化的“握手协议”,规定了非安全世界(EL1/EL2)如何向安全监控器(EL3)传递请求,以及 EL3 如何返回结果。


一、寄存器使用约定

SMC 调用本质上是函数调用,但通过寄存器传参。

1. 输入参数

寄存器 名称 说明
x0 Function ID 核心标识。EL3 根据这个值决定执行哪个服务
x1 Arg1 第 1 个参数
x2 Arg2 第 2 个参数
x3 Arg3 第 3 个参数
x7 Arg7 第 7 个参数

2. 输出结果

寄存器 说明
x0 返回码,表示执行状态(成功、失败、不支持等)
x1 返回值 1
x2 返回值 2
x3 返回值 3
x4 返回值 4

3. 重要限制

  • 最多传递 7 个参数(x1-x7)。
  • 最多返回 4 个输出值(x1-x4)。
  • x5 - x17:在 SMC 调用过程中被视为临时寄存器,调用完成后其值未定义,调用者必须自行保存/恢复。

二、Function ID (x0) 的结构解析

x0 是 SMC 调用的灵魂,EL3 的 runtime_svc 层会解析它来定位服务。

x0 是一个 32 位无符号整数,其位域结构如下:

31
(1 bit)

30
(1 bit)

29:24
(6 bits)

23:16
(8 bits)

15:0
(16 bits)

1. 位 [31]:调用类型

  • 0标准调用。通常用于 64 位调用者,参数通过 x0-x7 传递。
  • 1快速调用。通常用于 32 位调用者,参数通过 w0-w7 传递。

2. 位 [30]:调用指令类型

  • 0快速调用,由 SMC 指令触发。
  • 1标准调用,由 HVC 指令触发(用于 Hypervisor 场景)。

:在 TF-A 实现中,通常通过 is_smc_fid() 宏来区分。常见的 SMC FID 顶层十六进制为 0x8...0xC...

3. 位 [29:24]:服务类型

这是区分不同服务提供者的关键字段:

类型 说明
0x00 Arch 架构服务(如 PSCI)
0x01 SiP 硅片厂商服务
0x02 OEM OEM 厂商服务
0x03 Std 标准安全服务
0x04 PSCI 电源状态协调接口(实际常用)

4. 位 [23:16]:OEN

OEM 或 SiP 的具体编号,用于进一步细分服务来源。

5. 位 [15:0]:功能编号

具体的功能序号,由服务提供者自定义。


三、常见的 Function ID 示例

以 TF-A 中最常用的 PSCI 服务为例:

Function ID (Hex) 二进制关键位 含义
0x84000000 10 (Fast SMC) PSCI 版本查询
0x84000001 10 (Fast SMC) CPU 关机
0x84000002 10 (Fast SMC) CPU 开机
0x84000003 10 (Fast SMC) CPU 挂起
0xC4000000 11 (Std SMC) PSCI 版本查询 (64-bit)

解析 0x84000001 (PSCI_CPU_OFF):

  • 0x84 = 1000 0100 (二进制)
  • Bit 31 = 1 (Fast Call)
  • Bit 30 = 0 (SMC)
  • Bit 29:24 = 000100 = 4 (PSCI 类型)
  • Bit 15:0 = 1 (功能号 1,即 CPU_OFF)

四、标准返回码

SMC 调用返回后,x0 保存执行状态。SMCCC 定义了以下标准返回码:

返回码 (Hex) 名称 说明
0x00000000 SUCCESS 执行成功
0xFFFFFFFE NOT_SUPPORTED 功能不支持
0xFFFFFFFD INVALID_PARAMS 参数无效
0xFFFFFFFC DENIED 权限拒绝
0xFFFFFFFB NOT_PRESENT 服务不存在
0xFFFFFFF1 ON_PENDING CPU 开机操作挂起中

五、代码示例

1. 汇编层面发起 SMC

// 示例:调用 PSCI CPU_OFF (0x84000001)
// 输入:无参数
// 输出:x0 = 返回码

smc_cpu_off:
    mov x0, #0x84000001   // 设置 Function ID
    smc #0                // 触发 SMC 异常
    ret                   // 返回调用者 (x0 包含结果)

2. C 语言层面发起 SMC

在 Linux 内核或 U-Boot 中,通常使用 arm_smccc_smc 宏/函数:

#include <linux/arm-smccc.h>

// 定义输入参数结构体
struct arm_smccc_res res;

// 发起 SMC 调用
// 参数1: Function ID
// 参数2-7: 具体参数
arm_smccc_smc(0x84000001, 0, 0, 0, 0, 0, 0, 0, &res);

// 检查结果
if (res.a0 == 0) {
    pr_info("CPU off success\n");
} else {
    pr_err("CPU off failed: %lx\n", res.a0);
}

六、总结图示

EL3 (BL31) EL1/EL2 (调用者) EL3 (BL31) EL1/EL2 (调用者) 准备参数 x0=FuncID, x1-x7=Args 1. 读取 x0 2. 解析服务类型 3. 查表找到 Handler 设置返回 x0=RetCode, x1-x4=Out 检查 x0 判断结果 SMC 执行服务逻辑 ERET

核心规则:

  1. x0 是身份证:EL3 靠它识别你要干什么。
  2. x1-x7 是快递包:里面装着具体的参数。
  3. x0-x4 是回执单:EL3 告诉你结果如何。
  4. 遵守 SMCCC:这是 ARM 生态(Linux、TF-A、OP-TEE)互通的基础。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐