嵌入式开发论坛

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 71|回复: 0

32. secure world对smc请求的处理------open session操作在OP-TEE中的...

[复制链接]

59

主题

62

帖子

249

积分

版主

Rank: 7Rank: 7Rank: 7

积分
249
发表于 2018-11-25 14:28:23 | 显示全部楼层 |阅读模式
session是CA调用TA的基础,如果CA没与TA之间没有建立session,那么CA就无法调用TA中的任何command。在libteec中通过执行TEEC_OpenSession函数来建议CA与特定TA之间的session的操作,该函数执行时会调用到OP-TEE驱动中的optee_open_session函数通知OP-TEE开始执行创建session的操作。在OP-TEE中的TEE端一次对动态TA完整的open session操作的流程如下图所示:

  OP-TEE支持动态TA和静态TA。即可以将TA镜像与OP-TEE的镜像编译在同一image中,只是静态TA镜像会存放在OP-TEE镜像的特定分区中。在OP-TEE启动的时候会被加载到属性为MEM_AREA_TA_RAM的安全内存中。而动态TA的方式则是将TA镜像文件保存到文件系统中,在打开session的时候再通过RPC请求加载到OP-TEE的安全内存中。open session在OP-TEE中的操作都是根据UUID值找到对应的TA镜像,然后读取TA image head部分的数据,并将相关数据保存到tee_ta_ctx结构体变量中,然后将填充好的tee_ta_ctx结构体变量保存到tee_ctxes链表中,以便后期CA执行invoke操作来调用TA中的command的时候可以通过查找tee_ctxes链表来获取对应的session,然后根据session的内容进入到TA中根据command ID执行特定的command操作。由于该部分代码量较大,故不对每一步的代码进行讲解,只对比较迷惑或者关键的地方进行介绍。详细内容可以结合上图和实际代码进行了解。
1. 静态TA的open session操作  由于静态的TA是与OP-TEE OS镜像编译在一起,在OP-TEE的启动阶段该,静态TA镜像的内容会被加载OP-TEE的安全内存中,而且在启动过程中会调用verify_pseudo_tas_conformance函数对所有的静态TA镜像的内容进行检查。
  调用Open session操作后,OP-TEE首先会在已经被打开的session链表中查找是否有匹配,如果匹配则将该session的ID直接返回给REE侧,如果没有找到则会根据UUID去静态TA的段中进行查找,然后将找到的静态TA的相关信息填充到tee_ta_ctx结构体变量中,然后在添加到全局的tee_ctxes链表中。该函数内容如下:
  1. TEE_Result tee_ta_init_pseudo_ta_session(const TEE_UUID *uuid,
  2.                         struct tee_ta_session *s)
  3. {
  4.         struct pseudo_ta_ctx *stc = NULL;
  5.         struct tee_ta_ctx *ctx;
  6.         const struct pseudo_ta_head *ta;

  7.         DMSG("   Lookup for pseudo TA %pUl", (void *)uuid);

  8. /* 获取静态TA的head的起始地址 */
  9.         ta = &__start_ta_head_section;

  10. /* 进入到loop循环,遍历整个段,根据UUID是否匹配来判定在静态TA的head段中是否有
  11. 相应的TA */
  12.         while (true) {
  13.                 if (ta >= &__stop_ta_head_section)
  14.                         return TEE_ERROR_ITEM_NOT_FOUND;
  15.                 if (memcmp(&ta->uuid, uuid, sizeof(TEE_UUID)) == 0)
  16.                         break;
  17.                 ta++;
  18.         }

  19.         /* Load a new TA and create a session */
  20.         DMSG("      Open %s", ta->name);
  21. /* 分配存放pseudo_ta_ctx结构体变量的内存空间 */
  22.         stc = calloc(1, sizeof(struct pseudo_ta_ctx));
  23.         if (!stc)
  24.                 return TEE_ERROR_OUT_OF_MEMORY;
  25.         ctx = &stc->ctx;

  26. /* 填充数据,组合ctx变量,并将TA的operation的变量指针保存到ctx->ops中 */
  27.         ctx->ref_count = 1;
  28.         s->ctx = ctx;
  29.         ctx->flags = ta->flags;
  30.         stc->pseudo_ta = ta;
  31.         ctx->uuid = ta->uuid;
  32.         ctx->ops = &pseudo_ta_ops;
  33.         TAILQ_INSERT_TAIL(&tee_ctxes, ctx, link);

  34.         DMSG("      %s : %pUl", stc->pseudo_ta->name, (void *)&ctx->uuid);

  35.         return TEE_SUCCESS;
  36. }
复制代码

2. 动态TA的open session操作
  动态的TA image会被保存在REE侧的文件系统中。CA在执行动态TA的open session操作的时候,OP-TEE会根据UUID值使用RPC机制借助tee_supplicant来完成将动态TA image加载到OP-TEE的内存中的操作,并获取加载到内存中的动态TA的相关信息,将这些信息填充到tee_ta_ctx结构体变量中,然后在添加到全局的tee_ctxes链表中。以便后续在CA端调用invoke操作直接根据session ID值在链表中找到该session.加载动态TA的过程可参考开篇的那张图。
2.1 RPC请求的触发  在动态加载TA image的过程中是通过thread_rpc_cmd函数来触发RPC请求,借助tee_supplicant来完成TA image内容的读取和传输。该函数内容如下:
  1. uint32_t thread_rpc_cmd(uint32_t cmd, size_t num_params,
  2.                         struct optee_msg_param *params)
  3. {
  4.         uint32_t rpc_args[THREAD_RPC_NUM_ARGS] = { OPTEE_SMC_RETURN_RPC_CMD };
  5.         struct optee_msg_arg *arg;
  6.         uint64_t carg;
  7.         size_t n;
  8.         struct optee_msg_param *arg_params;

  9.         /*
  10.          * Break recursion in case plat_prng_add_jitter_entropy_norpc()
  11.          * sleeps on a mutex or unlocks a mutex with a sleeper (contended
  12.          * mutex).
  13.          */
  14.         if (cmd != OPTEE_MSG_RPC_CMD_WAIT_QUEUE)
  15.                 plat_prng_add_jitter_entropy_norpc();

  16. /* 获取需要通过RPC机制发送到REE侧的参数内容 */
  17.         if (!get_rpc_arg(cmd, num_params, &arg, &carg, &arg_params))
  18.                 return TEE_ERROR_OUT_OF_MEMORY;

  19. /* 拷贝操作 */
  20.         memcpy(arg_params, params, sizeof(*params) * num_params);

  21. /* 转换成64位,以便兼容64位系统 */
  22.         reg_pair_from_64(carg, rpc_args + 1, rpc_args + 2);
  23. /* 发送RPC请求,触发smc和suspend当前thread */
  24.         thread_rpc(rpc_args);
  25.         for (n = 0; n < num_params; n++) {
  26.                 switch (params[n].attr & OPTEE_MSG_ATTR_TYPE_MASK) {
  27.                 case OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT:
  28.                 case OPTEE_MSG_ATTR_TYPE_VALUE_INOUT:
  29.                 case OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT:
  30.                 case OPTEE_MSG_ATTR_TYPE_RMEM_INOUT:
  31.                 case OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT:
  32.                 case OPTEE_MSG_ATTR_TYPE_TMEM_INOUT:
  33.                         params[n] = arg_params[n];
  34.                         break;
  35.                 default:
  36.                         break;
  37.                 }
  38.         }
  39.         return arg->ret;
  40. }
复制代码

  RPC请求是如何发送出去的呢,发送RPC请求的时候做了哪些操作呢?这就得看thread_rpc函数做了哪些操作了,该函数是以汇编的方式实现的。
  1. FUNC thread_rpc , :
  2. /*
  3. * r0-r2 are used to pass parameters to normal world
  4. * r0-r5 are used to pass return vaule back from normal world
  5. *
  6. * note that r3 is used to pass "resume information", that is, which
  7. * thread it is that should resume.
  8. *
  9. * Since the this function is following AAPCS we need to preserve r4-r5
  10. * which are otherwise modified when returning back from normal world.
  11. */
  12. UNWIND(        .fnstart)
  13.         push        {r4-r5, lr}
  14. UNWIND(        .save        {r4-r5, lr})
  15.         push        {r0}
  16. UNWIND(        .save        {r0})

  17.         bl        thread_save_state //保存状态
  18.         mov        r4, r0                        /* Save original CPSR */

  19.         /*
  20.          * Switch to temporary stack and SVC mode. Save CPSR to resume into.
  21.          */
  22.         bl        thread_get_tmp_sp        //获取tmp栈空间
  23.         ldr        r5, [sp]                /* Get pointer to rv[] */
  24.         cps        #CPSR_MODE_SVC                /* Change to SVC mode */
  25.         mov        sp, r0                        /* Switch to tmp stack */

  26.         mov        r0, #THREAD_FLAGS_COPY_ARGS_ON_RETURN
  27.         mov        r1, r4                        /* CPSR to restore */
  28.         ldr        r2, =.thread_rpc_return        //thred_rpc_return为当前线程resume回来之后的PC值
  29.         bl        thread_state_suspend        //suspend当前线程
  30.         mov        r4, r0                        /* Supply thread index */
  31.         ldr        r0, =TEESMC_OPTEED_RETURN_CALL_DONE
  32.         ldm        r5, {r1-r3}                /* Load rv[] into r0-r2 */       
  33.         smc        #0        //触发smc操作,切回到REE侧
  34.         b        .        /* SMC should not return */

  35. .thread_rpc_return:
  36.         /*
  37.          * At this point has the stack pointer been restored to the value
  38.          * it had when thread_save_state() was called above.
  39.          *
  40.          * Jumps here from thread_resume above when RPC has returned. The
  41.          * IRQ and FIQ bits are restored to what they where when this
  42.          * function was originally entered.
  43.          */
  44.         pop        {r12}                        /* Get pointer to rv[] */
  45.         stm        r12, {r0-r5}                /* Store r0-r5 into rv[] */
  46.         pop        {r4-r5, pc}
  47. UNWIND(        .fnend)
  48. END_FUNC thread_rpc
复制代码

  调用thread_rpc 函数会将调用的thread挂起,然后触发smc操作,切回到REE侧,让tee_supplicant来进行动态TA image的读取,然后再次通过RPC类型的smc请求将数据发送给OP-TEE。在一个完整的动态TA image的加载过程中对多次调用thread_rpc_cmd函数来发送RPC请求通过tee_supplican来从文件系统中获取不同的数据信息。

2.2 thread的resume
  发送RPC请求到REE侧的时候会将当前thread suspend。等OP-TEE接收到来自REE侧的请求处理结果后会将该thread重新resume。当在REE侧处理完请求之后,驱动会发送一个RPC类型的smc操作,该请求最终会调用thread_resume_from_rpc来进行处理。该函数内容如下:
  1. static void thread_resume_from_rpc(struct thread_smc_args *args)
  2. {
  3.         size_t n = args->a3; /* thread id */        //获取需要被resume的thread ID       
  4.         struct thread_core_local *l = thread_get_core_local();
  5.         uint32_t rv = 0;

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

  7.         lock_global();

  8. /* 设定需要被唤醒的thread的状态变量 */
  9.         if (n < CFG_NUM_THREADS &&
  10.             threads[n].state == THREAD_STATE_SUSPENDED &&
  11.             args->a7 == threads[n].hyp_clnt_id)
  12.                 threads[n].state = THREAD_STATE_ACTIVE;
  13.         else
  14.                 rv = OPTEE_SMC_RETURN_ERESUME;

  15.         unlock_global();

  16.         if (rv) {
  17.                 args->a0 = rv;
  18.                 return;
  19.         }

  20. /* 指定当前thread的ID值 */
  21.         l->curr_thread = n;

  22. /* 判定需要被唤醒的线程是否属处于user态 */
  23.         if (is_user_mode(&threads[n].regs))
  24.                 tee_ta_update_session_utime_resume();

  25. /* 判定需要被唤醒的线程是否做了OP-TEE的userspace的内存映射 */
  26.         if (threads[n].have_user_map)
  27.                 core_mmu_set_user_map(&threads[n].user_map);

  28.         /*
  29.          * Return from RPC to request service of a foreign interrupt must not
  30.          * get parameters from non-secure world.
  31.          */
  32.         if (threads[n].flags & THREAD_FLAGS_COPY_ARGS_ON_RETURN) {
  33.                 copy_a0_to_a5(&threads[n].regs, args);                //获取来自REE的回复数据
  34.                 threads[n].flags &= ~THREAD_FLAGS_COPY_ARGS_ON_RETURN;
  35.         }

  36.         thread_lazy_save_ns_vfp();
  37.         thread_resume(&threads[n].regs);        //执行线程的resume操作
  38. }
复制代码
最终通过调用thread_resume函数来讲线程唤醒,该函数的内容如下:
  1. FUNC thread_resume , :
  2. UNWIND(        .fnstart)
  3. UNWIND(        .cantunwind)
  4.         add        r12, r0, #(13 * 4)        /* Restore registers r0-r12 later */

  5. /* 切换到sys模式并保存相关寄存器 */
  6.         cps        #CPSR_MODE_SYS
  7.         ldm        r12!, {sp, lr}

  8. /* 切换到svc模式并保存相关寄存器 */
  9.         cps        #CPSR_MODE_SVC
  10.         ldm        r12!, {r1, sp, lr}
  11.         msr        spsr_fsxc, r1

  12. /* 切换到SVC模式,并执行出栈操作 */
  13.         cps        #CPSR_MODE_SVC
  14.         ldm        r12, {r1, r2}
  15.         push        {r1, r2}

  16.         ldm        r0, {r0-r12}

  17.         /* Restore CPSR and jump to the instruction to resume at */
  18.         rfefd        sp!        //执行线程并返回
  19. UNWIND(        .fnend)
  20. END_FUNC thread_resume
复制代码

  如果线程是在执行openssion通过调用thread_resume_from_rpc函数被挂起的,那么执行完thread_resume函数之后就会去执行thread_rpc_return段的代码执行出栈等操作后返回到thread_rpc_cmd函数继续执行,完成剩下的opensession操作。

总结
  如果TA image是被存放在文件系统中,即该TA为动态TA,那么在执行open session操作的时候需要通过RPC机制,借助tee_supplicant将TA image加载到OP-TEE中。在这个过程中会有多次的normal world和secure world的切换,并且有多次的RPC请求。






回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|TEE and Virtualization

GMT+8, 2019-3-19 00:18 , Processed in 0.083833 second(s), 19 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表