以 TEEC_InvokeCommand
为例
CA—normal world EL0
//imx-optee-client\libteec\src\tee_client_api.c
TEEC_InvokeCommand
ioctl(session->ctx->fd, TEE_IOC_INVOKE, &buf_data)
通过syscall陷入内核态driver
linux driver—normal world EL1
tee_ioctl // drivers\tee\tee_core.c
tee_ioctl_invoke
ctx->teedev->desc->ops->invoke_func
optee_invoke_func // drivers\tee\optee\call.c
// Does and SMC to OP-TEE in secure world
// and handles eventual resulting Remote
// Procedure Calls (RPC) from OP-TEE.
// Returns return code from secure world, 0 is OK
optee_do_call_with_arg
optee->invoke_fn // get_invoke_func
optee_smccc_smc
arm_smccc_smc
__arm_smccc_smc
SMCCC SMCCC_SMC
smc #0 //同步异常陷入EL3,执行bl31的中断向量表
其中 ops->invoke_func
在 linux\drivers\tee\optee\core.c 中注册
static const struct tee_driver_ops optee_ops = {
.get_version = optee_get_version,
.open = optee_open,
.release = optee_release,
.open_session = optee_open_session,
.close_session = optee_close_session,
.invoke_func = optee_invoke_func,
.cancel_req = optee_cancel_req,
.shm_register = optee_shm_register,
.shm_unregister = optee_shm_unregister,
};
bl31—EL3
https://blog.csdn.net/orlando19860122/article/details/117034843
// atf\bl31\aarch64\runtime_exceptions.S
sync_exception_aarch64 //从EL1跳转到EL3,即从低到高
handle_sync_exception
smc_handler64
// 根据x0计算出一个runtime service(opteed_smc_handler),然后跳转
blr x15
// el3_exit 函数最后调用 eret 从 el3 跳到 secure el1
// 跳转到optee os的入口 fast_smc_entry、yield_smc_entry
b el3_exit
https://blog.csdn.net/yiyueming/article/details/72885273
opteed_smc_handler------Runtime Service
opteed_smc_handler
// 保存non sercure world context
cm_el1_sysregs_context_save(NON_SECURE);
// Set appropriate entry for SMC.
// fast_smc_entry or yield_smc_entry
cm_set_elr_el3(SECURE, (uint64_t)
&optee_vector_table->fast_smc_entry);
cm_set_elr_el3(SECURE, (uint64_t)
&optee_vector_table->yield_smc_entry);
// 保存 sercure world context
cm_el1_sysregs_context_restore(SECURE)
SMC_RET4 // 返回smc_handler64继续执行 b el3_exit
optee os—secure world EL1
std
vector_std_smc_entry // core\arch\arm\kernel\thread_optee_smc_a64.S
bl thread_handle_std_smc // core\arch\arm\kernel\thread_optee_smc.c
thread_alloc_and_run
__thread_alloc_and_run(a0, a1, a2, a3, a4, a5, 0, 0,
thread_std_smc_entry)
//core\arch\arm\kernel\thread_optee_smc_a64.S
thread_std_smc_entry
__thread_std_smc_entry
std_smc_entry
call_entry_std
tee_entry_std
__tee_entry_std // core\tee\entry_std.c
case OPTEE_MSG_CMD_INVOKE_COMMAND:
entry_invoke_command(arg, num_params)
tee_ta_invoke_command
ts_ctx->ops->enter_invoke_cmd(&sess->ts_sess, cmd)
//core\kernel\user_ta.c
user_ta_enter_invoke_cmd
user_ta_enter
//sercure world os space->user space
//返回到user space中 参数 entry_func
thread_enter_user_mode
smc #0
uta—secure world EL0
thread_enter_user_mode
从 sercure world os space(EL1、optee os)返回到sercure world user space(EL0、uta),返回地址是该函数的入参entry_func,TA_InvokeCommandEntryPoint,去执行uta的函数。执行完返回,最后调用 smc #0,触发异常,跳转到EL3。
bl31—EL3
异常进入EL3以后,我们知道之前退出EL3的时候sp_el3指向的是secure context,因此再进入EL3了,用到sp_el3就是指向secure context的。前面的执行流程相同,只是到了opteed_smc_handler函数,跳过了前面caller is secure的分支,因为我们是从secure的环境进入的,直接到了后面 switch(smc_fid) 的逻辑。
// atf\bl31\aarch64\runtime_exceptions.S
sync_exception_aarch64 //从EL1跳转到EL3,即从低到高
handle_sync_exception
smc_handler64
// 根据x0计算出一个runtime service(opteed_smc_handler),然后跳转
blr x15
// el3_exit 函数最后调用 eret 从 el3 跳到 secure el1
b el3_exit
opteed_smc_handler------Runtime Service
opteed_smc_handler
// Returning from OPTEE
switch (smc_fid) {
//...
case TEESMC_OPTEED_RETURN_CALL_DONE:
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);
//返回到b el3_exit,然后通过eret返回到REE侧的EL1
SMC_RET4(ns_cpu_context, x1, x2, x3, x4);
由于从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。
linux driver—normal world EL1
又回到了REE侧的EL1,我们上次走到这里还是只调用了一个smc #0,我们继续看arm_smccc_smc在调用smc以后的逻辑,即SMCCC的宏:
.macro SMCCC instr
\instr #0 /* 执行instr参数的内容,即执行smc切换 */
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 //返回到 el0 继续执行
.endm
根据SMC CALL CONVENTION规则,参数前8个是通过x0x7传递,第9个通过栈传递,现在我们看到的sp就是保存返回结果的结构体地址。我们从前面的分析知道,目前,OPTEE返回的结果保存在x0x3的寄存器里,所以我们看到后的操作就是把x0~x3的值写到保存结果的结构体,即struct arm_smccc_res res;