optee學習筆記_3

上一篇的demo做個小結先:

  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

整個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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章