Fedora kernel-2.6.17-1.2142_FC4 patched with stable patch-2.6.17.4-vs2.0.2-rc26.diff
[linux-2.6.git] / kernel / irq / spurious.c
index f6297c3..7df9abd 100644 (file)
 #include <linux/kallsyms.h>
 #include <linux/interrupt.h>
 
+static int irqfixup;
+
+/*
+ * Recovery handler for misrouted interrupts.
+ */
+
+static int misrouted_irq(int irq, struct pt_regs *regs)
+{
+       int i;
+       irq_desc_t *desc;
+       int ok = 0;
+       int work = 0;   /* Did we do work for a real IRQ */
+
+       for(i = 1; i < NR_IRQS; i++) {
+               struct irqaction *action;
+
+               if (i == irq)   /* Already tried */
+                       continue;
+               desc = &irq_desc[i];
+               spin_lock(&desc->lock);
+               action = desc->action;
+               /* Already running on another processor */
+               if (desc->status & IRQ_INPROGRESS) {
+                       /*
+                        * Already running: If it is shared get the other
+                        * CPU to go looking for our mystery interrupt too
+                        */
+                       if (desc->action && (desc->action->flags & SA_SHIRQ))
+                               desc->status |= IRQ_PENDING;
+                       spin_unlock(&desc->lock);
+                       continue;
+               }
+               /* Honour the normal IRQ locking */
+               desc->status |= IRQ_INPROGRESS;
+               spin_unlock(&desc->lock);
+               while (action) {
+                       /* Only shared IRQ handlers are safe to call */
+                       if (action->flags & SA_SHIRQ) {
+                               if (action->handler(i, action->dev_id, regs) ==
+                                               IRQ_HANDLED)
+                                       ok = 1;
+                       }
+                       action = action->next;
+               }
+               local_irq_disable();
+               /* Now clean up the flags */
+               spin_lock(&desc->lock);
+               action = desc->action;
+
+               /*
+                * While we were looking for a fixup someone queued a real
+                * IRQ clashing with our walk
+                */
+
+               while ((desc->status & IRQ_PENDING) && action) {
+                       /*
+                        * Perform real IRQ processing for the IRQ we deferred
+                        */
+                       work = 1;
+                       spin_unlock(&desc->lock);
+                       handle_IRQ_event(i, regs, action);
+                       spin_lock(&desc->lock);
+                       desc->status &= ~IRQ_PENDING;
+               }
+               desc->status &= ~IRQ_INPROGRESS;
+               /*
+                * If we did actual work for the real IRQ line we must let the
+                * IRQ controller clean up too
+                */
+               if(work)
+                       desc->handler->end(i);
+               spin_unlock(&desc->lock);
+       }
+       /* So the caller can adjust the irq error counts */
+       return ok;
+}
+
 /*
  * If 99,900 of the previous 100,000 interrupts have not been handled
  * then assume that the IRQ is stuck in some manner. Drop a diagnostic
@@ -31,7 +108,8 @@ __report_bad_irq(unsigned int irq, irq_desc_t *desc, irqreturn_t action_ret)
                printk(KERN_ERR "irq event %d: bogus return value %x\n",
                                irq, action_ret);
        } else {
-               printk(KERN_ERR "irq %d: nobody cared!\n", irq);
+               printk(KERN_ERR "irq %d: nobody cared (try booting with "
+                               "the \"irqpoll\" option)\n", irq);
        }
        dump_stack();
        printk(KERN_ERR "handlers:\n");
@@ -45,7 +123,7 @@ __report_bad_irq(unsigned int irq, irq_desc_t *desc, irqreturn_t action_ret)
        }
 }
 
-void report_bad_irq(unsigned int irq, irq_desc_t *desc, irqreturn_t action_ret)
+static void report_bad_irq(unsigned int irq, irq_desc_t *desc, irqreturn_t action_ret)
 {
        static int count = 100;
 
@@ -55,7 +133,8 @@ void report_bad_irq(unsigned int irq, irq_desc_t *desc, irqreturn_t action_ret)
        }
 }
 
-void note_interrupt(unsigned int irq, irq_desc_t *desc, irqreturn_t action_ret)
+void note_interrupt(unsigned int irq, irq_desc_t *desc, irqreturn_t action_ret,
+                       struct pt_regs *regs)
 {
        if (action_ret != IRQ_HANDLED) {
                desc->irqs_unhandled++;
@@ -63,6 +142,15 @@ void note_interrupt(unsigned int irq, irq_desc_t *desc, irqreturn_t action_ret)
                        report_bad_irq(irq, desc, action_ret);
        }
 
+       if (unlikely(irqfixup)) {
+               /* Don't punish working computers */
+               if ((irqfixup == 2 && irq == 0) || action_ret == IRQ_NONE) {
+                       int ok = misrouted_irq(irq, regs);
+                       if (action_ret == IRQ_NONE)
+                               desc->irqs_unhandled -= ok;
+               }
+       }
+
        desc->irq_count++;
        if (desc->irq_count < 100000)
                return;
@@ -94,3 +182,24 @@ int __init noirqdebug_setup(char *str)
 
 __setup("noirqdebug", noirqdebug_setup);
 
+static int __init irqfixup_setup(char *str)
+{
+       irqfixup = 1;
+       printk(KERN_WARNING "Misrouted IRQ fixup support enabled.\n");
+       printk(KERN_WARNING "This may impact system performance.\n");
+       return 1;
+}
+
+__setup("irqfixup", irqfixup_setup);
+
+static int __init irqpoll_setup(char *str)
+{
+       irqfixup = 2;
+       printk(KERN_WARNING "Misrouted IRQ fixup and polling support "
+                               "enabled\n");
+       printk(KERN_WARNING "This may significantly impact system "
+                               "performance\n");
+       return 1;
+}
+
+__setup("irqpoll", irqpoll_setup);