Skip to content

Commit 0a0c516

Browse files
committed
PM: Introduce functions for suspending and resuming device interrupts
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 <[email protected]> Acked-by: Ingo Molnar <[email protected]>
1 parent 019abbc commit 0a0c516

File tree

6 files changed

+116
-7
lines changed

6 files changed

+116
-7
lines changed

include/linux/interrupt.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ extern void disable_irq_nosync(unsigned int irq);
117117
extern void disable_irq(unsigned int irq);
118118
extern void enable_irq(unsigned int irq);
119119

120+
/* The following three functions are for the core kernel use only. */
121+
extern void suspend_device_irqs(void);
122+
extern void resume_device_irqs(void);
123+
#ifdef CONFIG_PM_SLEEP
124+
extern int check_wakeup_irqs(void);
125+
#else
126+
static inline int check_wakeup_irqs(void) { return 0; }
127+
#endif
128+
120129
#if defined(CONFIG_SMP) && defined(CONFIG_GENERIC_HARDIRQS)
121130

122131
extern cpumask_var_t irq_default_affinity;

include/linux/irq.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ typedef void (*irq_flow_handler_t)(unsigned int irq,
6767
#define IRQ_SPURIOUS_DISABLED 0x00800000 /* IRQ was disabled by the spurious trap */
6868
#define IRQ_MOVE_PCNTXT 0x01000000 /* IRQ migration from process context */
6969
#define IRQ_AFFINITY_SET 0x02000000 /* IRQ affinity was set from userspace*/
70+
#define IRQ_SUSPENDED 0x04000000 /* IRQ has gone through suspend sequence */
7071

7172
#ifdef CONFIG_IRQ_PER_CPU
7273
# define CHECK_IRQ_PER_CPU(var) ((var) & IRQ_PER_CPU)

kernel/irq/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ obj-$(CONFIG_GENERIC_IRQ_PROBE) += autoprobe.o
44
obj-$(CONFIG_PROC_FS) += proc.o
55
obj-$(CONFIG_GENERIC_PENDING_IRQ) += migration.o
66
obj-$(CONFIG_NUMA_MIGRATE_IRQ_DESC) += numa_migrate.o
7+
obj-$(CONFIG_PM_SLEEP) += pm.o

kernel/irq/internals.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ extern void compat_irq_chip_set_default_handler(struct irq_desc *desc);
1212

1313
extern int __irq_set_trigger(struct irq_desc *desc, unsigned int irq,
1414
unsigned long flags);
15+
extern void __disable_irq(struct irq_desc *desc, unsigned int irq, bool susp);
16+
extern void __enable_irq(struct irq_desc *desc, unsigned int irq, bool resume);
1517

1618
extern struct lock_class_key irq_desc_lock_class;
1719
extern void init_kstat_irqs(struct irq_desc *desc, int cpu, int nr);

kernel/irq/manage.c

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,20 @@ static inline int setup_affinity(unsigned int irq, struct irq_desc *desc)
162162
}
163163
#endif
164164

165+
void __disable_irq(struct irq_desc *desc, unsigned int irq, bool suspend)
166+
{
167+
if (suspend) {
168+
if (!desc->action || (desc->action->flags & IRQF_TIMER))
169+
return;
170+
desc->status |= IRQ_SUSPENDED;
171+
}
172+
173+
if (!desc->depth++) {
174+
desc->status |= IRQ_DISABLED;
175+
desc->chip->disable(irq);
176+
}
177+
}
178+
165179
/**
166180
* disable_irq_nosync - disable an irq without waiting
167181
* @irq: Interrupt to disable
@@ -182,10 +196,7 @@ void disable_irq_nosync(unsigned int irq)
182196
return;
183197

184198
spin_lock_irqsave(&desc->lock, flags);
185-
if (!desc->depth++) {
186-
desc->status |= IRQ_DISABLED;
187-
desc->chip->disable(irq);
188-
}
199+
__disable_irq(desc, irq, false);
189200
spin_unlock_irqrestore(&desc->lock, flags);
190201
}
191202
EXPORT_SYMBOL(disable_irq_nosync);
@@ -215,15 +226,21 @@ void disable_irq(unsigned int irq)
215226
}
216227
EXPORT_SYMBOL(disable_irq);
217228

218-
static void __enable_irq(struct irq_desc *desc, unsigned int irq)
229+
void __enable_irq(struct irq_desc *desc, unsigned int irq, bool resume)
219230
{
231+
if (resume)
232+
desc->status &= ~IRQ_SUSPENDED;
233+
220234
switch (desc->depth) {
221235
case 0:
236+
err_out:
222237
WARN(1, KERN_WARNING "Unbalanced enable for IRQ %d\n", irq);
223238
break;
224239
case 1: {
225240
unsigned int status = desc->status & ~IRQ_DISABLED;
226241

242+
if (desc->status & IRQ_SUSPENDED)
243+
goto err_out;
227244
/* Prevent probing on this irq: */
228245
desc->status = status | IRQ_NOPROBE;
229246
check_irq_resend(desc, irq);
@@ -253,7 +270,7 @@ void enable_irq(unsigned int irq)
253270
return;
254271

255272
spin_lock_irqsave(&desc->lock, flags);
256-
__enable_irq(desc, irq);
273+
__enable_irq(desc, irq, false);
257274
spin_unlock_irqrestore(&desc->lock, flags);
258275
}
259276
EXPORT_SYMBOL(enable_irq);
@@ -511,7 +528,7 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
511528
*/
512529
if (shared && (desc->status & IRQ_SPURIOUS_DISABLED)) {
513530
desc->status &= ~IRQ_SPURIOUS_DISABLED;
514-
__enable_irq(desc, irq);
531+
__enable_irq(desc, irq, false);
515532
}
516533

517534
spin_unlock_irqrestore(&desc->lock, flags);

kernel/irq/pm.c

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* linux/kernel/irq/pm.c
3+
*
4+
* Copyright (C) 2009 Rafael J. Wysocki <[email protected]>, Novell Inc.
5+
*
6+
* This file contains power management functions related to interrupts.
7+
*/
8+
9+
#include <linux/irq.h>
10+
#include <linux/module.h>
11+
#include <linux/interrupt.h>
12+
13+
#include "internals.h"
14+
15+
/**
16+
* suspend_device_irqs - disable all currently enabled interrupt lines
17+
*
18+
* During system-wide suspend or hibernation device interrupts need to be
19+
* disabled at the chip level and this function is provided for this purpose.
20+
* It disables all interrupt lines that are enabled at the moment and sets the
21+
* IRQ_SUSPENDED flag for them.
22+
*/
23+
void suspend_device_irqs(void)
24+
{
25+
struct irq_desc *desc;
26+
int irq;
27+
28+
for_each_irq_desc(irq, desc) {
29+
unsigned long flags;
30+
31+
spin_lock_irqsave(&desc->lock, flags);
32+
__disable_irq(desc, irq, true);
33+
spin_unlock_irqrestore(&desc->lock, flags);
34+
}
35+
36+
for_each_irq_desc(irq, desc)
37+
if (desc->status & IRQ_SUSPENDED)
38+
synchronize_irq(irq);
39+
}
40+
EXPORT_SYMBOL_GPL(suspend_device_irqs);
41+
42+
/**
43+
* resume_device_irqs - enable interrupt lines disabled by suspend_device_irqs()
44+
*
45+
* Enable all interrupt lines previously disabled by suspend_device_irqs() that
46+
* have the IRQ_SUSPENDED flag set.
47+
*/
48+
void resume_device_irqs(void)
49+
{
50+
struct irq_desc *desc;
51+
int irq;
52+
53+
for_each_irq_desc(irq, desc) {
54+
unsigned long flags;
55+
56+
if (!(desc->status & IRQ_SUSPENDED))
57+
continue;
58+
59+
spin_lock_irqsave(&desc->lock, flags);
60+
__enable_irq(desc, irq, true);
61+
spin_unlock_irqrestore(&desc->lock, flags);
62+
}
63+
}
64+
EXPORT_SYMBOL_GPL(resume_device_irqs);
65+
66+
/**
67+
* check_wakeup_irqs - check if any wake-up interrupts are pending
68+
*/
69+
int check_wakeup_irqs(void)
70+
{
71+
struct irq_desc *desc;
72+
int irq;
73+
74+
for_each_irq_desc(irq, desc)
75+
if ((desc->status & IRQ_WAKEUP) && (desc->status & IRQ_PENDING))
76+
return -EBUSY;
77+
78+
return 0;
79+
}

0 commit comments

Comments
 (0)