PM: Introduce functions for suspending and resuming device interrupts
authorRafael J. Wysocki <rjw@sisk.pl>
Mon, 16 Mar 2009 21:33:49 +0000 (22:33 +0100)
committerRafael J. Wysocki <rjw@sisk.pl>
Mon, 30 Mar 2009 19:46:54 +0000 (21:46 +0200)
Introduce helper functions allowing us to prevent device drivers from
getting any interrupts (without disabling interrupts on the CPU)
during suspend (or hibernation) and to make them start to receive
interrupts again during the subsequent resume.  These functions make it
possible to keep timer interrupts enabled while the "late" suspend and
"early" resume callbacks provided by device drivers are being
executed.  In turn, this allows device drivers' "late" suspend and
"early" resume callbacks to sleep, execute ACPI callbacks etc.

The functions introduced here will be used to rework the handling of
interrupts during suspend (hibernation) and resume.  Namely,
interrupts will only be disabled on the CPU right before suspending
sysdevs, while device drivers will be prevented from receiving
interrupts, with the help of the new helper function, before their
"late" suspend callbacks run (and analogously during resume).

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Acked-by: Ingo Molnar <mingo@elte.hu>
include/linux/interrupt.h
include/linux/irq.h
kernel/irq/Makefile
kernel/irq/internals.h
kernel/irq/manage.c
kernel/irq/pm.c [new file with mode: 0644]

index 0c9cb63e689530cfbc6a060b8a0b07ff2175bd62..c68bffd182bbe4d977848fd3507150da83be4d7b 100644 (file)
@@ -117,6 +117,15 @@ extern void disable_irq_nosync(unsigned int irq);
 extern void disable_irq(unsigned int irq);
 extern void enable_irq(unsigned int irq);
 
+/* The following three functions are for the core kernel use only. */
+extern void suspend_device_irqs(void);
+extern void resume_device_irqs(void);
+#ifdef CONFIG_PM_SLEEP
+extern int check_wakeup_irqs(void);
+#else
+static inline int check_wakeup_irqs(void) { return 0; }
+#endif
+
 #if defined(CONFIG_SMP) && defined(CONFIG_GENERIC_HARDIRQS)
 
 extern cpumask_var_t irq_default_affinity;
index 9c62fbe2ef3016ccb087e9694c48e420b0f9fc0c..974890b3c52fbfe0048df3cf7d736798caf873b6 100644 (file)
@@ -67,6 +67,7 @@ typedef       void (*irq_flow_handler_t)(unsigned int irq,
 #define IRQ_SPURIOUS_DISABLED  0x00800000      /* IRQ was disabled by the spurious trap */
 #define IRQ_MOVE_PCNTXT                0x01000000      /* IRQ migration from process context */
 #define IRQ_AFFINITY_SET       0x02000000      /* IRQ affinity was set from userspace*/
+#define IRQ_SUSPENDED          0x04000000      /* IRQ has gone through suspend sequence */
 
 #ifdef CONFIG_IRQ_PER_CPU
 # define CHECK_IRQ_PER_CPU(var) ((var) & IRQ_PER_CPU)
index 4dd5b1edac984bca7326297ecee3daf0239301f1..3394f8f52964b3d9b4433a3560ff94eb32b20cb8 100644 (file)
@@ -4,3 +4,4 @@ obj-$(CONFIG_GENERIC_IRQ_PROBE) += autoprobe.o
 obj-$(CONFIG_PROC_FS) += proc.o
 obj-$(CONFIG_GENERIC_PENDING_IRQ) += migration.o
 obj-$(CONFIG_NUMA_MIGRATE_IRQ_DESC) += numa_migrate.o
+obj-$(CONFIG_PM_SLEEP) += pm.o
index ee1aa9f8e8b9341af0c81ec9073f52e48375eb5b..01ce20eab38fed96a7b5a861940ba58d47c18fbf 100644 (file)
@@ -12,6 +12,8 @@ extern void compat_irq_chip_set_default_handler(struct irq_desc *desc);
 
 extern int __irq_set_trigger(struct irq_desc *desc, unsigned int irq,
                unsigned long flags);
+extern void __disable_irq(struct irq_desc *desc, unsigned int irq, bool susp);
+extern void __enable_irq(struct irq_desc *desc, unsigned int irq, bool resume);
 
 extern struct lock_class_key irq_desc_lock_class;
 extern void init_kstat_irqs(struct irq_desc *desc, int cpu, int nr);
index 6458e99984c08f3af7a108ac0f7db03b21a7e5e8..1516ab77355c928bd1e1f5c8b6759e53d3b0c7e3 100644 (file)
@@ -162,6 +162,20 @@ static inline int setup_affinity(unsigned int irq, struct irq_desc *desc)
 }
 #endif
 
+void __disable_irq(struct irq_desc *desc, unsigned int irq, bool suspend)
+{
+       if (suspend) {
+               if (!desc->action || (desc->action->flags & IRQF_TIMER))
+                       return;
+               desc->status |= IRQ_SUSPENDED;
+       }
+
+       if (!desc->depth++) {
+               desc->status |= IRQ_DISABLED;
+               desc->chip->disable(irq);
+       }
+}
+
 /**
  *     disable_irq_nosync - disable an irq without waiting
  *     @irq: Interrupt to disable
@@ -182,10 +196,7 @@ void disable_irq_nosync(unsigned int irq)
                return;
 
        spin_lock_irqsave(&desc->lock, flags);
-       if (!desc->depth++) {
-               desc->status |= IRQ_DISABLED;
-               desc->chip->disable(irq);
-       }
+       __disable_irq(desc, irq, false);
        spin_unlock_irqrestore(&desc->lock, flags);
 }
 EXPORT_SYMBOL(disable_irq_nosync);
@@ -215,15 +226,21 @@ void disable_irq(unsigned int irq)
 }
 EXPORT_SYMBOL(disable_irq);
 
-static void __enable_irq(struct irq_desc *desc, unsigned int irq)
+void __enable_irq(struct irq_desc *desc, unsigned int irq, bool resume)
 {
+       if (resume)
+               desc->status &= ~IRQ_SUSPENDED;
+
        switch (desc->depth) {
        case 0:
+ err_out:
                WARN(1, KERN_WARNING "Unbalanced enable for IRQ %d\n", irq);
                break;
        case 1: {
                unsigned int status = desc->status & ~IRQ_DISABLED;
 
+               if (desc->status & IRQ_SUSPENDED)
+                       goto err_out;
                /* Prevent probing on this irq: */
                desc->status = status | IRQ_NOPROBE;
                check_irq_resend(desc, irq);
@@ -253,7 +270,7 @@ void enable_irq(unsigned int irq)
                return;
 
        spin_lock_irqsave(&desc->lock, flags);
-       __enable_irq(desc, irq);
+       __enable_irq(desc, irq, false);
        spin_unlock_irqrestore(&desc->lock, flags);
 }
 EXPORT_SYMBOL(enable_irq);
@@ -511,7 +528,7 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
         */
        if (shared && (desc->status & IRQ_SPURIOUS_DISABLED)) {
                desc->status &= ~IRQ_SPURIOUS_DISABLED;
-               __enable_irq(desc, irq);
+               __enable_irq(desc, irq, false);
        }
 
        spin_unlock_irqrestore(&desc->lock, flags);
diff --git a/kernel/irq/pm.c b/kernel/irq/pm.c
new file mode 100644 (file)
index 0000000..638d8be
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * linux/kernel/irq/pm.c
+ *
+ * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
+ *
+ * This file contains power management functions related to interrupts.
+ */
+
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "internals.h"
+
+/**
+ * suspend_device_irqs - disable all currently enabled interrupt lines
+ *
+ * During system-wide suspend or hibernation device interrupts need to be
+ * disabled at the chip level and this function is provided for this purpose.
+ * It disables all interrupt lines that are enabled at the moment and sets the
+ * IRQ_SUSPENDED flag for them.
+ */
+void suspend_device_irqs(void)
+{
+       struct irq_desc *desc;
+       int irq;
+
+       for_each_irq_desc(irq, desc) {
+               unsigned long flags;
+
+               spin_lock_irqsave(&desc->lock, flags);
+               __disable_irq(desc, irq, true);
+               spin_unlock_irqrestore(&desc->lock, flags);
+       }
+
+       for_each_irq_desc(irq, desc)
+               if (desc->status & IRQ_SUSPENDED)
+                       synchronize_irq(irq);
+}
+EXPORT_SYMBOL_GPL(suspend_device_irqs);
+
+/**
+ * resume_device_irqs - enable interrupt lines disabled by suspend_device_irqs()
+ *
+ * Enable all interrupt lines previously disabled by suspend_device_irqs() that
+ * have the IRQ_SUSPENDED flag set.
+ */
+void resume_device_irqs(void)
+{
+       struct irq_desc *desc;
+       int irq;
+
+       for_each_irq_desc(irq, desc) {
+               unsigned long flags;
+
+               if (!(desc->status & IRQ_SUSPENDED))
+                       continue;
+
+               spin_lock_irqsave(&desc->lock, flags);
+               __enable_irq(desc, irq, true);
+               spin_unlock_irqrestore(&desc->lock, flags);
+       }
+}
+EXPORT_SYMBOL_GPL(resume_device_irqs);
+
+/**
+ * check_wakeup_irqs - check if any wake-up interrupts are pending
+ */
+int check_wakeup_irqs(void)
+{
+       struct irq_desc *desc;
+       int irq;
+
+       for_each_irq_desc(irq, desc)
+               if ((desc->status & IRQ_WAKEUP) && (desc->status & IRQ_PENDING))
+                       return -EBUSY;
+
+       return 0;
+}