在一个完整的系统中都会存在中断,如果支持trustzone并且具有TEE的支持,则每个CPU将具有三种状态:secure world, non-sercure world, monitor态,因此考虑到CPU各种状态下如何处理系统产生的中断并且保证中断的隔离就变得较复杂。
在ARM中secure world和non-secure world都具有独立的VBAR寄存器和中断向量表。而当CPU处于monitor态时,CPU将具有独立的中断向量表和MVBAR寄存器。如何实现各种中断在三种状态处理的统一性和正确性就需要确保各种状态下中断向量表的正确配置以及GIC的正确配置。在ARM的指导手册中建议在TEE中使用FIQ,在ROS中使用IRQ,也即是TEE端将会处理FIQ来的中断,而在linux kernel端将会处理IRQ的中断。而由于ATF(ARM Trusted Firmware)的使用,monitor状态下的处理代码将会在ATF中得到实现。所以本文将分两种情况讨论整个系统中断的配置和向量表的配置。
针对每个CPU,中断与每种CPU状态的关系图如下:
1. GIC的配置
在TEE启动的时候会对GIC进行配置,主要实现是规定FIQ信号属于secure world处理,IRQ将会由non-secure world处理。初始化操作的调用过程如下:
_start--> b reset-->b reset_primary-->bl generic_boot_init_primary-->init_primary_helper-->main_init_gic-->gic_init
在初始化阶段,最终会调用gic_init函数来完成GIC的配置,该函数的内容如下:
void gic_init(struct gic_data *gd, vaddr_t gicc_base __maybe_unused,
vaddr_t gicd_base)
{
size_t n;
/* 设置gic_data结构体中的成员,主要是标记该变量中配置的地址等相关信息 */
gic_init_base_addr(gd, gicc_base, gicd_base);
/* 使用loop循环配置每个CPU对应的GIC的内容 */
for (n = 0; n <= gd->max_it / NUM_INTS_PER_REG; n++) {
/* Disable interrupts */
/* 关闭中断 */
write32(0xffffffff, gd->gicd_base + GICD_ICENABLER(n));
/* Make interrupts non-pending */
write32(0xffffffff, gd->gicd_base + GICD_ICPENDR(n));
/* Mark interrupts non-secure */
/* 配置non-secure world的中断处理标记 */
if (n == 0) {
/* per-CPU inerrupts config:
* ID0-ID7(SGI) for Non-secure interrupts
* ID8-ID15(SGI) for Secure interrupts.
* All PPI config as Non-secure interrupts.
*/
write32(0xffff00ff, gd->gicd_base + GICD_IGROUPR(n));
} else {
write32(0xffffffff, gd->gicd_base + GICD_IGROUPR(n));
}
}
/* Set the priority mask to permit Non-secure interrupts, and to
* allow the Non-secure world to adjust the priority mask itself
*/
#if defined(CFG_ARM_GICV3)
write_icc_pmr(0x80);
write_icc_ctlr(GICC_CTLR_ENABLEGRP0 | GICC_CTLR_ENABLEGRP1 |
GICC_CTLR_FIQEN);
#else
write32(0x80, gd->gicc_base + GICC_PMR);
/* Enable GIC */
write32(GICC_CTLR_ENABLEGRP0 | GICC_CTLR_ENABLEGRP1 | GICC_CTLR_FIQEN,
gd->gicc_base + GICC_CTLR);
#endif
write32(read32(gd->gicd_base + GICD_CTLR) | GICD_CTLR_ENABLEGRP0 |
GICD_CTLR_ENABLEGRP1, gd->gicd_base + GICD_CTLR);
}
2.中断向量表的配置
在初始化阶段TEE的中断向量的加载和配置将会通过thread_init_vbar函数来实现,从初始化起始到配置中断向量表的整个调用过程如下:
_start--> b reset-->b reset_primary-->bl generic_boot_init_primary-->init_primary_helper-->thread_init_per_cpu-->thread_init_vbar
thread_init_vbar函数为汇编代码内容如下,定义在optee_os_core/arch/arm/kernel/thread_a32.S文件中:
FUNC thread_init_vbar , :
UNWIND( .fnstart)
/* Set vector (VBAR) */
ldr r0, =thread_vect_table //将中断向量表的地址赋值给r0寄存器
write_vbar r0//将r0寄存器指定的地址中的内容写入到VBAR寄存器中来设定TEE的中断向量表
bx lr //跳转返回
UNWIND( .fnend)
END_FUNC thread_init_vbar
而thread_vect_table向量表的内容如下,定义在optee_os_core/arch/arm/kernel/thread_a32.S文件中:
LOCAL_FUNC thread_vect_table , :
UNWIND( .fnstart)
UNWIND( .cantunwind)
b . /* Reset */
b thread_und_handler /* Undefined instruction */
b thread_svc_handler /* System call */
b thread_pabort_handler /* Prefetch abort */
b thread_dabort_handler /* Data abort */
b . /* Reserved */
b thread_irq_handler /* IRQ */
b thread_fiq_handler /* FIQ */
UNWIND( .fnend)
END_FUNC thread_vect_table
从上述中断向量表中可以知道,在TEE的中断向量表其实也定义了IRQ的处理函数thread_irq_handler ,但是该处理函数最终将会把中断让ROS(rich OS)来处理。
3.TEE中中断处理函数的配置
当中断向量表配置完成之后,那具体的中断才处理函数是什么呢?当有FIQ产生时跳转到thread_fiq_handler,中断是如何被实际处理的呢。
3.1handler变量
在OP-TEE的初始化过程中有一个很重要的变量handler.该变量在vexpress板级中被定义在optee_os/core/arch/arm/plat-vexpress/main.c文件中,其具体内容如下:
static const struct thread_handlers handlers = {
.std_smc = tee_entry_std, //smc处理函数
.fast_smc = tee_entry_fast, //快速smc处理函数
.nintr = main_fiq,
#if defined(CFG_WITH_ARM_TRUSTED_FW)
.cpu_on = cpu_on_handler, //CPU on处理函数
.cpu_off = pm_do_nothing, //CPU off处理函数
.cpu_suspend = pm_do_nothing, //CPU suspend处理函数
.cpu_resume = pm_do_nothing, //CPU resume处理函数
.system_off = pm_do_nothing, //系统挂掉时的处理函数
.system_reset = pm_do_nothing, //系统重启的处理函数
#else
.cpu_on = pm_panic,
.cpu_off = pm_panic,
.cpu_suspend = pm_panic,
.cpu_resume = pm_panic,
.system_off = pm_panic,
.system_reset = pm_panic,
#endif
};
上面的变量中就是在TEE中处理各种系统情况的处理函数。该变量在初始化的时候初始化系统thread被赋值给各种thread的具体处理变量。
3.2 thread处理函数指针的赋值
在初始化过程中会调用init_handlers来完成FIQ中断处理函数的指针的赋值以及其他thread情况的处理函数指针的赋值。init_handler函数的调用过程如下:
_start--> b reset-->b reset_primary-->bl generic_boot_init_primary-->init_primary_helper-->thread_init_primary-->init_handlers
init_handlers函数的内容如下,定义在optee_os/core/arch/arm/kernel/thread.c文件中:
static void init_handlers(const struct thread_handlers *handlers)
{
thread_std_smc_handler_ptr = handlers->std_smc;
thread_fast_smc_handler_ptr = handlers->fast_smc;
thread_nintr_handler_ptr = handlers->nintr;
thread_cpu_on_handler_ptr = handlers->cpu_on;
thread_cpu_off_handler_ptr = handlers->cpu_off;
thread_cpu_suspend_handler_ptr = handlers->cpu_suspend;
thread_cpu_resume_handler_ptr = handlers->cpu_resume;
thread_system_off_handler_ptr = handlers->system_off;
thread_system_reset_handler_ptr = handlers->system_reset;
}
在TEE中,如果产生FIQ,CPU将查找VBAR寄存器中存放的中断向量表,命中中断处理函数,最终调用到thread_fast_smc_handler_ptr 执行的函数来进行处理
3.3带ATF的thread vector table的返回
当系统使用了ATF的时候,为保证monitor态与secure world态中断处理的一致性,而带ATF时monitor的代码是运行在bl31中的,所以在TEE的初始化阶段,完成thread vector table的配置后需要将thread vector table的内容告知bl31,以便保证monitor态与secure world态中FIQ和其他状况处理的一致性,且减少了bl31中对相关情况的处理的配置。返回thread vector table的值到bl31是在TEE初始化时调用generic_boot_init_primary函数来实现的,内容如下,注意在不带ATF情况下,该函数是不需要返回值的。
struct thread_vector_table *
generic_boot_init_primary(unsigned long pageable_part, unsigned long u __unused,
unsigned long fdt)
{
init_primary_helper(pageable_part, PADDR_INVALID, fdt);
return &thread_vector_table;
}
那thread vector table在哪里?他是如何与3.2中的thread_xxx_xx关联起来的呢?在OP-TEE中定义了thread_vector_table变量,该变量被定义成一个向量表,内容如下,定义在optee_os_core/arch/arm/kernel/thread_a32.S文件中:
FUNC thread_vector_table , :
UNWIND( .fnstart)
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
UNWIND( .fnend)
END_FUNC thread_vector_table
当在monitor中发生FIQ或者其他情况,monitor态下的CPU将会切换好状态之后,然后查询该向量表来让TEE处理FIQ中断或者其他情况。以FIQ为例,当monitor下发生了FIQ时,cpu切换到secure world态之后会调用该向量表中的vector_fast_smc_entry函数进行处理。而该函数定义在optee_os_core/arch/arm/kernel/thread_a32.S文件中,内容如下:
LOCAL_FUNC vector_fast_smc_entry , :
UNWIND( .fnstart)
UNWIND( .cantunwind)
push {r0-r7}
mov r0, sp
bl thread_handle_fast_smc
pop {r1-r8}
ldr r0, =TEESMC_OPTEED_RETURN_CALL_DONE
smc #0
b . /* SMC should not return */
UNWIND( .fnend)
END_FUNC vector_fast_smc_entry
该函数执行的时候会调用thread_handle_fast_smc来进行FIQ事件的处理,而thread_handle_fast_smc执行的函数就是在init_handlers时被赋值的,即调用tee_entry_fast来完成真正的FIQ处理