Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

genirq: Simplify wakeup mechanism

Currently we suspend wakeup interrupts by lazy disabling them and
check later whether the interrupt has fired, but that's not sufficient
for suspend to idle as there is no way to check that once we
transitioned into the CPU idle state.

So we change the mechanism in the following way:

1) Leave the wakeup interrupts enabled across suspend

2) Add a check to irq_may_run() which is called at the beginning of
each flow handler whether the interrupt is an armed wakeup source.

This check is basically free as it just extends the existing check
for IRQD_IRQ_INPROGRESS. So no new conditional in the hot path.

If the IRQD_WAKEUP_ARMED flag is set, then the interrupt is
disabled, marked as pending/suspended and the pm core is notified
about the wakeup event.

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
[ rjw: syscore.c and put irq_pm_check_wakeup() into pm.c ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

authored by

Thomas Gleixner and committed by
Rafael J. Wysocki
9ce7a258 b76f1674

+53 -36
+3 -4
drivers/base/syscore.c
··· 9 9 #include <linux/syscore_ops.h> 10 10 #include <linux/mutex.h> 11 11 #include <linux/module.h> 12 - #include <linux/interrupt.h> 12 + #include <linux/suspend.h> 13 13 #include <trace/events/power.h> 14 14 15 15 static LIST_HEAD(syscore_ops_list); ··· 54 54 pr_debug("Checking wakeup interrupts\n"); 55 55 56 56 /* Return error code if there are any wakeup interrupts pending. */ 57 - ret = check_wakeup_irqs(); 58 - if (ret) 59 - return ret; 57 + if (pm_wakeup_pending()) 58 + return -EBUSY; 60 59 61 60 WARN_ONCE(!irqs_disabled(), 62 61 "Interrupts enabled before system core suspend.\n");
-5
include/linux/interrupt.h
··· 193 193 /* The following three functions are for the core kernel use only. */ 194 194 extern void suspend_device_irqs(void); 195 195 extern void resume_device_irqs(void); 196 - #ifdef CONFIG_PM_SLEEP 197 - extern int check_wakeup_irqs(void); 198 - #else 199 - static inline int check_wakeup_irqs(void) { return 0; } 200 - #endif 201 196 202 197 /** 203 198 * struct irq_affinity_notify - context for notification of IRQ affinity changes
+19 -1
kernel/irq/chip.c
··· 344 344 345 345 static bool irq_may_run(struct irq_desc *desc) 346 346 { 347 - if (!irqd_irq_inprogress(&desc->irq_data)) 347 + unsigned int mask = IRQD_IRQ_INPROGRESS | IRQD_WAKEUP_ARMED; 348 + 349 + /* 350 + * If the interrupt is not in progress and is not an armed 351 + * wakeup interrupt, proceed. 352 + */ 353 + if (!irqd_has_set(&desc->irq_data, mask)) 348 354 return true; 355 + 356 + /* 357 + * If the interrupt is an armed wakeup source, mark it pending 358 + * and suspended, disable it and notify the pm core about the 359 + * event. 360 + */ 361 + if (irq_pm_check_wakeup(desc)) 362 + return false; 363 + 364 + /* 365 + * Handle a potential concurrent poll on a different core. 366 + */ 349 367 return irq_check_poll(desc); 350 368 } 351 369
+2
kernel/irq/internals.h
··· 196 196 } 197 197 198 198 #ifdef CONFIG_PM_SLEEP 199 + bool irq_pm_check_wakeup(struct irq_desc *desc); 199 200 void irq_pm_install_action(struct irq_desc *desc, struct irqaction *action); 200 201 void irq_pm_remove_action(struct irq_desc *desc, struct irqaction *action); 201 202 #else 203 + static inline bool irq_pm_check_wakeup(struct irq_desc *desc) { return false; } 202 204 static inline void 203 205 irq_pm_install_action(struct irq_desc *desc, struct irqaction *action) { } 204 206 static inline void
+29 -26
kernel/irq/pm.c
··· 9 9 #include <linux/irq.h> 10 10 #include <linux/module.h> 11 11 #include <linux/interrupt.h> 12 + #include <linux/suspend.h> 12 13 #include <linux/syscore_ops.h> 13 14 14 15 #include "internals.h" 16 + 17 + bool irq_pm_check_wakeup(struct irq_desc *desc) 18 + { 19 + if (irqd_is_wakeup_armed(&desc->irq_data)) { 20 + irqd_clear(&desc->irq_data, IRQD_WAKEUP_ARMED); 21 + desc->istate |= IRQS_SUSPENDED | IRQS_PENDING; 22 + desc->depth++; 23 + irq_disable(desc); 24 + pm_system_wakeup(); 25 + return true; 26 + } 27 + return false; 28 + } 15 29 16 30 /* 17 31 * Called from __setup_irq() with desc->lock held after @action has ··· 68 54 if (!desc->action || desc->no_suspend_depth) 69 55 return false; 70 56 71 - if (irqd_is_wakeup_set(&desc->irq_data)) 57 + if (irqd_is_wakeup_set(&desc->irq_data)) { 72 58 irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED); 59 + /* 60 + * We return true here to force the caller to issue 61 + * synchronize_irq(). We need to make sure that the 62 + * IRQD_WAKEUP_ARMED is visible before we return from 63 + * suspend_device_irqs(). 64 + */ 65 + return true; 66 + } 73 67 74 68 desc->istate |= IRQS_SUSPENDED; 75 69 __disable_irq(desc, irq); ··· 101 79 * for this purpose. 102 80 * 103 81 * So we disable all interrupts and mark them IRQS_SUSPENDED except 104 - * for those which are unused and those which are marked as not 82 + * for those which are unused, those which are marked as not 105 83 * suspendable via an interrupt request with the flag IRQF_NO_SUSPEND 106 - * set. 84 + * set and those which are marked as active wakeup sources. 85 + * 86 + * The active wakeup sources are handled by the flow handler entry 87 + * code which checks for the IRQD_WAKEUP_ARMED flag, suspends the 88 + * interrupt and notifies the pm core about the wakeup. 107 89 */ 108 90 void suspend_device_irqs(void) 109 91 { ··· 199 173 resume_irqs(false); 200 174 } 201 175 EXPORT_SYMBOL_GPL(resume_device_irqs); 202 - 203 - /** 204 - * check_wakeup_irqs - check if any wake-up interrupts are pending 205 - */ 206 - int check_wakeup_irqs(void) 207 - { 208 - struct irq_desc *desc; 209 - int irq; 210 - 211 - for_each_irq_desc(irq, desc) { 212 - /* 213 - * Only interrupts which are marked as wakeup source 214 - * and have not been disabled before the suspend check 215 - * can abort suspend. 216 - */ 217 - if (irqd_is_wakeup_set(&desc->irq_data)) { 218 - if (desc->depth == 1 && desc->istate & IRQS_PENDING) 219 - return -EBUSY; 220 - } 221 - } 222 - 223 - return 0; 224 - }