]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - kernel/irq/manage.c
Merge branch 'linus' into irq/threaded
[karo-tx-linux.git] / kernel / irq / manage.c
index 1516ab77355c928bd1e1f5c8b6759e53d3b0c7e3..7e2e7dd4cd2f70e5d619f893161225e230bf487c 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);