欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

optee学习笔记_3

程序员文章站 2022-07-13 16:11:53
...

上一篇的demo做个小结先:

optee学习笔记_3

  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

optee

optee学习笔记_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