1. 共享中断的注册和处理
注册中断时,在setup_irq中,
struct irq_desc *desc = irq_desc + irq;
...
p = &desc->action;
old = *p;
if (old) {
如果已注册中断或者本次注册中断不是共享中断,则
if (!((old->flags & new->flags) & IRQF_SHARED) ||
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
old_name = old->name;
goto mismatch;
}
如果是共享中断,则
do {
p = &old->next;
old = *p;
} while (old);
shared = 1;
然后将本次注册中断添加到desc->action的链表上。
*p = new;
2. 执行中断时共享中断相关代码
执行handle_IRQ_event时,会依次执行所有注册到当前中断上的action->handler,所以当多个中断共享同一中断号时,每次中断都会依次执行所有注册的ISR。另外在执行action->handler之前,如果不是独占中断(IRQF_DISABLED),会重新使能中断,action->handler执行完毕再关闭中断。
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;
handle_dynamic_tick(action);
if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq();
do {
ret = action->handler(irq, action->dev_id);
if (ret == IRQ_HANDLED)
status |= action->flags;
retval |= ret;
action = action->next;
} while (action);
if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();
return retval;
}
3. 中断处理期间又来了一次当前正处理的中断
在执行驱动中断处理函数过程中,如果又来了一次与当前正执行中断类型相同的中断:
A. 离开当前中断处理,重新进入中断处理,如果正在处理(IRQ_INPROGRESS),则action为0直接返回,而status被设置了IRQ_PENDING。参考代码:
status |= IRQ_PENDING; /* we _want_ to handle it */
action = NULL;
if (likely(!(status & (IRQ_DISABLED | IRQ_INPROGRESS)))) {
action = desc->action;
status &= ~IRQ_PENDING; /* we commit to handling */
status |= IRQ_INPROGRESS; /* we are handling it */
}
desc->status = status;
if (unlikely(!action))
goto out;
B. 从新中断返回后,会回到前面正在执行的中断处理,此时,如果发现有IRQ_PENDING,会再次执行一遍中断处理流程,知道没有IRQ_PENDING被设置为止。
可参考在函数unsigned int __do_IRQ(unsigned int irq)中的代码:
/*
* Edge triggered interrupts need to remember
* pending events.
* This applies to any hw interrupts that allow a second
* instance of the same irq to arrive while we are in do_IRQ
* or in the handler. But the code here only handles the _second_
* instance of the irq, not the third or fourth. So it is mostly
* useful for irq hardware that does not mask cleanly in an
* SMP environment.
*/
for (;;) {
irqreturn_t action_ret;
spin_unlock(&desc->lock);
action_ret = handle_IRQ_event(irq, action);
if (!noirqdebug)
note_interrupt(irq, desc, action_ret);
spin_lock(&desc->lock);
if (likely(!(desc->status & IRQ_PENDING)))
break;
desc->status &= ~IRQ_PENDING;
}
注意:如果在一次中断执行过程中来了多次(n>1)同一种中断,则只会执行一次,有(n-1)个该类中断被“无视”了。