genirq: Provide forced interrupt threading
authorThomas Gleixner <tglx@linutronix.de>
Wed, 23 Feb 2011 23:52:23 +0000 (23:52 +0000)
committerThomas Gleixner <tglx@linutronix.de>
Sat, 26 Feb 2011 10:57:18 +0000 (11:57 +0100)
Add a commandline parameter "threadirqs" which forces all interrupts except
those marked IRQF_NO_THREAD to run threaded. That's mostly a debug option to
allow retrieving better debug data from crashing interrupt handlers. If
"threadirqs" is not enabled on the kernel command line, then there is no
impact in the interrupt hotpath.

Architecture code needs to select CONFIG_IRQ_FORCED_THREADING after
marking the interrupts which cant be threaded IRQF_NO_THREAD. All
interrupts which have IRQF_TIMER set are implict marked
IRQF_NO_THREAD. Also all PER_CPU interrupts are excluded.

Forced threading hard interrupts also forces all soft interrupt
handling into thread context.

When enabled it might slow down things a bit, but for debugging problems in
interrupt code it's a reasonable penalty as it does not immediately
crash and burn the machine when an interrupt handler is buggy.

Some test results on a Core2Duo machine:

Cache cold run of:
 # time git grep irq_desc

      non-threaded       threaded
 real 1m18.741s          1m19.061s
 user 0m1.874s           0m1.757s
 sys  0m5.843s           0m5.427s

 # iperf -c server
non-threaded
[  3]  0.0-10.0 sec  1.09 GBytes   933 Mbits/sec
[  3]  0.0-10.0 sec  1.09 GBytes   934 Mbits/sec
[  3]  0.0-10.0 sec  1.09 GBytes   933 Mbits/sec
threaded
[  3]  0.0-10.0 sec  1.09 GBytes   939 Mbits/sec
[  3]  0.0-10.0 sec  1.09 GBytes   934 Mbits/sec
[  3]  0.0-10.0 sec  1.09 GBytes   937 Mbits/sec

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Peter Zijlstra <peterz@infradead.org>
LKML-Reference: <20110223234956.772668648@linutronix.de>

Documentation/kernel-parameters.txt
include/linux/interrupt.h
kernel/irq/Kconfig
kernel/irq/internals.h
kernel/irq/manage.c
kernel/softirq.c

index 89835a4766a684468695b430ac8e7e7f1b76dabc..cac6cf9a588cdd1668921e17a9e25401dd609ff9 100644 (file)
@@ -2436,6 +2436,10 @@ and is between 256 and 4096 characters. It is defined in the file
                        <deci-seconds>: poll all this frequency
                        0: no polling (default)
 
+       threadirqs      [KNL]
+                       Force threading of all interrupt handlers except those
+                       marked explicitely IRQF_NO_THREAD.
+
        topology=       [S390]
                        Format: {off | on}
                        Specify if the kernel should make use of the cpu
index 0fc3eb9397b4867dc83ff3034b6764e32ad1ffbd..f8a8af108e0cd855b4eebdc861fbdd27f4a87517 100644 (file)
@@ -383,6 +383,13 @@ static inline int disable_irq_wake(unsigned int irq)
 }
 #endif /* CONFIG_GENERIC_HARDIRQS */
 
+
+#ifdef CONFIG_IRQ_FORCED_THREADING
+extern bool force_irqthreads;
+#else
+#define force_irqthreads       (0)
+#endif
+
 #ifndef __ARCH_SET_SOFTIRQ_PENDING
 #define set_softirq_pending(x) (local_softirq_pending() = (x))
 #define or_softirq_pending(x)  (local_softirq_pending() |= (x))
index 355b8c7957f5e062be77c94a69a25e7fc5a720dd..144db9dcfcde97ff9e94f3722607274a562905c9 100644 (file)
@@ -38,6 +38,9 @@ config HARDIRQS_SW_RESEND
 config IRQ_PREFLOW_FASTEOI
        bool
 
+config IRQ_FORCED_THREADING
+       bool
+
 config SPARSE_IRQ
        bool "Support sparse irq numbering"
        depends on HAVE_SPARSE_IRQ
index 935bec4bfa874841813cfc3fcabbd2efa6e2810b..6c6ec9a490274e7c313f78eb202aa1c5af84f9b7 100644 (file)
@@ -27,12 +27,14 @@ extern int noirqdebug;
  * IRQTF_DIED      - handler thread died
  * IRQTF_WARNED    - warning "IRQ_WAKE_THREAD w/o thread_fn" has been printed
  * IRQTF_AFFINITY  - irq thread is requested to adjust affinity
+ * IRQTF_FORCED_THREAD  - irq action is force threaded
  */
 enum {
        IRQTF_RUNTHREAD,
        IRQTF_DIED,
        IRQTF_WARNED,
        IRQTF_AFFINITY,
+       IRQTF_FORCED_THREAD,
 };
 
 /*
index 58c861367300dc44905453becc6bc3bad0fcec5d..acd599a43bfb56b6fdafe98d1bb531c16e71b77c 100644 (file)
 
 #include "internals.h"
 
+#ifdef CONFIG_IRQ_FORCED_THREADING
+__read_mostly bool force_irqthreads;
+
+static int __init setup_forced_irqthreads(char *arg)
+{
+       force_irqthreads = true;
+       return 0;
+}
+early_param("threadirqs", setup_forced_irqthreads);
+#endif
+
 /**
  *     synchronize_irq - wait for pending IRQ handlers (on other CPUs)
  *     @irq: interrupt number to wait for
@@ -701,6 +712,32 @@ static inline void
 irq_thread_check_affinity(struct irq_desc *desc, struct irqaction *action) { }
 #endif
 
+/*
+ * Interrupts which are not explicitely requested as threaded
+ * interrupts rely on the implicit bh/preempt disable of the hard irq
+ * context. So we need to disable bh here to avoid deadlocks and other
+ * side effects.
+ */
+static void
+irq_forced_thread_fn(struct irq_desc *desc, struct irqaction *action)
+{
+       local_bh_disable();
+       action->thread_fn(action->irq, action->dev_id);
+       irq_finalize_oneshot(desc, action, false);
+       local_bh_enable();
+}
+
+/*
+ * Interrupts explicitely requested as threaded interupts want to be
+ * preemtible - many of them need to sleep and wait for slow busses to
+ * complete.
+ */
+static void irq_thread_fn(struct irq_desc *desc, struct irqaction *action)
+{
+       action->thread_fn(action->irq, action->dev_id);
+       irq_finalize_oneshot(desc, action, false);
+}
+
 /*
  * Interrupt handler thread
  */
@@ -711,8 +748,15 @@ static int irq_thread(void *data)
        };
        struct irqaction *action = data;
        struct irq_desc *desc = irq_to_desc(action->irq);
+       void (*handler_fn)(struct irq_desc *desc, struct irqaction *action);
        int wake;
 
+       if (force_irqthreads & test_bit(IRQTF_FORCED_THREAD,
+                                       &action->thread_flags))
+               handler_fn = irq_forced_thread_fn;
+       else
+               handler_fn = irq_thread_fn;
+
        sched_setscheduler(current, SCHED_FIFO, &param);
        current->irqaction = action;
 
@@ -736,10 +780,7 @@ static int irq_thread(void *data)
                        raw_spin_unlock_irq(&desc->lock);
                } else {
                        raw_spin_unlock_irq(&desc->lock);
-
-                       action->thread_fn(action->irq, action->dev_id);
-
-                       irq_finalize_oneshot(desc, action, false);
+                       handler_fn(desc, action);
                }
 
                wake = atomic_dec_and_test(&desc->threads_active);
@@ -789,6 +830,22 @@ void exit_irq_thread(void)
        set_bit(IRQTF_DIED, &tsk->irqaction->flags);
 }
 
+static void irq_setup_forced_threading(struct irqaction *new)
+{
+       if (!force_irqthreads)
+               return;
+       if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))
+               return;
+
+       new->flags |= IRQF_ONESHOT;
+
+       if (!new->thread_fn) {
+               set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
+               new->thread_fn = new->handler;
+               new->handler = irq_default_primary_handler;
+       }
+}
+
 /*
  * Internal function to register an irqaction - typically used to
  * allocate special interrupts that are part of the architecture.
@@ -838,6 +895,8 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
                 * dummy function which warns when called.
                 */
                new->handler = irq_nested_primary_handler;
+       } else {
+               irq_setup_forced_threading(new);
        }
 
        /*
index c0490464e92f04630fc84a071f39a4303842be09..a33fb291124823dbc6f5ce9989d76e6a25b3ac33 100644 (file)
@@ -311,9 +311,21 @@ void irq_enter(void)
 }
 
 #ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED
-# define invoke_softirq()      __do_softirq()
+static inline void invoke_softirq(void)
+{
+       if (!force_irqthreads)
+               __do_softirq();
+       else
+               wakeup_softirqd();
+}
 #else
-# define invoke_softirq()      do_softirq()
+static inline void invoke_softirq(void)
+{
+       if (!force_irqthreads)
+               do_softirq();
+       else
+               wakeup_softirqd();
+}
 #endif
 
 /*