ARM支持的trustzone技术后提供了ARM cortex的虚拟化基础。众所周知ARM具有其中运行模式,但是支持trustzone技术的cortex就已经不再仅仅有其中模式了,而是具有八种模式和两个状态:secure world态和non-secure world态。本文将介绍secure world态和non-secure world态之间是如何实现切换的,以OP-TEE中在没有使用ATF的情况下sm操作代码为例。
1. 基础知识
本章节将介绍理解secure world与non-secure world之间进行切换所需要明白的一些寄存器和相关模式的知识。
1.1 ARM的运行模式
ARM在未支持trustzone技术之前,ARM具有七种运行模式,分别为:
1. usr模式(用户模式):正常程序的运行时的模式
2. fiq模式(快速中断模式):当配置了快速中断时,如果产生fiq事件,cortex将会切换到该模式
3. irq模式(用户模式):中断模式,一般用于通用中断处理,被ROS使用
4. svc模式(管理模式):操作系统使用的保护模式,
5. sys模式(系统模式):运行具有特权的操作系统任务
6. abt模式(数据访问终止模式):当数据或者指令预取值时终止则会进入该模式
7. und模式(未定义指令模式):当未定义指令执行的时候则会进入该模式
而支持trustzone技术之后,ARM增加了另外一种mon模式(monitor 模式),而mon模式就起到进行secure world与non-secure world之间进行切换的桥梁作用。所以总结下来在ARM的cortex中具有八种模式两种状态,每种状态下具有自己独立的八种模式。
1.2 NS位
在持trustzone技术的时候,ARM在AXI系统总线上增加了一个NS位(详细情况请查阅ARM给出的trustzone白皮书),而NS位就是用来标记当前的数据,指令时属于secure world态还是non-secure world态,NS位会被保存到scr寄存器的第0位。当NS=1时,处理器处于non-secure world态,当NS=0时,处理器处于secure world态。
1.3 重要寄存器
VBAR(Vector Base Address Register)寄存器:
异常向量基地址,该寄存器将保存异常向量表的基地址,在secure world态和non-secure world态都具有各自独有的VBAR寄存器用来存放两种状态各自独有的异常向量表所在的基地址。
MVBAR(Monitor Vector Base Address Register)寄存器:
monitor模式下的异常向量表基地址寄存器,该寄存器用来保存在monitor模式下异常向量表的基地址,该寄存器在secure world和non-secure world之间进行切换的时候起到关键作用。
SCR(Secure Configuration Register)寄存器
安全配置寄存器,处理器在运行的时候该寄存器中会保存相关的标志,其中用于标记处理器处于secure world态还是non-secure world态的NS位就被保存在该寄存器中。
SP(Stack Pointer)栈寄存器:
该寄存器用来存放处理器使用的栈的偏移地址
CPSR(Current Program Status Register)程序状态寄存器:
该寄存器将保存处理器运行时的各种标志位信息,包括标志域,状态域,扩展域和控制域
SPSR(Saved Program Status Register)程序状态保存寄存器:
当特定的异常中断发生时,spsr寄存器将保存当前长度cpsr寄存器中的内容,等异常中断退出自后,处理器会使用spsr寄存器中的数据来恢复cpsr寄存器中的数据。
LR(Link Register)子程序链接寄存器:
该寄存器一般用来保存子程序的返回地址。
1.4 smc汇编指令
当需要让处理器进入到monitor模式的时候,ARM要求执行smc指令来实现。如果该汇编指令执行成功,则处理器就切换到了monitor模式下并且更新monitor模式下的重要寄存器,包括CPSR, SPSR, LR。该操作与ARM进入到IRQ, ABT等模式的操作一样,采取的是产生异常来进行模式的切换。当系处理器进入到monitor之后,处理器就回去找寻该模式下的异常处理向量表的位置,而monitor模式下独有的异常向量表的基地址被保存在MVBAR寄存器中。
2. monitor模式下的处理过程
在secure world态或者是non-secure world态调用smc指令之后,处理器将会触发异常操作进入到monitor模式中并更新monitor模式下的CPSR, SPSR, LR,并从MVBAR寄存器中获取到monitor模式的异常中断向量表基地址,进而找到smc操作的异常处理函数。在《16. OP-TEE中的中断处理(二)------系统FIQ事件的处理》一文中介绍了monitor模式的异常中断向量表基地址是如何保存到MVBAR寄存器中的,在此就不再做冗余介绍。monitor模式下整个处理逻辑如下图所示。
2.1 进入到monitor模式的中断向量后的处理过程
在OP-TEE中monitor模式的异常中断向量表定义在optee_os/core/arch/arm/sm/sm_a32.S文件总,其内容如下:
LOCAL_FUNC sm_vect_table , :
UNWIND( .fnstart)
UNWIND( .cantunwind)
b . /* Reset */
b . /* Undefined instruction */
b sm_smc_entry /* Secure monitor call */
b . /* Prefetch abort */
b . /* Data abort */
b . /* Reserved */
b . /* IRQ */
b sm_fiq_entry /* FIQ */
UNWIND( .fnend)
END_FUNC sm_vect_table
所以当调用smc之后,处理器切换到monitor模式,查找到异常中断向量表,并执行b sm_smc_entry来对smc异常进行处理。该函数定义在optee_os/core/arch/arm/sm/sm_a32.S文件中。其完整内容如下:
LOCAL_FUNC sm_smc_entry , :
UNWIND( .fnstart)
UNWIND( .cantunwind)
srsdb sp!, #CPSR_MODE_MON//将当前模式的lr和spsr寄存器中的值分别存储在monitor模式的sp中
push {r0-r7} //将r0到r7中的值压入栈(sp)
clrex /* Clear the exclusive monitor *///独占清除,可以将关系紧密的独占访问监控器返回为开放模式
/* Find out if we're doing an secure or non-secure entry */
read_scr r1 //获取当前scr寄存器中的值,并将值保存在r1寄存器中
tst r1, #SCR_NS //判定scr寄存器中的值的NS位是否为1,如果是1则将会改变CPSR中的条件标志位为0
bne .smc_from_nsec //如果请求来自于non-secur world,则跳转到smc_from_nsec进行执行
/*
* As we're coming from secure world (NS bit cleared) the stack
* pointer points to sm_ctx.sec.r0 at this stage. After the
* instruction below the stack pointer points to sm_ctx.
*/
//将当前处于secure world态中,secure world的运行栈存放在r0中
//所以将当前sp的值减去offset就可以得到secure world的运行栈地址
//并将sp的值指向得到的secure world的运行栈地址
sub sp, sp, #(SM_CTX_SEC + SM_SEC_CTX_R0)
/* Save secure context */
add r0, sp, #SM_CTX_SEC//将sp的值加上secure world context的长度保存在r0寄存器中
//保存secure world中8中模式的主要寄存器的值,并将值存放到r0寄存器,
//而r0寄存器已经指向了CPU栈的位置中以便实现secure context的保存
bl sm_save_modes_regs
/*
* On FIQ exit we're restoring the non-secure context unchanged, on
* all other exits we're shifting r1-r4 from secure context into
* r0-r3 in non-secure context.
*/
add r8, sp, #(SM_CTX_SEC + SM_SEC_CTX_R0) //将sp的值将上secure world context中r0存放的位置
ldm r8, {r0-r4} //将r8寄存器中的值指向的地址中的值依次赋值给r0到r4
mov_imm r9, TEESMC_OPTEED_RETURN_FIQ_DONE // 将FIQ指向完的值保存到r9寄存器中
cmp r0, r9 //对比r0寄存器和r9寄存器中的值
addne r8, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R0) //如果r0与r9不相等则将sp加上non-secure context中的r0的值保存到r8寄存器中
stmne r8, {r1-r4} //如果r0与r9不相等,则将r1到r4寄存器中的值依次加载到r8指定的位置
/* Restore non-secure context */
add r0, sp, #SM_CTX_NSEC //将sp的值加上non-secure world context的长度保存到r0寄存去中
bl sm_restore_modes_regs //获取non-secure context的内容
//执行返回到non-seure world的操作
.sm_ret_to_nsec:
/*
* Return to non-secure world
*/
//将sp的值加上non-secure world context中从起始位置到r8寄存器的偏移值
//然后将结果保存到r0寄存器中
add r0, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R8)
ldm r0, {r8-r12} //r0寄存器中的值指向的地址中的值一次次赋值给r8和r12寄存器
/* Update SCR */
read_scr r0 //获取当前scr寄存器的值,并保存到r0寄存器中
//将scr中的NS位和FIQ位置1
orr r0, r0, #(SCR_NS | SCR_FIQ) /* Set NS and FIQ bit in SCR */
write_scr r0 //将修改后的r0的值写入到scr寄存器中
//将sp的值加上non-secure world context中从起始位置到r0寄存器的偏移值
//然后将结果保存到sp中
add sp, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R0)
b .sm_exit //跳转到sm_exit函数继续执行
//指向切换到secure world的操作
.smc_from_nsec:
/*
* As we're coming from non-secure world (NS bit set) the stack
* pointer points to sm_ctx.nsec.r0 at this stage. After the
* instruction below the stack pointer points to sm_ctx.
*/
//当前处于non-secure world态,栈指针就是sp
//所以将当前sp的值减去offset就可以得到non-secure world的运行栈地址
//并将sp的值指向得到的non-secure world的运行栈地址
sub sp, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R0)
//清除r1寄存器中的NS位和FIQ位
bic r1, r1, #(SCR_NS | SCR_FIQ) /* Clear NS and FIQ bit in SCR */
write_scr r1 //将r1寄存器中的值写入到scr寄存器中
//将sp的值将上non-secure world context中r8存放的位置
//然后将结果保存到r0寄存器中
add r0, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R8)
stm r0, {r8-r12} //将r8到r12寄存器中的值保存到r0指向的地址位置
mov r0, sp //将sp的值赋值给r0寄存器
bl sm_from_nsec //跳转到secure world中进行处理来之non-secure world的smc请求
cmp r0, #0 //对比返回值是否为零,即帕丁sm_form_nsec函数是否执行成功
beq .sm_ret_to_nsec //如果执行成功则执行返回到non-secure world的操作
/*
* Continue into secure world
*/
//如果sm_from_nsec函数并未执行成功,
//则将sp的值将上secure world context中r8存放的位置
//然后将结果保存到sp中
add sp, sp, #(SM_CTX_SEC + SM_SEC_CTX_R0)
//执行退出sm操作
.sm_exit:
pop {r0-r7} //将栈中的r0到r7寄存去中的值进行出栈操作
rfefd sp! //使用sp寄存器中的数据执行返回操作
UNWIND( .fnend)
END_FUNC sm_smc_entry
2.2 non-secure world态触发的smc异常的处理过程
当smc异常是在non-secure world态中触发时,则SCR寄存器中的NS位必定为1,则处理器会执行smc_from_nsec的分支正式进入对来之non-secure world的smc请求的具体处理。整个执行过程的流程图如下:
在整个处理过程中,当SCR寄存器中的NS位被设定之后即表示处理器的状态已经处于secure world态或者是non-secure world态。当判定该smc异常来自于non-secure world后将会执行到sm_smc_entry函数中的smc_from_nsec代码块。该段代码块如下:
.smc_from_nsec:
/*
* As we're coming from non-secure world (NS bit set) the stack
* pointer points to sm_ctx.nsec.r0 at this stage. After the
* instruction below the stack pointer points to sm_ctx.
*/
//当前处于non-secure world态,栈指针就是sp
//所以将当前sp的值减去offset就可以得到non-secure world的运行栈地址
//并将sp的值指向得到的non-secure world的运行栈地址
sub sp, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R0)
//清除r1寄存器中的NS位和FIQ位
bic r1, r1, #(SCR_NS | SCR_FIQ) /* Clear NS and FIQ bit in SCR */
write_scr r1 //将r1寄存器中的值写入到scr寄存器中
//将sp的值将上non-secure world context中r8存放的位置
//然后将结果保存到r0寄存器中
add r0, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R8)
stm r0, {r8-r12} //将r8到r12寄存器中的值保存到r0指向的地址位置
mov r0, sp //将sp的值赋值给r0寄存器
bl sm_from_nsec //跳转到secure world中进行处理来之non-secure world的smc请求
cmp r0, #0 //对比返回值是否为零,即帕丁sm_form_nsec函数是否执行成功
beq .sm_ret_to_nsec //如果执行成功则执行返回到non-secure world的操作
/*
* Continue into secure world
*/
//如果sm_from_nsec函数并未执行成功,
//则将sp的值将上secure world context中r8存放的位置
//然后将结果保存到sp中
add sp, sp, #(SM_CTX_SEC + SM_SEC_CTX_R0)
在该代码段中有重要的两条语句,将从sm_smc_entry开始获取到的scr值保存到r1寄存器中,然后清空r1寄存器中的NS为和FIQ位完成设定处理器状态和使能FIQ,然后再将r1寄存器重新载入到scr寄存器中来完成non-secure world态带secure world态的切换。
当non-secure world态中的smc请求被TEE处理完成之后,处理器将调用sm_ret_to_nsec函数重新回到non-secure world态。其中从secure world态切换到non-secure world态是通过以下代码来实现的,读取当前的scr寄存器的值到r0寄存器,然后将r0寄存器中的值的NS位和FIQ位设置成1来完成将处理器切换回non-secure world态和屏蔽FIQ的功能。再通过write_scr函数将修改后的r0寄存器的值重新载入到scr寄存器中。
2.3 secure world态中触发的smc异常的处理过程
当smc异常是在secure world态中触发时,则SCR寄存器中的NS位必定为0,则处理器会执行smc_ret_to_nsec的分支正式进入对来之secure world的smc请求的具体处理。整个执行过程的流程图如下:
在上述过程中,从secure world切换到non-secure world的方法也是通过修改SCR寄存器中的NS位来实现的。在执行切换之前需要保存secure world态的上下文信息并将当前处理器的上下文信息恢复到non-secure world态的上下文信息。等non-secure world上下文信息恢复之后再修改SCR寄存器的NS位来实现world的切换。保存secure world的上下文信息和恢复non-secure world的上下文信息的操作分别通过sm_save_modes_regs和sm_restore_modes_regs函数来实现,上述两个函数都定义在optee_os/core/arch/arm/sm/sm_a32.S文件中,内容如下,主要是保存或者恢复ARM其中模式的sp, lr信息:
FUNC sm_save_modes_regs , :
UNWIND( .fnstart)
UNWIND( .cantunwind)
/* User mode registers has to be saved from system mode */
cps #CPSR_MODE_SYS //设置CPSR寄存器中的相关位并进入到的SYS模式
stm r0!, {sp, lr} //将sp和lr寄存器中的值保存到r0指向的地址位置
cps #CPSR_MODE_IRQ //设置CPSR寄存器中的相关位并进入到IRQ模式中
mrs r2, spsr //复制spsr(备份程序状态寄存)到r2中
stm r0!, {r2, sp, lr} //将r2, sp, lr中的值保存到r0指向的地址中
cps #CPSR_MODE_FIQ //设置CPSR寄存器中的相关位并进入到FIQ模式中
mrs r2, spsr //复制spsr(备份程序状态寄存)到r2中
stm r0!, {r2, sp, lr} //将r2, sp, lr中的值保存到r0指向的地址中
cps #CPSR_MODE_SVC //设置CPSR寄存器中的相关位并进入到SVC模式中
mrs r2, spsr //复制spsr(备份程序状态寄存)到r2中
stm r0!, {r2, sp, lr} //将r2, sp, lr中的值保存到r0指向的地址中
cps #CPSR_MODE_ABT //设置CPSR寄存器中的相关位并进入到ABT模式中
mrs r2, spsr //复制spsr(备份程序状态寄存)到r2中
stm r0!, {r2, sp, lr} //将r2, sp, lr中的值保存到r0指向的地址中
cps #CPSR_MODE_UND //设置CPSR寄存器中的相关位并进入到UND模式中
mrs r2, spsr //复制spsr(备份程序状态寄存)到r2中
stm r0!, {r2, sp, lr} //将r2, sp, lr中的值保存到r0指向的地址中
cps #CPSR_MODE_MON //设置CPSR寄存器中的相关位并进入到monitor模式中
bx lr //返回调用函数
UNWIND( .fnend)
END_FUNC sm_save_modes_regs
/* Restores the mode specific registers */
FUNC sm_restore_modes_regs , :
UNWIND( .fnstart)
UNWIND( .cantunwind)
/* User mode registers has to be saved from system mode */
cps #CPSR_MODE_SYS //设置CPSR寄存器中的相关位并进入到的SYS模式
ldm r0!, {sp, lr} //将r0寄存器中指向的位置中的值加载到sp和lr寄存器中
cps #CPSR_MODE_IRQ //设置CPSR寄存器中的相关位并进入到IRQ模式中
ldm r0!, {r2, sp, lr} //将r0寄存器中指向的位置中的值加载到r2,sp和lr寄存器中
msr spsr_fsxc, r2 //将r2寄存器指向的地址的数据赋值到spsr寄存器的标志域,状态域,扩展域,控制域
cps #CPSR_MODE_FIQ //设置CPSR寄存器中的相关位并进入到FIQ模式中
ldm r0!, {r2, sp, lr} //将r0寄存器中指向的位置中的值加载到r2,sp和lr寄存器中
msr spsr_fsxc, r2 //将r2寄存器指向的地址的数据赋值到spsr寄存器的标志域,状态域,扩展域,控制域
cps #CPSR_MODE_SVC //设置CPSR寄存器中的相关位并进入到SVC模式中
ldm r0!, {r2, sp, lr} //将r0寄存器中指向的位置中的值加载到r2,sp和lr寄存器中
msr spsr_fsxc, r2 //将r2寄存器指向的地址的数据赋值到spsr寄存器的标志域,状态域,扩展域,控制域
cps #CPSR_MODE_ABT //设置CPSR寄存器中的相关位并进入到ABT模式中
ldm r0!, {r2, sp, lr} //将r0寄存器中指向的位置中的值加载到r2,sp和lr寄存器中
msr spsr_fsxc, r2 //将r2寄存器指向的地址的数据赋值到spsr寄存器的标志域,状态域,扩展域,控制域
cps #CPSR_MODE_UND //设置CPSR寄存器中的相关位并进入到UND模式中
ldm r0!, {r2, sp, lr} //将r0寄存器中指向的位置中的值加载到r2,sp和lr寄存器中
msr spsr_fsxc, r2 //将r2寄存器指向的地址的数据赋值到spsr寄存器的标志域,状态域,扩展域,控制域
cps #CPSR_MODE_MON //设置CPSR寄存器中的相关位并进入到MON模式中
bx lr //返回调用函数
UNWIND( .fnend)
END_FUNC sm_restore_modes_regs
3. 总结
non-secure world与secure world之间的切换都是通过修改当前的SCR寄存器中的NS位来实现的,而且在secure world态时需要使能FIQ的支持,而在non-secure world态时则需要禁止FIQ的支持。其在两种态之间的切换的时候需要做到对应的上下文的保存和恢复操作。