前文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.
总结下来,硬件做了以下几样事,我们需要关注:
- PSTATE里的状态都保存到了SPSR_EL3
- ELR_EL3保存了EL3返回到EL1的地址
- DAIF都设为1,屏蔽了IRQ, FIQ
- 跳转到EL3的中断向量
- 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_state | scr_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之后 |
x7 | x8 |
x6 | x7 |
x5 | x6 |
x4 | x5 |
x3 | x4 |
x2 | x3 |
x1 | x2 = OPTEE_MSG_REVISION_MINOR |
x0 | x1 = 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的线程管理。