對上一篇的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