baron · 3月28日 · 四川

optee对std smc的处理的详解

快速连接

👉👉👉【精选】ARMv8/ARMv9架构入门到精通-目录 👈👈👈


 title=

std smc跳转到optee的场景有两种:

  • REE主动发起std smc的调用,基本就算Globalplatform Client
    API(libteec.so)的调用,也就是openssion\invoke\closession等函数的调用;
  • Optee中发起了RPC反向调用REE后,从REE再切回来是,也是走std call流程

1、ATF跳转到optee的std_smc_entry

当REE发起std smc调用后,跳转到ATF后,ATF根据smc_fid的type判定该smc属于STD,则设置ELR寄存器的值为std_smc_entry(在ATF中保存着一份和optee中同样的线程向量表),那么当ATF ERET返回时,程序跳转到ELR中的地址处,也就optee中所定义的线程向量表中的std_smc_entry

uint64_t opteed_smc_handler(uint32_t smc_fid,
             uint64_t x1,
             uint64_t x2,
             uint64_t x3,
             uint64_t x4,
             void *cookie,
             void *handle,
             uint64_t flags)
{
......
        if (GET_SMC_TYPE(smc_fid) == SMC_TYPE_FAST) {
            cm_set_elr_el3(SECURE, (uint64_t)
                    &optee_vectors->fast_smc_entry);
        } else {
            cm_set_elr_el3(SECURE, (uint64_t)
                    &optee_vectors->std_smc_entry);  //------------------设置ELR寄存器
        }
......
        SMC_RET4(&optee_ctx->cpu_ctx, smc_fid, x1, x2, x3); //-------------ERET返回,程序跳转到ELR中的地址
    }

2、optee中的std_smc_entry流程

LOCAL_FUNC vector_std_smc_entry , :
    sub    sp, sp, #THREAD_SMC_ARGS_SIZE
    store_xregs sp, THREAD_SMC_ARGS_X0, 0, 7
    mov    x0, sp
    bl    thread_handle_std_smc  //------------------------------std业务逻辑的处理
    /*
     * Normally thread_handle_std_smc() should return via
     * thread_exit(), thread_rpc(), but if thread_handle_std_smc()
     * hasn't switched stack (error detected) it will do a normal "C"
     * return.
     */
    load_xregs sp, THREAD_SMC_ARGS_X0, 1, 8
    add    sp, sp, #THREAD_SMC_ARGS_SIZE
    ldr    x0, =TEESMC_OPTEED_RETURN_CALL_DONE
    smc    #0       //--------------------------------std业务处理完成后,再次将cpu切回REE
    b    .    /* SMC should not return */

如果是std call则调用thread_alloc_and_run,如果是RPC回来的(RPC回来也是走的std smc)则走thread_resume_from_rpc(args)

void thread_handle_std_smc(struct thread_smc_args *args)
{
    thread_check_canaries();

    if (args->a0 == OPTEE_SMC_CALL_RETURN_FROM_RPC)
        thread_resume_from_rpc(args);
    else
        thread_alloc_and_run(args);
}
(1)、RPC回来的std call的处理

thread_resume_from_rpc中,直接调用了thread_resume

static void thread_resume_from_rpc(struct thread_smc_args *args)
{
    size_t n = args->a3; /* thread id */
    struct thread_core_local *l = thread_get_core_local();
    uint32_t rv = 0;

    assert(l->curr_thread == -1);

    lock_global();

    if (n < CFG_NUM_THREADS &&
        threads[n].state == THREAD_STATE_SUSPENDED &&
        args->a7 == threads[n].hyp_clnt_id)
        threads[n].state = THREAD_STATE_ACTIVE;
    else
        rv = OPTEE_SMC_RETURN_ERESUME;

    unlock_global();

    if (rv) {
        args->a0 = rv;
        return;
    }

    l->curr_thread = n;

    if (is_user_mode(&threads[n].regs))
        tee_ta_update_session_utime_resume();

    if (threads[n].have_user_map)
        core_mmu_set_user_map(&threads[n].user_map);

    /*
     * Return from RPC to request service of a foreign interrupt must not
     * get parameters from non-secure world.
     */
    if (threads[n].flags & THREAD_FLAGS_COPY_ARGS_ON_RETURN) {
        copy_a0_to_a5(&threads[n].regs, args);
        threads[n].flags &= ~THREAD_FLAGS_COPY_ARGS_ON_RETURN;
    }

    thread_lazy_save_ns_vfp();
    thread_resume(&threads[n].regs);
}

而在thread_resume中,程序会通过eret返回,也就是跳转到ELR中的地址. 那么ELR中的值来在来自x2,x2又来自threads[n].regs结构体中的PC值

/* void thread_resume(struct thread_ctx_regs *regs) */
FUNC thread_resume , :
    load_xregs x0, THREAD_CTX_REGS_SP, 1, 3   //-------------------------x2等于struct thread_ctx_regs结构体中的pc值
    load_xregs x0, THREAD_CTX_REGS_X4, 4, 30
    mov    sp, x1
    msr    elr_el1, x2   //-------------------------将x2赋值到elr寄存器
    msr    spsr_el1, x3

    b_if_spsr_is_el0 w3, 1f

    load_xregs x0, THREAD_CTX_REGS_X1, 1, 3
    ldr    x0, [x0, THREAD_CTX_REGS_X0]
    eret   //------------------------------------eret返回,则跳转到elr中的地址

1:    load_xregs x0, THREAD_CTX_REGS_X1, 1, 3
    ldr    x0, [x0, THREAD_CTX_REGS_X0]

    msr    spsel, #1
    store_xregs sp, THREAD_CORE_LOCAL_X0, 0, 1
    b    eret_to_el0
END_FUNC thread_resume

那么threads[n].regs结构体中的PC值又来自哪里呢?
在optee发起RPC调用时,将RPC调用从REE回来后,需要接着运行的地址,写入到了x2中,然后又保存到了struct thread_ctx_regs结构体中

/* void thread_rpc(uint32_t rv[THREAD_RPC_NUM_ARGS]) */
FUNC thread_rpc , :
    /* Read daif and create an SPSR */
    mrs    x1, daif
    orr    x1, x1, #(SPSR_64_MODE_EL1 << SPSR_64_MODE_EL_SHIFT)

    /* Mask all maskable exceptions before switching to temporary stack */
    msr    daifset, #DAIFBIT_ALL
    push    x0, xzr
    push    x1, x30
    bl    thread_get_ctx_regs
    ldr    x30, [sp, #8]
    store_xregs x0, THREAD_CTX_REGS_X19, 19, 30
    mov    x19, x0

    bl    thread_get_tmp_sp
    pop    x1, xzr        /* Match "push x1, x30" above */
    mov    x2, sp
    str    x2, [x19, #THREAD_CTX_REGS_SP]
    ldr    x20, [sp]    /* Get pointer to rv[] */
    mov    sp, x0        /* Switch to tmp stack */

    adr    x2, .thread_rpc_return     //------------------------等RPC调用从REE再回来时,接着执行thread_rpc_return
    mov    w0, #THREAD_FLAGS_COPY_ARGS_ON_RETURN
    bl    thread_state_suspend
    mov    x4, x0        /* Supply thread index */
    ldr    w0, =TEESMC_OPTEED_RETURN_CALL_DONE
    load_wregs x20, 0, 1, 3    /* Load rv[] into w0-w2 */
    smc    #0    //-----------------------------------------------RPC调用,这里要切到REE了
    b    .        /* SMC should not return */

.thread_rpc_return:      //---------------------------------------------等RPC调用从REE再回来时,接着执行thread_rpc_return
    /*
     * At this point has the stack pointer been restored to the value
     * stored in THREAD_CTX above.
     *
     * Jumps here from thread_resume above when RPC has returned. The
     * IRQ and FIQ bits are restored to what they where when this
     * function was originally entered.
     */
    pop    x16, xzr    /* Get pointer to rv[] */
    store_wregs x16, 0, 0, 5    /* Store w0-w5 into rv[] */
    ret
END_FUNC thread_rpc
KEEP_PAGER thread_rpc
(2)、REE调用的std call的处理 : thread_std_smc_entry

thread_alloc_and_run函数中,先调用init_regs(threads + n, args), 然后再调用thread_resume(&threads[n].regs)

static void thread_alloc_and_run(struct thread_smc_args *args)
{
    size_t n;
    struct thread_core_local *l = thread_get_core_local();
    bool found_thread = false;

    assert(l->curr_thread == -1);

    lock_global();

    for (n = 0; n < CFG_NUM_THREADS; n++) {
        if (threads[n].state == THREAD_STATE_FREE) {
            threads[n].state = THREAD_STATE_ACTIVE;
            found_thread = true;
            break;
        }
    }

    unlock_global();

    if (!found_thread) {
        args->a0 = OPTEE_SMC_RETURN_ETHREAD_LIMIT;
        return;
    }

    l->curr_thread = n;

    threads[n].flags = 0;
    init_regs(threads + n, args);

    /* Save Hypervisor Client ID */
    threads[n].hyp_clnt_id = args->a7;

    thread_lazy_save_ns_vfp();
    thread_resume(&threads[n].regs);
}

而在init_regs中,将thread_std_smc_entry函数的地址,写入到了struct thread_ctx_regs结构体中的pc变量中
thread->regs.pc = (uint64_t)thread_std_smc_entry;

#ifdef ARM64
static void init_regs(struct thread_ctx *thread,
        struct thread_smc_args *args)
{
    thread->regs.pc = (uint64_t)thread_std_smc_entry;

    /*
     * Stdcalls starts in SVC mode with masked foreign interrupts, masked
     * Asynchronous abort and unmasked native interrupts.
     */
    thread->regs.cpsr = SPSR_64(SPSR_64_MODE_EL1, SPSR_64_MODE_SP_EL0,
                THREAD_EXCP_FOREIGN_INTR | DAIFBIT_ABT);
    /* Reinitialize stack pointer */
    thread->regs.sp = thread->stack_va_end;

    /*
     * Copy arguments into context. This will make the
     * arguments appear in x0-x7 when thread is started.
     */
    thread->regs.x[0] = args->a0;
    thread->regs.x[1] = args->a1;
    thread->regs.x[2] = args->a2;
    thread->regs.x[3] = args->a3;
    thread->regs.x[4] = args->a4;
    thread->regs.x[5] = args->a5;
    thread->regs.x[6] = args->a6;
    thread->regs.x[7] = args->a7;

    /* Set up frame pointer as per the Aarch64 AAPCS */
    thread->regs.x[29] = 0;
}
#endif /*ARM64*/

而在thread_resume中,将struct thread_ctx_regs结构体中的pc值赋给了x2, x2又赋给了ELR.
thread_resume程序返回后,就将执行ELR执行的地址,也就是对应着thread_std_smc_entry函数

/* void thread_resume(struct thread_ctx_regs *regs) */
FUNC thread_resume , :
    load_xregs x0, THREAD_CTX_REGS_SP, 1, 3   //-------------------------x2等于struct thread_ctx_regs结构体中的pc值
    load_xregs x0, THREAD_CTX_REGS_X4, 4, 30
    mov    sp, x1
    msr    elr_el1, x2   //-------------------------将x2赋值到elr寄存器
    msr    spsr_el1, x3

    b_if_spsr_is_el0 w3, 1f

    load_xregs x0, THREAD_CTX_REGS_X1, 1, 3
    ldr    x0, [x0, THREAD_CTX_REGS_X0]
    eret   //------------------------------------eret返回,则跳转到elr中的地址

1:    load_xregs x0, THREAD_CTX_REGS_X1, 1, 3
    ldr    x0, [x0, THREAD_CTX_REGS_X0]

    msr    spsel, #1
    store_xregs sp, THREAD_CORE_LOCAL_X0, 0, 1
    b    eret_to_el0
END_FUNC thread_resume

3、线程thread_std_smc_entry

当REE调用标准的std smc时,将进入optee中的thread_std_smc_entry程序,那么该程序都做了什么事情呢?

thread_std_smc_entry调用__thread_std_smc_entry,而__thread_std_smc_entry又会调用平台的std_smc_hander

void __weak __thread_std_smc_entry(struct thread_smc_args *args)
{
    thread_std_smc_handler_ptr(args);   //-------------------------------这里调用vendor厂商实现的std_smc_hander,在main.c中

    if (args->a0 == OPTEE_SMC_RETURN_OK) {
        struct thread_ctx *thr = threads + thread_get_id();

        tee_fs_rpc_cache_clear(&thr->tsd);
        if (!thread_prealloc_rpc_cache) {
            thread_rpc_free_arg(thr->rpc_carg);
            mobj_free(thr->rpc_mobj);
            thr->rpc_carg = 0;
            thr->rpc_arg = 0;
            thr->rpc_mobj = NULL;
        }
    }
}

而平台实现的std_smc_hander,其实都是指向entry_std.c中的std_smc_hander(意思就是平台可以自行实现,也可以用标准的entry_std.c中的)
从std_smc_hander的实现来看,std call也就是支持一些标准的命令,如open close invoke等

/*
 * Note: this function is weak just to make it possible to exclude it from
 * the unpaged area.
 */
void __weak tee_entry_std(struct thread_smc_args *smc_args)
{
    paddr_t parg;
    struct optee_msg_arg *arg = NULL;    /* fix gcc warning */
    uint32_t num_params = 0;        /* fix gcc warning */
    struct mobj *mobj;

    if (smc_args->a0 != OPTEE_SMC_CALL_WITH_ARG) {
        EMSG("Unknown SMC 0x%" PRIx64, (uint64_t)smc_args->a0);
        DMSG("Expected 0x%x\n", OPTEE_SMC_CALL_WITH_ARG);
        smc_args->a0 = OPTEE_SMC_RETURN_EBADCMD;
        return;
    }
    parg = (uint64_t)smc_args->a1 << 32 | smc_args->a2;

    /* Check if this region is in static shared space */
    if (core_pbuf_is(CORE_MEM_NSEC_SHM, parg,
              sizeof(struct optee_msg_arg))) {
        mobj = get_cmd_buffer(parg, &num_params);
    } else {
        if (parg & SMALL_PAGE_MASK) {
            smc_args->a0 = OPTEE_SMC_RETURN_EBADADDR;
            return;
        }
        mobj = map_cmd_buffer(parg, &num_params);
    }

    if (!mobj || !ALIGNMENT_IS_OK(parg, struct optee_msg_arg)) {
        EMSG("Bad arg address 0x%" PRIxPA, parg);
        smc_args->a0 = OPTEE_SMC_RETURN_EBADADDR;
        mobj_free(mobj);
        return;
    }

    arg = mobj_get_va(mobj, 0);
    assert(arg && mobj_is_nonsec(mobj));

    /* Enable foreign interrupts for STD calls */
    thread_set_foreign_intr(true);
    switch (arg->cmd) {
    case OPTEE_MSG_CMD_OPEN_SESSION:
        entry_open_session(smc_args, arg, num_params);
        break;
    case OPTEE_MSG_CMD_CLOSE_SESSION:
        entry_close_session(smc_args, arg, num_params);
        break;
    case OPTEE_MSG_CMD_INVOKE_COMMAND:
        entry_invoke_command(smc_args, arg, num_params);
        break;
    case OPTEE_MSG_CMD_CANCEL:
        entry_cancel(smc_args, arg, num_params);
        break;
    case OPTEE_MSG_CMD_REGISTER_SHM:
        register_shm(smc_args, arg, num_params);
        break;
    case OPTEE_MSG_CMD_UNREGISTER_SHM:
        unregister_shm(smc_args, arg, num_params);
        break;

    default:
        EMSG("Unknown cmd 0x%x\n", arg->cmd);
        smc_args->a0 = OPTEE_SMC_RETURN_EBADCMD;
    }
    mobj_free(mobj);
}

关注"Arm精选"公众号,备注进ARM交流讨论区。
图片1.png

推荐阅读
关注数
9437
内容数
191
以易懂、渐进、有序的方式,深入探讨ARMv8/ARMv9架构的核心概念。我们将从基础知识开始,逐步深入,覆盖最新的架构,不再纠缠于过时技术。本系列内容包含但不限于ARM基础、SOC芯片基础、Trustzone、gic、异常和中断、AMBA、Cache、MMU等内容,并将持续更新。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息