0%

ARM核安全态和非安全态的切换

ARMv8 有 el0-el3 四个异常等级, 有 NS 和 S 两个状态, ARMv8 对 TZ 技术有先天性的优势.

ARMv7 基础

在 armv7 上首次添加了 trustzone, 在 arm 原有七种运行模式的基础上扩展出了 monitor 模式
normal world 和 sec world 的切换是由 monitor 模式下的程序来完成.

arm 原有的其中运行模式

  • usr 模式(用户模式): 正常程序运行时的模式
  • fiq 模式(快速中断模式): 当配置有快速中断时, 如果产生 fiq 事件, arm 会切换到该模式
  • irq 模式(用户模式): 中断模式, 用于通用中断处理, 被 ROS 使用
  • svc 模式(管理模式): 操作系统使用的保护模式
  • abt 模式(数据访问终止模式): 当数据或者指令取值时终止则会进入该模式
  • und 模式(未定义指令模式): 当未定义指令执行会进入该模式
  • sys 模式(系统模式): 运行具有特权的操作系统任务

这些模式都是一种硬件反应态,AXI 总线强烈依赖访问时的模式。
支持 TZ 后, ARM 增加了 monitor 模式. monitor 模式是共享的, 在 monitor 模式下, 每种状态具有独立的七种模式

armv7 安全位扩展

在支持 TZ 后, ARM 在 axi 总线上增加了一个 NS 位(安全状态位), 用来标识当前的数据\指令是属于安全世界还是非安全世界, NS bit 保存在 scr 寄存器的 0 bit, NS=1, normal world, NS=0, sec world

除了对总线进行扩展之外,ARM 对 MMU 和 Cache 也同样进行了安全状态位的扩展,用于标记 MMU 中存放的物理内存映射后的地址是属于安全内存地址还是非安全地址,而对于 Cache 该位会被用来标记当前的 Cache 是属于安全态的 Cache 还是非安全态的 Cache。当 ARM 核访问物理地址时,会对该虚拟地址的安全状态位进行检查,而在访问物理内存时安全扩展组件会对地址进行权限检查,该权限检查操作属于硬件级别的检查,不受软件的控制。关于安全地址的配置则是在 IC 设计时通过配置安全组件的参数来设定的

armv7 vs armv8 特权等级(运行模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

LOCAL_FUNC sm_vect_table , :, align=32
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 */
END_FUNC sm_vect_table

| ------------------ | -------------- | ---------------------- |
| `VBAR_ELn + 0x000` | Synchronous | Current EL with SP0 |
| `+ 0x080` | IRQ/vIRQ | Current EL with SP0 |
| `+ 0x100` | FIQ/vFIQ | Current EL with SP0 |
| `+ 0x180` | SError/vSError | Current EL with SP0 |
| ------------------ | -------------- | ---------------------- |
| `+ 0x200` | Synchronous | Current EL with SPx |
| `+ 0x280` | IRQ/vIRQ | Current EL with SPx |
| `+ 0x300` | FIQ/vFIQ | Current EL with SPx |
| `+ 0x380` | SError/vSError | Current EL with SPx |
| ------------------ | -------------- | ---------------------- |
| `+ 0x400` | Synchronous | Lower EL using AArch64 |
| `+ 0x480` | IRQ/vIRQ | Lower EL using AArch64 |
| `+ 0x500` | FIQ/vFIQ | Lower EL using AArch64 |
| `+ 0x580` | SError/vSError | Lower EL using AArch64 |
| ------------------ | -------------- | ---------------------- |
| `+ 0x600` | Synchronous | Lower EL using AArch32 |
| `+ 0x680` | IRQ/vIRQ | Lower EL using AArch32 |
| `+ 0x700` | FIQ/vFIQ | Lower EL using AArch32 |
| `+ 0x780` | SError/vSError | Lower EL using AArch32 |
| ------------------ | -------------- | ---------------------- |

armv7 进入 monitor 模式的处理流程

monitor 模式的 vec 入口 放在了 optee_os 中, armv7 没有 ATF, armv8 上才有 ATF.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
LOCAL_FUNC sm_smc_entry , :      // 进入 monitor 模式
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 bit
bne .smc_from_nsec //如果NS=0, 表明来自于非安全世界, NS=1, 来自于 sec 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.
*/
sub sp, sp, #(SM_CTX_SEC + SM_SEC_CTX_R0) //当前sp的值减去offset就可以得到Secure World的运行栈地址

/* Save secure context */
add r0, sp, #SM_CTX_SEC //将sp的值加上secure world context的长度保存在r0寄存器中 r0 存放sec world context
bl sm_save_unbanked_regs //从r0 寄存器的地址的位置开始 保存sec world 中八种模式的主要寄存器的值

/*
* 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) // r8 保存 secure world 运行栈
ldm r8, {r0-r4} // r0-r4 读到 r8
mov_imm r9, TEESMC_OPTEED_RETURN_FIQ_DONE //r9 赋值为 TEESMC_OPTEED_RETURN_FIQ_DONE
cmp r0, r9 // r0 是否等于 r9
addne r8, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R0) // 如果 r0 != r9, r8 = sp + non_sec context + non_sec_r0 (保存八种主要模式寄存器的地方)
stmne r8, {r1-r4} // 如果 r0 != r9, r1-r4 一次加载到 sp + non_sec context + non_sec_r0

/* Restore non-secure context */
add r0, sp, #SM_CTX_NSEC // r0 = sp + non_sec context
bl sm_restore_unbanked_regs // 恢复 non sec context 到 r0 位置

.sm_ret_to_nsec:
/*
* Return to non-secure world
*/
add r0, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R8) // r0 = sp + non_sec_context + non_sec_context_r8
ldm r0, {r8-r12} // 依次读 r8-r12 到 non_sec_context 中保存r8-r12 的位置

/* Update SCR */
read_scr r0
orr r0, r0, #(SCR_NS | SCR_FIQ) /* Set NS and FIQ bit in SCR */ // NS 置1 , FIQ 置1
write_scr r0 // 更新 SCR.NS 和 SCR.FIQ , 表明进入 non sec world
/*
* isb not needed since we're doing an exception return below
* without dependency to the changes in SCR before that.
*/

add sp, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R0)
b .sm_exit

================================ 来自于 非安全世界时的处理 ==========================
.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.
*/
sub sp, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R0)

bic r1, r1, #(SCR_NS | SCR_FIQ) /* Clear NS and FIQ bit in SCR */ //清除 SCR.NS 和 SCR.FIQ , 表明接下来的操作要在 sec world下进行
write_scr r1
isb

add r0, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R8)
stm r0, {r8-r12}

mov r0, sp
bl sm_from_nsec
cmp r0, #SM_EXIT_TO_NON_SECURE
beq .sm_ret_to_nsec // 同上, 更新SCR.NS 和 SCR.FIQ

/*
* Continue into secure world
*/
add sp, sp, #(SM_CTX_SEC + SM_SEC_CTX_R0)

.sm_exit:
pop {r0-r7}
rfefd sp! // 从异常帧中恢复并递减堆栈指针 //进到非安全世界上下文
END_FUNC sm_smc_entry

在 sec world 中为什么要将 SCR.FIQ 置 0?

而 armv8 中使用 ATF 来处理切换安全世界和非安全世界

ARMv8调用 smc 指令产生安全监控模式调用后,ARM 核会切换到 EL3中,然后读取 MVBAR 寄存器中的异常向量表的基地址来获取异常向量表的内容,并命中安全监控模式调用请求处理函数。

进入 handle_sync_exception 后对触发 sec monitor 调用的世界进行判定, 并设定需要切换到的那个世界的状态并恢复对应的 cpu 上下文.
根据调用 id 进入具体分支, 将 arm 运行模式切换为 EL1 或 EL0, 待 sec monitor 调用处理完毕后,重新进入 el3 继续剩下流程


触发 monitor 调用过程

调用 smc , arm 核进入 el3 中, 从 MVBAR 获取 vec table, 找到 smc 的处理函数.
最后进入到 handle_sync_exception 中, 调用 opteed_smc_handler 函数对该 smc 调用进行处理
判定该 smc 调用是 scr.NS 是来自于安全世界还是非安全世界, 再根据 smc id 决定是否恢复 normal world 的 context

在安全世界状态下触发 smc 后, 处理完请求后, 会再次触发 smc 重新进入 el3, 进入到 TEESMC_OPTEED_RETURN_CALL_DONE 的分支,在该分支中会保存安全世界状态的运行上下文并恢复正常世界状态的运行上下文,然后调用 SMC_RET4返回到正常世界状态中继续运行。

而在非安全世界状态下触发 smc 后, 在 opteed_smc_handler 函数中会调用 is_caller_non_secure 来判定当前安全监控模式调用是来自正常世界状态还是安全世界状态, 如果是来自于非安全世界, 则会保存非安全世界上下文, 恢复安全世界状态下的上下文, 处理调用请求结束后, 恢复非安全世界的上下文, 最后调用 SMC_RET4 返回到非安全世界继续运行.

总结

armv7 对 smc 的处理是在 monitor 状态下完成, 该处理代码集成在了 optee 中.
armv8 对 smc 的处理则是在 ATF 的 bl31阶段(类似 riscv M-mode 的 opensbi), 在 ATF 中 ARM 为兼容不同的 TEE 方案, 提供了集成接口, 各 TEE 方案按照规范完成世界切换.

中断与异常的处理

ARM 核处于安全世界状态(SWS)和正常世界状态(NWS)都具有独立的 VBAR 寄存器和中断向量表。而当 ARM 核处于 Monitor 模式或者 EL3时,ARM 核将具有独立的中断向量表和 MVBAR 寄存器。想实现各种中断在三种状态下被处理的统一性和正确性,就需要确保各种状态下中断向量表以及 GIC 的正确配置。ARM 的指导手册中建议在 TEE 中使用 FIQ,在 ROS 中使用 IRQ,即 TEE 侧会处理由中断引起的 FIQ 事件,而 Linux 内核端将会处理中断引起的 IRQ 事件。而由于 ATF 的使用,Monitor 状态或者 EL3下中断的处理代码将会在 ATF 中实现。(ATF 来处理更高级及跟敏感的中断)

对于 armv7 核, 中断与 arm 核每种状态的关系图:

中断分本地中断和外部中断, FIQ 被 TEE 侧处理, IRQ 被 REE 处理
如果在 monitor (armv7) 或 EL3 (armv8) 阶段产生了中断, 则处于 monitor 或 el3 阶段的软件使用 MVBAR 寄存器 vec table 中处理函数对 FIQ 和 IRQ 进行处理

GIC 寄存器

GIC 模块的寄存器分为中断分发寄存器(GICD) 和 cpu 接口寄存器 (GICC)
GICD 接收所有的中断源, 根据中断优先级判定是否响应中断, 及将该中断信号转发给相应的 cpu
GICC 同各个核连接, 当收到 GICD 的中断信号后, 由 GICC 决定是否将中断请求发给 arm 核

支持安全扩展的 GIC 模块将终端分为两组: group0 和 group1 中断.

  • armv7 上, g0为安全中断, g1 为非安全中断
  • armv8, g0 为安全中断且优先级最高, g1 分安全中断和非安全中断

GIC 根据中断所在 group 安全类型及当前 arm 核运行模式决定发送 FIQ 还是 IRQ.

1
2
3
#define INTR_TYPE_S_EL1      0        // 该中断应该由Secure EL1处理
#define INTR_TYPE_EL3 1 // 该中断应该由EL3处理
#define INTR_TYPE_NS 2 // 该中断应该由Normal World 处理

不同版本 GIC 对于上面的三种类型的中断产生不同的 FIQ 或 IRQ 事件. 再根据 SCR.FIQ 和 SCR.IRQ 位来决定该中断是否会触发进入 EL3 阶段

对于 armv7

前面提出的 armv7 下 sec world 下为什么将 FIQ 置 0, 置 0后, FIQ 中断会直接进入 FIQ 中断入口, 由 optee_os 进行处理, 而不会再进 monitor 的 vec table

armv8的 SCR.FIQ SCR.IRQ

1
2
3
4
5
6
7
* NS:   Non Secure Mode
* 0: Routing Model 0
* 1: Routing Model 1
* 值对应SCR寄存器中的bit[2:0],定义如下
* bit[0]: SCR.NS (0: Secure, 1: Non Secure)
* bit[1]: SCR.IRQ (0: enter IRQ mode 1: enter EL3 monitor)
* bit[2]: SCR.FIQ (0: enter FIQ mode 1: enter EL3 monitor)

GICv2设定Group0为安全中断,Group1为非安全中断。中断号属于哪个 Group 是由其在 GICD_IGROUPRn 寄存器中的值来决定的。当 GIC 接收到中断信号后,如果中断属于 Group0则发送 IRQ 信号到目标 CPU,中断属于 Group1则发送 FIQ 信号到目标 CPU。

与 GICv2相比 GICv3的主要改进有以下几点:

  • 在软件中断 (SGI) 方面新增中断路由模式(affinity rounting), 能支持更大范围的 cpu id
  • GICv3 对 Group1 的中断类型进一步细分. Group0 还是为安全中断且有最高优先级, 而 Group1 中断分为非安全中断 (GINS) 和安全中断 (GIS)
  • 在 FIQ/IRQ 都使能时, 属于 Group0的中断始终触发 FIQ, 属于 Group1的中断根据当前 cpu 工作模式和中断类型分别触发 FIQ 或 IRQ
当前处理器模式 Group0 Group1 Group1
G1S G1NS
Secure EL1/EL0 FIQ IRQ FIQ
Non Secure EL1/EL0 FIQ FIQ IRQ
Secure EL3 FIQ FIQ FIQ

异常向量

REE/TEE/monitor (v7) 或 EL3 (v8) 都可以接收中断, 存在两个 VBAR 和一个 MVBAR

  • REE VBAR 存放 linux 内核的异常 vec table
  • TEE VBAR 存放 tee 的异常 vec table
  • monitor 或 EL3 MVBAR 存放其自己的 vec table

ARMv7中 Monitor 模式的异常向量表

Monitor模式属于安全世界状态,用于实现ARM核安全世界状态与正常世界状态之间的切换,且该模式具有独立的中断向量表。使用MVBAR寄存器来保存该运行模式的中断向量表的基地址。在OPTEE初始化过程中会调用sm_init函数来初始化Monitor模式的配置,并将Monitor模式的中断向量基地址写入到MVBAR寄存器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
FUNC sm_init , :
UNWIND( .fnstart)
mrs r1, cpsr //设置Monitor模式使用的栈
cps #CPSR_MODE_MON
sub sp, r0, #(SM_CTX_SIZE - SM_CTX_NSEC)
msr cpsr, r1
//将Monitor模式的异常向量表地址保存到r0寄存器
ldr r0, =sm_vect_table
//将Monitor模式的异常向量表基地址写入MVBAR寄存器中
write_mvbar r0
bx lr // 返回
END_FUNC sm_init

UNWIND( .fnstart)
UNWIND( .cantunwind)
b . /* 重启操作 */
b . /* 未定义指令操作 */
b sm_smc_entry /* smc异常处理函数 */
b . /* 执行时的abort操作 */
b . /* 数据abort操作 */
b . /* 预留 */
b . /* IRQ事件 */
b sm_fiq_entry /* FIQ中断处理入口函数 */
UNWIND( .fnend)
END_FUNC sm_vect_table

当在 Monitor 模式下接收到 FIQ 中断时,系统将会调用 sm_fiq_entry 函数对该 FIQ 中断进行处理

ARMv8中 EL3阶段的异常向量表

ARMv8使用 ATF 中的 bl31作为 EL3阶段的代码,其作用与 ARMv7中 Monitor 模式下运行的代码作用一致。在 ATF 的启动过程中,bl31通过调用 el3_entrypoint_common 函数来进行 EL3运行环境的初始化,在初始化过程中会执行 EL3阶段异常向量表的初始化,EL3的异常向量表的基地址为 runtime_exception_vectors。EL3异常向量表的内容如下:

从异常向量表来看,ARMv8架构中不管是 AArch32还是 AArch64,当在 EL3阶段产生了 FIQ 事件或者 IRQ 事件后,bl31将会调用 handle_interrupt_exception 宏来处理,该宏使用的参数就是产生的异常的标签。

OP-TEE 异常向量的配置

OP-TEE 异常向量的加载和配置会通过执行 thread_init_vbar 函数来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// aarch32
FUNC thread_init_vbar , :
/* Set vector (VBAR) */
write_vbar r0
bx lr
END_FUNC thread_init_vbar
DECLARE_KEEP_PAGER thread_init_vbar

// aarch64
FUNC thread_init_vbar , :
msr vbar_el1, x0
ret
END_FUNC thread_init_vbar
DECLARE_KEEP_PAGER thread_init_vbar

系统会到 VBAR 寄存器中获取 OP-TEE 的异常向量表基地址,然后根据异常类型获取到 FIQ 或 IRQ 事件的处理函数,并对不同的事件进行处理。针对不同的事件会调用线程向量表 thread_vector_table 变量中对应的处理函数来完成对该异常事件的处理。

thread_vector_table

optee 中会定义一个用于保存各种事件处理函数的线程向量表, 对 fast_smc std_smc FIQ cpu 关闭打开及系统关机和重启事件的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FUNC thread_vector_table , : , .identity_map
UNWIND( .cantunwind)
b vector_std_smc_entry
b vector_fast_smc_entry
b vector_cpu_on_entry
b vector_cpu_off_entry
b vector_cpu_resume_entry
b vector_cpu_suspend_entry
b vector_fiq_entry
b vector_system_off_entry
b vector_system_reset_entry
END_FUNC thread_vector_table
DECLARE_KEEP_PAGER thread_vector_table
#endif /*if defined(CFG_WITH_ARM_TRUSTED_FW)*/

armv8 下, 该线程向量表的地址会返回给 bl31, 当 EL3 阶段收到 smc 调用或者 FIQ 事件时可转到该 vec table 中对应的处理函数进行处理
armv7 下, 该变量被 monitor 模式下的程序(还是 optee)使用, 进而跳转到 vec table 对应的函数中处理

FIQ 处理

armv7


armv8

OP-TEE 对 FIQ 事件的处理

OP-TEE 启动时会调用 thread_init_vbar 函数来完成安全世界状态(SWS)的中断向量表的初始化,且在 GIC 中配置 FIQ 在安全世界状态时才有效。所以在安全世界状态中产生了 FIQ 事件时,CPU 将直接通过 VBAR 寄存器查找到中断向量表的基地址,并命中 FIQ 的处理函数。整个处理过程如图所示。

OP-TEE 对 IRQ 事件的处理

IRQ 事件一般由 REE 处理. 但当处于安全世界时, 系统产生的 IRQ 事件又不能丢弃. OPTEE 还是需要将 irq 转出来发给 REE

当系统在 ARM 核处于安全世界状态中产生 IRQ 事件时,系统通过 VBAR 寄存器获取到中断向量表的基地址,然后查找到 IRQ 对应的中断处理函数—— thread_irq_handler,使用该函数处理 IRQ 事件,整个处理过程的流程如图所示。

optee 收到 irq 后, armv7 中通过切换到 monitor 模式将该 irq 事件发送到 ree 侧进行处理
armv8 架构中 irq 中断会切换到 EL3 将该事件交给 REE 进行处理

完成了 IRQ 事件处理后, 会通过 smc 重新切回 monitor/EL3, 恢复安全世界状态被中断的线程

总结

armv7 安全世界状态包含 monitor 和 optee, 其中 monitor 的代码是包含在 optee_os 下的. 而在 armv8下安全世界包括 el3阶段的代码和 optee, el3阶段的代码在 ATF 的 bl31 中.
armv7 monitor 模式下运行的代码用来处理 fiq, armv8 在 el3阶段收到的 fiq 由 bl31 阶段的代码进行处理.
在安全世界中收到的 fiq 都是由 optee 的线程向量表中的 thread_fiq_handler 处理的
在安全世界中收到的 irq 都是由 optee 的线程向量表中的 thread_irq_handler 处理的, 转给 monitor/el3 进行转发, 最终转发给 REE 处理, 处理完成后, REE 需要发 smc 返回到 monitor/el3, 再进入到 optee 中恢复被 irq 打断的任务状态.