Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0 2 : : /* 3 : : * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. 4 : : * 5 : : * This file contains power management functions related to interrupts. 6 : : */ 7 : : 8 : : #include <linux/irq.h> 9 : : #include <linux/module.h> 10 : : #include <linux/interrupt.h> 11 : : #include <linux/suspend.h> 12 : : #include <linux/syscore_ops.h> 13 : : 14 : : #include "internals.h" 15 : : 16 : 0 : bool irq_pm_check_wakeup(struct irq_desc *desc) 17 : : { 18 [ # # ]: 0 : if (irqd_is_wakeup_armed(&desc->irq_data)) { 19 : 0 : irqd_clear(&desc->irq_data, IRQD_WAKEUP_ARMED); 20 : 0 : desc->istate |= IRQS_SUSPENDED | IRQS_PENDING; 21 : 0 : desc->depth++; 22 : 0 : irq_disable(desc); 23 : 0 : pm_system_irq_wakeup(irq_desc_get_irq(desc)); 24 : 0 : return true; 25 : : } 26 : : return false; 27 : : } 28 : : 29 : : /* 30 : : * Called from __setup_irq() with desc->lock held after @action has 31 : : * been installed in the action chain. 32 : : */ 33 : 210 : void irq_pm_install_action(struct irq_desc *desc, struct irqaction *action) 34 : : { 35 : 210 : desc->nr_actions++; 36 : : 37 [ - + ]: 210 : if (action->flags & IRQF_FORCE_RESUME) 38 : 0 : desc->force_resume_depth++; 39 : : 40 [ - + - - : 420 : WARN_ON_ONCE(desc->force_resume_depth && - + ] 41 : : desc->force_resume_depth != desc->nr_actions); 42 : : 43 [ + + ]: 210 : if (action->flags & IRQF_NO_SUSPEND) 44 : 21 : desc->no_suspend_depth++; 45 [ - + ]: 189 : else if (action->flags & IRQF_COND_SUSPEND) 46 : 0 : desc->cond_suspend_depth++; 47 : : 48 [ + + + - : 420 : WARN_ON_ONCE(desc->no_suspend_depth && - + ] 49 : : (desc->no_suspend_depth + 50 : : desc->cond_suspend_depth) != desc->nr_actions); 51 : 210 : } 52 : : 53 : : /* 54 : : * Called from __free_irq() with desc->lock held after @action has 55 : : * been removed from the action chain. 56 : : */ 57 : 21 : void irq_pm_remove_action(struct irq_desc *desc, struct irqaction *action) 58 : : { 59 : 21 : desc->nr_actions--; 60 : : 61 [ - + ]: 21 : if (action->flags & IRQF_FORCE_RESUME) 62 : 0 : desc->force_resume_depth--; 63 : : 64 [ - + ]: 21 : if (action->flags & IRQF_NO_SUSPEND) 65 : 0 : desc->no_suspend_depth--; 66 [ - + ]: 21 : else if (action->flags & IRQF_COND_SUSPEND) 67 : 0 : desc->cond_suspend_depth--; 68 : 21 : } 69 : : 70 : 0 : static bool suspend_device_irq(struct irq_desc *desc) 71 : : { 72 [ # # ]: 0 : if (!desc->action || irq_desc_is_chained(desc) || 73 [ # # ]: 0 : desc->no_suspend_depth) 74 : : return false; 75 : : 76 [ # # ]: 0 : if (irqd_is_wakeup_set(&desc->irq_data)) { 77 : 0 : irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED); 78 : : /* 79 : : * We return true here to force the caller to issue 80 : : * synchronize_irq(). We need to make sure that the 81 : : * IRQD_WAKEUP_ARMED is visible before we return from 82 : : * suspend_device_irqs(). 83 : : */ 84 : 0 : return true; 85 : : } 86 : : 87 : 0 : desc->istate |= IRQS_SUSPENDED; 88 : 0 : __disable_irq(desc); 89 : : 90 : : /* 91 : : * Hardware which has no wakeup source configuration facility 92 : : * requires that the non wakeup interrupts are masked at the 93 : : * chip level. The chip implementation indicates that with 94 : : * IRQCHIP_MASK_ON_SUSPEND. 95 : : */ 96 [ # # ]: 0 : if (irq_desc_get_chip(desc)->flags & IRQCHIP_MASK_ON_SUSPEND) 97 : 0 : mask_irq(desc); 98 : : return true; 99 : : } 100 : : 101 : : /** 102 : : * suspend_device_irqs - disable all currently enabled interrupt lines 103 : : * 104 : : * During system-wide suspend or hibernation device drivers need to be 105 : : * prevented from receiving interrupts and this function is provided 106 : : * for this purpose. 107 : : * 108 : : * So we disable all interrupts and mark them IRQS_SUSPENDED except 109 : : * for those which are unused, those which are marked as not 110 : : * suspendable via an interrupt request with the flag IRQF_NO_SUSPEND 111 : : * set and those which are marked as active wakeup sources. 112 : : * 113 : : * The active wakeup sources are handled by the flow handler entry 114 : : * code which checks for the IRQD_WAKEUP_ARMED flag, suspends the 115 : : * interrupt and notifies the pm core about the wakeup. 116 : : */ 117 : 0 : void suspend_device_irqs(void) 118 : : { 119 : 0 : struct irq_desc *desc; 120 : 0 : int irq; 121 : : 122 [ # # # # ]: 0 : for_each_irq_desc(irq, desc) { 123 : 0 : unsigned long flags; 124 : 0 : bool sync; 125 : : 126 [ # # ]: 0 : if (irq_settings_is_nested_thread(desc)) 127 : 0 : continue; 128 : 0 : raw_spin_lock_irqsave(&desc->lock, flags); 129 : 0 : sync = suspend_device_irq(desc); 130 : 0 : raw_spin_unlock_irqrestore(&desc->lock, flags); 131 : : 132 [ # # ]: 0 : if (sync) 133 : 0 : synchronize_irq(irq); 134 : : } 135 : 0 : } 136 : : EXPORT_SYMBOL_GPL(suspend_device_irqs); 137 : : 138 : 0 : static void resume_irq(struct irq_desc *desc) 139 : : { 140 [ # # ]: 0 : irqd_clear(&desc->irq_data, IRQD_WAKEUP_ARMED); 141 : : 142 [ # # ]: 0 : if (desc->istate & IRQS_SUSPENDED) 143 : 0 : goto resume; 144 : : 145 : : /* Force resume the interrupt? */ 146 [ # # ]: 0 : if (!desc->force_resume_depth) 147 : : return; 148 : : 149 : : /* Pretend that it got disabled ! */ 150 : 0 : desc->depth++; 151 : 0 : irq_state_set_disabled(desc); 152 : 0 : irq_state_set_masked(desc); 153 : 0 : resume: 154 : 0 : desc->istate &= ~IRQS_SUSPENDED; 155 : 0 : __enable_irq(desc); 156 : : } 157 : : 158 : 0 : static void resume_irqs(bool want_early) 159 : : { 160 : 0 : struct irq_desc *desc; 161 : 0 : int irq; 162 : : 163 [ # # # # ]: 0 : for_each_irq_desc(irq, desc) { 164 : 0 : unsigned long flags; 165 [ # # ]: 0 : bool is_early = desc->action && 166 [ # # ]: 0 : desc->action->flags & IRQF_EARLY_RESUME; 167 : : 168 [ # # ]: 0 : if (!is_early && want_early) 169 : 0 : continue; 170 [ # # ]: 0 : if (irq_settings_is_nested_thread(desc)) 171 : 0 : continue; 172 : : 173 : 0 : raw_spin_lock_irqsave(&desc->lock, flags); 174 : 0 : resume_irq(desc); 175 : 0 : raw_spin_unlock_irqrestore(&desc->lock, flags); 176 : : } 177 : 0 : } 178 : : 179 : : /** 180 : : * rearm_wake_irq - rearm a wakeup interrupt line after signaling wakeup 181 : : * @irq: Interrupt to rearm 182 : : */ 183 : 0 : void rearm_wake_irq(unsigned int irq) 184 : : { 185 : 0 : unsigned long flags; 186 : 0 : struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL); 187 : : 188 [ # # # # : 0 : if (!desc || !(desc->istate & IRQS_SUSPENDED) || # # ] 189 [ # # ]: 0 : !irqd_is_wakeup_set(&desc->irq_data)) 190 : 0 : return; 191 : : 192 : 0 : desc->istate &= ~IRQS_SUSPENDED; 193 : 0 : irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED); 194 : 0 : __enable_irq(desc); 195 : : 196 : 0 : irq_put_desc_busunlock(desc, flags); 197 : : } 198 : : 199 : : /** 200 : : * irq_pm_syscore_ops - enable interrupt lines early 201 : : * 202 : : * Enable all interrupt lines with %IRQF_EARLY_RESUME set. 203 : : */ 204 : 0 : static void irq_pm_syscore_resume(void) 205 : : { 206 : 0 : resume_irqs(true); 207 : 0 : } 208 : : 209 : : static struct syscore_ops irq_pm_syscore_ops = { 210 : : .resume = irq_pm_syscore_resume, 211 : : }; 212 : : 213 : 21 : static int __init irq_pm_init_ops(void) 214 : : { 215 : 21 : register_syscore_ops(&irq_pm_syscore_ops); 216 : 21 : return 0; 217 : : } 218 : : 219 : : device_initcall(irq_pm_init_ops); 220 : : 221 : : /** 222 : : * resume_device_irqs - enable interrupt lines disabled by suspend_device_irqs() 223 : : * 224 : : * Enable all non-%IRQF_EARLY_RESUME interrupt lines previously 225 : : * disabled by suspend_device_irqs() that have the IRQS_SUSPENDED flag 226 : : * set as well as those with %IRQF_FORCE_RESUME. 227 : : */ 228 : 0 : void resume_device_irqs(void) 229 : : { 230 : 0 : resume_irqs(false); 231 : 0 : } 232 : : EXPORT_SYMBOL_GPL(resume_device_irqs);