tree-wide: fix typos "ass?o[sc]iac?te" -> "associate" in comments
[safe/jmp/linux-2.6] / kernel / irq / spurious.c
index f6297c3..89fb90a 100644 (file)
  * This file contains spurious interrupt handling.
  */
 
+#include <linux/jiffies.h>
 #include <linux/irq.h>
 #include <linux/module.h>
 #include <linux/kallsyms.h>
 #include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+
+static int irqfixup __read_mostly;
+
+#define POLL_SPURIOUS_IRQ_INTERVAL (HZ/10)
+static void poll_spurious_irqs(unsigned long dummy);
+static DEFINE_TIMER(poll_spurious_irq_timer, poll_spurious_irqs, 0, 0);
+
+/*
+ * Recovery handler for misrouted interrupts.
+ */
+static int try_one_irq(int irq, struct irq_desc *desc)
+{
+       struct irqaction *action;
+       int ok = 0, work = 0;
+
+       raw_spin_lock(&desc->lock);
+       /* 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 & IRQF_SHARED))
+                       desc->status |= IRQ_PENDING;
+               raw_spin_unlock(&desc->lock);
+               return ok;
+       }
+       /* Honour the normal IRQ locking */
+       desc->status |= IRQ_INPROGRESS;
+       action = desc->action;
+       raw_spin_unlock(&desc->lock);
+
+       while (action) {
+               /* Only shared IRQ handlers are safe to call */
+               if (action->flags & IRQF_SHARED) {
+                       if (action->handler(irq, action->dev_id) ==
+                               IRQ_HANDLED)
+                               ok = 1;
+               }
+               action = action->next;
+       }
+       local_irq_disable();
+       /* Now clean up the flags */
+       raw_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;
+               raw_spin_unlock(&desc->lock);
+               handle_IRQ_event(irq, action);
+               raw_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->chip && desc->chip->end)
+               desc->chip->end(irq);
+       raw_spin_unlock(&desc->lock);
+
+       return ok;
+}
+
+static int misrouted_irq(int irq)
+{
+       struct irq_desc *desc;
+       int i, ok = 0;
+
+       for_each_irq_desc(i, desc) {
+               if (!i)
+                        continue;
+
+               if (i == irq)   /* Already tried */
+                       continue;
+
+               if (try_one_irq(i, desc))
+                       ok = 1;
+       }
+       /* So the caller can adjust the irq error counts */
+       return ok;
+}
+
+static void poll_spurious_irqs(unsigned long dummy)
+{
+       struct irq_desc *desc;
+       int i;
+
+       for_each_irq_desc(i, desc) {
+               unsigned int status;
+
+               if (!i)
+                        continue;
+
+               /* Racy but it doesn't matter */
+               status = desc->status;
+               barrier();
+               if (!(status & IRQ_SPURIOUS_DISABLED))
+                       continue;
+
+               local_irq_disable();
+               try_one_irq(i, desc);
+               local_irq_enable();
+       }
+
+       mod_timer(&poll_spurious_irq_timer,
+                 jiffies + POLL_SPURIOUS_IRQ_INTERVAL);
+}
 
 /*
  * If 99,900 of the previous 100,000 interrupts have not been handled
  */
 
 static void
-__report_bad_irq(unsigned int irq, irq_desc_t *desc, irqreturn_t action_ret)
+__report_bad_irq(unsigned int irq, struct irq_desc *desc,
+                irqreturn_t action_ret)
 {
        struct irqaction *action;
 
@@ -31,10 +151,12 @@ __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");
+
        action = desc->action;
        while (action) {
                printk(KERN_ERR "[<%p>]", action->handler);
@@ -45,7 +167,8 @@ __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, struct irq_desc *desc, irqreturn_t action_ret)
 {
        static int count = 100;
 
@@ -55,20 +178,72 @@ 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)
+static inline int
+try_misrouted_irq(unsigned int irq, struct irq_desc *desc,
+                 irqreturn_t action_ret)
+{
+       struct irqaction *action;
+
+       if (!irqfixup)
+               return 0;
+
+       /* We didn't actually handle the IRQ - see if it was misrouted? */
+       if (action_ret == IRQ_NONE)
+               return 1;
+
+       /*
+        * But for 'irqfixup == 2' we also do it for handled interrupts if
+        * they are marked as IRQF_IRQPOLL (or for irq zero, which is the
+        * traditional PC timer interrupt.. Legacy)
+        */
+       if (irqfixup < 2)
+               return 0;
+
+       if (!irq)
+               return 1;
+
+       /*
+        * Since we don't get the descriptor lock, "action" can
+        * change under us.  We don't really care, but we don't
+        * want to follow a NULL pointer. So tell the compiler to
+        * just load it once by using a barrier.
+        */
+       action = desc->action;
+       barrier();
+       return action && (action->flags & IRQF_IRQPOLL);
+}
+
+void note_interrupt(unsigned int irq, struct irq_desc *desc,
+                   irqreturn_t action_ret)
 {
-       if (action_ret != IRQ_HANDLED) {
-               desc->irqs_unhandled++;
-               if (action_ret != IRQ_NONE)
+       if (unlikely(action_ret != IRQ_HANDLED)) {
+               /*
+                * If we are seeing only the odd spurious IRQ caused by
+                * bus asynchronicity then don't eventually trigger an error,
+                * otherwise the counter becomes a doomsday timer for otherwise
+                * working systems
+                */
+               if (time_after(jiffies, desc->last_unhandled + HZ/10))
+                       desc->irqs_unhandled = 1;
+               else
+                       desc->irqs_unhandled++;
+               desc->last_unhandled = jiffies;
+               if (unlikely(action_ret != IRQ_NONE))
                        report_bad_irq(irq, desc, action_ret);
        }
 
+       if (unlikely(try_misrouted_irq(irq, desc, action_ret))) {
+               int ok = misrouted_irq(irq);
+               if (action_ret == IRQ_NONE)
+                       desc->irqs_unhandled -= ok;
+       }
+
        desc->irq_count++;
-       if (desc->irq_count < 100000)
+       if (likely(desc->irq_count < 100000))
                return;
 
        desc->irq_count = 0;
-       if (desc->irqs_unhandled > 99900) {
+       if (unlikely(desc->irqs_unhandled > 99900)) {
                /*
                 * The interrupt is stuck
                 */
@@ -77,20 +252,50 @@ void note_interrupt(unsigned int irq, irq_desc_t *desc, irqreturn_t action_ret)
                 * Now kill the IRQ
                 */
                printk(KERN_EMERG "Disabling IRQ #%d\n", irq);
-               desc->status |= IRQ_DISABLED;
-               desc->handler->disable(irq);
+               desc->status |= IRQ_DISABLED | IRQ_SPURIOUS_DISABLED;
+               desc->depth++;
+               desc->chip->disable(irq);
+
+               mod_timer(&poll_spurious_irq_timer,
+                         jiffies + POLL_SPURIOUS_IRQ_INTERVAL);
        }
        desc->irqs_unhandled = 0;
 }
 
-int noirqdebug;
+int noirqdebug __read_mostly;
 
-int __init noirqdebug_setup(char *str)
+int noirqdebug_setup(char *str)
 {
        noirqdebug = 1;
        printk(KERN_INFO "IRQ lockup detection disabled\n");
+
        return 1;
 }
 
 __setup("noirqdebug", noirqdebug_setup);
+module_param(noirqdebug, bool, 0644);
+MODULE_PARM_DESC(noirqdebug, "Disable irq lockup detection when true");
+
+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);
+module_param(irqfixup, int, 0644);
+
+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);