OP-TEE 驱动篇 学习笔记(1)
OP-TEE驱动主要作用是REE与TEE端进行数据交互的桥梁作用。
tee_supplicant(tee supplicant是optee os的ree侧也就是运行于linux应用层的守护进程,其作用为处理rpc请求。帮助tee侧访问ree侧的资源等)和libteec (libteec 是OP-TEE 提供给用户在linux userspace层面调用的接口实现)调用接口之后几乎都会首先通过系统调用陷入到kernel space(Linux 内核的运行空间),然后kernel根据传递的参数找到OP-TEE驱动,并命中驱动的operation结构体中的具体处理函数来完成实际的操作,对于OP-TEE驱动,一般都会触发SMC调用(安全监视器调用),并带参数进入到ARM cortex(指令架构)的monitor模式,在monitor模式中对执行normal world(正常世界状态)和secure world(安全世界状态)的切换,待状态切换完成之后,会将驱动端带入的参数传递給OP-TEE中的thread(线程)进行进一步的处理。OP-TEE驱动的源代码存放在linux/drivers/tee目录中,其内容如下;
1. OP-TEE驱动的加载
OP-TEE驱动的加载过程分为两部分,第一部分是创建class和分配设备号,第二部分就是probe过程。在正式介绍之前首先需要明白两个linux kernel中加载驱动的函数:subsys_initcall和module_init函数。OP-TEE驱动的第一部分是调用subsys_initcall函数来实现,而第二部分则是调用module_init来实现。
1.1 OP-TEE****驱动模块的编译后的存放位置和加载过程
OP-TEE驱动通过subsys_initcall和module_init 宏来告知系统在初始化的什么时候去加载OP-TEE驱动,subsys_initcall定义在linux/include/init.h文件中,内容如下:
\#define __define_initcall(fn, id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" #id ".init"))) = fn;
\#define core_initcall(fn) __define_initcall(fn, 1)
\#define core_initcall_sync(fn) __define_initcall(fn, 1s)
\#define postcore_initcall(fn) __define_initcall(fn, 2)
\#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
\#define arch_initcall(fn) __define_initcall(fn, 3)
\#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
\#define subsys_initcall(fn) __define_initcall(fn, 4)
\#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
\#define fs_initcall(fn) __define_initcall(fn, 5)
\#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
\#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
\#define device_initcall(fn) __define_initcall(fn, 6)
\#define device_initcall_sync(fn) __define_initcall(fn, 6s)
\#define late_initcall(fn) __define_initcall(fn, 7)
\#define late_initcall_sync(fn) __define_initcall(fn, 7s)
使用subsys_initcall宏定义的函数最终会被编译到.initcall4.init段中,linux系统在启动的时候会执行initcallx.init段中的所有内容,而使用subsys_initcall宏定义段的执行优先级为4.
module_init的定义和相关扩展在linux/include/linux/module.h文件和linux/include/linux/init.h中,内容如下:
#define device_initcall(fn) __define_initcall(fn, 6)
#define __initcall(fn) device_initcall(fn)
#define module_init(x) __initcall(x);
由此可见,使用module_init宏构造的函数将会在编译的时候被编译到initcall6.init段中,该段在linux系统启动的过程中的优先等级为6.
(init:初始化)
结合上述两点看,在系统加载OP-TEE驱动的时候,首先会执行OP-TEE驱动中使用subsys_init定义的函数,然后再执行使用module_init定义的函数。在OP-TEE驱动源代码中使用subsys_init定义的函数为tee_init,使用module_init定义的函数为optee_driver_init。
1.2 TEE_INIT****函数初始化设备号和CLASS
(class 分类:它主要是集合具有相似功能或属性的设备,这样就可以抽象出一套可以在多个设备之间共用的数据结构和接口函数。因而从属于相同Class的设备的驱动程序,就不再需要重复定义这些公共资源,直接从Class中继承即可)。
该函数定义在linux/drivers/tee/tee_core.c文件中,主要完成class的创建和设备号的分配,其内容如下:
1. static int __init tee_init(void)
2. {
3. int rc;
4.
5. */** *分配OP-TEE**驱动的class \*/*
6. tee_class = class_create(THIS_MODULE, "tee");
7. if (IS_ERR(tee_class)) {
8. pr_err("couldn't create class\n");
9. return PTR_ERR(tee_class);
10. }
11.
12. */** *分配OP-TEE**的设备号 \*/*
13. rc = alloc_chrdev_region(&tee_devt, 0, TEE_NUM_DEVICES, "tee");
14. if (rc) {
15. pr_err("failed to allocate char dev region\n");
16. class_destroy(tee_class);
17. tee_class = NULL;
18. }
19.
20. return rc;
21. }
设备号和class将会在驱动执行probe的时候被使用到
1.3 OPTEE_DRIVER_INIT****函数执行
linux启动过程中会执行moudule_init宏定义的函数,在OP-TEE驱动的挂载过程中将会执行optee_driver_init函数,该函数定义在linux/drivers/tee/optee/core.c文件中,其内容如下:
1. static int __init optee_driver_init(void)
2. {
3. struct device_node *fw_np;
4. struct device_node *np;
5. struct optee *optee;
6.
7. */\* Node is supposed to be below /firmware \*/*
8. */** *从device tree**中查找到firware**的节点 \*/*
9. fw_np = of_find_node_by_name(NULL, "firmware");
10. if (!fw_np)
11. return -ENODEV;
12.
13. */** *匹配device tree**中firmware**节点下节点为linaro,optee-tz**内容的节点 \*/*
14. np = of_find_matching_node(fw_np, optee_match);
15. of_node_put(fw_np);
16. if (!np)
17. return -ENODEV;
18.
19. */** *使用查找到的节点执行OP-TEE**驱动的probe**操作 \*/*
20. optee = optee_probe(np);
21. of_node_put(np);
22.
23. if (IS_ERR(optee))
24. return PTR_ERR(optee);
25. */** *保存初始化完成之后OP-TEE**的设备信息到optee_svc**中,以备在卸载的是使用 \*/*
26. optee_svc = optee;
27.
28. return 0;
29. }
2. OP-TEE驱动初始化时的PROBE操作
Probe:查找,探寻
OP-TEE驱动在optee_driver_init函数来完成probe操作。该函数首先会通过device tree找到OP-TEE驱动设备信息,然后将获取到的信息传递給optee_probe函数执行probe操作。optee_probe函数内容如下:
1. static struct optee *optee_probe(struct device_node *np)
2. {
3. optee_invoke_fn *invoke_fn;
4. struct tee_shm_pool *pool;
5. struct optee *optee = NULL;
6. void *memremaped_shm = NULL;
7. struct tee_device *teedev;
8. u32 sec_caps;
9. int rc;
10.
11. */** *获取OP-TEE**驱动在device tree**中节点描述内容中定义的执行切换到monitor**模式的接口 \*/*
12. invoke_fn = get_invoke_func(np);
13. if (IS_ERR(invoke_fn))
14. return (void *)invoke_fn;
15.
16. */** *调用到secure world**中获取API**的版本信息是否匹配 \*/*
17. if (!optee_msg_api_uid_is_optee_api(invoke_fn)) {
18. pr_warn("api uid mismatch\n");
19. return ERR_PTR(-EINVAL);
20. }
21.
22. */** *调用到secure world**中获取版本信息检查是否匹配 \*/*
23. if (!optee_msg_api_revision_is_compatible(invoke_fn)) {
24. pr_warn("api revision mismatch\n");
25. return ERR_PTR(-EINVAL);
26. }
27.
28. */** *调用到secure world**中获取secure world**是否reserved share memory \*/*
29. if (!optee_msg_exchange_capabilities(invoke_fn, &sec_caps)) {
30. pr_warn("capabilities mismatch\n");
31. return ERR_PTR(-EINVAL);
32. }
33.
34. */**
35. ** We have no other option for shared memory, if secure world*
36. ** doesn't have any reserved memory we can use we can't continue.*
37. **/*
38. */** *判定sercure world**中是否reserve**了share memory**,如果没有则报错 \*/*
39. if (!(sec_caps & OPTEE_SMC_SEC_CAP_HAVE_RESERVED_SHM))
40. return ERR_PTR(-EINVAL);
41.
42. */** *配置secure world**与驱动之间的share memory**,并进行地址映射建立共享内存池 \*/*
43. pool = optee_config_shm_memremap(invoke_fn, &memremaped_shm);
44. if (IS_ERR(pool))
45. return (void *)pool;
46.
47. */** *在kernel space**内存空间中分配一块内存用于存放OP-TEE**驱动的结构体变量 \*/*
48. optee = kzalloc(sizeof(*optee), GFP_KERNEL);
49. if (!optee) {
50. rc = -ENOMEM;
51. goto err;
52. }
53.
54. */** *将驱动用于实现进入monitor**模式的接口赋值到optee**结构体中的invoke_fn**成员中 \*/*
55. optee->invoke_fn = invoke_fn;
56.
57. */** *分配设备信息,填充被libteec**使用的驱动文件信息和operation**结构体并创建/dev/tee0**文*
58. *件,libteec**将会使用该文件来使用op-tee**驱动 \*/*
59. teedev = tee_device_alloc(&optee_desc, NULL, pool, optee);
60. if (IS_ERR(teedev)) {
61. rc = PTR_ERR(teedev);
62. goto err;
63. }
64. optee->teedev = teedev; *//libteec**使用的驱动文件信息填充到optee**中的teedev**成员中*
65.
66. */** *分配设备信息,填充被tee_supplicant**使用的驱动文件信息和operation**结构体并创*
67. *建/dev/teepriv0**文件,tee_supplicant**将会使用该文件来使用op-tee**驱动 \*/*
68. teedev = tee_device_alloc(&optee_supp_desc, NULL, pool, optee);
69. if (IS_ERR(teedev)) {
70. rc = PTR_ERR(teedev);
71. goto err;
72. }
73. *//**将tee_supplicant**使用的驱动文件信息填充到optee**中的supp_teedev**成员中*
74. optee->supp_teedev = teedev;
75.
76. */** *将被libteec**使用的设备信息注册到系统设备中 \*/*
77. rc = tee_device_register(optee->teedev);
78. if (rc)
79. goto err;
80.
81. */** *将被tee_supplicant**使用的设备信息注册到系统设备中 \*/*
82. rc = tee_device_register(optee->supp_teedev);
83. if (rc)
84. goto err;
85.
86. mutex_init(&optee->call_queue.mutex);
87. INIT_LIST_HEAD(&optee->call_queue.waiters);
88.
89. */** *初始化RPC**操作队列 \*/*
90. optee_wait_queue_init(&optee->wait_queue);
91.
92. */** *初始化被tee_supplicant**用到的用于存放来自TA**的请求的队列 \*/*
93. optee_supp_init(&optee->supp);
94.
95. */** *填充optee**中的共享内存地址信息和共享内存池信息成员 \*/*
96. optee->memremaped_shm = memremaped_shm;
97. optee->pool = pool;
98.
99. */** *使能共享内存的cache \*/*
100. optee_enable_shm_cache(optee);
101.
102. pr_info("initialized driver\n");
103. return optee;
104. err:
105. if (optee) {
106. */**
107. ** tee_device_unregister() is safe to call even if the*
108. ** devices hasn't been registered with*
109. ** tee_device_register() yet.*
110. **/*
111. tee_device_unregister(optee->supp_teedev);
112. tee_device_unregister(optee->teedev);
113. kfree(optee);
114. }
115. if (pool)
116. tee_shm_pool_free(pool);
117. if (memremaped_shm)
118. memunmap(memremaped_shm);
119. return ERR_PTR(rc);
120. }
2.1 获取切换到MONITOR****模式的接口
normal world穿透到secure world是通过在monitor模式下设定SCR寄存器中的NS位来实现的,OP-TEE驱动被上层调用时,最终会通过出发smc切换到monitor,见数据发送给secure world来进行处理。而用户出发smc请求的接口函数将在驱动初始化的时候被填充到OP-TEE驱动的device info中,在OP-TEE驱动中通过调用get_invoke_func函数来获取,该函数的内如如下:
1. static optee_invoke_fn *get_invoke_func(struct device_node *np)
2. {
3. const char *method;
4.
5. pr_info("probing for conduit method from DT.\n");
6.
7. */** *获取op-tee**驱动在device tree**中的节点中的method**属性的值 \*/*
8. if (of_property_read_string(np, "method", &method)) {
9. pr_warn("missing \"method\" property\n");
10. return ERR_PTR(-ENXIO);
11. }
12.
13. */** *判定op-tee**驱动是使用smc**的方式还是使用hvc**的方式来实现进入monitor**模式的操作,*
14. *根据method**的值与hvc**还是smc**匹配来决定那种切换方法,并将用于切换到monitor**的*
15. *接口 \*/*
16. if (!strcmp("hvc", method))
17. return optee_smccc_hvc;
18. else if (!strcmp("smc", method))
19. return optee_smccc_smc;
20.
21. pr_warn("invalid \"method\" property: %s\n", method);
22. return ERR_PTR(-EINVAL);
23. }
以optee_smccc_smc为例,该函数的内容如下:
1. static void optee_smccc_smc(unsigned long a0, unsigned long a1,
2. unsigned long a2, unsigned long a3,
3. unsigned long a4, unsigned long a5,
4. unsigned long a6, unsigned long a7,
5. struct arm_smccc_res *res)
6. {
7. arm_smccc_smc(a0, a1, a2, a3, a4, a5, a6, a7, res);
8. }
也即是函数get_invoke_func执行完成之后会返回arm_smccc_smc函数的地址。arm_smccc_smc函数就是驱动用来将cortex切换到monitor模式的函数,该函数是以汇编的方式编写,定义在linux/arch/arm/kernel/smccc-call.S文件中。如果是64位系统,则该函数定义在linux//arch/arm64/kernel/smccc-call.S目录中,本文以32位系统为例,该函数内容如下:
- /*
- ** Wrap c macros in asm macros to delay expansion until after the*
- ** SMCCC asm macro is expanded.*
- */
- */*SMCCC_SMC**宏,*触发smc*/
- .macro SMCCC_SMC
- __SMC(0)
- .endm
- */*SMCCC_HVC**宏,*触发hvc*/
- .macro SMCCC_HVC
- __HVC(0)
- .endm
- /* 定义SMCCC**宏,其参数为instr */
- .macro SMCCC instr
- /* 将normal world**中的寄存器入栈,保存现场 */
- UNWIND( .fnstart)
- mov r12, sp
- push {r4-r7}
- UNWIND( .save {r4-r7})
- ldm r12, {r4-r7}
- \instr /* 执行instr参数的内容,即执行smc切换 */
- pop {r4-r7} /* 出栈操作,恢复现场 */
- ldr r12, [sp, #(4 * 4)]
- stm r12, {r0-r3}
- bx lr
- UNWIND( .fnend)
- .endm
- /*
- ** void smccc_smc(unsigned long a0, unsigned long a1, unsigned long a2,*
- ** unsigned long a3, unsigned long a4, unsigned long a5,*
- ** unsigned long a6, unsigned long a7, struct arm_smccc_res *res)*
- */
- ENTRY(arm_smccc_smc)
- SMCCC SMCCC_SMC
- ENDPROC(arm_smccc_smc)
2.2 校验API的UID和OP-TEE****的版本信息
Api:程序编程接口
Uid:用户身份证明
驱动加载过程中获取到REE与TEE之间进行交互的接口函数(调用get_invoke_func函数返回的函数地址)之后,op-tee驱动会对API的UID和版本信息进行校验。上述操作是通过调用optee_msg_api_uid_is_optee_api函数和optee_msg_api_revision_is_compatible函数来实现的。两个函数的内容如下:
1. static bool optee_msg_api_uid_is_optee_api(optee_invoke_fn *invoke_fn)
2. {
3. struct arm_smccc_res res;
4.
5. */** *调用执行smc**操作的接口函数,带入的commond ID**为OPTEE_SMC_CALLS_UID \*/*
6. invoke_fn(OPTEE_SMC_CALLS_UID, 0, 0, 0, 0, 0, 0, 0, &res);
7.
8. */** *比较返回的UID**的值与在驱动中定义的UID**的值是否匹配 \*/*
9. if (res.a0 == OPTEE_MSG_UID_0 && res.a1 == OPTEE_MSG_UID_1 &&
10. res.a2 == OPTEE_MSG_UID_2 && res.a3 == OPTEE_MSG_UID_3)
11. return true;
12. return false;
13. }
14.
15. static bool optee_msg_api_revision_is_compatible(optee_invoke_fn *invoke_fn)
16. {
17. union {
18. struct arm_smccc_res smccc;
19. struct optee_smc_calls_revision_result result;
20. } res;
21.
22. */** *调用执行smc**操作的接口函数,带入的commond ID**为OPTEE_SMC_CALLS_REVISION\*/*
23. invoke_fn(OPTEE_SMC_CALLS_REVISION, 0, 0, 0, 0, 0, 0, 0, &res.smccc);
24.
25. */** *比较返回的版本信息的值与驱动中定义的版本值是否匹配 \*/*
26. if (res.result.major == OPTEE_MSG_REVISION_MAJOR &&
27. (int)res.result.minor >= OPTEE_MSG_REVISION_MINOR)
28. return true;
29. return false;
30. }
2.3 判定SECURE WORLD是否预留了驱动与SECURE WORLD之间的共享内存空间
驱动与secure world之间需要进行数据的交互,而进行数据交互则需要一定的共享内存来保存sercure world和驱动之间共有的数据。所以在驱动初始化的时候需要检查该共享内存空间是否被预留出来。通过获取secure world中的相关变量的值并判定该flag是否相等来判定secure world是否预留了共享内存空间,在OP-TEE OS启动的时候,执行MMU初始化的时候会初始化该变量。在驱动端通过调用optee_msg_exchange_capabilities函数来获取该变量的值,其内容如下:
1. static bool optee_msg_exchange_capabilities(optee_invoke_fn *invoke_fn,
2. u32 *sec_caps)
3. {
4. union {
5. struct arm_smccc_res smccc;
6. struct optee_smc_exchange_capabilities_result result;
7. } res;
8. u32 a1 = 0;
9.
10. */**
11. ** TODO This isn't enough to tell if it's UP system (from kernel*
12. ** point of view) or not, is_smp() returns the the information*
13. ** needed, but can't be called directly from here.*
14. **/*
15. if (!IS_ENABLED(CONFIG_SMP) || nr_cpu_ids == 1)
16. a1 |= OPTEE_SMC_NSEC_CAP_UNIPROCESSOR;
17.
18. */** *调用smc**操作接口,获取secure world**中的变量 \*/*
19. invoke_fn(OPTEE_SMC_EXCHANGE_CAPABILITIES, a1, 0, 0, 0, 0, 0, 0,
20. &res.smccc);
21.
22. if (res.result.status != OPTEE_SMC_RETURN_OK)
23. return false;
24.
25. *sec_caps = res.result.capabilities; *//**将返回值中的变量赋值为sec_caps*
26. return true;
27. }
当驱动获取到sec_caps的值之后会查看该值是否为宏OPTEE_SMC_SEC_CAP_HAVE_RESERVED_SHM定义的值BIT(0),如果该值不为BIT(0),则会报错,因为在secure world端都没有预留share memory空间,那驱动与secure world之间也就没法传输数据,所以有没有驱动也就没有必要了。
学习总结:
1. OP-TEE驱动的加载
1.1 OP-TEE驱动模块的编译后的存放位置和加载过程
1.2 TEE_INIT函数初始化设备号和CLASS
1.3 OPTEE_DRIVER_INIT函数执行
2. OP-TEE驱动初始化时的PROBE操作
2.1 获取切换到MONITOR模式的接口
2.2 校验API的UID和OP-TEE的版本信息
2.3 判定SECURE WORLD是否预留了驱动与SECURE WORLD之间的共享内存空间