OPTEE学习笔记 - AArch64 RPC(一)

前文OPTEE学习笔记 - REE与TEE通信记录了AArch32的RPC调用流程,这边总结一下OPTEE AArch64的RPC调用流程,基于optee 3.11版本以及TF-A 2.4

REE侧EL1

RPC调用最为常见的是起于REE侧EL0或者EL1的调用,以我手上的linux optee driver中获取optee revision为例,kernel space调用的代码如下

static void optee_msg_get_os_revision(optee_invoke_fn *invoke_fn)
{
	union {
		struct arm_smccc_res smccc;
		struct optee_smc_call_get_os_revision_result result;
	} res = {
		.result = {
			.build_id = 0
		}
	};

	invoke_fn(OPTEE_SMC_CALL_GET_OS_REVISION, 0, 0, 0, 0, 0, 0, 0,
		  &res.smccc);

	if (res.result.build_id)
		pr_info("revision %lu.%lu (%08lx)", res.result.major,
			res.result.minor, res.result.build_id);
	else
		pr_info("revision %lu.%lu", res.result.major, res.result.minor);
}

invoke_fn其实是optee_smccc_smc函数,传递了9个参数进去,前面8个是入参,最后一个是从OPTEE得到的结果,optee_smccc_smc函数和结果的结构体定义如下

struct arm_smccc_res {
	unsigned long a0;
	unsigned long a1;
	unsigned long a2;
	unsigned long a3;
};

static void optee_smccc_smc(unsigned long a0, unsigned long a1,
			    unsigned long a2, unsigned long a3,
			    unsigned long a4, unsigned long a5,
			    unsigned long a6, unsigned long a7,
			    struct arm_smccc_res *res)
{
	arm_smccc_smc(a0, a1, a2, a3, a4, a5, a6, a7, res);
}

arm_smccc_smc的实现中首先就是smc #0触发中断,触发中断后的操作在后面的函数返回环节中说明。需要说明的是,当前的x0~x7寄存器保存了入参,栈上保存了出参的地址,遵循SMCCC规范。当前sp寄存器用的是SP_EL0。

EL3

SMC触发中断后,cpu陷入EL3,在TF-A框架下,转入BL31处理,首先从中断向量表开始。ARM-v8的中断向量表的偏移量如下

exception level迁移情况

Synchronous exception的offset值

IRQ和vIRQ exception的offset值

FIQ和vFIQ exception的offset值

SError和vSError exception的offset值

同级exception level迁移,使用SP_EL0。例如EL1迁移到EL1

0x000

0x080

0x100

0x180

同级exception level迁移,使用SP_ELx。例如EL1迁移到EL1

0x200

0x280

0x300

0x380

ELx迁移到ELy,其中y > x并且ELx处于AArch64状态

0x400

0x480

0x500

0x580

ELx迁移到ELy,其中y > x并且ELx处于AArch32状态

0x600

0x680

0x700

0x780

 因此BL31的runtime_exceptions的代码如下,其中SMC触发的中断是同步中断,并且是从EL1迁移到EL3的,并且EL1是AArch64状态。因此向量偏移是0x400,进入的是vector_entry sync_exception_sp_elx。

vector_base runtime_exceptions

	/* ---------------------------------------------------------------------
	 * Current EL with SP_EL0 : 0x0 - 0x200
	 * ---------------------------------------------------------------------
	 */
vector_entry sync_exception_sp_el0
	/* We don't expect any synchronous exceptions from EL3 */
	b	report_unhandled_exception
end_vector_entry sync_exception_sp_el0

vector_entry irq_sp_el0
	/*
	 * EL3 code is non-reentrant. Any asynchronous exception is a serious
	 * error. Loop infinitely.
	 */
	b	report_unhandled_interrupt
end_vector_entry irq_sp_el0

vector_entry fiq_sp_el0
	b	report_unhandled_interrupt
end_vector_entry fiq_sp_el0

vector_entry serror_sp_el0
	no_ret	plat_handle_el3_ea
end_vector_entry serror_sp_el0

	/* ---------------------------------------------------------------------
	 * Current EL with SP_ELx: 0x200 - 0x400
	 * ---------------------------------------------------------------------
	 */
vector_entry sync_exception_sp_elx
	b	report_unhandled_exception
end_vector_entry sync_exception_sp_elx

vector_entry irq_sp_elx
	b	report_unhandled_interrupt
end_vector_entry irq_sp_elx

vector_entry fiq_sp_elx
	b	report_unhandled_interrupt
end_vector_entry fiq_sp_elx

vector_entry serror_sp_elx
	no_ret	plat_handle_el3_ea
end_vector_entry serror_sp_elx

	/* ---------------------------------------------------------------------
	 * Lower EL using AArch64 : 0x400 - 0x600
	 * ---------------------------------------------------------------------
	 */
vector_entry sync_exception_aarch64
	apply_at_speculative_wa
	check_and_unmask_ea
	handle_sync_exception
end_vector_entry sync_exception_aarch64

vector_entry irq_aarch64
	apply_at_speculative_wa
	check_and_unmask_ea
	handle_interrupt_exception irq_aarch64
end_vector_entry irq_aarch64

vector_entry fiq_aarch64
	apply_at_speculative_wa
	check_and_unmask_ea
	handle_interrupt_exception fiq_aarch64
end_vector_entry fiq_aarch64

vector_entry serror_aarch64
	apply_at_speculative_wa
	msr	daifclr, #DAIF_ABT_BIT
	b	enter_lower_el_async_ea
end_vector_entry serror_aarch64

	/* ---------------------------------------------------------------------
	 * Lower EL using AArch32 : 0x600 - 0x800
	 * ---------------------------------------------------------------------
	 */
vector_entry sync_exception_aarch32
	apply_at_speculative_wa
	check_and_unmask_ea
	handle_sync_exception
end_vector_entry sync_exception_aarch32

vector_entry irq_aarch32
	apply_at_speculative_wa
	check_and_unmask_ea
	handle_interrupt_exception irq_aarch32
end_vector_entry irq_aarch32

vector_entry fiq_aarch32
	apply_at_speculative_wa
	check_and_unmask_ea
	handle_interrupt_exception fiq_aarch32
end_vector_entry fiq_aarch32

vector_entry serror_aarch32
	apply_at_speculative_wa
	msr	daifclr, #DAIF_ABT_BIT
	b	enter_lower_el_async_ea
end_vector_entry serror_aarch32

《ARMv8-A_Architecture_Reference_Manual》文档中介绍了exception entry时硬件的行为,原文如下:

On taking an exception to AArch64 state:
• The PE state is saved in the SPSR_ELx at the Exception level the exception is taken to. See Saved Program Status Registers (SPSRs) on page D1-1417.
• The preferred return address is saved in the ELR_ELx at the Exception level the exception is taken to. See Exception Link Registers (ELRs) on page D1-1420.
• All of PSTATE.{D, A, I, F} are set to 1. See Process state, PSTATE on page D1-1421.
• If the exception is a synchronous exception or an SError interrupt, information characterizing the reason for the exception is saved in the ESR_ELx at the Exception level the exception is taken to. See Use of the ESR_EL1, ESR_EL2, and ESR_EL3 on page D1-1512.
• Execution moves to the target Exception level, and starts at the address defined by the exception vector. Which exception vector is used is also an indicator of whether the exception came from a lower Exception level or the current Exception level. See Exception vectors on page D1-1430.
• The stack pointer register selected is the dedicated stack pointer register for the target Exception level. See The stack pointer registers on page D1-1416.

 总结下来,硬件做了以下几样事,我们需要关注:

  1. PSTATE里的状态都保存到了SPSR_EL3
  2. ELR_EL3保存了EL3返回到EL1的地址
  3. DAIF都设为1,屏蔽了IRQ, FIQ
  4. 跳转到EL3的中断向量
  5. sp使用SP_EL3

下面我们看一下中断的具体处理。中断向量调用了3个函数,其中第二个函数有两步操作:mask了ABT中断;把x30寄存器(即lr寄存器)的存在了sp + CTX_GPREG_LR的位置。第三个函数为handle_sync_exception,经过一些处理后跳转到了smc_handler64

smc_handler64:

	bl	save_gp_pmcr_pauth_regs

	/*
	 * Populate the parameters for the SMC handler.
	 * We already have x0-x4 in place. x5 will point to a cookie (not used
	 * now). x6 will point to the context structure (SP_EL3) and x7 will
	 * contain flags we need to pass to the handler.
	 */
	mov	x5, xzr
	mov	x6, sp //x6指向sp_el3

	ldr	x12, [x6, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP] //获取C执行空间的sp地址

	/* Switch to SP_EL0 */
	msr	spsel, #MODE_SP_EL0 //切到sp_el0,此时sp指向的是REE侧用到的sp

	/*
	 * Save the SPSR_EL3, ELR_EL3, & SCR_EL3 in case there is a world
	 * switch during SMC handling.
	 * TODO: Revisit if all system registers can be saved later.
	 */
	mrs	x16, spsr_el3
	mrs	x17, elr_el3
	mrs	x18, scr_el3
	stp	x16, x17, [x6, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3] //保存spsr_el3, elr_el3
	str	x18, [x6, #CTX_EL3STATE_OFFSET + CTX_SCR_EL3] //保存scr_el3

	/* Copy SCR_EL3.NS bit to the flag to indicate caller's security */
	bfi	x7, x18, #0, #1 //把SCR_EL3.NS标记位存在x7,即C函数的第八个参数

	mov	sp, x12 //sp_el0恢复为C执行空间的sp地址

	/* Get the unique owning entity number */
	ubfx	x16, x0, #FUNCID_OEN_SHIFT, #FUNCID_OEN_WIDTH
	ubfx	x15, x0, #FUNCID_TYPE_SHIFT, #FUNCID_TYPE_WIDTH
	orr	x16, x16, x15, lsl #FUNCID_OEN_WIDTH

	/* Load descriptor index from array of indices */
	adrp	x14, rt_svc_descs_indices
	add	x14, x14, :lo12:rt_svc_descs_indices
	ldrb	w15, [x14, x16]

	/* Any index greater than 127 is invalid. Check bit 7. */
	tbnz	w15, 7, smc_unknown

	/*
	 * Get the descriptor using the index
	 * x11 = (base + off), w15 = index
	 *
	 * handler = (base + off) + (index << log2(size))
	 */
	adr	x11, (__RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE)
	lsl	w10, w15, #RT_SVC_SIZE_LOG2
	ldr	x15, [x11, w10, uxtw]

	/*
	 * Call the Secure Monitor Call handler and then drop directly into
	 * el3_exit() which will program any remaining architectural state
	 * prior to issuing the ERET to the desired lower EL.
	 */
#if DEBUG
	cbz	x15, rt_svc_fw_critical_error
#endif
	blr	x15 //跳转到opteed_smc_handler

	b	el3_exit

save_gp_pmcr_pauth_regs函数是保存general purpose和pauth寄存器

func save_gp_pmcr_pauth_regs
	stp	x0, x1, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X0]
	stp	x2, x3, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X2]
	stp	x4, x5, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X4]
	stp	x6, x7, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X6]
	stp	x8, x9, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X8]
	stp	x10, x11, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X10]
	stp	x12, x13, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X12]
	stp	x14, x15, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X14]
	stp	x16, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X16]
	stp	x18, x19, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X18]
	stp	x20, x21, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X20]
	stp	x22, x23, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X22]
	stp	x24, x25, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X24]
	stp	x26, x27, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X26]
	stp	x28, x29, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X28]
	mrs	x18, sp_el0
	str	x18, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_SP_EL0]

	/* ----------------------------------------------------------
	 * Check if earlier initialization MDCR_EL3.SCCD to 1 failed,
	 * meaning that ARMv8-PMU is not implemented and PMCR_EL0
	 * should be saved in non-secure context.
	 * ----------------------------------------------------------
	 */
	mrs	x9, mdcr_el3
	tst	x9, #MDCR_SCCD_BIT
	bne	1f

	/* Secure Cycle Counter is not disabled */
	mrs	x9, pmcr_el0

	/* Check caller's security state */
	mrs	x10, scr_el3
	tst	x10, #SCR_NS_BIT
	beq	2f

	/* Save PMCR_EL0 if called from Non-secure state */
	str	x9, [sp, #CTX_EL3STATE_OFFSET + CTX_PMCR_EL0]

	/* Disable cycle counter when event counting is prohibited */
2:	orr	x9, x9, #PMCR_EL0_DP_BIT
	msr	pmcr_el0, x9
	isb
1:

	ret
endfunc save_gp_pmcr_pauth_regs

经过这个函数,EL1时候所有寄存器的值都被存在了EL3的栈上,包括lr寄存器和sp寄存器。需要注意的是,此时EL3的所谓栈空间并不是真正的栈,而是通过cm_set_context函数设置的一个struct cpu_context的地址。设置的时机是在前一次从EL3退出时设置的,即在el3_exit设置的。再次进入EL3用的sp就指向这个结构体,当前指向的是non-secure的context,后面会看到,在切换到secure world之前,sp指针会指向secure的context。这个结构体的数据存在cpu_data_t percpu_data[PLATFORM_CORE_COUNT];中。struct cpu_data的结构和struct cpu_context的结构如下

typedef struct cpu_context {
	gp_regs_t gpregs_ctx;
	el3_state_t el3state_ctx;
	el1_sysregs_t el1_sysregs_ctx;
#if CTX_INCLUDE_EL2_REGS
	el2_sysregs_t el2_sysregs_ctx;
#endif
#if CTX_INCLUDE_FPREGS
	fp_regs_t fpregs_ctx;
#endif
	cve_2018_3639_t cve_2018_3639_ctx;
#if CTX_INCLUDE_PAUTH_REGS
	pauth_t pauth_ctx;
#endif
} cpu_context_t;

typedef struct cpu_data {
#ifdef __aarch64__
	void *cpu_context[2]; //有2个cpu context,一个是secure的,一个是non-secure的
#endif
	uintptr_t cpu_ops_ptr;
	struct psci_cpu_data psci_svc_cpu_data;
#if ENABLE_PAUTH
	uint64_t apiakey[2];
#endif
#if CRASH_REPORTING
	u_register_t crash_buf[CPU_DATA_CRASH_BUF_SIZE >> 3];
#endif
#if ENABLE_RUNTIME_INSTRUMENTATION
	uint64_t cpu_data_pmf_ts[CPU_DATA_PMF_TS_COUNT];
#endif
#if PLAT_PCPU_DATA_SIZE
	uint8_t platform_cpu_data[PLAT_PCPU_DATA_SIZE];
#endif
#if defined(IMAGE_BL31) && EL3_EXCEPTION_HANDLING
	pe_exc_data_t ehf_data;
#endif
} __aligned(CACHE_WRITEBACK_GRANULE) cpu_data_t;

执行完save_gp_pmcr_pauth_regs后,smc_handler64继续执行,根据前面的注释可知,一些重要的寄存器都被保存在context环境中了,此时EL3的栈空间如下,SP_EL3栈指针指向non-secure context所在地址。 

struct el1_sysregs(高地址)

...
 pmcr_el0
 elr_el3

 

spsr_el3

 

runtime_sp,保存的是sp_el0的地址,用于C代码的执行

 

esr_el3
struct el3_statescr_el3
 sp_el0

 

lr
 x29
 ...
 x1

struct gp_regs(低地址)

x0

smc_handler64中恢复使用了sp_el0的寄存器,并且把context中记录的C代码执行环境的栈指针赋值给了sp。后面就跳转到C代码中继续执行。根据OPTEE学习笔记 - 启动流程(一)的介绍,smc_handler64此时跳转的是opteed_smc_handler函数。

static uintptr_t opteed_smc_handler(uint32_t smc_fid,
			 u_register_t x1,
			 u_register_t x2,
			 u_register_t x3,
			 u_register_t x4,
			 void *cookie,
			 void *handle, //handle是x6寄存器,保存的是sp_el3的值
			 u_register_t flags) //flags标记着SCR_EL3.NS
{
	cpu_context_t *ns_cpu_context;
	uint32_t linear_id = plat_my_core_pos();
	optee_context_t *optee_ctx = &opteed_sp_context[linear_id];
	uint64_t rc;

	/*
	 * Determine which security state this SMC originated from
	 */

	if (is_caller_non_secure(flags)) {
		/*
		 * This is a fresh request from the non-secure client.
		 * The parameters are in x1 and x2. Figure out which
		 * registers need to be preserved, save the non-secure
		 * state and send the request to the secure payload.
		 */
		assert(handle == cm_get_context(NON_SECURE));

		cm_el1_sysregs_context_save(NON_SECURE); //保存non-secure的el1寄存器

		/*
		 * We are done stashing the non-secure context. Ask the
		 * OPTEE to do the work now.
		 */

		/*
		 * Verify if there is a valid context to use, copy the
		 * operation type and parameters to the secure context
		 * and jump to the fast smc entry point in the secure
		 * payload. Entry into S-EL1 will take place upon exit
		 * from this function.
		 */
		assert(&optee_ctx->cpu_ctx == cm_get_context(SECURE));

		/* Set appropriate entry for SMC.
		 * We expect OPTEE to manage the PSTATE.I and PSTATE.F
		 * flags as appropriate.
		 */
        //设置下一跳的入口函数,从注释可知,此时fiq和irq都还没打开
		if (GET_SMC_TYPE(smc_fid) == SMC_TYPE_FAST) {
			cm_set_elr_el3(SECURE, (uint64_t)
					&optee_vector_table->fast_smc_entry); //对应optee的thread_vector_table->fast_smc_entry
		} else {
			cm_set_elr_el3(SECURE, (uint64_t)
					&optee_vector_table->yield_smc_entry); //对应optee的thread_vector_table->vector_std_smc_entry
		}

		cm_el1_sysregs_context_restore(SECURE); //恢复secure el1寄存器
		cm_set_next_eret_context(SECURE); //恢复secure的context,准备下一跳到optee

        //x0,x1,x2,x3寄存器的值会在后面被保存,因此这里只要保存x4,x5,x6,x7即可
        //这部分代码的意思是把保存在non-secure context的x0~x7保存到secure context,将来切换世界以后相当于参数传递
		write_ctx_reg(get_gpregs_ctx(&optee_ctx->cpu_ctx),
			      CTX_GPREG_X4,
			      read_ctx_reg(get_gpregs_ctx(handle),
					   CTX_GPREG_X4));
		write_ctx_reg(get_gpregs_ctx(&optee_ctx->cpu_ctx),
			      CTX_GPREG_X5,
			      read_ctx_reg(get_gpregs_ctx(handle),
					   CTX_GPREG_X5));
		write_ctx_reg(get_gpregs_ctx(&optee_ctx->cpu_ctx),
			      CTX_GPREG_X6,
			      read_ctx_reg(get_gpregs_ctx(handle),
					   CTX_GPREG_X6));
		/* Propagate hypervisor client ID */
		write_ctx_reg(get_gpregs_ctx(&optee_ctx->cpu_ctx),
			      CTX_GPREG_X7,
			      read_ctx_reg(get_gpregs_ctx(handle),
					   CTX_GPREG_X7));

		SMC_RET4(&optee_ctx->cpu_ctx, smc_fid, x1, x2, x3); //保存了x0~x3寄存器的值,目前optee_ctx->cpu_ctx保存了x0~x7的值
	}

...

由于我们是从non-secure调用的,因此走了如上的分支。SMC_RET4在保存了x0~x3寄存器的值到optee_ctx->cpu_ctx后,就return了,回到了smc_handler64。smc_handler64后面的一条命令是el3_exit。

这里需要说明一点,secure context其实就指向了optee_ctx->cpu_ctx,在optee初始化的时候设置的;同时,non-secure context指向的是cpu_context_t psci_ns_context[PLATFORM_CORE_COUNT]。在psci setup的时候设置的。目前我看下来的结果是这样的,请大家指正。

func el3_exit

	/* ----------------------------------------------------------
	 * Save the current SP_EL0 i.e. the EL3 runtime stack which
	 * will be used for handling the next SMC.
	 * Then switch to SP_EL3.
	 * ----------------------------------------------------------
	 */
	mov	x17, sp
	msr	spsel, #MODE_SP_ELX
	str	x17, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP] //保存当前的sp_el0指针到cpu_context

	/* ----------------------------------------------------------
	 * Restore SPSR_EL3, ELR_EL3 and SCR_EL3 prior to ERET
	 * ----------------------------------------------------------
	 */
	ldr	x18, [sp, #CTX_EL3STATE_OFFSET + CTX_SCR_EL3]
	ldp	x16, x17, [sp, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3]
	msr	scr_el3, x18
	msr	spsr_el3, x16
	msr	elr_el3, x17

	restore_ptw_el1_sys_regs //恢复el1的sys寄存器

	/* ----------------------------------------------------------
	 * Restore general purpose (including x30), PMCR_EL0 and
	 * ARMv8.3-PAuth registers.
	 * Exit EL3 via ERET to a lower exception level.
 	 * ----------------------------------------------------------
 	 */
	bl	restore_gp_pmcr_pauth_regs //恢复通用寄存器等
	ldr	x30, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_LR]

	exception_return //eret 跳到secure el1

endfunc el3_exit

TEE侧EL1

由于我们以OPTEE为例,所以这边跳转的是OPTEE内部的函数。我们以fast call为例,从EL3跳出后首先调用的是thread_vector_table->fast_smc_entry函数。

LOCAL_FUNC vector_fast_smc_entry , : , .identity_map
	readjust_pc
	sub	sp, sp, #THREAD_SMC_ARGS_SIZE //开辟struct thread_smc_args大小的sp空间
	store_xregs sp, THREAD_SMC_ARGS_X0, 0, 7 //保存x0~x7的寄存器到struct thread_smc_args
	mov	x0, sp //x0为当前sp指针地址,其实指向的是struct thread_smc_args数据结构
	bl	thread_handle_fast_smc //跳转到thread_handle_fast_smc
	load_xregs sp, THREAD_SMC_ARGS_X0, 1, 8 //把sp指向的地址的8个变量赋值给了x1~x8寄存器
	add	sp, sp, #THREAD_SMC_ARGS_SIZE //恢复栈
	ldr	x0, =TEESMC_OPTEED_RETURN_CALL_DONE
	smc	#0
	b	.	/* SMC should not return */
END_FUNC vector_fast_smc_entry

struct thread_smc_args {
	uint64_t a0;	/* SMC function ID */
	uint64_t a1;	/* Parameter */
	uint64_t a2;	/* Parameter */
	uint64_t a3;	/* Thread ID when returning from RPC */
	uint64_t a4;	/* Not used */
	uint64_t a5;	/* Not used */
	uint64_t a6;	/* Not used */
	uint64_t a7;	/* Hypervisor Client ID */
};

vector_fast_smc_entry 函数调用了thread_handle_fast_smc,thread_handle_fast_smc调用了tee_entry_fast,tee_entry_fast调用了__tee_entry_fast。需要注意的是,fast call在过程中不能unmask任何exceptions,因为fast call没有做线程处理,是一个同步的实时call,如果这个过程被打断,则会出现context不同步的情况。这也是为什么叫fast call的原因。

void __tee_entry_fast(struct thread_smc_args *args)
{
	switch (args->a0) {

	/* Generic functions */
	case OPTEE_SMC_CALLS_COUNT:
		tee_entry_get_api_call_count(args);
		break;
	case OPTEE_SMC_CALLS_UID:
		tee_entry_get_api_uuid(args);
		break;
	case OPTEE_SMC_CALLS_REVISION:
		tee_entry_get_api_revision(args);
		break;
...

	default:
		args->a0 = OPTEE_SMC_RETURN_UNKNOWN_FUNCTION;
		break;
	}
}


void __weak tee_entry_get_api_revision(struct thread_smc_args *args)
{
	args->a0 = OPTEE_MSG_REVISION_MAJOR;
	args->a1 = OPTEE_MSG_REVISION_MINOR;
}

__tee_entry_fast则会跟据optee call id来调用对应的处理函数,针对get api revision函数来说,args的a0和a1保存了结果,即被赋值了OPTEE_MSG_REVISION_MAJOR和OPTEE_MSG_REVISION_MINOR。然后函数就同步返回了。

返回到vector_fast_smc_entry中,sp又指向了原来的位置,即struct thread_smc_args数据结构。注意,之前的函数调用中,struct thread_smc_args数据结构里的a0和a1被赋值为了返回结果。vector_fast_smc_entry在后面的操作中,从当前sp指向的位置,恢复了x1~x8的寄存器。针对get api revision功能来说,OPTEE_MSG_REVISION_MAJOR和OPTEE_MSG_REVISION_MINOR被赋值了x1和x2,然后之前的x2~x7被赋值给了x3~x8。x0被赋值为了TEESMC_OPTEED_RETURN_CALL_DONE,最后调用smc #0进入EL3

调用thread_handle_fast_smc之前调用thread_handle_fast_smc之后
x7x8
x6x7
x5x6
x4x5
x3x4
x2x3
x1x2 = OPTEE_MSG_REVISION_MINOR
x0x1 = OPTEE_MSG_REVISION_MAJOR
 x0 = TEESMC_OPTEED_RETURN_CALL_DONE

EL3

进入EL3以后,我们知道之前退出EL3的时候sp_el3指向的是secure context,因此再进入EL3了,用到sp_el3就是指向secure context的。前面的执行流程相同,只是到了opteed_smc_handler函数,跳过了前面caller is secure的分支,因为我们是从secure的环境进入的,直接到了后面的逻辑。

	/*
	 * Returning from OPTEE
	 */

	switch (smc_fid) {
	/*
	 * OPTEE has finished initialising itself after a cold boot
	 */
	case TEESMC_OPTEED_RETURN_ENTRY_DONE:
...
	case TEESMC_OPTEED_RETURN_ON_DONE:
	case TEESMC_OPTEED_RETURN_RESUME_DONE:
	case TEESMC_OPTEED_RETURN_OFF_DONE:
	case TEESMC_OPTEED_RETURN_SUSPEND_DONE:
	case TEESMC_OPTEED_RETURN_SYSTEM_OFF_DONE:
	case TEESMC_OPTEED_RETURN_SYSTEM_RESET_DONE:
...
	/*
	 * OPTEE is returning from a call or being preempted from a call, in
	 * either case execution should resume in the normal world.
	 */
	case TEESMC_OPTEED_RETURN_CALL_DONE:
		/*
		 * This is the result from the secure client of an
		 * earlier request. The results are in x0-x3. Copy it
		 * into the non-secure context, save the secure state
		 * and return to the non-secure state.
		 */
		assert(handle == cm_get_context(SECURE));
		cm_el1_sysregs_context_save(SECURE);

		/* Get a reference to the non-secure context */
		ns_cpu_context = cm_get_context(NON_SECURE);
		assert(ns_cpu_context);

		/* Restore non-secure state */
		cm_el1_sysregs_context_restore(NON_SECURE);
		cm_set_next_eret_context(NON_SECURE);

		SMC_RET4(ns_cpu_context, x1, x2, x3, x4);

	/*
	 * OPTEE has finished handling a S-EL1 FIQ interrupt. Execution
	 * should resume in the normal world.
	 */
	case TEESMC_OPTEED_RETURN_FIQ_DONE:
...

	default:
		panic();
	}
}

由于从secure el1返回的时候,x0带的参数是TEESMC_OPTEED_RETURN_CALL_DONE,即smc_fid = TEESMC_OPTEED_RETURN_CALL_DONE。所以我们直接看这个分支的处理函数。

  • 首先保存了secure el1的sys寄存器,用于下次secure调用
  • 获取non-secure的context,这个在之前是保过的
  • 然后恢复non-secure el1的sys寄存器
  • 设置non-secure的context,用于下次non-secure的调用,注意这里会配置sp_el3寄存器,指向了non-secure context,也就是为什么在最开始,我们从non-secure调用下来,默认使用的是non-secure context,就是每次使用完毕以后,在这里都会设置一下,供下次使用。
  • 设置了non-secure context的前4个变量a0~a3为x1, x2, x3, x4。其实就是thread_handle_fast_smc调用结束后的x0, x1, x2, x3。现在算是把原本的值填回到了本来的位置。然后return返回

返回后的位置是smc_handler64最后一行,最后smc_handler64通过el3_exit返回到了REE侧的EL1。后面列一下SMC中断退出后硬件做的一些事情的伪代码。可以看到硬件从spsr_el3恢复了PSTATE,说明了此时unmask了fiq和irq。

AArch64.ExceptionReturn(bits(64) new_pc, bits(32) spsr)
    SetPSTATEFromSPSR(spsr);
    ClearExclusiveLocal(ProcessorID());
    EventRegisterSet();
    if spsr<4> == ‘1’ then // Attempted to change to AArch32 state
        if PSTATE.IL == ‘0’ || ConstrainUnpredictableBool() then
            if spsr<5> == ‘1’ then // T32 or T32EE state
                new_pc = Align(new_pc, 2);
            else // A32 state
                new_pc = Align(new_pc, 4);

    if PSTATE.IL == ‘0’ || ConstrainUnpredictableBool() then
        new_pc<63:32> = Zeros();
    if PSTATE.nRW == ‘1’ then
        BranchTo(new_pc<31:0>, BranchType_UNKNOWN);
    else
        BranchTo(new_pc, BranchType_ERET);
    return;

REE侧EL1

又回到了REE侧的EL1,我们上次走到这里还是只调用了一个smc #0,我们继续看arm_smccc_smc在调用smc以后的逻辑,即SMCCC的宏:

	.macro SMCCC instr
	\instr	#0
	ldr	x4, [sp]
	stp	x0, x1, [x4, #ARM_SMCCC_RES_X0_OFFS]
	stp	x2, x3, [x4, #ARM_SMCCC_RES_X2_OFFS]
	ldr	x4, [sp, #8]
	cbz	x4, 1f /* no quirk structure */
	ldr	x9, [x4, #ARM_SMCCC_QUIRK_ID_OFFS]
	cmp	x9, #ARM_SMCCC_QUIRK_QCOM_A6
	b.ne	1f
	str	x6, [x4, ARM_SMCCC_QUIRK_STATE_OFFS]
1:	ret
	.endm

根据SMC CALL CONVENTION规则,参数前8个是通过x0~x7传递,第9个通过栈传递,现在我们看到的sp就是保存返回结果的结构体地址。我们从前面的分析知道,目前,OPTEE返回的结果保存在x0~x3的寄存器里,所以我们看到后的操作就是把x0~x3的值写到保存结果的结构体,即struct arm_smccc_res res;

总结

至此,AArch64上fast call的RPC调用流程就介绍完了。后面我会理清一下std call的流程,其中会涉及OPTEE的线程管理。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CentOS 7.9版本(CentOS-7-aarch64-Everything-2009)适用于ARM64 (aarch64),必须集齐9个文件才能一起解压缩使用,9个文件下载地址: CentOS-7-aarch64-Everything-2009.part09.rar https://download.csdn.net/download/weixin_43800734/20419195 CentOS-7-aarch64-Everything-2009.part08.rar https://download.csdn.net/download/weixin_43800734/20419107 CentOS-7-aarch64-Everything-2009.part07.rar https://download.csdn.net/download/weixin_43800734/20419029 CentOS-7-aarch64-Everything-2009.part06.rar https://download.csdn.net/download/weixin_43800734/20418995 CentOS-7-aarch64-Everything-2009.part05.rar https://download.csdn.net/download/weixin_43800734/20418492 CentOS-7-aarch64-Everything-2009.part04.rar https://download.csdn.net/download/weixin_43800734/20418455 CentOS-7-aarch64-Everything-2009.part03.rar https://download.csdn.net/download/weixin_43800734/20418366 CentOS-7-aarch64-Everything-2009.part02.rar https://download.csdn.net/download/weixin_43800734/20418341 CentOS-7-aarch64-Everything-2009.part01.rar https://download.csdn.net/download/weixin_43800734/20418267 CentOS是免费的、开源的、可以重新分发的开源操作系统,CentOS(Community Enterprise Operating System,中文意思是社区企业操作系统)是Linux发行版之一。 CentOS Linux发行版是一个稳定的,可预测的,可管理的和可复现的平台,源于Red Hat Enterprise Linux(RHEL)依照开放源代码(大部分是GPL开源协议 )规定释出的源码所编译而成。自2004年3月以来,CentOS Linux一直是社区驱动的开源项目,旨在与RHEL在功能上兼容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值