4. 运行时服务核心概念

在 TF-A(Trusted Firmware-A)的运行时服务体系中,安全监控调用(SMC,Secure Monitor Call)是普通世界与安全世界之间唯一的标准化入口。无论是 Linux 内核发起 PSCI CPU 热插拔,还是 OP-TEE 请求随机数服务,最终都会转化为一条 SMC 指令,由 EL3 的异常向量捕获并分发。理解 SMC 处理、服务注册、服务 ID 分配以及同步/异步调用机制,是掌握 TF-A 运行时服务框架的关键。

一、SMC 处理:从异常入口到服务分发

SMC 指令由非安全世界(或安全世界)执行后触发同步异常,处理器跳转到 EL3 的异常向量表。TF-A 在 runtime_exceptions.S 中为 AArch64 的 SMC 异常设置了入口,保存现场后进入 C 语言处理函数 handle_smc()。整个处理链路可以概括为:异常向量 → 上下文保存 → SMC 参数解析 → 服务查找 → 服务处理 → 返回值写回 → 上下文恢复 → ERET 返回。

SMC 调用约定遵循 ARM SMC Calling Convention(SMCCC)。调用者通过 x0 传递 Function ID,x1~x6 传递参数,返回值放在 x0~x3。Function ID 的高 32 位编码了调用类型、服务拥有者、是否快速调用等关键信息,低 16 位则是具体功能号。TF-A 正是依据 Function ID 的位域结构完成服务路由。

SMC 调用处理与服务分发链路 非安全世界 Linux / U-Boot SMC 指令 x0=Function ID EL3 异常向量 runtime_exceptions.S handle_smc() 参数解析 / 路由 运行时服务注册表 rt_svc_descs[] · 按服务 ID 排序 handle / init / get_auth_result 回调 PSCI 服务 ID: 0x84000000 CPU 热插拔 / 复位 OP-TEE 服务 ID: 0x80000000 可信应用调度 SiP 服务 ID: 0x82000000 厂商定制功能 SPD 服务 ID: 0x8000FF00 安全分区调度 返回值 x0~x3 写回 → ERET 返回非安全世界

二、服务注册:rt_svc_descs 与链接器段

TF-A 并不在运行时动态创建服务,而是通过链接器段 .rt_svc_descs 在编译期收集所有服务描述符。每个服务实现一个 rt_svc_desc_t 结构体,并通过 DECLARE_RT_SVC() 宏将其放入该段。启动阶段 runtime_svc_init() 遍历该段,按 start_oen 排序,并调用每个服务的 init() 回调完成初始化。

/* 服务描述符定义示例 */
typedef struct rt_svc_desc {
    uint8_t start_oen;      /* 服务 ID 起始值 */
    uint8_t end_oen;        /* 服务 ID 结束值 */
    uint8_t call_type;      /* FAST / YIELD */
    const char *name;
    rt_svc_init_fn_t init;
    rt_svc_handle_fn_t handle;
} rt_svc_desc_t;

/* 注册 PSCI 服务 */
DECLARE_RT_SVC(
    psci_svc,
    OEN_STD_START,          /* 0x84 */
    OEN_STD_END,            /* 0x84 */
    SMC_TYPE_FAST,
    psci_setup,
    psci_smc_handler
);

注册表按 start_oen 升序排列后,查找服务时可以使用二分查找,将 O(n) 降为 O(log n)。这是 TF-A 在 SMC 高频调用场景下的重要优化。

三、服务 ID 分配:OEN 与 Function ID 位域

SMCCC 将 32 位 Function ID 划分为多个位域,其中 OEN(Owning Entity Number)标识服务拥有者,是服务路由的核心依据。TF-A 预定义了若干标准 OEN 区间,厂商和第三方也可申请自有区间。下表列出常见 OEN 及其用途:

OEN 值 服务名称 典型 Function ID 用途说明
0x84PSCI0x84000000CPU 热插拔、系统复位、电源管理
0x82SiP0x82000000芯片厂商自定义服务
0x80OEM / OP-TEE0x80000000可信操作系统调度
0x86SPD0x8600FF00安全分区调度器
0xC4FF-A0xC4000000固件框架分区通信
提示:Function ID 的 bit30 表示调用类型:0 为 YIELD(可阻塞),1 为 FAST(不可阻塞)。bit31 表示是否为 64 位调用。服务实现必须与注册时声明的 call_type 一致,否则会被框架拒绝。

四、同步与异步调用:FAST 与 YIELD 的取舍

TF-A 支持两种 SMC 调用类型,其行为差异直接影响系统实时性与吞吐量:

  • FAST 调用:处理函数必须立即返回,不允许阻塞或睡眠。适用于读取寄存器、查询状态等短操作。典型如 PSCI 的 PSCI_VERSION。
  • YIELD 调用:处理函数可以阻塞,允许在等待期间让出 CPU。适用于需要与安全世界长时间交互的操作,如 OP-TEE 的可信应用执行。

在 handle_smc() 中,框架会检查 Function ID 的 bit30 与注册服务的 call_type 是否匹配。若调用者以 FAST 方式请求一个 YIELD 服务,框架将返回 SMC_UNKNOWN 错误,避免非法阻塞导致系统挂死。

常见误区:许多开发者在实现自定义 SiP 服务时,将耗时操作(如等待硬件就绪)放在 FAST 处理函数中,导致 EL3 长时间占用 CPU,非安全世界的中断被延迟响应,严重时触发看门狗复位。正确做法是将其注册为 YIELD 类型,或在 FAST 处理函数中仅做状态查询,把耗时逻辑拆分到后续调用。

五、案例复盘:一次 SMC 返回异常的排查

某项目在移植自定义 SiP 服务后,Linux 侧调用 sip_smc_handler 始终返回 0xFFFFFFFF。排查过程如下:

  1. 确认 Function ID 为 0x82000001,OEN=0x82,属于 SiP 区间。
  2. 检查 DECLARE_RT_SVC 宏,发现 start_oen 与 end_oen 均写为 OEN_SIP_START,但 call_type 误写为 SMC_TYPE_YIELD。
  3. Linux 侧以 FAST 方式发起调用,框架校验类型不匹配,直接返回错误。
  4. 将 call_type 改为 SMC_TYPE_FAST 后问题解决。

该案例说明:服务 ID 分配、注册声明与调用方式三者必须严格一致。任何一处不匹配,SMC 都会在框架层被拦截,而不会进入服务处理函数,给调试带来迷惑性。

核心要点:SMC 是入口,注册表是路由中枢,OEN 是地址,FAST/YIELD 是行为契约。四者协同工作,构成 TF-A 运行时服务的稳定骨架。理解它们之间的约束关系,比记住某个具体 Function ID 更重要。