Linux中断(interrupt)子系统之三:中断流控处理层

1. 中断流控层简介

早期的内核版本中,几乎所有的中断都是由__do_IRQ函数进行处理,但是,因为各种中断请求的电气特性会有所不同,又或者中断控制器的特性也不同,这会导致以下这些处理也会有所不同:

/*****************************************************************************************************/声明:本博内容均由原创,转载请注明出处,谢谢!/*****************************************************************************************************/为此,通用中断子系统把几种常用的流控类型进行了抽象,并为它们实现了相应的标准函数,我们只要选择相应的函数,赋值给irq所对应的irq_desc结构的handle_irq字段中即可。这些标准的回调函数都是irq_flow_handler_t类型:typedefvoid (*irq_flow_handler_t)(unsigned int irq,struct irq_desc *desc);目前的通用中断子系统实现了以下这些标准流控回调函数,这些函数都定义在:kernel/irq/chip.c中,handle_simple_irq 用于简易流控处理;handle_level_irq 用于电平触发中断的流控处理;handle_edge_irq 用于边沿触发中断的流控处理;handle_fasteoi_irq 用于需要响应eoi的中断控制器;handle_percpu_irq 用于只在单一cpu响应的中断;handle_nested_irq 用于处理使用线程的嵌套中断;

驱动程序和板级代码可以通过以下几个API设置irq的流控函数:

irq_set_handler();irq_set_chip_and_handler();irq_set_chip_and_handler_name();

以下这个序列图展示了整个通用中断子系统的中断响应过程,flow_handle一栏就是中断流控层的生命周期:

图1.1 通用中断子系统的中断响应过程

2. handle_simple_irq

该函数没有实现任何实质性的流控操作,在把irq_desc结构锁住后,直接调用handle_irq_event处理irq_desc中的action链表,它通常用于多路复用(类似于中断控制器级联)中的子中断,由父中断的流控回调中调用。或者用于无需进行硬件控制的中断中。以下是它的经过简化的代码:

voidhandle_simple_irq(unsigned int irq, struct irq_desc *desc){raw_spin_lock(&desc->lock);……handle_irq_event(desc);out_unlock:raw_spin_unlock(&desc->lock);}3. handle_level_irq

该函数用于处理电平中断的流控操作。电平中断的特点是,只要设备的中断请求引脚(中断线)保持在预设的触发电平,中断就会一直被请求,所以,为了避免同一中断被重复响应,必须在处理中断前先把mask irq,然后ack irq,以便复位设备的中断请求引脚,响应完成后再unmask irq。实际的情况稍稍复杂一点,在mask和ack之后,还要判断IRQ_INPROGRESS标志位,如果该标志已经置位,则直接退出,不再做实质性的处理,IRQ_INPROGRESS标志在handle_irq_event的开始设置,在handle_irq_event结束时清除,如果监测到IRQ_INPROGRESS被置位,表明该irq正在被另一个CPU处理中,所以直接退出,对电平中断来说是正确的处理方法。但是我觉得在ARM系统中,这种情况根本就不会发生,因为在没有进入handle_level_irq之前,,中断控制器没有收到ack通知,它不会向第二个CPU再次发出中断请求,而当程序进入handle_level_irq之后,第一个动作就是mask irq,然后ack irq(通常是联合起来的:mask_ack_irq),这时候就算设备再次发出中断请求,也是在handle_irq_event结束,unmask irq之后,这时IRQ_INPROGRESS标志已经被清除。我不知道其他像X86之类的体系是否有不同的行为,有知道的朋友请告知我一下。以下是handle_level_irq经过简化之后的代码:

voidhandle_level_irq(unsigned int irq, struct irq_desc *desc){raw_spin_lock(&desc->lock);mask_ack_irq(desc);if (unlikely(irqd_irq_inprogress(&desc->irq_data)))goto out_unlock;……if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data)))goto out_unlock;handle_irq_event(desc);if (!irqd_irq_disabled(&desc->irq_data) && !(desc->istate & IRQS_ONESHOT))unmask_irq(desc);out_unlock:raw_spin_unlock(&desc->lock);}虽然handle_level_irq对电平中断的流控进行了必要的处理,因为电平中断的特性:只要没有ack irq,中断线会一直有效,所以我们不会错过某次中断请求,但是驱动程序的开发人员如果对该过程理解不透彻,特别容易发生某次中断被多次处理的情况。特别是使用了中断线程(action->thread_fn)来响应中断的时候:通常mask_ack_irq只会清除中断控制器的pending状态,很多慢速设备(例如通过i2c或spi控制的设备)需要在中断线程中清除中断线的pending状态,但是未等到中断线程被调度执行的时候,handle_level_irq早就返回了,这时已经执行过unmask_irq,设备的中断线pending处于有效状态,中断控制器会再次发出中断请求,结果是设备的一次中断请求,产生了两次中断响应。要避免这种情况,最好的办法就是不要单独使用中断线程处理中断,而是要实现request_threaded_irq()的第二个参数irq_handler_t:handler,在handle回调中使用disable_irq()关闭该irq,然后在退出中断线程回调前再enable_irq()。假设action->handler没有屏蔽irq,以下这幅图展示了电平中断期间IRQ_PROGRESS标志、本地中断状态和触发其他CPU的状态:

图3.1 电平触发中断状态

上图中颜色分别代表不同的状态:

状态红色绿色

IRQ_PROGRESS TRUE FALSE

是否允许本地cpu中断 禁止 允许

是否允许该设备再次触发中断(可能由其它cpu响应) 禁止 允许

4. handle_edge_irq于千万年之中,于千万人之中,在时间无涯的荒野中,

Linux中断(interrupt)子系统之三:中断流控处理层

相关文章:

你感兴趣的文章:

标签云: