[PATCH 1/3] irq / PM: New driver interface for wakeup interrupts

Rafael J. Wysocki
2014-07-30T21:51:04Z

From: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

Device drivers currently use enable_irq_wake() to configure their
interrupts for system wakeup, but that API is not particularly
well suited for this purpose, because it goes directly all the
way to the hardware and attempts to change the IRQ configuration
at the chip level.

The first problem with this approach is that the IRQ subsystem
is not told which interrupt handler is supposed to handle
interrupts from the wakeup line should they occur during system
suspend or resume. That is problematic if the IRQ is shared
and the other devices sharing it with the wakeup device in question
are not wakeup devices. In that case their drivers may not be
prepared to handle interrupts after the devices have been powered
down and they may expect suspend_device_irqs() to disable the
interrupt. For this reason, the IRQ should not be left enabled
by suspend_device_irqs() in that case. On the other hand, though,
it needs to be left enabled to prevent wakeup events occuring
after suspend_device_irqs() has returned from being lost.

The second problem is that on some platforms enable_irq_wake()
results in moving the IRQ over to a special interrupt controller
whose voltage is not removed in the final platform state. That
allows the platform to react to wakeup signals from the IRQ while
suspended, but the IRQ stops generating regular interrupts at that
point. That may lead to the loss of wakeup interrupts if they
come in after calling enable_irq_wake() and before the platform
is put into the final state. Moreover, if the IRQ is shared
and enable_irq_wake() is called from a device driver's .suspend()
callback, for example, it may prevent interrupts generated by
the other devices sharing the line from being handled.

To address the above issues introduce a new interface that can be
used by drivers to request that IRQs be configured for system
wakeup. That interface doesn't actually change the hardware
state, but tells the IRQ subsystem that the given interrupt should
or should not be configured for system wakeup at the right time.

First, enable_device_irq_wake() takes two arguments, the IRQ
number and the device cookie used when requesting the IRQ. The
cookie is used to identify the irqaction that should be used for
handling interrups during system suspend and resume. Namely,
the (new) IRQF_WAKEUP flag is set for that irqaction (if not
set already) and the (new) wake_needed field of the irq_desc
identified by the first argument is incremented.

Second, suspend_device_irqs() is modified to treat irqactions with
IRQF_WAKEUP set in the same way as irqactions with IRQF_NO_SUSPEND
set. That is, the handlers of those irqactions are left enabled
for the entire duration of system suspend/resume.

Next, the (new) syscore suspend routine for the IRQ subsystem,
irq_pm_syscore_suspend(), browses all irq_descs and calls
enable_irq_wake() for the ones with wake_needed set. If that is
successful, the (new) IRQS_WAKE_READY flag is set for the given
irq_desc to indicate that the IRQ should be switched back from
the wakeup mode during resume.

The IRQ subsystem's syscore resume routine, irq_pm_syscore_resume(),
is modified to call disable_irq_wake() for each irq_desc with
IRQS_WAKE_READY set and clears that flag for all of them.

Finally, disable_device_irq_wake() takes the same arguments as
enable_device_irq_wake(), finds the irqaction identified by the
second argument, clears IRQF_WAKEUP for it and decrements
wake_needed for the irq_desc identified by the first argument.

This organization of code guarantees that suspend_device_irqs()
will leave wakeup IRQs enabled, but also will block the execution
of interrupt handlers that should not be invoked going forward,
which allows wakeup interrupts to be handled and prevents possible
driver bugs from being tripped over at the same time. It also
ensures that IRQs will be reconfigured for wakeup at the point
where that should not disturb any legitimate functionality.

Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
---
include/linux/interrupt.h | 12 ++++++++++
include/linux/irqdesc.h | 1
kernel/irq/internals.h | 1
kernel/irq/manage.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++
kernel/irq/pm.c | 47 ++++++++++++++++++++++++++++++++++++----
5 files changed, 109 insertions(+), 5 deletions(-)

Index: linux-pm/include/linux/interrupt.h
===================================================================
--- linux-pm.orig/include/linux/interrupt.h
+++ linux-pm/include/linux/interrupt.h
@@ -70,8 +70,10 @@
#define IRQF_FORCE_RESUME 0x00008000
#define IRQF_NO_THREAD 0x00010000
#define IRQF_EARLY_RESUME 0x00020000
+#define IRQF_WAKEUP 0x00040000

#define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
+#define IRQF_INHIBIT_SUSPEND (IRQF_NO_SUSPEND | IRQF_WAKEUP)

/*
* These values can be returned by request_any_context_irq() and
@@ -350,6 +352,7 @@ static inline void enable_irq_lockdep_ir

/* IRQ wakeup (PM) control: */
extern int irq_set_irq_wake(unsigned int irq, unsigned int on);
+extern int device_irq_wake(unsigned int irq, void *dev_id, bool enable);

static inline int enable_irq_wake(unsigned int irq)
{
@@ -361,6 +364,15 @@ static inline int disable_irq_wake(unsig
return irq_set_irq_wake(irq, 0);
}

+static inline int enable_device_irq_wake(unsigned int irq, void *dev_id)
+{
+ return device_irq_wake(irq, dev_id, true);
+}
+
+static inline int disable_device_irq_wake(unsigned int irq, void *dev_id)
+{
+ return device_irq_wake(irq, dev_id, false);
+}

#ifdef CONFIG_IRQ_FORCED_THREADING
extern bool force_irqthreads;
Index: linux-pm/include/linux/irqdesc.h
===================================================================
--- linux-pm.orig/include/linux/irqdesc.h
+++ linux-pm/include/linux/irqdesc.h
@@ -53,6 +53,7 @@ struct irq_desc {
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
+ unsigned int wake_needed;
unsigned int irq_count; /* For detecting broken IRQs */
unsigned long last_unhandled; /* Aging timer for unhandled count */
unsigned int irqs_unhandled;
Index: linux-pm/kernel/irq/internals.h
===================================================================
--- linux-pm.orig/kernel/irq/internals.h
+++ linux-pm/kernel/irq/internals.h
@@ -53,6 +53,7 @@ enum {
IRQS_REPLAY = 0x00000040,
IRQS_WAITING = 0x00000080,
IRQS_PENDING = 0x00000200,
+ IRQS_WAKE_READY = 0x00000400,
IRQS_SUSPENDED = 0x00000800,
};

Index: linux-pm/kernel/irq/pm.c
===================================================================
--- linux-pm.orig/kernel/irq/pm.c
+++ linux-pm/kernel/irq/pm.c
@@ -21,7 +21,7 @@ static void suspend_irq(struct irq_desc
if (!action)
return;

- no_suspend = IRQF_NO_SUSPEND;
+ no_suspend = IRQF_INHIBIT_SUSPEND;
flags = 0;
do {
no_suspend &= action->flags;
@@ -33,7 +33,7 @@ static void suspend_irq(struct irq_desc

desc->istate |= IRQS_SUSPENDED;

- if ((flags & IRQF_NO_SUSPEND) &&
+ if ((flags & IRQF_INHIBIT_SUSPEND) &&
!(desc->istate & IRQS_SPURIOUS_DISABLED)) {
struct irqaction *active = NULL;
struct irqaction *suspended = NULL;
@@ -42,7 +42,7 @@ static void suspend_irq(struct irq_desc
do {
action = head;
head = action->next;
- if (action->flags & IRQF_NO_SUSPEND) {
+ if (action->flags & IRQF_INHIBIT_SUSPEND) {
action->next = active;
active = action;
} else {
@@ -138,16 +138,53 @@ static void resume_irqs(bool want_early)
}

/**
- * irq_pm_syscore_ops - enable interrupt lines early
+ * irq_pm_syscore_suspend - configure interrupts for system wakeup
*
- * Enable all interrupt lines with %IRQF_EARLY_RESUME set.
+ * Configure all interrupt lines with %wake_needed set for system wakeup.
+ */
+static int irq_pm_syscore_suspend(void)
+{
+ struct irq_desc *desc;
+ int irq;
+
+ for_each_irq_desc(irq, desc)
+ if (desc->wake_needed) {
+ int error = enable_irq_wake(irq);
+
+ if (error) {
+ /* Ignore missing callbacks. */
+ if (error != -ENXIO)
+ return error;
+ } else {
+ desc->istate |= IRQS_WAKE_READY;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * irq_pm_syscore_resume - enable interrupt lines early
+ *
+ * Switch wakeup interrupt lines back to the normal mode of operation and
+ * enable all interrupt lines with %IRQF_EARLY_RESUME set.
*/
static void irq_pm_syscore_resume(void)
{
+ struct irq_desc *desc;
+ int irq;
+
+ for_each_irq_desc(irq, desc)
+ if (desc->istate & IRQS_WAKE_READY) {
+ disable_irq_wake(irq);
+ desc->istate &= ~IRQS_WAKE_READY;
+ }
+
resume_irqs(true);
}

static struct syscore_ops irq_pm_syscore_ops = {
+ .suspend = irq_pm_syscore_suspend,
.resume = irq_pm_syscore_resume,
};

Index: linux-pm/kernel/irq/manage.c
===================================================================
--- linux-pm.orig/kernel/irq/manage.c
+++ linux-pm/kernel/irq/manage.c
@@ -547,6 +547,59 @@ int irq_set_irq_wake(unsigned int irq, u
}
EXPORT_SYMBOL(irq_set_irq_wake);

+/**
+ * device_irq_wake - set/unset device irq PM wakeup requirement
+ * @irq: interrupt to control
+ * @dev_id: interrupt handler device cookie
+ * @enable: whether or not the interrupt should wake up the system
+ *
+ * Tell the IRQ subsystem whether or not the given interrupt should be used
+ * for system wakeup from sleep states (like suspend-to-RAM). This doesn't
+ * change the hardware configuration, but notes whether or not to change it
+ * at the syscore stage of system suspend and resume.
+ *
+ * @dev_id may be NULL if the IRQ has not been requested as a shared one.
+ * Otherwise, it must be the same as the one used when requesting the IRQ.
+ */
+int device_irq_wake(unsigned int irq, void *dev_id, bool enable)
+{
+ unsigned long flags;
+ struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);
+ struct irqaction *action;
+
+ if (!desc || !desc->action)
+ return -EINVAL;
+
+ if (dev_id) {
+ for (action = desc->action; action; action = action->next)
+ if (action->dev_id == dev_id)
+ break;
+ } else {
+ action = desc->action;
+ if (action->flags & IRQF_SHARED)
+ action = NULL;
+ }
+ if (!action) {
+ irq_put_desc_busunlock(desc, flags);
+ return -ENODEV;
+ }
+ if (enable) {
+ if (!(action->flags & IRQF_WAKEUP)) {
+ action->flags |= IRQF_WAKEUP;
+ desc->wake_needed++;
+ }
+ } else {
+ if (action->flags & IRQF_WAKEUP) {
+ action->flags &= ~IRQF_WAKEUP;
+ desc->wake_needed--;
+ }
+ }
+
+ irq_put_desc_busunlock(desc, flags);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(device_irq_wake);
+
/*
* Internal function that tells the architecture code whether a
* particular irq has been exclusively allocated or is available