diff options
| author | Rafael J. Wysocki <rjw@sisk.pl> | 2012-04-29 16:53:22 -0400 |
|---|---|---|
| committer | Rafael J. Wysocki <rjw@sisk.pl> | 2012-05-01 15:25:38 -0400 |
| commit | 7483b4a4d9abf9dcf1ffe6e805ead2847ec3264e (patch) | |
| tree | d03af746dc3be6480580ec569e0c2d708031f0bd | |
| parent | 6791e36c4a40e8930e08669e60077eea6770c429 (diff) | |
PM / Sleep: Implement opportunistic sleep, v2
Introduce a mechanism by which the kernel can trigger global
transitions to a sleep state chosen by user space if there are no
active wakeup sources.
It consists of a new sysfs attribute, /sys/power/autosleep, that
can be written one of the strings returned by reads from
/sys/power/state, an ordered workqueue and a work item carrying out
the "suspend" operations. If a string representing the system's
sleep state is written to /sys/power/autosleep, the work item
triggering transitions to that state is queued up and it requeues
itself after every execution until user space writes "off" to
/sys/power/autosleep.
That work item enables the detection of wakeup events using the
functions already defined in drivers/base/power/wakeup.c (with one
small modification) and calls either pm_suspend(), or hibernate() to
put the system into a sleep state. If a wakeup event is reported
while the transition is in progress, it will abort the transition and
the "system suspend" work item will be queued up again.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Reviewed-by: NeilBrown <neilb@suse.de>
| -rw-r--r-- | Documentation/ABI/testing/sysfs-power | 17 | ||||
| -rw-r--r-- | drivers/base/power/wakeup.c | 34 | ||||
| -rw-r--r-- | include/linux/suspend.h | 13 | ||||
| -rw-r--r-- | kernel/power/Kconfig | 8 | ||||
| -rw-r--r-- | kernel/power/Makefile | 1 | ||||
| -rw-r--r-- | kernel/power/autosleep.c | 123 | ||||
| -rw-r--r-- | kernel/power/main.c | 119 | ||||
| -rw-r--r-- | kernel/power/power.h | 18 |
8 files changed, 298 insertions, 35 deletions
diff --git a/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/testing/sysfs-power index b464d12761ba..237c735db6c9 100644 --- a/Documentation/ABI/testing/sysfs-power +++ b/Documentation/ABI/testing/sysfs-power | |||
| @@ -172,3 +172,20 @@ Description: | |||
| 172 | 172 | ||
| 173 | Reading from this file will display the current value, which is | 173 | Reading from this file will display the current value, which is |
| 174 | set to 1 MB by default. | 174 | set to 1 MB by default. |
| 175 | |||
| 176 | What: /sys/power/autosleep | ||
| 177 | Date: April 2012 | ||
| 178 | Contact: Rafael J. Wysocki <rjw@sisk.pl> | ||
| 179 | Description: | ||
| 180 | The /sys/power/autosleep file can be written one of the strings | ||
| 181 | returned by reads from /sys/power/state. If that happens, a | ||
| 182 | work item attempting to trigger a transition of the system to | ||
| 183 | the sleep state represented by that string is queued up. This | ||
| 184 | attempt will only succeed if there are no active wakeup sources | ||
| 185 | in the system at that time. After every execution, regardless | ||
| 186 | of whether or not the attempt to put the system to sleep has | ||
| 187 | succeeded, the work item requeues itself until user space | ||
| 188 | writes "off" to /sys/power/autosleep. | ||
| 189 | |||
| 190 | Reading from this file causes the last string successfully | ||
| 191 | written to it to be returned. | ||
diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 1132799421cd..cf1706df7610 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c | |||
| @@ -660,29 +660,33 @@ bool pm_wakeup_pending(void) | |||
| 660 | /** | 660 | /** |
| 661 | * pm_get_wakeup_count - Read the number of registered wakeup events. | 661 | * pm_get_wakeup_count - Read the number of registered wakeup events. |
| 662 | * @count: Address to store the value at. | 662 | * @count: Address to store the value at. |
| 663 | * @block: Whether or not to block. | ||
| 663 | * | 664 | * |
| 664 | * Store the number of registered wakeup events at the address in @count. Block | 665 | * Store the number of registered wakeup events at the address in @count. If |
| 665 | * if the current number of wakeup events being processed is nonzero. | 666 | * @block is set, block until the current number of wakeup events being |
| 667 | * processed is zero. | ||
| 666 | * | 668 | * |
| 667 | * Return 'false' if the wait for the number of wakeup events being processed to | 669 | * Return 'false' if the current number of wakeup events being processed is |
| 668 | * drop down to zero has been interrupted by a signal (and the current number | 670 | * nonzero. Otherwise return 'true'. |
| 669 | * of wakeup events being processed is still nonzero). Otherwise return 'true'. | ||
| 670 | */ | 671 | */ |
| 671 | bool pm_get_wakeup_count(unsigned int *count) | 672 | bool pm_get_wakeup_count(unsigned int *count, bool block) |
| 672 | { | 673 | { |
| 673 | unsigned int cnt, inpr; | 674 | unsigned int cnt, inpr; |
| 674 | DEFINE_WAIT(wait); | ||
| 675 | 675 | ||
| 676 | for (;;) { | 676 | if (block) { |
| 677 | prepare_to_wait(&wakeup_count_wait_queue, &wait, | 677 | DEFINE_WAIT(wait); |
| 678 | TASK_INTERRUPTIBLE); | 678 | |
| 679 | split_counters(&cnt, &inpr); | 679 | for (;;) { |
| 680 | if (inpr == 0 || signal_pending(current)) | 680 | prepare_to_wait(&wakeup_count_wait_queue, &wait, |
| 681 | break; | 681 | TASK_INTERRUPTIBLE); |
| 682 | split_counters(&cnt, &inpr); | ||
| 683 | if (inpr == 0 || signal_pending(current)) | ||
| 684 | break; | ||
| 682 | 685 | ||
| 683 | schedule(); | 686 | schedule(); |
| 687 | } | ||
| 688 | finish_wait(&wakeup_count_wait_queue, &wait); | ||
| 684 | } | 689 | } |
| 685 | finish_wait(&wakeup_count_wait_queue, &wait); | ||
| 686 | 690 | ||
| 687 | split_counters(&cnt, &inpr); | 691 | split_counters(&cnt, &inpr); |
| 688 | *count = cnt; | 692 | *count = cnt; |
diff --git a/include/linux/suspend.h b/include/linux/suspend.h index ac1c114c499d..76b7ec7d3a81 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h | |||
| @@ -356,7 +356,7 @@ extern int unregister_pm_notifier(struct notifier_block *nb); | |||
| 356 | extern bool events_check_enabled; | 356 | extern bool events_check_enabled; |
| 357 | 357 | ||
| 358 | extern bool pm_wakeup_pending(void); | 358 | extern bool pm_wakeup_pending(void); |
| 359 | extern bool pm_get_wakeup_count(unsigned int *count); | 359 | extern bool pm_get_wakeup_count(unsigned int *count, bool block); |
| 360 | extern bool pm_save_wakeup_count(unsigned int count); | 360 | extern bool pm_save_wakeup_count(unsigned int count); |
| 361 | 361 | ||
| 362 | static inline void lock_system_sleep(void) | 362 | static inline void lock_system_sleep(void) |
| @@ -407,6 +407,17 @@ static inline void unlock_system_sleep(void) {} | |||
| 407 | 407 | ||
| 408 | #endif /* !CONFIG_PM_SLEEP */ | 408 | #endif /* !CONFIG_PM_SLEEP */ |
| 409 | 409 | ||
| 410 | #ifdef CONFIG_PM_AUTOSLEEP | ||
| 411 | |||
| 412 | /* kernel/power/autosleep.c */ | ||
| 413 | void queue_up_suspend_work(void); | ||
| 414 | |||
| 415 | #else /* !CONFIG_PM_AUTOSLEEP */ | ||
| 416 | |||
| 417 | static inline void queue_up_suspend_work(void) {} | ||
| 418 | |||
| 419 | #endif /* !CONFIG_PM_AUTOSLEEP */ | ||
| 420 | |||
| 410 | #ifdef CONFIG_ARCH_SAVE_PAGE_KEYS | 421 | #ifdef CONFIG_ARCH_SAVE_PAGE_KEYS |
| 411 | /* | 422 | /* |
| 412 | * The ARCH_SAVE_PAGE_KEYS functions can be used by an architecture | 423 | * The ARCH_SAVE_PAGE_KEYS functions can be used by an architecture |
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index deb5461e3216..67947083f842 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig | |||
| @@ -103,6 +103,14 @@ config PM_SLEEP_SMP | |||
| 103 | select HOTPLUG | 103 | select HOTPLUG |
| 104 | select HOTPLUG_CPU | 104 | select HOTPLUG_CPU |
| 105 | 105 | ||
| 106 | config PM_AUTOSLEEP | ||
| 107 | bool "Opportunistic sleep" | ||
| 108 | depends on PM_SLEEP | ||
| 109 | default n | ||
| 110 | ---help--- | ||
| 111 | Allow the kernel to trigger a system transition into a global sleep | ||
| 112 | state automatically whenever there are no active wakeup sources. | ||
| 113 | |||
| 106 | config PM_RUNTIME | 114 | config PM_RUNTIME |
| 107 | bool "Run-time PM core functionality" | 115 | bool "Run-time PM core functionality" |
| 108 | depends on !IA64_HP_SIM | 116 | depends on !IA64_HP_SIM |
diff --git a/kernel/power/Makefile b/kernel/power/Makefile index 66d808ec5252..010b2f7e148c 100644 --- a/kernel/power/Makefile +++ b/kernel/power/Makefile | |||
| @@ -9,5 +9,6 @@ obj-$(CONFIG_SUSPEND) += suspend.o | |||
| 9 | obj-$(CONFIG_PM_TEST_SUSPEND) += suspend_test.o | 9 | obj-$(CONFIG_PM_TEST_SUSPEND) += suspend_test.o |
| 10 | obj-$(CONFIG_HIBERNATION) += hibernate.o snapshot.o swap.o user.o \ | 10 | obj-$(CONFIG_HIBERNATION) += hibernate.o snapshot.o swap.o user.o \ |
| 11 | block_io.o | 11 | block_io.o |
| 12 | obj-$(CONFIG_PM_AUTOSLEEP) += autosleep.o | ||
| 12 | 13 | ||
| 13 | obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o | 14 | obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o |
diff --git a/kernel/power/autosleep.c b/kernel/power/autosleep.c new file mode 100644 index 000000000000..42348e3589d3 --- /dev/null +++ b/kernel/power/autosleep.c | |||
| @@ -0,0 +1,123 @@ | |||
| 1 | /* | ||
| 2 | * kernel/power/autosleep.c | ||
| 3 | * | ||
| 4 | * Opportunistic sleep support. | ||
| 5 | * | ||
| 6 | * Copyright (C) 2012 Rafael J. Wysocki <rjw@sisk.pl> | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <linux/device.h> | ||
| 10 | #include <linux/mutex.h> | ||
| 11 | #include <linux/pm_wakeup.h> | ||
| 12 | |||
| 13 | #include "power.h" | ||
| 14 | |||
| 15 | static suspend_state_t autosleep_state; | ||
| 16 | static struct workqueue_struct *autosleep_wq; | ||
| 17 | /* | ||
| 18 | * Note: it is only safe to mutex_lock(&autosleep_lock) if a wakeup_source | ||
| 19 | * is active, otherwise a deadlock with try_to_suspend() is possible. | ||
| 20 | * Alternatively mutex_lock_interruptible() can be used. This will then fail | ||
| 21 | * if an auto_sleep cycle tries to freeze processes. | ||
| 22 | */ | ||
| 23 | static DEFINE_MUTEX(autosleep_lock); | ||
| 24 | static struct wakeup_source *autosleep_ws; | ||
| 25 | |||
| 26 | static void try_to_suspend(struct work_struct *work) | ||
| 27 | { | ||
| 28 | unsigned int initial_count, final_count; | ||
| 29 | |||
| 30 | if (!pm_get_wakeup_count(&initial_count, true)) | ||
| 31 | goto out; | ||
| 32 | |||
| 33 | mutex_lock(&autosleep_lock); | ||
| 34 | |||
| 35 | if (!pm_save_wakeup_count(initial_count)) { | ||
| 36 | mutex_unlock(&autosleep_lock); | ||
| 37 | goto out; | ||
| 38 | } | ||
| 39 | |||
