0%

周谈(61)- ARM SMC接口实战

前言

最近在项目开发中,需要从 REE(Rich Execution Environment,即普通执行环境)触发一个需要更高异常等级才能执行的操作。最终,我们通过 SMC(Secure Monitor Call)接口实现了这一功能。借此机会,也系统学习了一下 SMC 接口的使用方法。

ARM SMC(Secure Monitor Call)接口是 ARM 架构中用于在非安全世界(Normal World)和安全世界(Secure World)之间进行上下文切换的关键机制,其底层基于 ARM TrustZone 技术实现。通过执行 SMC 指令,软件可以从当前运行环境(例如 Linux 内核所在的非安全世界)切换到安全监控器(Secure Monitor),从而与运行在安全世界的可信执行环境(TEE,Trusted Execution Environment)进行安全交互。

一、SMC接口的基本原理

  1. 触发方式
    通过执行 SMC 汇编指令触发异常,指令中可通过寄存器传递参数,用于指定操作类型及相关数据。

  2. 世界切换
    执行 SMC 指令后,处理器会从当前异常等级(如 EL1,对应内核态)切换到安全监控器所在的更高异常等级(如 EL3,适用于 ARMv8 及以上架构)。

  3. 安全监控器的作用
    运行在 EL3 的安全监控器负责解析 SMC 请求,判断其合法性,并决定是否允许访问安全世界资源,最后将结果返回给调用方。

二、SMC调用的参数传递

在 ARM 架构中,SMC 调用的参数通过通用寄存器传递,具体规范如下(不同架构或 TEE 实现可能略有差异):

寄存器 调用时用途 返回时用途
x0 SMC 功能号(Function ID) 返回状态码或数据
x1-x7 输入参数 输出参数
x8 部分实现中用于传递 SMC 状态 -
  • 功能号(Function ID):用于标识具体的 SMC 操作,通常由芯片厂商或 TEE 系统定义,例如在 OP-TEE 中,功能号通常以 0x32000000 开头。
  • 参数传递限制:最多可通过 x1-x7 传递 7 个参数,若参数数量超出或需要传递复杂数据结构,可通过共享内存的方式传递其物理地址。

三、SMC调用的类型

以 ARMv8 为例,SMC 调用主要分为以下几类:

  1. 标准 SMC(Standard SMC)
    由 ARM 架构标准定义,功能号通常以 0x80000000 开头,用于基本的世界切换或系统信息查询。

  2. 厂商自定义 SMC(Vendor SMC)
    由芯片厂商或 TEE 系统自定义实现,功能号范围通常为 0x00000000-0x7FFFFFFF

  3. Hypervisor SMC
    用于虚拟化场景,与 Hypervisor 交互,功能号以 0x40000000 开头。

四、示例:Linux内核中的SMC调用

在 Linux 内核中,可以通过 smc()hvc()(用于虚拟化调用)函数触发 SMC,以下是一个基于 ARMv8 汇编的简化示例:

1
2
3
4
5
6
7
// 函数:smc_call
// 输入:x0 = 功能号,x1-x7 = 参数
// 输出:x0 = 返回值

smc_call:
smc #0 // 执行 SMC 指令,#0 表示无立即数,参数由寄存器传递
ret // 返回,结果保存在 x0 中

在 C 代码中,可以使用内联汇编或调用内核封装好的接口:

1
2
3
4
5
6
7
8
9
10
11
12
#include <linux/arm-smccc.h>

struct arm_smccc_res res; // 存储返回结果

// 调用 SMC:功能号为 0x12345678,输入参数 x1=1, x2=2
arm_smccc_smc(0x12345678, 1, 2, 0, 0, 0, 0, 0, &res);

if (res.a0 == 0) {
// 调用成功,可通过 res.a1 - res.a7 获取返回参数
} else {
// 调用失败
}
  • arm_smccc_smc() 是 Linux 内核中封装好的 SMC 调用函数,定义在 include/linux/arm-smccc.h 中,会自动处理寄存器传递和结果保存。

五、注意事项

  1. 权限控制
    SMC 调用通常需要在内核态执行,用户态程序直接执行 SMC 指令会触发异常,因此一般需通过系统调用(如 ioctl)间接调用内核中的 SMC 接口。

  2. 安全限制
    安全监控器会对所有 SMC 请求进行安全检查,非法请求会被拒绝并返回错误码。

  3. 兼容性
    不同 ARM 架构(如 ARMv7 与 ARMv8)及不同 TEE 实现(如 OP-TEE、Trusty)之间的 SMC 功能号及参数规范可能存在差异,开发时应参考对应平台的说明文档。

六、测试 Demo

以下是一个基于 Linux 内核模块的 SMC 测试示例,通过设备节点供用户态触发 SMC 调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// 定义设备名称
#define SMC_TEST_DEV "smc_test"
static int major;

// 定义 ARM 标准 SMC 功能号(参考 ARM DEN 0028)
#define SMC_STD_VERSION 0x80000000 // 查询安全监控器版本
#define SMC_STD_DISCOVER_FEATURES 0x80000001 // 发现支持的功能
#define SMC_STD_GET_SOC_ID 0x80000003 // 获取 SoC ID(部分设备支持)

// 测试:查询安全监控器版本
static void test_smc_version(void)
{
struct arm_smccc_res res;

pr_info("\n=== Testing SMC_STD_VERSION (0x%x) ===\n", SMC_STD_VERSION);
arm_smccc_smc(SMC_STD_VERSION, 0, 0, 0, 0, 0, 0, 0, &res);

if (res.a0 == 0xFFFFFFFF) {
pr_info("Standard SMC version not supported\n");
return;
}

// 解析版本号(主版本 << 16 | 次版本)
u32 major_ver = (res.a0 >> 16) & 0xFFFF;
u32 minor_ver = res.a0 & 0xFFFF;
pr_info("Secure Monitor Version: v%d.%d\n", major_ver, minor_ver);
pr_info("Additional info: a1=0x%llx, a2=0x%llx\n", res.a1, res.a2);
}

// 测试:查询 TrustZone 支持特性
static void test_smc_trustzone(void)
{
struct arm_smccc_res res;

pr_info("\n=== Testing SMC_STD_DISCOVER_FEATURES (0x%lx) ===\n",
SMC_STD_DISCOVER_FEATURES);
arm_smccc_smc(SMC_STD_DISCOVER_FEATURES, 0, 0, 0, 0, 0, 0, 0, &res);

if (res.a0 == 0) {
pr_info("TrustZone supported: Yes\n");
pr_info("Feature flags: 0x%llx\n", res.a1);
} else {
pr_info("TrustZone supported: No (error: 0x%llx)\n", res.a0);
}
}

// 设备节点的读操作函数
static ssize_t smc_test_read(struct file *file, char __user *buf,
size_t len, loff_t *off)
{
test_smc_version();
test_smc_trustzone();
return 0;
}

// 文件操作结构体
static const struct file_operations smc_test_fops = {
.read = smc_test_read,
};

// 模块初始化
static int __init smc_test_init(void)
{
major = register_chrdev(0, SMC_TEST_DEV, &smc_test_fops);
if (major < 0) {
pr_err("Failed to register device\n");
return major;
}
pr_info("SMC test module loaded. Major: %d\n", major);
pr_info("Run 'mknod /dev/%s c %d 0' to create device node\n",
SMC_TEST_DEV, major);
pr_info("Read /dev/%s to trigger SMC tests\n", SMC_TEST_DEV);
return 0;
}

// 模块退出
static void __exit smc_test_exit(void)
{
unregister_chrdev(major, SMC_TEST_DEV);
pr_info("SMC test module unloaded\n");
}

module_init(smc_test_init);
module_exit(smc_test_exit);

测试结果如图所示:
smc_test

完整代码已开源,可在Gitee仓库查看

总结

通过 SMC 接口,系统能够在不同安全世界之间建立可控的通信机制,使得敏感操作(例如加密计算、密钥管理与安全存储)可以在受保护的环境中执行,从而有效防止非安全世界直接访问关键资源,极大提升了系统的整体安全性。


行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。

欢迎关注

博客地址: https://fishmwei.github.io

掘金主页: https://juejin.cn/user/2084329776486919