genirq: Plug race in report_bad_irq()
authorThomas Gleixner <tglx@linutronix.de>
Mon, 7 Feb 2011 08:05:05 +0000 (09:05 +0100)
committerThomas Gleixner <tglx@linutronix.de>
Sat, 19 Feb 2011 11:58:08 +0000 (12:58 +0100)
We cannot walk the action chain unlocked. Even if IRQ_INPROGRESS is
set an action can be removed and we follow a null pointer. It's safe
to take the lock there, because the code which removes the action will
call synchronize_irq() which waits unlocked for IRQ_INPROGRESS going
away.

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
kernel/irq/spurious.c

index 3089d3b9d5f3912643d49bf46960fe7938a46e6a..2fbfda2716e1253c33aeef02037766723f789760 100644 (file)
@@ -139,15 +139,13 @@ static void poll_spurious_irqs(unsigned long dummy)
  *
  * (The other 100-of-100,000 interrupts may have been a correctly
  *  functioning device sharing an IRQ with the failing one)
- *
- * Called under desc->lock
  */
-
 static void
 __report_bad_irq(unsigned int irq, struct irq_desc *desc,
                 irqreturn_t action_ret)
 {
        struct irqaction *action;
+       unsigned long flags;
 
        if (action_ret != IRQ_HANDLED && action_ret != IRQ_NONE) {
                printk(KERN_ERR "irq event %d: bogus return value %x\n",
@@ -159,6 +157,13 @@ __report_bad_irq(unsigned int irq, struct irq_desc *desc,
        dump_stack();
        printk(KERN_ERR "handlers:\n");
 
+       /*
+        * We need to take desc->lock here. note_interrupt() is called
+        * w/o desc->lock held, but IRQ_PROGRESS set. We might race
+        * with something else removing an action. It's ok to take
+        * desc->lock here. See synchronize_irq().
+        */
+       raw_spin_lock_irqsave(&desc->lock, flags);
        action = desc->action;
        while (action) {
                printk(KERN_ERR "[<%p>]", action->handler);
@@ -167,6 +172,7 @@ __report_bad_irq(unsigned int irq, struct irq_desc *desc,
                printk("\n");
                action = action->next;
        }
+       raw_spin_unlock_irqrestore(&desc->lock, flags);
 }
 
 static void