clinux-kernelinterruptinterrupt-handlingirq

Why does the Linux kernel not stop at the first handler for a shared IRQ that returns IRQ_HANDLED?


I'm sure there's a good reason for this, but I can't see what it is. Inside __handle_irq_event_percpu the kernel loops over all the handlers registered for a particular IRQ line and calls it. What I don't understand is why this loop isn't exited when the first handler returning IRQ_HANDLED is reached? It seems like a simple performance improvement, so there must be something I don't understand.

Does anyone know why?


Solution

  • In the Linux source tree, __handle_irq_event_percpu() is in kernel/irq/handle.c:

    irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
    {
        irqreturn_t retval = IRQ_NONE;
        unsigned int irq = desc->irq_data.irq;
        struct irqaction *action;
    
        record_irq_time(desc);
    
        for_each_action_of_desc(desc, action) {
            irqreturn_t res;
    
            trace_irq_handler_entry(irq, action);
            res = action->handler(irq, action->dev_id);
            trace_irq_handler_exit(irq, action, res);
    
            if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n",
                      irq, action->handler))
                local_irq_disable();
    
            switch (res) {
            case IRQ_WAKE_THREAD:
                /*
                 * Catch drivers which return WAKE_THREAD but
                 * did not set up a thread function
                 */
                if (unlikely(!action->thread_fn)) {
                    warn_no_thread(irq, action);
                    break;
                }
    
                __irq_wake_thread(desc, action);
    
                /* Fall through - to add to randomness */
            case IRQ_HANDLED:
                *flags |= action->flags;
                break;
    
            default:
                break;
            }
    
            retval |= res;
        }
    
        return retval;
    }
    

    The for_each_action_of_desc(desc, action) macro travels in the action list of the IRQ descriptor:

    #define for_each_action_of_desc(desc, act)          \
        for (act = desc->action; act; act = act->next)
    [...]
    struct irq_desc {
        struct irq_common_data  irq_common_data;
        struct irq_data     irq_data;
        unsigned int __percpu   *kstat_irqs;
        irq_flow_handler_t  handle_irq;
        struct irqaction    *action;    /* IRQ action list */
    [...]
    struct irqaction {
        irq_handler_t       handler;
        void            *dev_id;
        void __percpu       *percpu_dev_id;
        struct irqaction    *next;
        irq_handler_t       thread_fn;
        struct task_struct  *thread;
        struct irqaction    *secondary;
        unsigned int        irq;
        unsigned int        flags;
        unsigned long       thread_flags;
        unsigned long       thread_mask;
        const char      *name;
        struct proc_dir_entry   *dir;
    } ____cacheline_internodealigned_in_smp;
    

    There are multiple entries in the action list if the interrupt line is shared by several devices. So, several devices may enter in interrupt state at the same time. Hence, the action is to be called for all the devices sharing the line to check if there is something to do.

    N.B.: