4.7 chained中断如何流动
4.7.1 Linux chained中断处理流程
什么是chained中断?
如下图所示,假设有三个中断控制器,名字分别为INTC_A、INTC_B、INTC_C,各自有自己的物理中断号。
INTC_B的两个物理中断INTC_B_hwirq32和INTC_B_hwirq33,任意一个触发后,都会触发INTC_A的物理中断INTC_A_hwirq32,最终触发ARM64的IRQ。INTC_A_hwirq32称为INTC_B_hwirq32和INTC_B_hwirq33的父中断。INTC_A称为INTC_B的父中断控制器,反之,INTC_B是INTC_A的子中断控制器。这种中断级联的方式称为chained中断。
同理,INTC_B是INTC_C的父中断控制器,INTC_C是INTC_B的子中断控制器,这样三个中断控制器形成了多级级联的chained中断。
为啥要举多级chained中断的例子?后面分析__ipipe_dispatch_irq会用到。

如《4.2.3 根据DTS完成中断控制器初始化》所述,Linux启动过程中,会完成中断控制器的初始化。
在子中断控制器INTC_B初始化的过程中,如果发现自己存在父,则注册父中断INTC_A_hwirq32,得到virq 10,并把virq 10的handler_irq设置为子中断控制器INTC_B的处理程序INTC_B_handle_chained_irq。同理,中断控制器INTC_C会完成上述步骤。
举一个实际的例子,在GIC中断控制器(irq-gic.c)初始化过程中,如果存在父,则把父中断的irq_desc.handle_irq设置为gic_handle_cascade_irq。
int __init
gic_of_init(struct device_node *node, struct device_node *parent)
{
……
if (parent) {
irq = irq_of_parse_and_map(node, 0);
gic_cascade_irq(gic_cnt, irq);
}
……
}
void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq)
{
BUG_ON(gic_nr >= CONFIG_ARM_GIC_MAX_NR);
irq_set_chained_handler_and_data(irq, gic_handle_cascade_irq,
&gic_data[gic_nr]);
}
除了中断控制器之外,隶属于各个中断控制器的设备会正常初始化,并申请各自的virq和irq_desc。

接下来描述Linux处理chained中断流程。调用关系比较复杂,图中只标出关键的函数。想要完成chained中断的处理,需要多次嵌套调用generic_handle_irq,每次都传入正确的virq。
第一步,当中断INTC_C_hwirq32发生时,依次触发INTC_B_hwirq32、INTC_A_hwirq32,最终触发ARM64 IRQ。
第二步,参考《4.3 Linux的中断处理流程》,Linux中断子系统会根据INTC_A_hwirq32找到对应的virq 10,会第一次调用generic_handle_irq(irq=10)。generic_handle_irq(irq=10)调用的中断流控函数handle_irq实际指向了INTC_B_handle_chained_irq。
第三步,INTC_B_handle_chained_irq要找出在INTC_B中,具体是哪个物理中断触发了?结果发现是INTC_B_hwirq32,其对应的virq是12,第二次调用generic_handle_irq(irq=12)。generic_handle_irq(irq=12)调用的中断流控函数handle_irq实际指向了INTC_C_handle_chained_irq。
第四步,INTC_C_handle_chained_irq要找出在INTC_C中,具体是哪个物理中断触发了?结果发现是INTC_C_hwirq32,其对应的virq是14,第三次调用generic_handle_irq(irq=14)。generic_handle_irq(irq=14)调用的中断流控函数handle_irq是handle_fasteoi_irq,会最终调用irq_desc->action->handler/thread_fn真正完成INTC_C_hwirq32的处理。
4.7.2 IPIPE chained中断处理流程
接着上一个章节分析,三个中断控制器INTC_A、INTC_B、INTC_C,形成了多级级联的chained中断。
根据上文的分析,Linux想要完成chained中断的处理,需要在Linux中断子系统中多次嵌套调用generic_handle_irq,每次都传入正确的virq。这对于IPIPE是不可接受的,因为Linux中断子系统的处理延迟是不可控的。
IPIPE的实现方式,是把多次嵌套调用generic_handle_irq,改成了多次嵌套调用__ipipe_dispatch_irq。

第一步,当中断INTC_C_hwirq32发生时,依次触发INTC_B_hwirq32、INTC_A_hwirq32,最终触发ARM64 IRQ。
第二步,参考《4.4 timer中断从Xenomai流向Linux》,IPIPE会根据接管中断管理,根据INTC_A_hwirq32找到对应的virq 10,会第一次调用__ipipe_dispatch_irq(irq=10, flags=0)。
(1)__ipipe_dispatch_irq(irq=10, flags=0)首先初始化desc和chained_irq。通过irq_to_desc函数获取中断描述符desc,并判断该中断是否为chained_irq。

chained_irq的值,取决于desc->handle_irq是否等于__ipipe_chained_irq,如果是,则chained_irq 为1,否则chained_irq 为0。
在当前的例子中,chained_irq 为1。在什么时候会把什么中断的desc->handle_irq初始化成__ipipe_chained_irq?
举一个实际的例子,在GIC中断控制器(irq-gic.c)初始化过程中,如果存在父,则把父中断的irq_desc.handle_irq设置为gic_handle_cascade_irq。
gic_of_init
-> gic_cascade_irq
-> irq_set_chained_handler_and_data(irq, gic_handle_cascade_irq, &gic_data[gic_nr]);
-> __irq_do_set_handler(desc, handle, 1, NULL) //其中第3个参数即is_chained=1
-> handle = __fixup_irq_handler(desc, handle, is_chained)//第3个参数is_chained=1
-> desc->handle = handle //此处handle是__fixup_irq_handler修正过的
在Linux的__irq_do_set_handler中,IPIPE增加了__fixup_irq_handler的调用。

第1047行,把原来的中断流控函数gic_handle_cascade_irq传递给了ipipe_ack。
第1049行,设置handle为__ipipe_chained_irq。而这个__ipipe_chained_irq函数是空函数,所以它的作用就是用于记录chained_irq的信息。
(2)__ipipe_dispatch_irq(irq=10, flags=0)继续执行ackfn。如《4.4.2 timer中断的应答(__ipipe_dispatch_irq上半部)》分析,IPIPE引入了ack和end的概念,在irq_desc和ipipe_irqdesc中都定义了ack函数,默认情况下,irq_desc->ipipe_ack、root域和head域的ipipe_irqdesc->ackfn都是相同的。

在当前的例子中,ackfn就是gic_handle_cascade_irq,执行后会转到第三步。当从ackfn返回后,因为chained_irq是1,flags是0,所以一定会执行goto sync,跳转到__ipipe_sync_pipeline函数进行interrupt log回放。
第三步,执行gic_handle_cascade_irq。这个函数被IPIPIE改造了,在INTC_B找到hwirq 32,再找出virq 12后,调用ipipe_handle_demuxed_irq(cascade_irq=12)-> __ipipe_dispatch_irq(irq=12, flags=IPIPE_IRQF_NOSYNC),进入第四步。

第四步,执行__ipipe_dispatch_irq(irq=12, flags=IPIPE_IRQF_NOSYNC)。

在当前的例子中,ackfn就是gic_handle_cascade_irq,执行后会转到第五步。当从ackfn返回后,因为chained_irq是1,flags是IPIPE_IRQF_NOSYNC,所以会直接return,不会执行任何SYNC操作。
第五步,执行gic_handle_cascade_irq。这个函数被IPIPIE改造了,在INTC_C找到hwirq 32,再找出virq 14后,调用ipipe_handle_demuxed_irq(cascade_irq=14)-> __ipipe_dispatch_irq(irq=14, flags=IPIPE_IRQF_NOSYNC),进入第六步。

第六步,执行__ipipe_dispatch_irq(irq=14, flags=IPIPE_IRQF_NOSYNC)。

在当前的例子中,ackfn就是handle_fasteoi_irq,执行之后,在第1518行的判断不成立,所以既不会sync,也不会return,会继续向下执行。
继续向下之后,无论此中断(virq 14)在head domain注册了中断处理程序,还是在root domain注册了中断处理程序,都是把中断记录到interrupt log,然后根据flags=IPIPE_IRQF_NOSYNC直接return。

在第六步return后,相当于返回了第四步中的__ipipe_dispatch_irq(irq=12, flags=IPIPE_IRQF_NOSYNC)。第四步中也会return,相当于返回了第二步中的__ipipe_dispatch_irq(irq=10, flags=0),在第二步中会继续走到sync:跳转到__ipipe_sync_pipeline函数对第六步中记录的interrupt log进行回放。
小结一下:在处理chained中断过程中,IPIPE把Linux多次嵌套调用generic_handle_irq,改成了多次嵌套调用__ipipe_dispatch_irq,在最后一次调用中,把中断记录到interrupt log中。在每一层的嵌套调用中,总是利用flags=IPIPE_IRQF_NOSYNC直接return,最终回到第一次调用__ipipe_dispatch_irq,实现对interrupt log的回放。
797

被折叠的 条评论
为什么被折叠?



