optee学习笔记_3

对上一篇的demo做个小结先:

optee学习笔记_3_第1张图片

  1. 在/dev目录下,会生成两个节点,一个是tee_supplican使用,tee0供libteec使用
  2. CA调用TEEC_InitializeContext后,在libteec中open tee0节点,并通过ioctl检查版本号等
  3. 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函数。
  4. CA打开一个session之后,就可以调用TEEC_InvokeCommand函数,向TA发送command,该command会穿过libtee、driver、optee core,最终到达TA中的TA_InvokeCommandEntryPoint函数。在这里,用户可根据自己的实际需求解析command。
  5. 最终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学习笔记_3_第2张图片

optee

optee学习笔记_3_第3张图片

整个tee驱动,主要是通过subsys_initcallmodule_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函数中,主要做了以下工作:

  1. 通过get_invoke_func(np);函数,获取smc指令
  2. 版本检查
  3. 分配了一个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

你可能感兴趣的:(OP-TEE)