ATF 运行时服务
ATF 运行时服务
前言
芯片所有的软硬件资源都能够在 NXP 官网找到,本文档也是对 NXP 开源 LSDK 代码工程的学习与分析。
官网链接如下:
LSDK SDK资料
LX2160 芯片资料
本笔记是 lx2160 芯片的 ATF 固件工程源码的学习记录。
0. 关于可行根中的运行时服务
每一个服务通过两个概念来区分:
- 服务类型:fast = 1 或 yeild = 0;
- 服务 oen 编号: 每一个服务会被分配一个 oen 编号或一个 oen 范围;
比如,psci 服务的类型为 fast, oen 编号为4。
服务类型与 oen 编号会在进行 smc 调用时,体现在 smcid 中。其中 bit31 表示服务类型,bit[29:24] 表示 oen 范围。
1. psci 服务注册
psci 服务属于 arm 标准规范的运行时服务, oen 编号为 4,在 atf 代码中,BL31 的代码作为可信根服务.会一致驻留在 DDR0 的最后66MB 范围的安全内存中:0xfbe00000 : 0xffffffff
.
bl31 镜像使用 DECLARE_RT_SVC
定义一个运行时服务描述符,宏定义在 atf\include\common\runtime_svc.h
:
/** Convenience macros to declare a service descriptor* 辅助宏声明服务描述符*/
#define DECLARE_RT_SVC(_name, _start, _end, _type, _setup, _smch) \static const rt_svc_desc_t __svc_desc_ ## _name \__section("rt_svc_descs") __used = { \.start_oen = (_start), \.end_oen = (_end), \.call_type = (_type), \.name = #_name, \.init = (_setup), \.handle = (_smch) \}
该宏会定义在 .section rt_svc_descs
段中定义一个 rt_svc_desc_t
描述符变量:__svc_desc_ ## _name
, 运行时服务描述符对象类型定义如下, 文件atf\include\common\runtime_svc.h
:
typedef struct rt_svc_desc {uint8_t start_oen;uint8_t end_oen;uint8_t call_type;/* 运行时服务函数类型 */const char *name;rt_svc_init_t init; /* 初始化函数 */rt_svc_handle_t handle; /* 运行时服务处理函数 */
} rt_svc_desc_t;
成员说明如下:
成员 | 描述 |
---|---|
start_oen | 当前服务所属的起始 oen 编号 |
end_oen | 当前服务所属的结束 oen 编号 |
call_type | 服务类型,fast 为1,yeild 为0 |
name | 服务名 |
init | 服务初始化函数 |
handle | smc 调用处理函数中会调用的服务处理函数 |
PSCI 服务定义与注册在文件 atf\services\std_svc\std_svc_setup.c
中:
/* Register Standard Service Calls as runtime service */
DECLARE_RT_SVC(std_svc,OEN_STD_START,OEN_STD_END,SMC_TYPE_FAST,std_svc_setup,std_svc_smc_handler
);
上述代码会将 psci 服务注册到 .section rt_svc_descs
段, 该段只保存 rt_svc_desc_t
对象,可以理解为数组。
在 bl31_main()
中,运行定义在文件atf\common\runtime_svc.c
文件中的runtime_svc_init()
会调用注册的运行时服务初始化函数:
void __init runtime_svc_init(void)
{int rc = 0;uint8_t index, start_idx, end_idx;rt_svc_desc_t *rt_svc_descs;assert((RT_SVC_DESCS_END >= RT_SVC_DESCS_START) &&(RT_SVC_DECS_NUM < MAX_RT_SVCS));if (RT_SVC_DECS_NUM == 0U)return;(void)memset(rt_svc_descs_indices, -1, sizeof(rt_svc_descs_indices));rt_svc_descs = (rt_svc_desc_t *) RT_SVC_DESCS_START;for (index = 0U; index < RT_SVC_DECS_NUM; index++) {rt_svc_desc_t *service = &rt_svc_descs[index];rc = validate_rt_svc_desc(service);if (rc != 0) {ERROR("Invalid runtime service descriptor %p\n",(void *) service);panic();}if (service->init != NULL) {rc = service->init();if (rc != 0) {ERROR("Error initializing runtime service %s\n",service->name);continue;}}start_idx = (uint8_t)get_unique_oen(service->start_oen,service->call_type);end_idx = (uint8_t)get_unique_oen(service->end_oen,service->call_type);assert(start_idx <= end_idx);assert(end_idx < MAX_RT_SVCS);for (; start_idx <= end_idx; start_idx++)rt_svc_descs_indices[start_idx] = index;}
}
同时会根据服务所属的 oen 编号范围,与服务类型,初始化 oen 描述符索引数组rt_svc_descs_indices[]
,数组索引的计算方式为:
index=(type<<6)+oenindex = (type << 6) + oen index=(type<<6)+oen
数组成员的值为服务描述符在 rt_svc_descs
段中的索引:rt_svc_descs_indices[start_idx] = index
。
BL31 的 SMC handler 根据保存在 x0 的 smcid 中bit[31]与 bit[29:24] (上一节也有提及), 可以直接换算出低异常等级请求的 oen 数组索引。
1. psci 服务调用
当低异常等级触发 SMC 调用后,会运行 BL31 注册的 SMC 服务(BL31阶段的代码会完整保留在 DDR 的 0xfbe00000-0xffffffff
中,因为这部分内存在 EL3 的 MMU 配置页表中被配置为安全内存,非安全 EL2及更低异常等级无法正常访问。)
BL31 异常向量表定义在文件atf\bl31\aarch64\runtime_exceptions.S
中,
如果低异常等级为 aarch64, 那么进行 SMC 调用时,根据 armv8 异常模型,会进入 VBAR_EL3 + 0x400 向量地址处(aarch64 状态的低异常等级触发了 EL3 同步异常), 也就是函数 sync_exception_aarch64
:
/* ---------------------------------------------------------------------* Lower EL using AArch64 : 0x400 - 0x600, 低异常等级触发的异常,低异常等级处于 aarch64 状态* ---------------------------------------------------------------------*/
vector_entry sync_exception_aarch64/* 低异常等级调用 SMC 后,此时 sp 使用 sp_el3, 指向上下文对象的地址 *//** This exception vector will be the entry point for SMCs and traps* that are unhandled at lower ELs most commonly. SP_EL3 should point* to a valid cpu context where the general purpose and system register* state can be saved.*/apply_at_speculative_wa/* check_and_unmask_ea 会解除 SError 的屏蔽, 并将 x30 的值保存到上下文对象中 */check_and_unmask_ea/* 处理同步异常, 包括 SMC 调用 */handle_sync_exception
end_vector_entry sync_exception_aarch64
结合反汇编代码,上述汇编代码扩展为:
00000000fbe0b400 <sync_exception_aarch64>:fbe0b400: d50344ff msr daifclr, #0x4fbe0b404: f9007bfe str x30, [sp, #240]fbe0b408: d53e521e mrs x30, esr_el3fbe0b40c: d35a7fde ubfx x30, x30, #26, #6fbe0b410: f1004fdf cmp x30, #0x13fbe0b414: 54fc9d60 b.eq fbe047c0 <smc_handler> // b.nonefbe0b418: f1005fdf cmp x30, #0x17fbe0b41c: 54fc9d40 b.eq fbe047c4 <smc_handler64> // b.nonefbe0b420: f9407bfe ldr x30, [sp, #240]fbe0b424: 17ffe35e b fbe0419c <enter_lower_el_sync_ea>
在上述代码中,根据 esr_el3
中 bit[31:26] 的 EC
值,判断是否是低异常等级触发的 SMC 调用:
EC 值 | 描述 |
---|---|
EC 值 = 0x13 | 低异常等级为 AARCH32 触发的 SMC 调用,运行 smc_handler |
EC 值 = 0x17 | 低异常等级为 AARCH64 触发的 SMC 调用, 运行 smc_handler64 |
其他 EC 值 | 请根据 arm 参考手册自行分析 |
在上述代码中,入口处 sp 指针使用的是 sp_el3
, 该指针保存了当前核的 cpu_cotext_t 对象的地址。(可以查看 《BL31 启动流程分析》,最后在跳转到低异常等级的函数 el3_exit()
代码中,将 EL3 使用的 sp 切换为了 sp_el3
.)
代码 str x30, [sp, #240]
表示保存 x30 的值到 cpu_context_t 对象中。
2. smc_handler64 实现
smc_handler64
() 同样定义在 atf\bl31\aarch64\runtime_exceptions.S
文件中,该函数不会返回,反汇编如下(请结合源码阅读):
00000000fbe047c4 <smc_handler64>:/* 保存低异常等级的通用寄存器到 cpu_context_t 中 */fbe047c4: 97ffffe2 bl fbe0474c <save_gp_pmcr_pauth_regs>/* x5 清0 */fbe047c8: aa1f03e5 mov x5, xzr/* 将 cpu_context_t 的地址保存在 x6 中 */fbe047cc: 910003e6 mov x6, sp/* 获取之前 EL3 等级的运行栈 */fbe047d0: f94088cc ldr x12, [x6, #272]/* 将 sp 指针切换为 sp_el0 */fbe047d4: d50040bf msr spsel, #0x0/* 保存特殊寄存器到 cpu_context_t 对象中 */fbe047d8: d53e4010 mrs x16, spsr_el3fbe047dc: d53e4031 mrs x17, elr_el3fbe047e0: d53e1112 mrs x18, scr_el3fbe047e4: a911c4d0 stp x16, x17, [x6, #280]fbe047e8: f90080d2 str x18, [x6, #256]/* x7 保存低异常等级安全状态 */fbe047ec: b3400247 bfxil x7, x18, #0, #1/* 恢复 el3 运行栈 */fbe047f0: 9100019f mov sp, x12/* 根据 x0 传入的 smc_func_id 计算 oen 描述符数组 rt_svc_descs_indices[] 索引 *//* index = (type << 6) + oen */fbe047f4: d3587410 ubfx x16, x0, #24, #6fbe047f8: d35f7c0f ubfx x15, x0, #31, #1fbe047fc: aa0f1a10 orr x16, x16, x15, lsl #6fbe04800: 900000ee adrp x14, fbe20000 <type_el3_interrupt_table+0x1b8>fbe04804: 911791ce add x14, x14, #0x5e4/* 根据描述符数组索引获取 rt_svc_descs 数组索引,并保存在 w15 */fbe04808: 387069cf ldrb w15, [x14, x16]/* 判断数组是否越界 */fbe0480c: 373800cf tbnz w15, #7, fbe04824 <smc_unknown>/* 根据 oen 描述符数组成员值,获取 rt_svc_descs 数组中的某一个具体描述符 */fbe04810: 10041e0b adr x11, fbe0cbd0 <__RT_SVC_DESCS_START__+0x18>fbe04814: 531b69ea lsl w10, w15, #5/* 获取 handler 地址 */fbe04818: f86a496f ldr x15, [x11, w10, uxtw]/* 跳转到运行时服务的 handler 进行处理 */fbe0481c: d63f01e0 blr x15/* 根据 cpu_context_t 返回低异常等级 */fbe04820: 17fffe35 b fbe040f4 <el3_exit>
该函数主要作用为保存低异常等级的代码执行环境(上下文)到 cpu_context_t 对象中,包括通用目的寄存器 x0-x30
, 特殊寄存器spsr_el3
, elr_el3
, scr_el3
等。。
低异常等级的代码执行环境(上下文)保存完毕后,会根据 smcid 计算请求的服务在 oen 数组 rt_svc_descs_indices[]
的成员索引号:
index = (type << 6) | oen
其中,type 为 bit31, oen 为 bit[29:24]。
rt_svc_descs_indices[]
数组保存的是具体服务描述符在 rt_svc_descs
数组(section)中的索引。
最后,会通过 blr x15
跳转到具体的服务 handler
函数, 其中:
- x0 保存 smcid,由低异常等级传入;
- x1 由低异常等级传入;
- x2 由低异常等级传入;
- x3 由低异常等级传入;
- x4 由低异常等级传入;
- x5 在 smc_handler64 中设置为 0;
- x6 在 smc_handler64 中设置为 cpu_context_t 对象地址;
- x7 为低异常等级安全状态,目前固定为非安全为 1。
handler 函数执行完毕后,最后会执行 el3_exit()
函数根据之前配置的 cpu_context_t
对象返回低异常等级中继续执行,而且在该代码中,会将 sp 指针切换为 sp_el3
, sp_el3
固定保存 cpu_context_t
对象地址。