optee运行时来了一个REE(linux)中断--代码导读
快速鏈接:
.
👉👉👉 個人博客筆記導(dǎo)讀目錄(全部) 👈👈👈
環(huán)境:
linux kernel 4.4, (SCR.IRQ=0、SCR.FIQ=1)
optee 3.6 (SCR.IRQ=0、SCR.FIQ=0)
ARMV8
GICV3
當(dāng)cpu處于secure側(cè)時,來了一個非安全中斷,根據(jù)SCR.NS=0/中斷在non-secure group1組,cpu interface將會給cpu一個FIQ,(由于SCR.FIQ=0,FIQ將被routing到EL1),跳轉(zhuǎn)至optee的fiq中斷異常向量表,
再optee的fiq處理函數(shù)中,調(diào)用了smc跳轉(zhuǎn)到ATF, ATF再切換至normal EL1(linux), 此時SCR.NS的狀態(tài)發(fā)生變化,根據(jù)SCR.NS=1/中斷在non-secure group1組,cpu interface會再給cpu發(fā)送一個IRQ異常,
cpu跳轉(zhuǎn)至linux的irq中斷異常向量表,處理完畢后,再依次返回到ATF—返回到optee
我們從那代碼中,依次拆解以上步驟:
在cpu進(jìn)入TEE之前,CPU是通過optee_open_session()、optee_close_session()、optee_invoke_func()等函數(shù)進(jìn)入TEE,而這些函數(shù)都是調(diào)用了optee_do_call_with_arg(),在該函數(shù)中再調(diào)用smc。
optee_do_call_with_arg()函數(shù)原型如下:(注意中文注釋)
u32 optee_do_call_with_arg(struct tee_context *ctx, phys_addr_t parg) {struct optee *optee = tee_get_drvdata(ctx->teedev);struct optee_call_waiter w;struct optee_rpc_param param = { };struct optee_call_ctx call_ctx = { };u32 ret;param.a0 = OPTEE_SMC_CALL_WITH_ARG;reg_pair_from_64(¶m.a1, ¶m.a2, parg);/* Initialize waiter */optee_cq_wait_init(&optee->call_queue, &w);while (true) {struct arm_smccc_res res;// 注意,這里調(diào)用smc,-->ATF-->TEEoptee->invoke_fn(param.a0, param.a1, param.a2, param.a3,param.a4, param.a5, param.a6, param.a7,&res); // 從TEE回來之后,執(zhí)行這里(無論是TEE正常返回,還是RPC返回,還是中斷切過來的)// 如果是REE中斷導(dǎo)致TEE切過來的,那么這里已經(jīng)觸發(fā)irq了,帶irq執(zhí)行完畢后,程序繼續(xù)從這里往下執(zhí)行// 注:REE中斷導(dǎo)致的cpu從TEE切過來,也屬于RPC返回if (res.a0 == OPTEE_SMC_RETURN_ETHREAD_LIMIT) {/** Out of threads in secure world, wait for a thread* become available.*/optee_cq_wait_for_completion(&optee->call_queue, &w);} else if (OPTEE_SMC_RETURN_IS_RPC(res.a0)) { // 如果是RPC、中斷返回走這里, 然后看下optee_handle_rpc()函數(shù)原型param.a0 = res.a0; param.a1 = res.a1;param.a2 = res.a2;param.a3 = res.a3;optee_handle_rpc(ctx, ¶m, &call_ctx);// 如果是中斷切過來的,optee_handle_rpc()函數(shù)相當(dāng)于啥都沒干,程序繼續(xù)執(zhí)行上面的while(true),然后又 // 會調(diào)用optee->invoke_fn,cpu又切回了TEE} else { // 如果是正常返回,走這里,退出optee_do_call_with_arg()函數(shù)ret = res.a0;break;}}optee_rpc_finalize_call(&call_ctx);/** We're done with our thread in secure world, if there's any* thread waiters wake up one.*/optee_cq_wait_final(&optee->call_queue, &w);return ret; }然后我們再看步驟1和步驟2 :在optee中產(chǎn)生FIQ,跳轉(zhuǎn)到FIQ中斷向量表,然后調(diào)用smc切換到ATF
LOCAL_FUNC elx_irq , : #if defined(CFG_ARM_GICV3)native_intr_handler irq #elseforeign_intr_handler irq #endif END_FUNC elx_irqLOCAL_FUNC elx_fiq , : #if defined(CFG_ARM_GICV3)foreign_intr_handler fiq // 在optee運(yùn)行時,來了REE中斷,觸發(fā)FIQ,程序會調(diào)用到這里 #elsenative_intr_handler fiq #endif END_FUNC elx_fiq然后看下foreign_intr_handler 的具體實(shí)現(xiàn):其實(shí)就是將當(dāng)前進(jìn)程的一些寄存器和棧寄存器保存,恢復(fù)中斷模式的寄存器和tmp_stack棧,然后調(diào)用smc切換到ATF,其中smdid = TEESMC_OPTEED_RETURN_CALL_DONE
/* The handler of foreign interrupt. */ .macro foreign_intr_handler mode:req/** Update core local flags*/ldr w1, [sp, #THREAD_CORE_LOCAL_FLAGS]lsl w1, w1, #THREAD_CLF_SAVED_SHIFTorr w1, w1, #THREAD_CLF_TMP.ifc \mode\(),fiqorr w1, w1, #THREAD_CLF_FIQ.elseorr w1, w1, #THREAD_CLF_IRQ.endifstr w1, [sp, #THREAD_CORE_LOCAL_FLAGS]/* get pointer to current thread context in x0 */get_thread_ctx sp, 0, 1, 2/* Keep original SP_EL0 */mrs x2, sp_el0/* Store original sp_el0 */str x2, [x0, #THREAD_CTX_REGS_SP]/* store x4..x30 */store_xregs x0, THREAD_CTX_REGS_X4, 4, 30/* Load original x0..x3 into x10..x13 */load_xregs sp, THREAD_CORE_LOCAL_X0, 10, 13/* Save original x0..x3 */store_xregs x0, THREAD_CTX_REGS_X0, 10, 13/* load tmp_stack_va_end */ldr x1, [sp, #THREAD_CORE_LOCAL_TMP_STACK_VA_END]/* Switch to SP_EL0 */msr spsel, #0mov sp, x1/** Mark current thread as suspended*/mov w0, #THREAD_FLAGS_EXIT_ON_FOREIGN_INTRmrs x1, spsr_el1mrs x2, elr_el1bl thread_state_suspendmov w4, w0 /* Supply thread index *//* Update core local flags *//* Switch to SP_EL1 */msr spsel, #1ldr w0, [sp, #THREAD_CORE_LOCAL_FLAGS]lsr w0, w0, #THREAD_CLF_SAVED_SHIFTstr w0, [sp, #THREAD_CORE_LOCAL_FLAGS]msr spsel, #0/** Note that we're exiting with SP_EL0 selected since the entry* functions expects to have SP_EL0 selected with the tmp stack* set.*/ldr w0, =TEESMC_OPTEED_RETURN_CALL_DONEldr w1, =OPTEE_SMC_RETURN_RPC_FOREIGN_INTRmov w2, #0mov w3, #0/* w4 is already filled in above */smc #0b . /* SMC should not return */ .endm然后到了步驟3:看ATF的opteed_smc_handler()函數(shù),我們直接來看case TEESMC_OPTEED_RETURN_CALL_DONE處。
在optee時,觸發(fā)了FIQ,foreign_intr_handler調(diào)用smc,進(jìn)入ATF后,走這里,這里將恢復(fù)linux系統(tǒng)的寄存器,ELR_EL3填充linux側(cè)的PC指針值,SMC_RET4后cpu將切回linux
接著又到了步驟4和步驟5:該部分對應(yīng)的代碼就是本篇一開始貼出的optee_do_call_with_arg(), 程序回到次函數(shù)后,由于SCR.NS的狀態(tài)發(fā)生了變化,cpu interface會再次給ARM Core發(fā)送一個IRQ,此時立即進(jìn)入了linux kernel的IRQ中斷向量表,待中斷處理函數(shù)執(zhí)行完畢后。PC再次指向此處,接著也就是下面這段邏輯了
else if (OPTEE_SMC_RETURN_IS_RPC(res.a0)) { // 如果是RPC、中斷返回走這里, 然后看下optee_handle_rpc()函數(shù)原型param.a0 = res.a0; param.a1 = res.a1;param.a2 = res.a2;param.a3 = res.a3;optee_handle_rpc(ctx, ¶m, &call_ctx);// 如果是中斷切過來的,optee_handle_rpc()函數(shù)相當(dāng)于啥都沒干,程序繼續(xù)執(zhí)行上面的while(true),然后又 // 會調(diào)用optee->invoke_fn,cpu又切回了TEE}在optee_handle_rpc()中的OPTEE_SMC_RPC_FUNC_FOREIGN_INTR業(yè)務(wù)邏輯中,其實(shí)啥邏輯都沒干,直接返回. 子函數(shù)返回后,optee_do_call_with_arg()中的while循環(huán)繼續(xù)執(zhí)行,optee->invoke_fn()再次將CPU切到ATF。
void optee_handle_rpc(struct tee_context *ctx, struct optee_rpc_param *param,struct optee_call_ctx *call_ctx) {struct tee_device *teedev = ctx->teedev;struct optee *optee = tee_get_drvdata(teedev);struct tee_shm *shm;phys_addr_t pa;switch (OPTEE_SMC_RETURN_GET_RPC_FUNC(param->a0)) {case OPTEE_SMC_RPC_FUNC_ALLOC:shm = tee_shm_alloc(ctx, param->a1, TEE_SHM_MAPPED);if (!IS_ERR(shm) && !tee_shm_get_pa(shm, 0, &pa)) {reg_pair_from_64(¶m->a1, ¶m->a2, pa);reg_pair_from_64(¶m->a4, ¶m->a5,(unsigned long)shm);} else {param->a1 = 0;param->a2 = 0;param->a4 = 0;param->a5 = 0;}break;case OPTEE_SMC_RPC_FUNC_FREE:shm = reg_pair_to_ptr(param->a1, param->a2);tee_shm_free(shm);break;case OPTEE_SMC_RPC_FUNC_FOREIGN_INTR: //---看下面的英文注釋吧,如果是中斷切過來的,啥都不干/** A foreign interrupt was raised while secure world was* executing, since they are handled in Linux a dummy RPC is* performed to let Linux take the interrupt through the normal* vector.*/break;case OPTEE_SMC_RPC_FUNC_CMD:shm = reg_pair_to_ptr(param->a1, param->a2);handle_rpc_func_cmd(ctx, optee, shm, call_ctx);break;default:pr_warn("Unknown RPC func 0x%x\n",(u32)OPTEE_SMC_RETURN_GET_RPC_FUNC(param->a0));break;}param->a0 = OPTEE_SMC_CALL_RETURN_FROM_RPC; }接下來步驟6 :再次回到了ATF, 進(jìn)入ATF的opteed_smc_handler()函數(shù)中,然后將optee_vectors->fast_smc_entry賦值給ELR_EL3,然后ERET退出ATF,跳轉(zhuǎn)到optee中線程向量表的fast_smc_entry中
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);/** 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.*/if (GET_SMC_TYPE(smc_fid) == SMC_TYPE_FAST) {cm_set_elr_el3(SECURE, (uint64_t)&optee_vectors->fast_smc_entry); // linux處理完中斷再回TEE時,走這里,將fast_smc_entry地址賦給了ELR_EL3, fast_smc_entry地址是optee中// fast_smc_entry函數(shù)的地址,是optee開機(jī)初始化時,傳過來的,然后ATF保存到全局變量中了。}最后,在optee的線程向量表的fast_smc_entry向量中,將恢復(fù)optee之前進(jìn)程的寄存器和PC值,至此整個中斷處理 流程完成。
總結(jié)
以上是生活随笔為你收集整理的optee运行时来了一个REE(linux)中断--代码导读的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: optee返回REE的几种方式
- 下一篇: [register]-ARMV8系统中通