在REE侧的CA执行open session成功之后,CA就可以使用获取到的session和command ID调用TEEC_InvokeCommand接口来实现让TA指定特定的command的操作。在REE侧调用TEEC_InvokeCommand接口之后,该函数会将调用时带入的session变量,command ID,以及需要传递給TA的参数信息通过ioctl的系统调用发送到OP-TEE的驱动中,驱动最终会调用optee_invoke_func函数会将需要传递給TA的参数信息保存在共享内存中,并触发smc操作,切换到monitor模式进行secure world端的处理,invoke command操作的smc请求最终会被作为标准smc(std smc)进行解析并建立一个专门的thread进入thread_std_smc_entry函数执行,线程运行到tee_entry_std函数时会对smc请求做出判定进入invoke command分支。出发smc之后在secure world端的完成执行流程如下图所示:
1. 切换到userspace运行
线程会调用session中注册的ops成员中的enter_invoke_cmd指定的函数来处理invoke command操作,而enter_invoke_cmd指向user_ta_enter_invoke_cmd函数,该函数会调用user_ta_enter执行,在user_ta_enter函数中加载userspace的上下文然后调用__thread_enter_user_mode函数将该线程切换到userspace继续执行,该函数是以汇编的方式实现,其内容如下:
FUNC __thread_enter_user_mode , :
UNWIND( .fnstart)
UNWIND( .cantunwind)
/*
* Save all registers to allow syscall_return() to resume execution
* as if this function would have returned. This is also used in
* syscall_panic().
*
* If stack usage of this function is changed
* thread_unwind_user_mode() has to be updated.
*/
push {r4-r12,lr} //保存r4~r12和lr寄存器中的值
/* 将用户栈地址保存到r4寄存去中 */
ldr r4, [sp, #(10 * 0x4)] /* user stack pointer */
/* 将user的入口功能函数的地址保存到r5寄存器中 */
ldr r5, [sp, #(11 * 0x4)] /* user function */
/* 将spsr的数据存放到r6寄存器中 */
ldr r6, [sp, #(12 * 0x4)] /* spsr */
/*
* Set the saved Processors Status Register to user mode to allow
* entry of user mode through movs below.
*/
msr spsr_cxsf, r6 //设置状态保存寄存器,允许记录用户模式
/*
* Save old user sp and set new user sp.
*/
cps #CPSR_MODE_SYS //进入SYS模式
mov r6, sp //保存sys模式的sp值到r6
mov sp, r4 //将user的sp指针保存到sp中
cps #CPSR_MODE_SVC //切换到SVC模式
push {r6,r7}
/*
* Don't allow return from this function, return is done through
* thread_unwind_user_mode() below.
*/
mov lr, #0
/* Call the user function with its arguments */
movs pc, r5 //将跳转地址存放到pc中下一指令就会跳转到指定的user functon执行
UNWIND( .fnend)
movs pc, r5是将参数中指定的user function的地址赋值给pc,下一条指令就会跳转到pc执行的地址继续运行,sp寄存器也被赋值成了userspace的栈指针,且spsr也被设置成了传入的r6寄存器的数据,r6存放的是spsr数据,该数据在调用__thread_enter_user_mode 之前通过执行get_spsr函数被置成user模式的spsr。所以完成了pc指针的赋值之后就能够直接进入到userspace中运行指定的user function.
2. user function的值
user function的值就是在调用thread_enter_user_mode传入的utc->entry_func指定的函数地址。该地址在Open session的操作是调用ta_load的时候被赋值成ta_head->entry.ptr64。代码如下
utc->entry_func = ta_head->entry.ptr64;
而ta_head即指向与uuid想对应的TA image中的ta_head段中的内容,而ta_head段中存放的内容可从user_ta_head.c文件中查看到,如下:
const struct ta_head ta_head __section(".ta_head") = {
/* UUID, unique to each TA */
.uuid = TA_UUID, //该TA的UUID值
/*
* According to GP Internal API, TA_FRAMEWORK_STACK_SIZE corresponds to
* the stack size used by the TA code itself and does not include stack
* space possibly used by the Trusted Core Framework.
* Hence, stack_size which is the size of the stack to use,
* must be enlarged
*/
/* 设定该TA在userspace栈的大小 */
.stack_size = TA_STACK_SIZE + TA_FRAMEWORK_STACK_SIZE,
.flags = TA_FLAG_USER_MODE | TA_FLAGS, //设定flag
#ifdef __ILP32__
/*
* This workaround is neded on 32-bit because it seems we can't
* initialize a 64-bit integer from the address of a function.
*/
.entry.ptr32 = { .lo = (uint32_t)__utee_entry }, //指定TA的entry fuctiong函数指针
#else
.entry.ptr64 = (uint64_t)__utee_entry,
#endif
};
为兼容64位系统,在传入user function的时候使用的是ptr64。也就是说在thread_enter_user_mode函数中传入到__thread_enter_user_mode 函数中的entry_func的值是__utee_entry函数的地址。由此可见调用完__thread_enter_user_mode之后程序是进入到__utee_entry函数继续执行。__utee_entry函数的内容如下:
void __noreturn __utee_entry(unsigned long func, unsigned long session_id,
struct utee_params *up, unsigned long cmd_id)
{
TEE_Result res;
/* 根据传入的func值来确定进入那个分支 */
switch (func) {
/* 在user space中处理open session操作 */
case UTEE_ENTRY_FUNC_OPEN_SESSION:
res = entry_open_session(session_id, up);
break;
/* 在user space中处理close session操作 */
case UTEE_ENTRY_FUNC_CLOSE_SESSION:
res = entry_close_session(session_id);
break;
/* 在user space中处理invoke command操作 */
case UTEE_ENTRY_FUNC_INVOKE_COMMAND:
res = entry_invoke_command(session_id, up, cmd_id);
break;
default:
res = 0xffffffff;
TEE_Panic(0);
break;
}
ta_header_save_params(0, NULL);
utee_return(res);
}
3. user space中的entry_invoke_command
在OP-TEE中定义了两个entry_invoke_command函数,一个是kernel space的函数,一个是user space的函数。当程序运行到user space后则会调用user space的entry_invoke_command函数执行invoke command的操作。user space的entry_invoke_command函数定义在optee_os/lib/libutee/arch/arm/user_ta_entry.c文件中,该函数内容如下:
static TEE_Result entry_invoke_command(unsigned long session_id,
struct utee_params *up, unsigned long cmd_id)
{
TEE_Result res;
uint32_t param_types;
TEE_Param params[TEE_NUM_PARAMS];
struct ta_session *session = ta_header_get_session(session_id);
if (!session)
return TEE_ERROR_BAD_STATE;
/* 检查传入到user space的参数是否合法 */
__utee_to_param(params, ¶m_types, up);
ta_header_save_params(param_types, params);
/* 调用TA的TA_InvokeCommandEntryPoint函数 */
res = TA_InvokeCommandEntryPoint(session->session_ctx, cmd_id,
param_types, params);
__utee_from_param(up, param_types, params);
return res;
}
调用到user space的entry_invoke_command函数的时候,其实线程已经进入到了TA image上下文中运行了,所以当调用TA_InvokeCommandEntryPoint函数的时候就会去执行TA image中定义的TA_InvokeCommandEntryPoint函数,而该函数具体会执行什么操作就由特定的TA决定了,一般做法是根据command id的值去执行特定的操作