Merge branch 'x86-apic-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[linux-block.git] / kernel / irq / manage.c
index 78f3ddeb7fe44a297229ebd65f28e591f4e3a56f..e8f7f179bf77e6a721deaaf349462edbc14ef8f3 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/module.h>
 #include <linux/random.h>
 #include <linux/interrupt.h>
+#include <linux/irqdomain.h>
 #include <linux/slab.h>
 #include <linux/sched.h>
 #include <linux/sched/rt.h>
@@ -34,8 +35,9 @@ static int __init setup_forced_irqthreads(char *arg)
 early_param("threadirqs", setup_forced_irqthreads);
 #endif
 
-static void __synchronize_hardirq(struct irq_desc *desc)
+static void __synchronize_hardirq(struct irq_desc *desc, bool sync_chip)
 {
+       struct irq_data *irqd = irq_desc_get_irq_data(desc);
        bool inprogress;
 
        do {
@@ -51,6 +53,20 @@ static void __synchronize_hardirq(struct irq_desc *desc)
                /* Ok, that indicated we're done: double-check carefully. */
                raw_spin_lock_irqsave(&desc->lock, flags);
                inprogress = irqd_irq_inprogress(&desc->irq_data);
+
+               /*
+                * If requested and supported, check at the chip whether it
+                * is in flight at the hardware level, i.e. already pending
+                * in a CPU and waiting for service and acknowledge.
+                */
+               if (!inprogress && sync_chip) {
+                       /*
+                        * Ignore the return code. inprogress is only updated
+                        * when the chip supports it.
+                        */
+                       __irq_get_irqchip_state(irqd, IRQCHIP_STATE_ACTIVE,
+                                               &inprogress);
+               }
                raw_spin_unlock_irqrestore(&desc->lock, flags);
 
                /* Oops, that failed? */
@@ -73,13 +89,18 @@ static void __synchronize_hardirq(struct irq_desc *desc)
  *     Returns: false if a threaded handler is active.
  *
  *     This function may be called - with care - from IRQ context.
+ *
+ *     It does not check whether there is an interrupt in flight at the
+ *     hardware level, but not serviced yet, as this might deadlock when
+ *     called with interrupts disabled and the target CPU of the interrupt
+ *     is the current CPU.
  */
 bool synchronize_hardirq(unsigned int irq)
 {
        struct irq_desc *desc = irq_to_desc(irq);
 
        if (desc) {
-               __synchronize_hardirq(desc);
+               __synchronize_hardirq(desc, false);
                return !atomic_read(&desc->threads_active);
        }
 
@@ -95,14 +116,19 @@ EXPORT_SYMBOL(synchronize_hardirq);
  *     to complete before returning. If you use this function while
  *     holding a resource the IRQ handler may need you will deadlock.
  *
- *     This function may be called - with care - from IRQ context.
+ *     Can only be called from preemptible code as it might sleep when
+ *     an interrupt thread is associated to @irq.
+ *
+ *     It optionally makes sure (when the irq chip supports that method)
+ *     that the interrupt is not pending in any CPU and waiting for
+ *     service.
  */
 void synchronize_irq(unsigned int irq)
 {
        struct irq_desc *desc = irq_to_desc(irq);
 
        if (desc) {
-               __synchronize_hardirq(desc);
+               __synchronize_hardirq(desc, true);
                /*
                 * We made sure that no hardirq handler is
                 * running. Now verify that no threaded handlers are
@@ -1699,6 +1725,7 @@ static struct irqaction *__free_irq(struct irq_desc *desc, void *dev_id)
        /* If this was the last handler, shut down the IRQ line: */
        if (!desc->action) {
                irq_settings_clr_disable_unlazy(desc);
+               /* Only shutdown. Deactivate after synchronize_hardirq() */
                irq_shutdown(desc);
        }
 
@@ -1727,8 +1754,12 @@ static struct irqaction *__free_irq(struct irq_desc *desc, void *dev_id)
 
        unregister_handler_proc(irq, action);
 
-       /* Make sure it's not being used on another CPU: */
-       synchronize_hardirq(irq);
+       /*
+        * Make sure it's not being used on another CPU and if the chip
+        * supports it also make sure that there is no (not yet serviced)
+        * interrupt in flight at the hardware level.
+        */
+       __synchronize_hardirq(desc, true);
 
 #ifdef CONFIG_DEBUG_SHIRQ
        /*
@@ -1768,6 +1799,14 @@ static struct irqaction *__free_irq(struct irq_desc *desc, void *dev_id)
                 * require it to deallocate resources over the slow bus.
                 */
                chip_bus_lock(desc);
+               /*
+                * There is no interrupt on the fly anymore. Deactivate it
+                * completely.
+                */
+               raw_spin_lock_irqsave(&desc->lock, flags);
+               irq_domain_deactivate_irq(&desc->irq_data);
+               raw_spin_unlock_irqrestore(&desc->lock, flags);
+
                irq_release_resources(desc);
                chip_bus_sync_unlock(desc);
                irq_remove_timings(desc);
@@ -1855,7 +1894,7 @@ static const void *__cleanup_nmi(unsigned int irq, struct irq_desc *desc)
        }
 
        irq_settings_clr_disable_unlazy(desc);
-       irq_shutdown(desc);
+       irq_shutdown_and_deactivate(desc);
 
        irq_release_resources(desc);
 
@@ -2578,6 +2617,28 @@ out:
        irq_put_desc_unlock(desc, flags);
 }
 
+int __irq_get_irqchip_state(struct irq_data *data, enum irqchip_irq_state which,
+                           bool *state)
+{
+       struct irq_chip *chip;
+       int err = -EINVAL;
+
+       do {
+               chip = irq_data_get_irq_chip(data);
+               if (chip->irq_get_irqchip_state)
+                       break;
+#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
+               data = data->parent_data;
+#else
+               data = NULL;
+#endif
+       } while (data);
+
+       if (data)
+               err = chip->irq_get_irqchip_state(data, which, state);
+       return err;
+}
+
 /**
  *     irq_get_irqchip_state - returns the irqchip state of a interrupt.
  *     @irq: Interrupt line that is forwarded to a VM
@@ -2596,7 +2657,6 @@ int irq_get_irqchip_state(unsigned int irq, enum irqchip_irq_state which,
 {
        struct irq_desc *desc;
        struct irq_data *data;
-       struct irq_chip *chip;
        unsigned long flags;
        int err = -EINVAL;
 
@@ -2606,19 +2666,7 @@ int irq_get_irqchip_state(unsigned int irq, enum irqchip_irq_state which,
 
        data = irq_desc_get_irq_data(desc);
 
-       do {
-               chip = irq_data_get_irq_chip(data);
-               if (chip->irq_get_irqchip_state)
-                       break;
-#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
-               data = data->parent_data;
-#else
-               data = NULL;
-#endif
-       } while (data);
-
-       if (data)
-               err = chip->irq_get_irqchip_state(data, which, state);
+       err = __irq_get_irqchip_state(data, which, state);
 
        irq_put_desc_busunlock(desc, flags);
        return err;