diff options
| author | Rafael J. Wysocki <rjw@sisk.pl> | 2011-03-14 19:43:46 -0400 |
|---|---|---|
| committer | Rafael J. Wysocki <rjw@sisk.pl> | 2011-03-14 19:43:46 -0400 |
| commit | 40dc166cb5dddbd36aa4ad11c03915ea538f5a61 (patch) | |
| tree | 0a778159cf89ddee9e7d3134ae40569bdccd2a24 | |
| parent | f9b9e806ae0ede772cbb9916d9ac7354a123d044 (diff) | |
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>
| -rw-r--r-- | drivers/base/Makefile | 2 | ||||
| -rw-r--r-- | drivers/base/syscore.c | 117 | ||||
| -rw-r--r-- | include/linux/syscore_ops.h | 29 | ||||
| -rw-r--r-- | kernel/power/hibernate.c | 9 | ||||
| -rw-r--r-- | kernel/power/suspend.c | 4 | ||||
| -rw-r--r-- | kernel/sys.c | 4 |
6 files changed, 164 insertions, 1 deletions
diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 5f51c3b4451e..4c5701c15f53 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | # Makefile for the Linux device tree | 1 | # Makefile for the Linux device tree |
| 2 | 2 | ||
| 3 | obj-y := core.o sys.o bus.o dd.o \ | 3 | obj-y := core.o sys.o bus.o dd.o syscore.o \ |
| 4 | driver.o class.o platform.o \ | 4 | driver.o class.o platform.o \ |
| 5 | cpu.o firmware.o init.o map.o devres.o \ | 5 | cpu.o firmware.o init.o map.o devres.o \ |
| 6 | attribute_container.o transport_class.o | 6 | attribute_container.o transport_class.o |
diff --git a/drivers/base/syscore.c b/drivers/base/syscore.c new file mode 100644 index 000000000000..90af2943f9e4 --- /dev/null +++ b/drivers/base/syscore.c | |||
| @@ -0,0 +1,117 @@ | |||
| 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 | } | ||
diff --git a/include/linux/syscore_ops.h b/include/linux/syscore_ops.h new file mode 100644 index 000000000000..27b3b0bc41a9 --- /dev/null +++ b/include/linux/syscore_ops.h | |||
| @@ -0,0 +1,29 @@ | |||
| 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 | ||
diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index 1832bd264219..aeabd26e3342 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c | |||
| @@ -23,6 +23,7 @@ | |||
| 23 | #include <linux/cpu.h> | 23 | #include <linux/cpu.h> |
| 24 | #include <linux/freezer.h> | 24 | #include <linux/freezer.h> |
| 25 | #include <linux/gfp.h> | 25 | #include <linux/gfp.h> |
| 26 | #include <linux/syscore_ops.h> | ||
| 26 | #include <scsi/scsi_scan.h> | 27 | #include <scsi/scsi_scan.h> |
| 27 | #include <asm/suspend.h> | 28 | #include <asm/suspend.h> |
| 28 | 29 | ||
| @@ -272,6 +273,8 @@ static int create_image(int platform_mode) | |||
| 272 | local_irq_disable(); | 273 | local_irq_disable(); |
| 273 | 274 | ||
| 274 | error = sysdev_suspend(PMSG_FREEZE); | 275 | error = sysdev_suspend(PMSG_FREEZE); |
| 276 | if (!error) | ||
| 277 | error = syscore_suspend(); | ||
| 275 | if (error) { | 278 | if (error) { |
| 276 | printk(KERN_ERR "PM: Some system devices failed to power down, " | 279 | printk(KERN_ERR "PM: Some system devices failed to power down, " |
| 277 | "aborting hibernation\n"); | 280 | "aborting hibernation\n"); |
| @@ -295,6 +298,7 @@ static int create_image(int platform_mode) | |||
| 295 | } | 298 | } |
| 296 | 299 | ||
| 297 | Power_up: | 300 | Power_up: |
| 301 | syscore_resume(); | ||
| 298 | sysdev_resume(); | 302 | sysdev_resume(); |
| 299 | /* NOTE: dpm_resume_noirq() is just a resume() for devices | 303 | /* NOTE: dpm_resume_noirq() is just a resume() for devices |
| 300 | * that suspended with irqs off ... no overall powerup. | 304 | * that suspended with irqs off ... no overall powerup. |
| @@ -403,6 +407,8 @@ static int resume_target_kernel(bool platform_mode) | |||
| 403 | local_irq_disable(); | 407 | local_irq_disable(); |
| 404 | 408 | ||
| 405 | error = sysdev_suspend(PMSG_QUIESCE); | 409 | error = sysdev_suspend(PMSG_QUIESCE); |
| 410 | if (!error) | ||
| 411 | error = syscore_suspend(); | ||
| 406 | if (error) | 412 | if (error) |
| 407 | goto Enable_irqs; | 413 | goto Enable_irqs; |
| 408 | 414 | ||
| @@ -429,6 +435,7 @@ static int resume_target_kernel(bool platform_mode) | |||
| 429 | restore_processor_state(); | 435 | restore_processor_state(); |
| 430 | touch_softlockup_watchdog(); | 436 | touch_softlockup_watchdog(); |
| 431 | 437 | ||
| 438 | syscore_resume(); | ||
| 432 | sysdev_resume(); | 439 | sysdev_resume(); |
| 433 | 440 | ||
| 434 | Enable_irqs: | 441 | Enable_irqs: |
| @@ -516,6 +523,7 @@ int hibernation_platform_enter(void) | |||
| 516 | 523 | ||
| 517 | local_irq_disable(); | 524 | local_irq_disable(); |
| 518 | sysdev_suspend(PMSG_HIBERNATE); | 525 | sysdev_suspend(PMSG_HIBERNATE); |
| 526 | syscore_suspend(); | ||
| 519 | if (pm_wakeup_pending()) { | 527 | if (pm_wakeup_pending()) { |
| 520 | error = -EAGAIN; | 528 | error = -EAGAIN; |
| 521 | goto Power_up; | 529 | goto Power_up; |
| @@ -526,6 +534,7 @@ int hibernation_platform_enter(void) | |||
| 526 | while (1); | 534 | while (1); |
| 527 | 535 | ||
| 528 | Power_up: | 536 | Power_up: |
| 537 | syscore_resume(); | ||
| 529 | sysdev_resume(); | 538 | sysdev_resume(); |
| 530 | local_irq_enable(); | 539 | local_irq_enable(); |
| 531 | enable_nonboot_cpus(); | 540 | enable_nonboot_cpus(); |
diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c index de6f86bfa303..2814c32aed51 100644 --- a/kernel/power/suspend.c +++ b/kernel/power/suspend.c | |||
| @@ -22,6 +22,7 @@ | |||
| 22 | #include <linux/mm.h> | 22 | #include <linux/mm.h> |
| 23 | #include <linux/slab.h> | 23 | #include <linux/slab.h> |
| 24 | #include <linux/suspend.h> | 24 | #include <linux/suspend.h> |
| 25 | #include <linux/syscore_ops.h> | ||
| 25 | #include <trace/events/power.h> | 26 | #include <trace/events/power.h> |
| 26 | 27 | ||
| 27 | #include "power.h" | 28 | #include "power.h" |
| @@ -163,11 +164,14 @@ static int suspend_enter(suspend_state_t state) | |||
| 163 | BUG_ON(!irqs_disabled()); | 164 | BUG_ON(!irqs_disabled()); |
| 164 | 165 | ||
| 165 | error = sysdev_suspend(PMSG_SUSPEND); | 166 | error = sysdev_suspend(PMSG_SUSPEND); |
| 167 | if (!error) | ||
| 168 | error = syscore_suspend(); | ||
| 166 | if (!error) { | 169 | if (!error) { |
| 167 | if (!(suspend_test(TEST_CORE) || pm_wakeup_pending())) { | 170 | if (!(suspend_test(TEST_CORE) || pm_wakeup_pending())) { |
| 168 | error = suspend_ops->enter(state); | 171 | error = suspend_ops->enter(state); |
| 169 | events_check_enabled = false; | 172 | events_check_enabled = false; |
| 170 | } | 173 | } |
| 174 | syscore_resume(); | ||
| 171 | sysdev_resume(); | 175 | sysdev_resume(); |
| 172 | } | 176 | } |
| 173 | 177 | ||
diff --git a/kernel/sys.c b/kernel/sys.c index 18da702ec813..1ad48b3b9068 100644 --- a/kernel/sys.c +++ b/kernel/sys.c | |||
| @@ -37,6 +37,7 @@ | |||
| 37 | #include <linux/ptrace.h> | 37 | #include <linux/ptrace.h> |
| 38 | #include <linux/fs_struct.h> | 38 | #include <linux/fs_struct.h> |
| 39 | #include <linux/gfp.h> | 39 | #include <linux/gfp.h> |
| 40 | #include <linux/syscore_ops.h> | ||
| 40 | 41 | ||
| 41 | #include <linux/compat.h> | 42 | #include <linux/compat.h> |
| 42 | #include <linux/syscalls.h> | 43 | #include <linux/syscalls.h> |
| @@ -298,6 +299,7 @@ void kernel_restart_prepare(char *cmd) | |||
| 298 | system_state = SYSTEM_RESTART; | 299 | system_state = SYSTEM_RESTART; |
| 299 | device_shutdown(); | 300 | device_shutdown(); |
| 300 | sysdev_shutdown(); | 301 | sysdev_shutdown(); |
| 302 | syscore_shutdown(); | ||
| 301 | } | 303 | } |
| 302 | 304 | ||
| 303 | /** | 305 | /** |
| @@ -336,6 +338,7 @@ void kernel_halt(void) | |||
| 336 | { | 338 | { |
| 337 | kernel_shutdown_prepare(SYSTEM_HALT); | 339 | kernel_shutdown_prepare(SYSTEM_HALT); |
| 338 | sysdev_shutdown(); | 340 | sysdev_shutdown(); |
| 341 | syscore_shutdown(); | ||
| 339 | printk(KERN_EMERG "System halted.\n"); | 342 | printk(KERN_EMERG "System halted.\n"); |
| 340 | kmsg_dump(KMSG_DUMP_HALT); | 343 | kmsg_dump(KMSG_DUMP_HALT); |
| 341 | machine_halt(); | 344 | machine_halt(); |
| @@ -355,6 +358,7 @@ void kernel_power_off(void) | |||
| 355 | pm_power_off_prepare(); | 358 | pm_power_off_prepare(); |
| 356 | disable_nonboot_cpus(); | 359 | disable_nonboot_cpus(); |
| 357 | sysdev_shutdown(); | 360 | sysdev_shutdown(); |
| 361 | syscore_shutdown(); | ||
| 358 | printk(KERN_EMERG "Power down.\n"); | 362 | printk(KERN_EMERG "Power down.\n"); |
| 359 | kmsg_dump(KMSG_DUMP_POWEROFF); | 363 | kmsg_dump(KMSG_DUMP_POWEROFF); |
| 360 | machine_power_off(); | 364 | machine_power_off(); |
