Merge branch 'irq/threaded' of git://git.kernel.org/pub/scm/linux/kernel/git/tip...
authorLinus Torvalds <torvalds@linux-foundation.org>
Tue, 7 Apr 2009 21:07:52 +0000 (14:07 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 7 Apr 2009 21:07:52 +0000 (14:07 -0700)
* 'irq/threaded' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/linux-2.6-tip:
  genirq: fix devres.o build for GENERIC_HARDIRQS=n
  genirq: provide old request_irq() for CONFIG_GENERIC_HARDIRQ=n
  genirq: threaded irq handlers review fixups
  genirq: add support for threaded interrupts to devres
  genirq: add threaded interrupt handler support

include/linux/hardirq.h
include/linux/interrupt.h
include/linux/irq.h
include/linux/irqreturn.h
include/linux/sched.h
kernel/exit.c
kernel/irq/devres.c
kernel/irq/handle.c
kernel/irq/manage.c

index faa1cf8..4525747 100644 (file)
 # define IRQ_EXIT_OFFSET HARDIRQ_OFFSET
 #endif
 
-#ifdef CONFIG_SMP
+#if defined(CONFIG_SMP) || defined(CONFIG_GENERIC_HARDIRQS)
 extern void synchronize_irq(unsigned int irq);
 #else
 # define synchronize_irq(irq)  barrier()
index 8a9613d..91bb76f 100644 (file)
 #define IRQF_NOBALANCING       0x00000800
 #define IRQF_IRQPOLL           0x00001000
 
+/*
+ * Bits used by threaded handlers:
+ * IRQTF_RUNTHREAD - signals that the interrupt handler thread should run
+ * IRQTF_DIED      - handler thread died
+ * IRQTF_WARNED    - warning "IRQ_WAKE_THREAD w/o thread_fn" has been printed
+ */
+enum {
+       IRQTF_RUNTHREAD,
+       IRQTF_DIED,
+       IRQTF_WARNED,
+};
+
 typedef irqreturn_t (*irq_handler_t)(int, void *);
 
 /**
@@ -71,6 +83,9 @@ typedef irqreturn_t (*irq_handler_t)(int, void *);
  * @next:      pointer to the next irqaction for shared interrupts
  * @irq:       interrupt number
  * @dir:       pointer to the proc/irq/NN/name entry
+ * @thread_fn: interupt handler function for threaded interrupts
+ * @thread:    thread pointer for threaded interrupts
+ * @thread_flags:      flags related to @thread
  */
 struct irqaction {
        irq_handler_t handler;
@@ -81,18 +96,68 @@ struct irqaction {
        struct irqaction *next;
        int irq;
        struct proc_dir_entry *dir;
+       irq_handler_t thread_fn;
+       struct task_struct *thread;
+       unsigned long thread_flags;
 };
 
 extern irqreturn_t no_action(int cpl, void *dev_id);
-extern int __must_check request_irq(unsigned int, irq_handler_t handler,
-                      unsigned long, const char *, void *);
+
+#ifdef CONFIG_GENERIC_HARDIRQS
+extern int __must_check
+request_threaded_irq(unsigned int irq, irq_handler_t handler,
+                    irq_handler_t thread_fn,
+                    unsigned long flags, const char *name, void *dev);
+
+static inline int __must_check
+request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
+           const char *name, void *dev)
+{
+       return request_threaded_irq(irq, handler, NULL, flags, name, dev);
+}
+
+extern void exit_irq_thread(void);
+#else
+
+extern int __must_check
+request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
+           const char *name, void *dev);
+
+/*
+ * Special function to avoid ifdeffery in kernel/irq/devres.c which
+ * gets magically built by GENERIC_HARDIRQS=n architectures (sparc,
+ * m68k). I really love these $@%#!* obvious Makefile references:
+ * ../../../kernel/irq/devres.o
+ */
+static inline int __must_check
+request_threaded_irq(unsigned int irq, irq_handler_t handler,
+                    irq_handler_t thread_fn,
+                    unsigned long flags, const char *name, void *dev)
+{
+       return request_irq(irq, handler, flags, name, dev);
+}
+
+static inline void exit_irq_thread(void) { }
+#endif
+
 extern void free_irq(unsigned int, void *);
 
 struct device;
 
-extern int __must_check devm_request_irq(struct device *dev, unsigned int irq,
-                           irq_handler_t handler, unsigned long irqflags,
-                           const char *devname, void *dev_id);
+extern int __must_check
+devm_request_threaded_irq(struct device *dev, unsigned int irq,
+                         irq_handler_t handler, irq_handler_t thread_fn,
+                         unsigned long irqflags, const char *devname,
+                         void *dev_id);
+
+static inline int __must_check
+devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
+                unsigned long irqflags, const char *devname, void *dev_id)
+{
+       return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags,
+                                        devname, dev_id);
+}
+
 extern void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id);
 
 /*
index 974890b..ca507c9 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/irqnr.h>
 #include <linux/errno.h>
 #include <linux/topology.h>
+#include <linux/wait.h>
 
 #include <asm/irq.h>
 #include <asm/ptrace.h>
@@ -158,6 +159,8 @@ struct irq_2_iommu;
  * @affinity:          IRQ affinity on SMP
  * @cpu:               cpu index useful for balancing
  * @pending_mask:      pending rebalanced interrupts
+ * @threads_active:    number of irqaction threads currently running
+ * @wait_for_threads:  wait queue for sync_irq to wait for threaded handlers
  * @dir:               /proc/irq/ procfs entry
  * @name:              flow handler name for /proc/interrupts output
  */
@@ -189,6 +192,8 @@ struct irq_desc {
        cpumask_var_t           pending_mask;
 #endif
 #endif
+       atomic_t                threads_active;
+       wait_queue_head_t       wait_for_threads;
 #ifdef CONFIG_PROC_FS
        struct proc_dir_entry   *dir;
 #endif
index c5584ca..819acaa 100644 (file)
@@ -5,10 +5,12 @@
  * enum irqreturn
  * @IRQ_NONE           interrupt was not from this device
  * @IRQ_HANDLED                interrupt was handled by this device
+ * @IRQ_WAKE_THREAD    handler requests to wake the handler thread
  */
 enum irqreturn {
        IRQ_NONE,
        IRQ_HANDLED,
+       IRQ_WAKE_THREAD,
 };
 
 typedef enum irqreturn irqreturn_t;
index b94f354..c961402 100644 (file)
@@ -1294,6 +1294,11 @@ struct task_struct {
 /* Protection of (de-)allocation: mm, files, fs, tty, keyrings */
        spinlock_t alloc_lock;
 
+#ifdef CONFIG_GENERIC_HARDIRQS
+       /* IRQ handler threads */
+       struct irqaction *irqaction;
+#endif
+
        /* Protection of the PI data structures: */
        spinlock_t pi_lock;
 
index 32cbf26..abf9cf3 100644 (file)
@@ -923,6 +923,8 @@ NORET_TYPE void do_exit(long code)
                schedule();
        }
 
+       exit_irq_thread();
+
        exit_signals(tsk);  /* sets PF_EXITING */
        /*
         * tsk->flags are checked in the futex code to protect against
index 38a25b8..d06df9c 100644 (file)
@@ -26,10 +26,12 @@ static int devm_irq_match(struct device *dev, void *res, void *data)
 }
 
 /**
- *     devm_request_irq - allocate an interrupt line for a managed device
+ *     devm_request_threaded_irq - allocate an interrupt line for a managed device
  *     @dev: device to request interrupt for
  *     @irq: Interrupt line to allocate
  *     @handler: Function to be called when the IRQ occurs
+ *     @thread_fn: function to be called in a threaded interrupt context. NULL
+ *                 for devices which handle everything in @handler
  *     @irqflags: Interrupt type flags
  *     @devname: An ascii name for the claiming device
  *     @dev_id: A cookie passed back to the handler function
@@ -42,9 +44,10 @@ static int devm_irq_match(struct device *dev, void *res, void *data)
  *     If an IRQ allocated with this function needs to be freed
  *     separately, dev_free_irq() must be used.
  */
-int devm_request_irq(struct device *dev, unsigned int irq,
-                    irq_handler_t handler, unsigned long irqflags,
-                    const char *devname, void *dev_id)
+int devm_request_threaded_irq(struct device *dev, unsigned int irq,
+                             irq_handler_t handler, irq_handler_t thread_fn,
+                             unsigned long irqflags, const char *devname,
+                             void *dev_id)
 {
        struct irq_devres *dr;
        int rc;
@@ -54,7 +57,8 @@ int devm_request_irq(struct device *dev, unsigned int irq,
        if (!dr)
                return -ENOMEM;
 
-       rc = request_irq(irq, handler, irqflags, devname, dev_id);
+       rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
+                                 dev_id);
        if (rc) {
                devres_free(dr);
                return rc;
@@ -66,7 +70,7 @@ int devm_request_irq(struct device *dev, unsigned int irq,
 
        return 0;
 }
-EXPORT_SYMBOL(devm_request_irq);
+EXPORT_SYMBOL(devm_request_threaded_irq);
 
 /**
  *     devm_free_irq - free an interrupt
index 343acec..d82142b 100644 (file)
@@ -339,6 +339,15 @@ irqreturn_t no_action(int cpl, void *dev_id)
        return IRQ_NONE;
 }
 
+static void warn_no_thread(unsigned int irq, struct irqaction *action)
+{
+       if (test_and_set_bit(IRQTF_WARNED, &action->thread_flags))
+               return;
+
+       printk(KERN_WARNING "IRQ %d device %s returned IRQ_WAKE_THREAD "
+              "but no thread function available.", irq, action->name);
+}
+
 DEFINE_TRACE(irq_handler_entry);
 DEFINE_TRACE(irq_handler_exit);
 
@@ -363,8 +372,47 @@ irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
                trace_irq_handler_entry(irq, action);
                ret = action->handler(irq, action->dev_id);
                trace_irq_handler_exit(irq, action, ret);
-               if (ret == IRQ_HANDLED)
+
+               switch (ret) {
+               case IRQ_WAKE_THREAD:
+                       /*
+                        * Set result to handled so the spurious check
+                        * does not trigger.
+                        */
+                       ret = IRQ_HANDLED;
+
+                       /*
+                        * 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;
+                       }
+
+                       /*
+                        * Wake up the handler thread for this
+                        * action. In case the thread crashed and was
+                        * killed we just pretend that we handled the
+                        * interrupt. The hardirq handler above has
+                        * disabled the device interrupt, so no irq
+                        * storm is lurking.
+                        */
+                       if (likely(!test_bit(IRQTF_DIED,
+                                            &action->thread_flags))) {
+                               set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
+                               wake_up_process(action->thread);
+                       }
+
+                       /* Fall through to add to randomness */
+               case IRQ_HANDLED:
                        status |= action->flags;
+                       break;
+
+               default:
+                       break;
+               }
+
                retval |= ret;
                action = action->next;
        } while (action);
index 1516ab7..7e2e7dd 100644 (file)
@@ -8,16 +8,15 @@
  */
 
 #include <linux/irq.h>
+#include <linux/kthread.h>
 #include <linux/module.h>
 #include <linux/random.h>
 #include <linux/interrupt.h>
 #include <linux/slab.h>
+#include <linux/sched.h>
 
 #include "internals.h"
 
-#if defined(CONFIG_SMP) && defined(CONFIG_GENERIC_HARDIRQS)
-cpumask_var_t irq_default_affinity;
-
 /**
  *     synchronize_irq - wait for pending IRQ handlers (on other CPUs)
  *     @irq: interrupt number to wait for
@@ -53,9 +52,18 @@ void synchronize_irq(unsigned int irq)
 
                /* Oops, that failed? */
        } while (status & IRQ_INPROGRESS);
+
+       /*
+        * We made sure that no hardirq handler is running. Now verify
+        * that no threaded handlers are active.
+        */
+       wait_event(desc->wait_for_threads, !atomic_read(&desc->threads_active));
 }
 EXPORT_SYMBOL(synchronize_irq);
 
+#ifdef CONFIG_SMP
+cpumask_var_t irq_default_affinity;
+
 /**
  *     irq_can_set_affinity - Check if the affinity of a given irq can be set
  *     @irq:           Interrupt to check
@@ -72,6 +80,18 @@ int irq_can_set_affinity(unsigned int irq)
        return 1;
 }
 
+static void
+irq_set_thread_affinity(struct irq_desc *desc, const struct cpumask *cpumask)
+{
+       struct irqaction *action = desc->action;
+
+       while (action) {
+               if (action->thread)
+                       set_cpus_allowed_ptr(action->thread, cpumask);
+               action = action->next;
+       }
+}
+
 /**
  *     irq_set_affinity - Set the irq affinity of a given irq
  *     @irq:           Interrupt to set affinity
@@ -100,6 +120,7 @@ int irq_set_affinity(unsigned int irq, const struct cpumask *cpumask)
        cpumask_copy(desc->affinity, cpumask);
        desc->chip->set_affinity(irq, cpumask);
 #endif
+       irq_set_thread_affinity(desc, cpumask);
        desc->status |= IRQ_AFFINITY_SET;
        spin_unlock_irqrestore(&desc->lock, flags);
        return 0;
@@ -150,6 +171,8 @@ int irq_select_affinity_usr(unsigned int irq)
 
        spin_lock_irqsave(&desc->lock, flags);
        ret = setup_affinity(irq, desc);
+       if (!ret)
+               irq_set_thread_affinity(desc, desc->affinity);
        spin_unlock_irqrestore(&desc->lock, flags);
 
        return ret;
@@ -401,6 +424,90 @@ int __irq_set_trigger(struct irq_desc *desc, unsigned int irq,
        return ret;
 }
 
+static int irq_wait_for_interrupt(struct irqaction *action)
+{
+       while (!kthread_should_stop()) {
+               set_current_state(TASK_INTERRUPTIBLE);
+
+               if (test_and_clear_bit(IRQTF_RUNTHREAD,
+                                      &action->thread_flags)) {
+                       __set_current_state(TASK_RUNNING);
+                       return 0;
+               }
+               schedule();
+       }
+       return -1;
+}
+
+/*
+ * Interrupt handler thread
+ */
+static int irq_thread(void *data)
+{
+       struct sched_param param = { .sched_priority = MAX_USER_RT_PRIO/2, };
+       struct irqaction *action = data;
+       struct irq_desc *desc = irq_to_desc(action->irq);
+       int wake;
+
+       sched_setscheduler(current, SCHED_FIFO, &param);
+       current->irqaction = action;
+
+       while (!irq_wait_for_interrupt(action)) {
+
+               atomic_inc(&desc->threads_active);
+
+               spin_lock_irq(&desc->lock);
+               if (unlikely(desc->status & IRQ_DISABLED)) {
+                       /*
+                        * CHECKME: We might need a dedicated
+                        * IRQ_THREAD_PENDING flag here, which
+                        * retriggers the thread in check_irq_resend()
+                        * but AFAICT IRQ_PENDING should be fine as it
+                        * retriggers the interrupt itself --- tglx
+                        */
+                       desc->status |= IRQ_PENDING;
+                       spin_unlock_irq(&desc->lock);
+               } else {
+                       spin_unlock_irq(&desc->lock);
+
+                       action->thread_fn(action->irq, action->dev_id);
+               }
+
+               wake = atomic_dec_and_test(&desc->threads_active);
+
+               if (wake && waitqueue_active(&desc->wait_for_threads))
+                       wake_up(&desc->wait_for_threads);
+       }
+
+       /*
+        * Clear irqaction. Otherwise exit_irq_thread() would make
+        * fuzz about an active irq thread going into nirvana.
+        */
+       current->irqaction = NULL;
+       return 0;
+}
+
+/*
+ * Called from do_exit()
+ */
+void exit_irq_thread(void)
+{
+       struct task_struct *tsk = current;
+
+       if (!tsk->irqaction)
+               return;
+
+       printk(KERN_ERR
+              "exiting task \"%s\" (%d) is an active IRQ thread (irq %d)\n",
+              tsk->comm ? tsk->comm : "", tsk->pid, tsk->irqaction->irq);
+
+       /*
+        * Set the THREAD DIED flag to prevent further wakeups of the
+        * soon to be gone threaded handler.
+        */
+       set_bit(IRQTF_DIED, &tsk->irqaction->flags);
+}
+
 /*
  * Internal function to register an irqaction - typically used to
  * allocate special interrupts that are part of the architecture.
@@ -436,6 +543,26 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
                rand_initialize_irq(irq);
        }
 
+       /*
+        * Threaded handler ?
+        */
+       if (new->thread_fn) {
+               struct task_struct *t;
+
+               t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
+                                  new->name);
+               if (IS_ERR(t))
+                       return PTR_ERR(t);
+               /*
+                * We keep the reference to the task struct even if
+                * the thread dies to avoid that the interrupt code
+                * references an already freed task_struct.
+                */
+               get_task_struct(t);
+               new->thread = t;
+               wake_up_process(t);
+       }
+
        /*
         * The following block of code has to be executed atomically
         */
@@ -473,15 +600,15 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
        if (!shared) {
                irq_chip_set_defaults(desc->chip);
 
+               init_waitqueue_head(&desc->wait_for_threads);
+
                /* Setup the type (level, edge polarity) if configured: */
                if (new->flags & IRQF_TRIGGER_MASK) {
                        ret = __irq_set_trigger(desc, irq,
                                        new->flags & IRQF_TRIGGER_MASK);
 
-                       if (ret) {
-                               spin_unlock_irqrestore(&desc->lock, flags);
-                               return ret;
-                       }
+                       if (ret)
+                               goto out_thread;
                } else
                        compat_irq_chip_set_default_handler(desc);
 #if defined(CONFIG_IRQ_PER_CPU)
@@ -549,8 +676,19 @@ mismatch:
                dump_stack();
        }
 #endif
+       ret = -EBUSY;
+
+out_thread:
        spin_unlock_irqrestore(&desc->lock, flags);
-       return -EBUSY;
+       if (new->thread) {
+               struct task_struct *t = new->thread;
+
+               new->thread = NULL;
+               if (likely(!test_bit(IRQTF_DIED, &new->thread_flags)))
+                       kthread_stop(t);
+               put_task_struct(t);
+       }
+       return ret;
 }
 
 /**
@@ -576,6 +714,7 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
 {
        struct irq_desc *desc = irq_to_desc(irq);
        struct irqaction *action, **action_ptr;
+       struct task_struct *irqthread;
        unsigned long flags;
 
        WARN(in_interrupt(), "Trying to free IRQ %d from IRQ context!\n", irq);
@@ -622,6 +761,10 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
                else
                        desc->chip->disable(irq);
        }
+
+       irqthread = action->thread;
+       action->thread = NULL;
+
        spin_unlock_irqrestore(&desc->lock, flags);
 
        unregister_handler_proc(irq, action);
@@ -629,6 +772,12 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
        /* Make sure it's not being used on another CPU: */
        synchronize_irq(irq);
 
+       if (irqthread) {
+               if (!test_bit(IRQTF_DIED, &action->thread_flags))
+                       kthread_stop(irqthread);
+               put_task_struct(irqthread);
+       }
+
 #ifdef CONFIG_DEBUG_SHIRQ
        /*
         * It's a shared IRQ -- the driver ought to be prepared for an IRQ
@@ -681,9 +830,12 @@ void free_irq(unsigned int irq, void *dev_id)
 EXPORT_SYMBOL(free_irq);
 
 /**
- *     request_irq - allocate an interrupt line
+ *     request_threaded_irq - allocate an interrupt line
  *     @irq: Interrupt line to allocate
- *     @handler: Function to be called when the IRQ occurs
+ *     @handler: Function to be called when the IRQ occurs.
+ *               Primary handler for threaded interrupts
+ *     @thread_fn: Function called from the irq handler thread
+ *                 If NULL, no irq thread is created
  *     @irqflags: Interrupt type flags
  *     @devname: An ascii name for the claiming device
  *     @dev_id: A cookie passed back to the handler function
@@ -695,6 +847,15 @@ EXPORT_SYMBOL(free_irq);
  *     raises, you must take care both to initialise your hardware
  *     and to set up the interrupt handler in the right order.
  *
+ *     If you want to set up a threaded irq handler for your device
+ *     then you need to supply @handler and @thread_fn. @handler ist
+ *     still called in hard interrupt context and has to check
+ *     whether the interrupt originates from the device. If yes it
+ *     needs to disable the interrupt on the device and return
+ *     IRQ_THREAD_WAKE which will wake up the handler thread and run
+ *     @thread_fn. This split handler design is necessary to support
+ *     shared interrupts.
+ *
  *     Dev_id must be globally unique. Normally the address of the
  *     device data structure is used as the cookie. Since the handler
  *     receives this value it makes sense to use it.
@@ -710,8 +871,9 @@ EXPORT_SYMBOL(free_irq);
  *     IRQF_TRIGGER_*          Specify active edge(s) or level
  *
  */
-int request_irq(unsigned int irq, irq_handler_t handler,
-               unsigned long irqflags, const char *devname, void *dev_id)
+int request_threaded_irq(unsigned int irq, irq_handler_t handler,
+                        irq_handler_t thread_fn, unsigned long irqflags,
+                        const char *devname, void *dev_id)
 {
        struct irqaction *action;
        struct irq_desc *desc;
@@ -759,6 +921,7 @@ int request_irq(unsigned int irq, irq_handler_t handler,
                return -ENOMEM;
 
        action->handler = handler;
+       action->thread_fn = thread_fn;
        action->flags = irqflags;
        action->name = devname;
        action->dev_id = dev_id;
@@ -788,4 +951,4 @@ int request_irq(unsigned int irq, irq_handler_t handler,
 #endif
        return retval;
 }
-EXPORT_SYMBOL(request_irq);
+EXPORT_SYMBOL(request_threaded_irq);