optee学习笔记_3
对上一篇的demo做个小结先:
- 在/dev目录下,会生成两个节点,一个是tee_supplican使用,tee0供libteec使用
- CA调用TEEC_InitializeContext后,在libteec中open tee0节点,并通过ioctl检查版本号等
- CA调用TEEC_OpenSession,然后经libteec,到达driver,并由driver调用smc指令转换至secure world,然后optee core根据UUID去寻找对应的TA,如TA文件是存在在REE侧的动态ta文件,则opteecore 会发起RPC请求,该请求会从secure world到达Normal World,由tee_supplicat进程响应请求,并去指定的位置load ta文件。然后再调用TA中的TA_OpenSessionEntryPoint函数。
- CA打开一个session之后,就可以调用TEEC_InvokeCommand函数,向TA发送command,该command会穿过libtee、driver、optee core,最终到达TA中的TA_InvokeCommandEntryPoint函数。在这里,用户可根据自己的实际需求解析command。
- 最终CA需要调用TEEC_OpenSession函数关闭当前session,在调用TEEC_FinalizeContext函数,释放tee0设备。
tee_supplicant
tee_supplicat作为REE侧的一个守护进程,主要是为了辅助optee core来访问REE侧的资源,因为optee core本身是不能直接访问REE侧资源的。如optee core要load TA文件,或进行安全存储,这都需要tee_supplicat提供帮助。
tee_supplicat的源码位置位于optee_client目录中,编译后会生成一个tee_supplicat可执行文件,在系统启动时,这一执行文件需作为后台进程启动。
下面是简化后tee_supplicat的main()函数(optee_client/tee_supplicat/src/tee_supplicat.c)
int main(int argc, char *argv[])
{
e = pthread_mutex_init(&arg.mutex, NULL);
if (dev) {
arg.fd = open_dev(dev, &arg.gen_caps);
if (arg.fd < 0) {
EMSG("failed to open \"%s\"", argv[1]);
exit(EXIT_FAILURE);
}
} else {
arg.fd = get_dev_fd(&arg.gen_caps);
if (arg.fd < 0) {
EMSG("failed to find an OP-TEE supplicant device");
exit(EXIT_FAILURE);
}
}
if (daemonize && daemon(0, 0) < 0) {
EMSG("daemon(): %s", strerror(errno));
exit(EXIT_FAILURE);
}
while (!arg.abort) {
if (!process_one_request(&arg))
arg.abort = true;
}
close(arg.fd);
return EXIT_FAILURE;
}
在这里可以看到,main函数中,首先打开了tee_priv0节点,然后就进入一个while无限循环,通过调用process_one_request函数来监控,接收,处理以及回复来自secure world的请求。
static bool process_one_request(struct thread_arg *arg)
{
request.recv.num_params = RPC_NUM_PARAMS;
/* Let it be known that we can deal with meta parameters */
params = (struct tee_ioctl_param *)(&request.send + 1);
params->attr = TEE_IOCTL_PARAM_ATTR_META;
num_waiters_inc(arg);
if (!read_request(arg->fd, &request))
return false;
switch (func) {
case OPTEE_MSG_RPC_CMD_LOAD_TA:
ret = load_ta(num_params, params);
break;
case OPTEE_MSG_RPC_CMD_FS:
ret = tee_supp_fs_process(num_params, params);
break;
case OPTEE_MSG_RPC_CMD_RPMB:
ret = process_rpmb(num_params, params);
break;
case OPTEE_MSG_RPC_CMD_SHM_ALLOC:
ret = process_alloc(arg, num_params, params);
break;
case OPTEE_MSG_RPC_CMD_SHM_FREE:
ret = process_free(num_params, params);
break;
case OPTEE_MSG_RPC_CMD_GPROF:
ret = prof_process(num_params, params, "gmon-");
break;
case OPTEE_MSG_RPC_CMD_SOCKET:
ret = tee_socket_process(num_params, params);
break;
case OPTEE_MSG_RPC_CMD_FTRACE:
ret = prof_process(num_params, params, "ftrace-");
break;
default:
EMSG("Cmd [0x%" PRIx32 "] not supported", func);
/* Not supported. */
ret = TEEC_ERROR_NOT_SUPPORTED;
break;
}
request.send.ret = ret;
return write_response(arg->fd, &request);
}
在process_one_request函数中,首先通过read_quest函数中的ioctl(TEE_IOC_SUPPL_RECV)接收请求,TEE_IOC_SUPPL_RECV操作将会阻塞等待来自secure World的请求。
当接收到一个请求之后,则会调用相应的函数进行处理,主要的RPC请求以下几种:
OPTEE_MSG_RPC_CMD_LOAD_TA:
OPTEE_MSG_RPC_CMD_FS:
OPTEE_MSG_RPC_CMD_RPMB:
OPTEE_MSG_RPC_CMD_SHM_ALLOC:
OPTEE_MSG_RPC_CMD_SHM_FREE:
OPTEE_MSG_RPC_CMD_GPROF:
OPTEE_MSG_RPC_CMD_SOCKET:
OPTEE_MSG_RPC_CMD_FTRACE:
TEE驱动
linux kernel的source code中已经自带了tee驱动,位置:driver/tee
整个目录结构如下:
optee
整个tee驱动,主要是通过subsys_initcall和module_init宏来告诉系统什么时候加载tee驱动
首先是subsys_initcall(tee_init);
在tee_init函数中,主要完成了class的创建和设备号的分配
tee_class = class_create(THIS_MODULE, "tee");
rc = alloc_chrdev_region(&tee_devt, 0, TEE_NUM_DEVICES, "tee");
rc = bus_register(&tee_bus_type);
然后是在core.c中,通过module_init(optee_driver_init)我们可以看到,入口是optee_driver_init函数
static int __init optee_driver_init(void)
{
/* Node is supposed to be below /firmware */
fw_np = of_find_node_by_name(NULL, "firmware");
if (!fw_np)
return -ENODEV;
np = of_find_matching_node(fw_np, optee_match);
if (!np || !of_device_is_available(np)) {
of_node_put(np);
return -ENODEV;
}
optee = optee_probe(np);
of_node_put(np);
optee_svc = optee;
}
在optee_driver_init函数中,首先遍历device tree节点,看是否支持TrustZone,然后调用probe函数,在probe函数中,主要做了以下工作:
- 通过get_invoke_func(np);函数,获取smc指令
- 版本检查
- 分配了一个optee,并分配和注册了两个device放在optee中
optee = kzalloc(sizeof(*optee), GFP_KERNEL);
optee->invoke_fn = invoke_fn;
optee->sec_caps = sec_caps;
teedev = tee_device_alloc(&optee_desc, NULL, pool, optee);
optee->teedev = teedev;
teedev = tee_device_alloc(&optee_supp_desc, NULL, pool, optee);
optee->supp_teedev = teedev;
rc = tee_device_register(optee->teedev);
rc = tee_device_register(optee->supp_teedev);
在这里,较为重要的是分配并注册了两个tee_device,分别用于libteec库和tee_supplicat使用,在libteec中调用的open、close、ioctl等,最终都会调用到optee_desc中的具体函数。而tee_supplicat则会调用到optee_supp_desc中具体的函数。
TA镜像的加载、验签
当CA打开调用TEEC_OpenSession函数后,optee core就会开始去load相应的TA,如果对应的TA是动态TA的话,则optee core则会发起RPC请求,请求将发送到tee_supplicat,由tee_supplicat将ta文件加载至共享内存中,然后在拷贝到secure World的user空间,最终将加载至TA运行的内存中。
TA文件的验签是在load进共享内存之后,调用check_shdr函数进行验签。/optee_os/arch/arm/kernel/user_ta.c
OP-TEE 系统调用
optee运行时分为用户空间和内核空间,TA和外部库运行在用户空间。
Optee用户空间的接口一般定义成utee_xxx_xxx的形式,而对应的系统调用则为syscall_xxx_xxx。
utee_xxx_xxx大部分定义在libutee中
也就是:
调用TEE_xxx接口->libutee(utee_xxx_xxx)->svc中断,根据系统调用ID,命中系统调用,系统调用表在/libutee/arch/arm/utee_syscalls_asm.S