4.7_chained中断如何流动

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的回放。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值