在中断流程举例的章节中,图中第1步骤中,给REE的SCR.FIQ=1, 在normal EL0/EL1或EL3时来了一个secure group1的中断,该中断将会被标记为FIQ,target到EL3,然后在EL3的软件逻辑,会将CPU切换到TEE进行处理…
以上的流程,最近又被小伙伴们问起,那么我们贴下相关文档和代码看下吧。
中断为何被标记FIQ?
看gicv3的官方文档,可知当cpu处于non-secure EL0/1/2时,来了一个secure group1中断,该中断将被标记为FIQ;当cpu处于EL3时,来了一个secure group1中断,也是被标记为FIQ
那么中断为什么target到EL3?
从armv8的官方文档中的SCR_EL3寄存器中,可以看到,当SCR_EL3.FIQ=1时,来了一个FIQ,该FIQ会直接target到EL3。
或者去看armv8文档中的rounting表也行,由下图可以看到,当SCR寄存器的EA或IRQ或FIQ等于1时,这个配置下,cpu在EL0/EL1/EL3产生的EA/IRQ/FIQ异常都将直接target到EL3
那么FIQ target到EL3之后,意味着什么?
意味着CPU将跳转到VBAR_EL3 + offset_fiq, 我们知道向量表有4个FIQ,那么跳转到哪一个呢?如下图所示,标出了cpu在EL3时来的FIQ、cpu在EL0/EL1时来的FIQ跳转的偏移
硬件介绍完了,开始撸代码
//这一段一段未实现,猜测,cpu在EL3时,PSTATE.F是disabled,中断不会被PE acknowledged,中断pendind状态
[思考]-ATF中异常向量表为何没有实现“Current Exception level with SP_ELx, x>0.“
.align 7
fiq_sp_elx:
bl report_unhandled_interrupt
check_vector_size fiq_sp_elx
这一段,是cpu在EL0/1时,产生的target到EL3的FIQ的向量表偏移
.align 7
fiq_aarch64:
handle_interrupt_exception fiq_aarch64
check_vector_size fiq_aarch64
handle_interrupt_exception的具体实现如下
.macro handle_interrupt_exception label
/* Enable the SError interrupt */
msr daifclr, #DAIF_ABT_BIT
str x30, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_LR]
bl save_gp_registers
/*
* Save the EL3 system registers needed to return from
* this exception.
*/
mrs x0, spsr_el3
mrs x1, elr_el3
stp x0, x1, [sp, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3]
/* Switch to the runtime stack i.e. SP_EL0 */
ldr x2, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
mov x20, sp
msr spsel, #0
mov sp, x2
/*
* Find out whether this is a valid interrupt type. If the
* interrupt controller reports a spurious interrupt then
* return to where we came from.
*/
bl plat_ic_get_pending_interrupt_type
cmp x0, #INTR_TYPE_INVAL
b.eq interrupt_exit_\label
/*
* Get the registered handler for this interrupt type. A
* NULL return value could be 'cause of the following
* conditions:
*
* a. An interrupt of a type was routed correctly but a
* handler for its type was not registered.
*
* b. An interrupt of a type was not routed correctly so
* a handler for its type was not registered.
*
* c. An interrupt of a type was routed correctly to EL3,
* but was deasserted before its pending state could
* be read. Another interrupt of a different type pended
* at the same time and its type was reported as pending
* instead. However, a handler for this type was not
* registered.
*
* a. and b. can only happen due to a programming error.
* The occurrence of c. could be beyond the control of
* Trusted Firmware. It makes sense to return from this
* exception instead of reporting an error.
*/
bl get_interrupt_type_handler
cbz x0, interrupt_exit_\label
mov x21, x0
mov x0, #INTR_ID_UNAVAILABLE
/* Set the current security state in the 'flags' parameter */
mrs x2, scr_el3
ubfx x1, x2, #0, #1
/* Restore the reference to the 'handle' i.e. SP_EL3 */
mov x2, x20
/* x3 will point to a cookie (not used now) */
mov x3, xzr
/* Call the interrupt type handler */
blr x21
interrupt_exit_\label:
/* Return from exception, possibly in a different security state */
b el3_exit
.endm
剖析下上述代码,在get_interrupt_type_handler,其实是获取SPD(Secure Payload Dispatcher,何为SPD,请点击这里)初始化时注册的中断。
获取到中断handler后,handler地址返回给X0,又拷贝给X21,最后又blr x21,跳转到handler函数
bl get_interrupt_type_handler
cbz x0, interrupt_exit_\label
mov x21, x0
/* Call the interrupt type handler */
blr x21
那么我们在看看handler函数里都干了什么,我的代码环境使用的optee, 我们就看下SPD=opteed的情况,在ATF代码的opteed_main.c中,如下代码所示,其实就是保存下non-secure寄存器,恢复secure寄存器,然后跳转到secure的线程向量表中。
注意这个线程向量表optee_vectors是在optee中软件定义的,然后又将物理地址传到了ATF的全局变量中。
static uint64_t opteed_sel1_interrupt_handler(uint32_t id,
uint32_t flags,
void *handle,
void *cookie)
{
uint32_t linear_id;
optee_context_t *optee_ctx;
/* Check the security state when the exception was generated */
assert(get_interrupt_src_ss(flags) == NON_SECURE);
/* Sanity check the pointer to this cpu's context */
assert(handle == cm_get_context(NON_SECURE));
/* Save the non-secure context before entering the OPTEE */
cm_el1_sysregs_context_save(NON_SECURE);
/* Get a reference to this cpu's OPTEE context */
linear_id = plat_my_core_pos();
optee_ctx = &opteed_sp_context[linear_id];
assert(&optee_ctx->cpu_ctx == cm_get_context(SECURE));
cm_set_elr_el3(SECURE, (uint64_t)&optee_vectors->fiq_entry);
cm_el1_sysregs_context_restore(SECURE);
cm_set_next_eret_context(SECURE);
/*
* Tell the OPTEE that it has to handle an FIQ (synchronously).
* Also the instruction in normal world where the interrupt was
* generated is passed for debugging purposes. It is safe to
* retrieve this address from ELR_EL3 as the secure context will
* not take effect until el3_exit().
*/
SMC_RET1(&optee_ctx->cpu_ctx, read_elr_el3());
}
至此,你以为讲完了吗,
再补充一点在gicv3文档中1020和1021中断号的描述
我们应该会用到1020,那么用在哪里的呢?请看上述汇编代码bl plat_ic_get_pending_interrupt_type的具体实现:
uint32_t plat_ic_get_pending_interrupt_type(void)
{
unsigned int irqnr;
assert(IS_IN_EL3());
irqnr = gicv3_get_pending_interrupt_type();
switch (irqnr) {
case PENDING_G1S_INTID:
return INTR_TYPE_S_EL1;
case PENDING_G1NS_INTID:
return INTR_TYPE_NS;
case GIC_SPURIOUS_INTERRUPT:
return INTR_TYPE_INVAL;
default:
return INTR_TYPE_EL3;
}
}
其实就是在读取pending的中断号,看看有没有1020或1021,从而获得此次的中断是从secure或non-secure过来的,还是在EL3产生的。然后走相应的逻辑。