接上文,本文继续介绍Linux软件部分逻辑。
参考内核版本:kernel-4.19
目录
1.中断信号在各级中断控制器中的应答
2.supports_deactivate_key意义
3.中断嵌套
1.中断信号在各级中断控制器中的应答
本章主要从内核软件层面来看各中断控制器对中断信号处理&应答(关于硬件层面的处理硬件过程可以参考前文)。同样使用前文中GICv3 & PIO 中断控制器为硬件框架,介绍PIO接受到外部中断时软件层各控制器的应答(文中设定supports_deactivate_key 为false)。
如下图触发中断(EL1 、SPI类型)后软件通过操作硬件中断控制器对中断应答操作(卷尺框图):
#1.读取GICv3中断控制器的IAR(中断应答寄存器)获取GIC的硬件中断号
#2.通过PIO中断控制器mask&ack对应中断;
#3.通过PIO中断控制器unmask对应中断;
#4.操作GICv3中断控制器EOI寄存器,标记该GIC中断为deactivted并回退GIC的中优先级
从 #2、#3代码上逻辑上看一推出:
a.因在执行该中断的处理handle时,PIO中断控制器上mask该中断故认为该中断在该CPU不 能实现中断嵌套;
b.对于GIC已经通过IAR寄存器响应了来自PIO的中断,则处理该中断的CPU不能相应低于PIO 中断优先级的中断;
2.supports_deactivate_key意义
supports_deactivate_key 是用指示GIC采用 ICC_CTLR_ELn.EOImode的形式(mode0或者mode1),值为true时表示采用mode1即EOI寄存器被置位时只是回退到该中断处理的前的优先级、需要再操作DIR寄存器将当前中断状态切换到非激活状态,值为false时表示采用mode0即操作EOI寄存器就会完成优先级回退和切换该中断到非激活状态。GICv3的驱动代码可以明确体现上述逻辑:
static void gic_cpu_sys_reg_init(void)
{
…
if (static_branch_likely(&supports_deactivate_key)) {
/*当supports_deactivate_key使能时配置为EOI为mode1形式*/
gic_write_ctlr(ICC_CTLR_EL1_EOImode_drop);
} else {
/*当supports_deactivate_key使能时配置为EOI为mode0形式*/
gic_write_ctlr(ICC_CTLR_EL1_EOImode_drop_dir);
}
…
}
supports_deactivate_key的值与hyp mode(Arm的虚拟化模式),默认值为ture当判定hyp mode未使能时则设置该置为false。代码逻辑如下:
static int __init gic_init_bases(void __iomem *dist_base,
struct redist_region *rdist_regs,
u32 nr_redist_regions,
u64 redist_stride,
struct fwnode_handle *handle)
{
…
/*hyp mode未使能,设置supports_deactivate_key为false*/
if (!is_hyp_mode_available())
static_branch_disable(&supports_deactivate_key);
/*supports_deactivate_key值为true时,表示EOL & Deactivate 分开处理*/
if (static_branch_likely(&supports_deactivate_key))
pr_info("GIC: Using split EOI/Deactivate mode\n");
…
}
3.中断嵌套
据说是从kernel-2.6.34开始不支持。其实内核中有很多代码都可以证明不支持中断嵌套处理逻辑,如下便是一例作证:
static int __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
/*
* request_irq() -> __setup_irq()
* 如下当irq设置 IRQ_NESTED_THREAD 支持嵌套中断属性时,如果注册的中断处理无thread_fn
* 函数时支持返回中断注册EINVAL错误.
* 如果有注册thread_fn则将中断handle处理函数设置为irq_nested_primary_handler,
* 因为irq_nested_primary_handler函数直接返回IRQ_NONE,则thread_fn也无法得到执行。
* 故支持中断嵌套处理的函数不能得到处理,所以Linu内核不支持嵌套中断。
*/
/*
* Check whether the interrupt nests into another interrupt
* thread.
*/
nested = irq_settings_is_nested_thread(desc);
if (nested) {
if (!new->thread_fn) {
ret = -EINVAL;
goto out_mput;
}
/*
* Replace the primary handler which was provided from
* the driver for non nested interrupt handling by the
* dummy function which warns when called.
*/
new->handler = irq_nested_primary_handler;
...
}
另外从处理器硬件层面也限制了中断嵌套处理的可能。如下ARM异常处理的文档中描述:当处理器捕获到异常并进入异常处理状态时,该处理器的PSTATE.DAIF中断掩码被自动设置,也是说该处理不能再响应对应的异常处理。当完成异常处理时,系统通过调用eret指令完成PSTATE状态和被中断指令地址的恢复,即同时恢复了PSTATE.DAIF状态,可能把当前处理器从异常中断状态恢复出来。
在系统进行IRQ异常处理的过程中,可能有局部使能或者关闭(local_irq_enable/local_irq_disable)当前CPU IRQ的行为,如:bottom half处理的softirq& tasklet处理过程,会使能当前CPU的IRQ但也会进行成对的关闭行为(这点符合局部对称的设计要要求);但因为softirq阶段有短暂的使能本地中断行为,所以在中断的bottom half阶段可能有高优先级的中断被路由到当前CPU导致中断嵌套。
总结:当前内核不支持top half阶段的中断的嵌套,支持bottom half阶段的中断嵌套。