From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754197AbaG3Vgh (ORCPT ); Wed, 30 Jul 2014 17:36:37 -0400 Received: from v094114.home.net.pl ([79.96.170.134]:63206 "HELO v094114.home.net.pl" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with SMTP id S1751452AbaG3Vgd (ORCPT ); Wed, 30 Jul 2014 17:36:33 -0400 From: "Rafael J. Wysocki" To: Thomas Gleixner Cc: Peter Zijlstra , linux-kernel@vger.kernel.org, Linux PM list , Dmitry Torokhov Subject: [PATCH 1/3] irq / PM: New driver interface for wakeup interrupts Date: Wed, 30 Jul 2014 23:51:04 +0200 Message-ID: <5808955.xYYC2DJrBV@vostro.rjw.lan> User-Agent: KMail/4.11.5 (Linux/3.16.0-rc5+; KDE/4.11.5; x86_64; ; ) In-Reply-To: <8151374.tpuvaHv3nd@vostro.rjw.lan> References: <20140724212620.GO3935@laptop> <3042738.6Ohp3GcNCj@vostro.rjw.lan> <8151374.tpuvaHv3nd@vostro.rjw.lan> MIME-Version: 1.0 Content-Transfer-Encoding: 7Bit Content-Type: text/plain; charset="utf-8" Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Rafael J. Wysocki 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 --- 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