OP-TEE design
OP-TEE也就是所谓的可信运行环境(缩写为TEE),是支持Trustzone技术的ARM底层的关键部分,OP-TEE由以下三个部分组成:
OP-TEE client(op-tee client):运行在普通世界用户空间的客户端API。
OP-TEE Linux Kernel device driver (optee_linuxdriver):用以控制普通世界用户空间和安全世界通信的设备驱动。
OP-TEE Trusted OS (optee_os):运行在安全世界的可信操作系统。
OP-TEE在设计时考虑到了可扩展性和可移植性,到目前为止它已经被移植到多个厂商的少数几个平台之上,支持ARMv7和ARMv8-A的都有。(详情请见README.md)
在阅读这个部分的时候,请先看下面的插图,这能更好的诠释执行的过程和步骤:
1. 当系统自动的时候,连接脚本中指定的入口函数tz_sinit将会被调用(tz-template.lds) ,这个方法将会初始化所有的计算核心,主要的设置是使用一个计算核心,在此计算核心达到初始化阶段之前,其他计算核心陷入等待,达到之后释放其他计算核心,这个方法也负责对缓存进行配置和设置MMU。
2. 在此阶段下,main_init和main_init_helper方法将会在各个计算核心中被调用这些初始化函数是负责初始化UART,清除BSS段,初始化金丝雀(用来检测缓冲区溢出),初始化GIC和登记监测矢量表。它也在这些函数中,您正在注册线程处理程序。也就是说,当你进入安全的世界时,要调用的函数的处理程序。
*笔者注:
1. UART:通用异步收发传输器,用以将并行传输转换为串行传输的转换芯片。
2. BSS区域:全称Block Started by Symbol,用以存放未初始化的变量及数据。
3. canaries:用以探测是否受到溢出攻击的一种技术。参考解释:canaries
4. GIC:中断控制器,具备总的中断使能,各个子中断使能,优先级排序,在多核系统中对于SGI还可以指定中断发往哪个CPU core等功能,详见:GIC*
3.1 SMC操作
安全世界和普通世界的通信通过SMC异常来实现,在平台初始化期间有一个被称作监控器向量表的注册向量表,这种注册方式是通过写来注册监控器向量表(MVBAR),当发生SMC异常的时候监控向量表状态的函数将会被调用,当前的监控器被设计为监听常规的SMC调用和FIQ请求两种异常(详情见:sm_vect_table in sm_a32.S)
1. 来自于普通世界的此种调用将会被Linux内核中的TEE设备驱动所初始化,首先要做的是依据TEE SMC接口将参数(一个或多个)放入,然后通过smc #0
触发SMC异常。
2. 此类异常将会被监控器即monitor捕捉到,然后做一些记录并通过查看SRC.NS
决定下一步操作。(通过查看SRC.NS,我们可以知道异常来自于那一边)。
3. 当此调用进入到安全世界中时,首先发生的事情是将会分配一个线程给这项任务。分配完毕后,此线程将会启动或恢复在PC寄存器中存储的属于此线程的上下文。当此类调用是标准调用的情况下,方法thread_alloc_and_run
(thread.c)将会被调用。通过此方法可信OS(即OPTEE_OS)将会为此请求尝试寻找一个未使用的线程。如果未查询到可用的线程(即所有的线程都正在被使用中),然后可信OS将会返回OPTEE_SMC_RETURN_ETHREAD_LIMIT
给普通世界(optee_smc.h),若可信OS找到一个未使用的线程,则复制相关寄存器并设置PC寄存器准备跳转,也就是方法thread_std_smc_entry
的功能,当所有的操作都完成后,线程已经准备好启动了。
4. 当线程被启动或者恢复,方法thread_std_smc_entry
(thread_a32.S) 将会被调用,此方法将会调用方法thread_stdcall_handler_ptr
这是指向注册线程处理程序时使用的函数,通常是指向方法main_tee_entry
(entry.c)。但是,在我们准备实践平台,不需要任何特殊处理,可以忽略调用(并注册此方法),所以通过调用一般方法tee_entry
来代替。
5. tee_entry将调用任何预定义的服务核心在TEE。
下面你将会发现调用过程在每个阶段下更加详细的说明下,注意方法thread_std_smc_entry
,我们在此方法中跳转到普通世界下(通过安全监控器secure monitor)。
3.2 SMC接口
OP-TEE 的SMC接口通过使用 optee_smc.h 和optee_msg.h被定义在两层中,前一个文件定义了SMC的标识符和通过寄存器的每个SMC的内容,后一个文件定义op-tee消息协议不限于SMC即使目前是唯一的选择。
用于此通信的主要结构是:
/**
* struct optee_msg_arg - call argument
* @cmd: Command, one of OPTEE_MSG_CMD_* or OPTEE_MSG_RPC_CMD_*
* @func: Trusted Application function, specific to the Trusted Application,
* used if cmd == OPTEE_MSG_CMD_INVOKE_COMMAND
* @session: In parameter for all OPTEE_MSG_CMD_* except
* OPTEE_MSG_CMD_OPEN_SESSION where it's an output parameter instead
* @cancel_id: Cancellation id, a unique value to identify this request
* @ret: return value
* @ret_origin: origin of the return value
* @num_params: number of parameters supplied to the OS Command
* @params: the parameters supplied to the OS Command
*
* All normal calls to Trusted OS uses this struct. If cmd requires further
* information than what these field holds it can be passed as a parameter
* tagged as meta (setting the OPTEE_MSG_ATTR_META bit in corresponding
* attrs field). All parameters tagged as meta has to come first.
*
* Temp memref parameters can be fragmented if supported by the Trusted OS
* (when optee_smc.h is bearer of this protocol this is indicated with
* OPTEE_SMC_SEC_CAP_UNREGISTERED_SHM). If a logical memref parameter is
* fragmented then has all but the last fragment the
* OPTEE_MSG_ATTR_FRAGMENT bit set in attrs. Even if a memref is fragmented
* it will still be presented as a single logical memref to the Trusted
* Application.
*/
struct optee_msg_arg {
uint32_t cmd;
uint32_t func;
uint32_t session;
uint32_t cancel_id;
uint32_t pad;
uint32_t ret;
uint32_t ret_origin;
uint32_t num_params;
/*
* this struct is 8 byte aligned since the 'struct optee_msg_param'
* which follows requires 8 byte alignment.
*
* Commented out element used to visualize the layout dynamic part
* of the struct. This field is not available at all if
* num_params == 0.
*
* params is accessed through the macro OPTEE_MSG_GET_PARAMS
*
* struct optee_msg_param params[num_params];
*/
}
更多的信息请参照optee_msg.h。
3.3 SMC接口通信
如果我们查看源代码,我们将会发现通信的实现主要是使用optee_msg_arg 和thread_smc_args,其中optee_msg_arg 可以在主要结构查询到,接下来TEE驱动将会获取来自于普通用户空间或者Linux内核的内部服务下参数,这个驱动将会使用此参数和一些额外的记录信息来新建结构体optee_msg_arg
。SMC参数通过寄存器1到7,寄存器0用来存储SMC ID无论是标准调用或者是快速调用都一样。
Trusted OS使用一组线程来支持多任务并行运行(并不是完全支持!),下面是对不同任务的处理。当Trusted OS接收到标准或者快速calls 、FIQ、 SVC 和ABORT 甚至还有 PSCI calls时,在文件thread.c中负责分配处理程序的方法thread_init_handlers
将会被调用。这些处理程序是对应特定平台的,换句话说,就是这些处理程序不具备很好的移植性,这个处理程序需要依据自己的运行平台自行设计。
同步
OP-TEE对应于线程和多核的同步有三种基本原语,分别是:spin-lock,mutex,和 condvar。
Spin-lock
一个Spain-lock由一个无符号整型来实现。这是最为原始的锁,在尝试添加spain-lock之前中断将不可用,而且应该保持不可用直到锁被释放。
一个Spain-lock由SPINLOCK_UNLOCK
进行初始化。
cpu_spin_lock()
添加 一个 spin-lock锁。
cpu_spin_trylock()
添加一个spin-lock锁 若 此spin-lock处于未锁状态则返回0 若 spin-lock 状态未发生改变即已经被上锁 则 此方法 返回 !0。
cpu_spin_unlock()
打开一个 spin-lock锁。
Mutex
互斥由struct mutex
结构体来实现,互斥锁可以伴随着中断的可用和禁用来加锁和解锁,但仅仅是在普通的线程中可以如此。互斥锁不能够在中断处理程序、终止处理程序中或者一个线程被CPU选中之前使用。
一个互斥锁可以由MUTEX_INITIALIZER
或者 mutex_init()
进行初始化。
mutex_lock()
为互斥添加锁. 若这个互斥未被加锁将会是一个很快的操作,若不是此互斥将会发送一个远程过程调用在普通世界陷入等待。
mutex_unlock()
为互斥解锁。若无等待者将会是一个很快的操作,若不是此方法将会发送一个远程过程调用唤醒在普通世界的等待者。
mutex_trylock()
尝试加锁 若互斥未锁返回true若互斥状态未变化即已经在此之前上锁则返回false。
mutex_destroy()
互斥未上锁并且没有等待者,那么这个互斥所占有的内存将会被释放。
当互斥通过调用mutex_lock()
或者 mutex_trylock()
锁上它自己后,那么此互斥只有通过此线程的互斥来进行解锁。
一个线程当未释放互斥之前不应退出可信应用程序用户空间。
Condvar
Condvar 由结构体condvar来实现,一个Condvar类似pthread标准下的pthread_condvar_t,只进行了少量的改进。条件变量被用来等待某些条件被满足,而且总是和互斥锁一起被使用。一旦这个条件变量伴随着某个互斥锁一起被使用了,那么这个变量将有且仅被这个互斥锁所使用知道这个互斥锁被释放。一个 Condvar通过CONDVAR_INITIALIZER或者condvar_init()进行初始化。
condvar_wait() :通过一个RPC向条件变量发送信号来自动打开提供的互斥锁和普通世界中等待,在此方法返回后互斥锁再次加锁。
condvar_signal():唤醒一个条件变量的等待者(通过condvar_wait()方法陷入等待的)
condvar_broadcast() :唤醒所有的条件变量的等待者。
方法condvar_signal() 和condvar_broadcast()的调用者必须使用条件变量保持互斥关联来保证等待者不会错过这个信号。