前面我学习了线程方面的东西,这个假期,空闲了来看看《手机安全和可信应用开发指南》这本书的中断篇。
中断处理
一个完整的系统都会存在中断,ARMv7架构扩展出了Monitor模式而ARMv8使用EL的方式对ARM异常运行模式进行了重新定义,分为EL0~EL3。
在ARMv8架构系统中,OP-TEE运行于安全侧的EL1, bl31运行于EL3。系统运行过程中任何阶段都有可能会产生外部中断。本章将主要介绍FIQ事件和IRQ事件在OP-TEE、ARMv7架构中的Monitor模式、ARMv8架构中的EL3的处理过程。
(前面其实如果你读过这本书的前面,会知道我们对于通过SMC的方式去陷入到EL3的方式与处理流程以及有所认识,但是大多数时候都避开了中断的方式。这里好好关注一下中断的方式。其次在ARMv8的架构体系中,EL3是拉通的,而EL2到EL0是分为安全与非安的。当然EL3肯定是安全的。)
1 系统的中断处理
ARM核处于安全世界状态(SWS)和正常世界状态(NWS)都具有独立的VBAR寄存器和中断向量表。
VBAR(Vector Base Address Register)的寄存器有:如果是aarch64:
VBAR_EL1
VBAR_EL2
VBAR_EL3如果是aarch32
VBAR
HVBAR
MVBAR
(VBAR(Vector Base Address Register)就是寄存器基地址)
而当ARM核处于Monitor模式或者EL3时,ARM核将具有独立的中断向量表和MVBAR寄存器。
(secure 和 non-secure state 的切换必须经过 monitor mode,而 cpu mode 的切换必须又异常触发,那么触发进入 monitor mode 的异常处理函数应该在哪里配置呢?处于安全考虑,显然不能和 kernel OS 共用一套 vector table,因此一个新的寄存器: MVBAR (Monitor Vector Base Address Register)由此引入。)
因此这里是三种中断,想实现各种中断在三种状态下被处理的统一性和正确性,就需要确保各种状态下 中断向量表以及GIC的正确配置。
RM的指导手册中建议在TEE中使用FIQ,在ROS中使用IRQ,即TEE侧会处理由中断引起的FIQ事件,而Linux内核端将会处理中断引起的IRQ事件。
而由于ATF的使用,Monitor状态或者EL3下中断的处理代码将会在ATF中实现。
针对ARM核,中断与ARM核每种状态的关系图如图12-1所示。
系统中的中断主要被分为Native Interrupt(内部中断)和Foreign Interrupt(外部中断)事件,FIQ会被TEE侧处理,IRQ会被REE侧处理。
如果在Monitor模式或EL3阶段产生了中断,则处于Monitor模式或者EL3的软件会使用MVBAR寄存器中保存的异常向量表中的处理函数对FIQ或者IRQ事件进行处理。
2 中断控制器
中断控制器(General Interruption Controller, GIC)模块是CPU的外设之一,它的作用是接收来自其他外设的中断引脚输入,然后根据中断触发模式、中断类型优先级等 设置来控制发送不同的中断信号到CPU。
(这就是通过GIC对外设的中断进行了统一的管理)
ARM对GIC的架构也在不断改进,已经从GICv1发展到现在的GICv4版本。目前主要使用的是GICv2和GICv3架构。(书里面介绍在支持TEE安全扩展的ARM处理器平台上这两个版本的中断控制器是如何工作的。)
2.1 GIC寄存器
GIC模块中的寄存器主要分为**中断控制分发寄存器(缩写为GICD)以及CPU接口寄存器(缩写为GICC)**两部分。
GICD接收所有的中断源,然后根据中断的优先级来判定是否响应中断,以及是否将该中断信号转发到对应的CPU。
GICC和各个ARM核相连。当收到来自GICD的中断信号时,由GICC来决定是否将中断请求发送给ARM核。
支持安全扩展的GIC模块将中断分为了两组:Group0中断和Group1中断。
对于ARMv7架构,Group0为安全中断,Group1为非安全中断。
对于ARMv8架构,Group0为安全中断且有最高的优先级,而Group1又分安全中断(Group1Secure, G1S)和非安全中断(Group1 NonSecure,G1NS)。
GIC会根据中断所在的Group安全类型及当前ARM核运行模式来决定是发送FIQ还是IRQ信号到ARM核。
根据GIC版本的不同其决定方式也不同。关于这点将在接下来的章节分开介绍。
另外,当ARM核收到FIQ/IRQ信号后会进入哪种模式是由SCR寄存器来决定的。
ARMv8架构中,OP-TEE根据中断要求触发的模式将中断类型分为三类,其定义如下:
#define INTR_TYPE_S_EL1 0 // 该中断应该由Secure EL1处理#define INTR_TYPE_EL3 1 // 该中断应该由EL3处理#define INTR_TYPE_NS 2 // 该中断应该由Normal World处理
不同版本的GIC对于以上三种类型的中断将会产生不同的IRQ或FIQ事件,故需要先根据GIC版本来确定上述三种类型的中断所产生的是IRQ还是FIQ事件,然后再设定SCR寄存器中SCR.FIQ和SCR.IRQ位来决定该中断是否会触发ARM核进入EL3阶段。
2.2 ARMv7 SCR寄存器的设定
ARMv7架构中,SCR寄存器中的值是在optee_os/core/arch/arm/sm/sm_a32.S文件被设定,其内容大家阅读书籍,这里我的关注点主要在ARMv8上。
2.3 ARMv8 SCR寄存器的设定
首先ATF在bl31/interrupt_mgmt.h下分别定义了Secure EL1、NonSecure以及EL3模式下Group0和Group1中断的路由模式。
/* 以下分别定义了在EL3/SEL1/NS模式下中断的路由模式,定义名称格式如下* RM: Routing Model* SEL1: Secure EL1 Mode(optee os)* 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)*//* 从NS进入EL3.并在安全态的EL1进行处理 */#define INTR_SEL1_VALID_RM0 0x2/* 从NS或者安全态进入EL3 */#define INTR_SEL1_VALID_RM1 0x3/* 从NS进入EL1/EL2并转切到安全态的EL1 */#define INTR_NS_VALID_RM0 0x0/* 从NS到EL1/EL2或从安全态进入EL3#define INTR_NS_VALID_RM1 0x1/* 从NS进入EL3,并进入安全态的EL1,最终进入EL3 */#define INTR_EL3_VALID_RM0 0x2/* 从NS或安全态进入EL3 */#define INTR_EL3_VALID_RM1 0x3/* 默认模式转移路径 */#define INTR_DEFAULT_RM 0x0
为兼容GICv2和G ICv3平台,在初始化CPU时将IRQ和FIQ位同时设置为1,设置相关代码如下:
void css_cpu_standby(plat_local_state_t cpu_state){unsigned int scr;assert(cpu_state == ARM_LOCAL_STATE_RET);scr = read_scr_el3();/*对于非安全中断,如果当前CPU运行在EL3,对于GICv3平台非安全Group1中断会触发FIQ中断,而对于 GICv2平台Group1中断会触发IRQ中断,这里同时将FIQ和IRQ位设置成1所以GICv2/v3平台的非安全中断都会进入EL3 */write_scr_el3(scr | SCR_IRQ_BIT | SCR_FIQ_BIT);isb();dsb();//等待非安全中断触发wfi();//恢复SCR寄存器的原始值write_scr_el3(scr);}
CPU初始化过程中会调用register_interrupt_type_handler来设定Secure EL1下的SCR寄存器,其内容如下:
case TEESMC_OPTEED_RETURN_ENTRY_DONE:assert(optee_vectors == NULL);optee_vectors = (optee_vectors_t *) x1;if (optee_vectors) {set_optee_pstate(optee_ctx->state, OPTEE_PSTATE_ON);//OP-TEE初始化成功,安装psci处理函数psci_register_spd_pm_hook(&opteed_pm);//设置flag为ON_SECURE定义为1flags = 0;set_interrupt_rm_flag(flags, NON_SECURE);//设定进入Secure EL1状态时SCR应使用的值rc = register_interrupt_type_handler(INTR_TYPE_S_EL1,opteed_sel1_interrupt_handler, flags);if (rc)panic();
register_interrypt_type_handler函数会调用set_routing_model来定义三种不同目标的中断在EL3和EL1的SCR寄存器的值,该函数内容如下:
int32_t set_routing_model(uint32_t type, uint32_t flags){int32_t rc;rc = validate_interrupt_type(type);if (rc)return rc;/* 检查将要设定的SCR的值是否是之前interrupt_mgmt.h中预定义的有效值 */rc = validate_routing_model(type, flags);if (rc)return rc;/* 结构体变量intr_type_descs用来描述安全/正常模式下SCR的设定 */intr_type_descs[type].flags = flags;/* 设置在CPU安全模式下(SCR.NS=0)的SCR.IRQ、SCR.FIQ位 */set_scr_el3_from_rm(type, flags, SECURE);/* 设置在CPU正常模式下(SCR.NS=1)的SCR.IRQ、SCR.FIQ位 */set_scr_el3_from_rm(type, flags, NON_SECURE);return 0;}set_scr_el3_from_rm函数实现如下:static void set_scr_el3_from_rm(uint32_t type, uint32_t interrupt_type_flags,uint32_t security_state){uint32_t flag, bit_pos;/** 这里根据security_state状态来获取对应SCR要设定的值* 如果之前调用的是set_interrupt_rm_flag(flags, NON_SECURE)* 1. security_state == SECUREflag = (0xb10 >> SECURE) & 0xb1 = 0* 2. security_state == NONSECURE: flag = (0xb10 >> NONSECURE) & 0xb1 = 1* 如果之前调用的是set_interrupt_rm_flag(flags, SECURE) 同理* 1. security_state == SECURE: flag = 1* 2. security_state == NONSECURE: flag = 0*/flag = get_interrupt_rm_flag(interrupt_type_flags, security_state);/* 这个函数根据GIC的版本决定设定SCR寄存器中FIQ/IRQ位 */bit_pos = plat_interrupt_type_to_line(type, security_state);intr_type_descs[type].scr_el3[security_state] = flag << bit_pos;/* 如果当前上下文有效则可以在这里直接更新scr_el3否则将要设定的SCR的值保存在* intr_type_descs中,之后通过get_scr_els3_from_routing_model()函数来获取并* 写入SCR寄存器中*/if (cm_get_context(security_state))cm_write_scr_el3_bit(security_state, bit_pos, flag);}