ATF 运行时服务

发布于:2025-07-30 ⋅ 阅读:(19) ⋅ 点赞:(0)

ATF 运行时服务

前言

芯片所有的软硬件资源都能够在 NXP 官网找到,本文档也是对 NXP 开源 LSDK 代码工程的学习与分析。
官网链接如下:
LSDK SDK资料
LX2160 芯片资料

本笔记是 lx2160 芯片的 ATF 固件工程源码的学习记录。

0. 关于可行根中的运行时服务

每一个服务通过两个概念来区分:

  1. 服务类型:fast = 1 或 yeild = 0;
  2. 服务 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[] ,数组索引的计算方式为:
i n d e x = ( t y p e < < 6 ) + o e n index = (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, #0x4
    fbe0b404:	f9007bfe 	str	x30, [sp, #240]
    fbe0b408:	d53e521e 	mrs	x30, esr_el3
    fbe0b40c:	d35a7fde 	ubfx	x30, x30, #26, #6
    fbe0b410:	f1004fdf 	cmp	x30, #0x13
    fbe0b414:	54fc9d60 	b.eq	fbe047c0 <smc_handler>  // b.none
    fbe0b418:	f1005fdf 	cmp	x30, #0x17
    fbe0b41c:	54fc9d40 	b.eq	fbe047c4 <smc_handler64>  // b.none
    fbe0b420:	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_el3
    fbe047dc:	d53e4031 	mrs	x17, elr_el3
    fbe047e0:	d53e1112 	mrs	x18, scr_el3
    fbe047e4:	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, #6
    fbe047f8:	d35f7c0f 	ubfx	x15, x0, #31, #1
    fbe047fc:	aa0f1a10 	orr	x16, x16, x15, lsl #6
    fbe04800:	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 函数, 其中:

  1. x0 保存 smcid,由低异常等级传入;
  2. x1 由低异常等级传入;
  3. x2 由低异常等级传入;
  4. x3 由低异常等级传入;
  5. x4 由低异常等级传入;
  6. x5 在 smc_handler64 中设置为 0;
  7. x6 在 smc_handler64 中设置为 cpu_context_t 对象地址;
  8. x7 为低异常等级安全状态,目前固定为非安全为 1。

handler 函数执行完毕后,最后会执行 el3_exit() 函数根据之前配置的 cpu_context_t 对象返回低异常等级中继续执行,而且在该代码中,会将 sp 指针切换为 sp_el3, sp_el3固定保存 cpu_context_t 对象地址。


网站公告

今日签到

点亮在社区的每一天
去签到