Commit 40dc166cb5dddbd36aa4ad11c03915ea538f5a61
1 parent
f9b9e806ae
Exists in
master
and in
4 other branches
PM / Core: Introduce struct syscore_ops for core subsystems PM
Some subsystems need to carry out suspend/resume and shutdown operations with one CPU on-line and interrupts disabled. The only way to register such operations is to define a sysdev class and a sysdev specifically for this purpose which is cumbersome and inefficient. Moreover, the arguments taken by sysdev suspend, resume and shutdown callbacks are practically never necessary. For this reason, introduce a simpler interface allowing subsystems to register operations to be executed very late during system suspend and shutdown and very early during resume in the form of strcut syscore_ops objects. Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> Acked-by: Greg Kroah-Hartman <gregkh@suse.de>
Showing 6 changed files with 164 additions and 1 deletions Side-by-side Diff
drivers/base/Makefile
drivers/base/syscore.c
1 | +/* | |
2 | + * syscore.c - Execution of system core operations. | |
3 | + * | |
4 | + * Copyright (C) 2011 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. | |
5 | + * | |
6 | + * This file is released under the GPLv2. | |
7 | + */ | |
8 | + | |
9 | +#include <linux/syscore_ops.h> | |
10 | +#include <linux/mutex.h> | |
11 | +#include <linux/module.h> | |
12 | + | |
13 | +static LIST_HEAD(syscore_ops_list); | |
14 | +static DEFINE_MUTEX(syscore_ops_lock); | |
15 | + | |
16 | +/** | |
17 | + * register_syscore_ops - Register a set of system core operations. | |
18 | + * @ops: System core operations to register. | |
19 | + */ | |
20 | +void register_syscore_ops(struct syscore_ops *ops) | |
21 | +{ | |
22 | + mutex_lock(&syscore_ops_lock); | |
23 | + list_add_tail(&ops->node, &syscore_ops_list); | |
24 | + mutex_unlock(&syscore_ops_lock); | |
25 | +} | |
26 | +EXPORT_SYMBOL_GPL(register_syscore_ops); | |
27 | + | |
28 | +/** | |
29 | + * unregister_syscore_ops - Unregister a set of system core operations. | |
30 | + * @ops: System core operations to unregister. | |
31 | + */ | |
32 | +void unregister_syscore_ops(struct syscore_ops *ops) | |
33 | +{ | |
34 | + mutex_lock(&syscore_ops_lock); | |
35 | + list_del(&ops->node); | |
36 | + mutex_unlock(&syscore_ops_lock); | |
37 | +} | |
38 | +EXPORT_SYMBOL_GPL(unregister_syscore_ops); | |
39 | + | |
40 | +#ifdef CONFIG_PM_SLEEP | |
41 | +/** | |
42 | + * syscore_suspend - Execute all the registered system core suspend callbacks. | |
43 | + * | |
44 | + * This function is executed with one CPU on-line and disabled interrupts. | |
45 | + */ | |
46 | +int syscore_suspend(void) | |
47 | +{ | |
48 | + struct syscore_ops *ops; | |
49 | + int ret = 0; | |
50 | + | |
51 | + WARN_ONCE(!irqs_disabled(), | |
52 | + "Interrupts enabled before system core suspend.\n"); | |
53 | + | |
54 | + list_for_each_entry_reverse(ops, &syscore_ops_list, node) | |
55 | + if (ops->suspend) { | |
56 | + if (initcall_debug) | |
57 | + pr_info("PM: Calling %pF\n", ops->suspend); | |
58 | + ret = ops->suspend(); | |
59 | + if (ret) | |
60 | + goto err_out; | |
61 | + WARN_ONCE(!irqs_disabled(), | |
62 | + "Interrupts enabled after %pF\n", ops->suspend); | |
63 | + } | |
64 | + | |
65 | + return 0; | |
66 | + | |
67 | + err_out: | |
68 | + pr_err("PM: System core suspend callback %pF failed.\n", ops->suspend); | |
69 | + | |
70 | + list_for_each_entry_continue(ops, &syscore_ops_list, node) | |
71 | + if (ops->resume) | |
72 | + ops->resume(); | |
73 | + | |
74 | + return ret; | |
75 | +} | |
76 | + | |
77 | +/** | |
78 | + * syscore_resume - Execute all the registered system core resume callbacks. | |
79 | + * | |
80 | + * This function is executed with one CPU on-line and disabled interrupts. | |
81 | + */ | |
82 | +void syscore_resume(void) | |
83 | +{ | |
84 | + struct syscore_ops *ops; | |
85 | + | |
86 | + WARN_ONCE(!irqs_disabled(), | |
87 | + "Interrupts enabled before system core resume.\n"); | |
88 | + | |
89 | + list_for_each_entry(ops, &syscore_ops_list, node) | |
90 | + if (ops->resume) { | |
91 | + if (initcall_debug) | |
92 | + pr_info("PM: Calling %pF\n", ops->resume); | |
93 | + ops->resume(); | |
94 | + WARN_ONCE(!irqs_disabled(), | |
95 | + "Interrupts enabled after %pF\n", ops->resume); | |
96 | + } | |
97 | +} | |
98 | +#endif /* CONFIG_PM_SLEEP */ | |
99 | + | |
100 | +/** | |
101 | + * syscore_shutdown - Execute all the registered system core shutdown callbacks. | |
102 | + */ | |
103 | +void syscore_shutdown(void) | |
104 | +{ | |
105 | + struct syscore_ops *ops; | |
106 | + | |
107 | + mutex_lock(&syscore_ops_lock); | |
108 | + | |
109 | + list_for_each_entry_reverse(ops, &syscore_ops_list, node) | |
110 | + if (ops->shutdown) { | |
111 | + if (initcall_debug) | |
112 | + pr_info("PM: Calling %pF\n", ops->shutdown); | |
113 | + ops->shutdown(); | |
114 | + } | |
115 | + | |
116 | + mutex_unlock(&syscore_ops_lock); | |
117 | +} |
include/linux/syscore_ops.h
1 | +/* | |
2 | + * syscore_ops.h - System core operations. | |
3 | + * | |
4 | + * Copyright (C) 2011 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. | |
5 | + * | |
6 | + * This file is released under the GPLv2. | |
7 | + */ | |
8 | + | |
9 | +#ifndef _LINUX_SYSCORE_OPS_H | |
10 | +#define _LINUX_SYSCORE_OPS_H | |
11 | + | |
12 | +#include <linux/list.h> | |
13 | + | |
14 | +struct syscore_ops { | |
15 | + struct list_head node; | |
16 | + int (*suspend)(void); | |
17 | + void (*resume)(void); | |
18 | + void (*shutdown)(void); | |
19 | +}; | |
20 | + | |
21 | +extern void register_syscore_ops(struct syscore_ops *ops); | |
22 | +extern void unregister_syscore_ops(struct syscore_ops *ops); | |
23 | +#ifdef CONFIG_PM_SLEEP | |
24 | +extern int syscore_suspend(void); | |
25 | +extern void syscore_resume(void); | |
26 | +#endif | |
27 | +extern void syscore_shutdown(void); | |
28 | + | |
29 | +#endif |
kernel/power/hibernate.c
... | ... | @@ -23,6 +23,7 @@ |
23 | 23 | #include <linux/cpu.h> |
24 | 24 | #include <linux/freezer.h> |
25 | 25 | #include <linux/gfp.h> |
26 | +#include <linux/syscore_ops.h> | |
26 | 27 | #include <scsi/scsi_scan.h> |
27 | 28 | #include <asm/suspend.h> |
28 | 29 | |
... | ... | @@ -272,6 +273,8 @@ |
272 | 273 | local_irq_disable(); |
273 | 274 | |
274 | 275 | error = sysdev_suspend(PMSG_FREEZE); |
276 | + if (!error) | |
277 | + error = syscore_suspend(); | |
275 | 278 | if (error) { |
276 | 279 | printk(KERN_ERR "PM: Some system devices failed to power down, " |
277 | 280 | "aborting hibernation\n"); |
... | ... | @@ -295,6 +298,7 @@ |
295 | 298 | } |
296 | 299 | |
297 | 300 | Power_up: |
301 | + syscore_resume(); | |
298 | 302 | sysdev_resume(); |
299 | 303 | /* NOTE: dpm_resume_noirq() is just a resume() for devices |
300 | 304 | * that suspended with irqs off ... no overall powerup. |
... | ... | @@ -403,6 +407,8 @@ |
403 | 407 | local_irq_disable(); |
404 | 408 | |
405 | 409 | error = sysdev_suspend(PMSG_QUIESCE); |
410 | + if (!error) | |
411 | + error = syscore_suspend(); | |
406 | 412 | if (error) |
407 | 413 | goto Enable_irqs; |
408 | 414 | |
... | ... | @@ -429,6 +435,7 @@ |
429 | 435 | restore_processor_state(); |
430 | 436 | touch_softlockup_watchdog(); |
431 | 437 | |
438 | + syscore_resume(); | |
432 | 439 | sysdev_resume(); |
433 | 440 | |
434 | 441 | Enable_irqs: |
... | ... | @@ -516,6 +523,7 @@ |
516 | 523 | |
517 | 524 | local_irq_disable(); |
518 | 525 | sysdev_suspend(PMSG_HIBERNATE); |
526 | + syscore_suspend(); | |
519 | 527 | if (pm_wakeup_pending()) { |
520 | 528 | error = -EAGAIN; |
521 | 529 | goto Power_up; |
... | ... | @@ -526,6 +534,7 @@ |
526 | 534 | while (1); |
527 | 535 | |
528 | 536 | Power_up: |
537 | + syscore_resume(); | |
529 | 538 | sysdev_resume(); |
530 | 539 | local_irq_enable(); |
531 | 540 | enable_nonboot_cpus(); |
kernel/power/suspend.c
... | ... | @@ -22,6 +22,7 @@ |
22 | 22 | #include <linux/mm.h> |
23 | 23 | #include <linux/slab.h> |
24 | 24 | #include <linux/suspend.h> |
25 | +#include <linux/syscore_ops.h> | |
25 | 26 | #include <trace/events/power.h> |
26 | 27 | |
27 | 28 | #include "power.h" |
28 | 29 | |
... | ... | @@ -163,11 +164,14 @@ |
163 | 164 | BUG_ON(!irqs_disabled()); |
164 | 165 | |
165 | 166 | error = sysdev_suspend(PMSG_SUSPEND); |
167 | + if (!error) | |
168 | + error = syscore_suspend(); | |
166 | 169 | if (!error) { |
167 | 170 | if (!(suspend_test(TEST_CORE) || pm_wakeup_pending())) { |
168 | 171 | error = suspend_ops->enter(state); |
169 | 172 | events_check_enabled = false; |
170 | 173 | } |
174 | + syscore_resume(); | |
171 | 175 | sysdev_resume(); |
172 | 176 | } |
173 | 177 |
kernel/sys.c
... | ... | @@ -37,6 +37,7 @@ |
37 | 37 | #include <linux/ptrace.h> |
38 | 38 | #include <linux/fs_struct.h> |
39 | 39 | #include <linux/gfp.h> |
40 | +#include <linux/syscore_ops.h> | |
40 | 41 | |
41 | 42 | #include <linux/compat.h> |
42 | 43 | #include <linux/syscalls.h> |
... | ... | @@ -298,6 +299,7 @@ |
298 | 299 | system_state = SYSTEM_RESTART; |
299 | 300 | device_shutdown(); |
300 | 301 | sysdev_shutdown(); |
302 | + syscore_shutdown(); | |
301 | 303 | } |
302 | 304 | |
303 | 305 | /** |
... | ... | @@ -336,6 +338,7 @@ |
336 | 338 | { |
337 | 339 | kernel_shutdown_prepare(SYSTEM_HALT); |
338 | 340 | sysdev_shutdown(); |
341 | + syscore_shutdown(); | |
339 | 342 | printk(KERN_EMERG "System halted.\n"); |
340 | 343 | kmsg_dump(KMSG_DUMP_HALT); |
341 | 344 | machine_halt(); |
... | ... | @@ -355,6 +358,7 @@ |
355 | 358 | pm_power_off_prepare(); |
356 | 359 | disable_nonboot_cpus(); |
357 | 360 | sysdev_shutdown(); |
361 | + syscore_shutdown(); | |
358 | 362 | printk(KERN_EMERG "Power down.\n"); |
359 | 363 | kmsg_dump(KMSG_DUMP_POWEROFF); |
360 | 364 | machine_power_off(); |