Skip to content

Commit

Permalink
PM / Sleep: Implement opportunistic sleep, v2
Browse files Browse the repository at this point in the history
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 <[email protected]>
Acked-by: Greg Kroah-Hartman <[email protected]>
Reviewed-by: NeilBrown <[email protected]>
  • Loading branch information
rjwysocki committed May 1, 2012
1 parent 6791e36 commit 7483b4a
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 35 deletions.
17 changes: 17 additions & 0 deletions Documentation/ABI/testing/sysfs-power
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,20 @@ Description:

Reading from this file will display the current value, which is
set to 1 MB by default.

What: /sys/power/autosleep
Date: April 2012
Contact: Rafael J. Wysocki <[email protected]>
Description:
The /sys/power/autosleep file can be written one of the strings
returned by reads from /sys/power/state. If that happens, a
work item attempting to trigger a transition of the system to
the sleep state represented by that string is queued up. This
attempt will only succeed if there are no active wakeup sources
in the system at that time. After every execution, regardless
of whether or not the attempt to put the system to sleep has
succeeded, the work item requeues itself until user space
writes "off" to /sys/power/autosleep.

Reading from this file causes the last string successfully
written to it to be returned.
34 changes: 19 additions & 15 deletions drivers/base/power/wakeup.c
Original file line number Diff line number Diff line change
Expand Up @@ -660,29 +660,33 @@ bool pm_wakeup_pending(void)
/**
* pm_get_wakeup_count - Read the number of registered wakeup events.
* @count: Address to store the value at.
* @block: Whether or not to block.
*
* Store the number of registered wakeup events at the address in @count. Block
* if the current number of wakeup events being processed is nonzero.
* Store the number of registered wakeup events at the address in @count. If
* @block is set, block until the current number of wakeup events being
* processed is zero.
*
* Return 'false' if the wait for the number of wakeup events being processed to
* drop down to zero has been interrupted by a signal (and the current number
* of wakeup events being processed is still nonzero). Otherwise return 'true'.
* Return 'false' if the current number of wakeup events being processed is
* nonzero. Otherwise return 'true'.
*/
bool pm_get_wakeup_count(unsigned int *count)
bool pm_get_wakeup_count(unsigned int *count, bool block)
{
unsigned int cnt, inpr;
DEFINE_WAIT(wait);

for (;;) {
prepare_to_wait(&wakeup_count_wait_queue, &wait,
TASK_INTERRUPTIBLE);
split_counters(&cnt, &inpr);
if (inpr == 0 || signal_pending(current))
break;
if (block) {
DEFINE_WAIT(wait);

for (;;) {
prepare_to_wait(&wakeup_count_wait_queue, &wait,
TASK_INTERRUPTIBLE);
split_counters(&cnt, &inpr);
if (inpr == 0 || signal_pending(current))
break;

schedule();
schedule();
}
finish_wait(&wakeup_count_wait_queue, &wait);
}
finish_wait(&wakeup_count_wait_queue, &wait);

split_counters(&cnt, &inpr);
*count = cnt;
Expand Down
13 changes: 12 additions & 1 deletion include/linux/suspend.h
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ extern int unregister_pm_notifier(struct notifier_block *nb);
extern bool events_check_enabled;

extern bool pm_wakeup_pending(void);
extern bool pm_get_wakeup_count(unsigned int *count);
extern bool pm_get_wakeup_count(unsigned int *count, bool block);
extern bool pm_save_wakeup_count(unsigned int count);

static inline void lock_system_sleep(void)
Expand Down Expand Up @@ -407,6 +407,17 @@ static inline void unlock_system_sleep(void) {}

#endif /* !CONFIG_PM_SLEEP */

#ifdef CONFIG_PM_AUTOSLEEP

/* kernel/power/autosleep.c */
void queue_up_suspend_work(void);

#else /* !CONFIG_PM_AUTOSLEEP */

static inline void queue_up_suspend_work(void) {}

#endif /* !CONFIG_PM_AUTOSLEEP */

#ifdef CONFIG_ARCH_SAVE_PAGE_KEYS
/*
* The ARCH_SAVE_PAGE_KEYS functions can be used by an architecture
Expand Down
8 changes: 8 additions & 0 deletions kernel/power/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ config PM_SLEEP_SMP
select HOTPLUG
select HOTPLUG_CPU

config PM_AUTOSLEEP
bool "Opportunistic sleep"
depends on PM_SLEEP
default n
---help---
Allow the kernel to trigger a system transition into a global sleep
state automatically whenever there are no active wakeup sources.

config PM_RUNTIME
bool "Run-time PM core functionality"
depends on !IA64_HP_SIM
Expand Down
1 change: 1 addition & 0 deletions kernel/power/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ obj-$(CONFIG_SUSPEND) += suspend.o
obj-$(CONFIG_PM_TEST_SUSPEND) += suspend_test.o
obj-$(CONFIG_HIBERNATION) += hibernate.o snapshot.o swap.o user.o \
block_io.o
obj-$(CONFIG_PM_AUTOSLEEP) += autosleep.o

obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o
123 changes: 123 additions & 0 deletions kernel/power/autosleep.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* kernel/power/autosleep.c
*
* Opportunistic sleep support.
*
* Copyright (C) 2012 Rafael J. Wysocki <[email protected]>
*/

#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/pm_wakeup.h>

#include "power.h"

static suspend_state_t autosleep_state;
static struct workqueue_struct *autosleep_wq;
/*
* Note: it is only safe to mutex_lock(&autosleep_lock) if a wakeup_source
* is active, otherwise a deadlock with try_to_suspend() is possible.
* Alternatively mutex_lock_interruptible() can be used. This will then fail
* if an auto_sleep cycle tries to freeze processes.
*/
static DEFINE_MUTEX(autosleep_lock);
static struct wakeup_source *autosleep_ws;

static void try_to_suspend(struct work_struct *work)
{
unsigned int initial_count, final_count;

if (!pm_get_wakeup_count(&initial_count, true))
goto out;

mutex_lock(&autosleep_lock);

if (!pm_save_wakeup_count(initial_count)) {
mutex_unlock(&autosleep_lock);
goto out;
}

if (autosleep_state == PM_SUSPEND_ON) {
mutex_unlock(&autosleep_lock);
return;
}
if (autosleep_state >= PM_SUSPEND_MAX)
hibernate();
else
pm_suspend(autosleep_state);

mutex_unlock(&autosleep_lock);

if (!pm_get_wakeup_count(&final_count, false))
goto out;

/*
* If the wakeup occured for an unknown reason, wait to prevent the
* system from trying to suspend and waking up in a tight loop.
*/
if (final_count == initial_count)
schedule_timeout_uninterruptible(HZ / 2);

out:
queue_up_suspend_work();
}

static DECLARE_WORK(suspend_work, try_to_suspend);

void queue_up_suspend_work(void)
{
if (!work_pending(&suspend_work) && autosleep_state > PM_SUSPEND_ON)
queue_work(autosleep_wq, &suspend_work);
}

suspend_state_t pm_autosleep_state(void)
{
return autosleep_state;
}

int pm_autosleep_lock(void)
{
return mutex_lock_interruptible(&autosleep_lock);
}

void pm_autosleep_unlock(void)
{
mutex_unlock(&autosleep_lock);
}

int pm_autosleep_set_state(suspend_state_t state)
{

#ifndef CONFIG_HIBERNATION
if (state >= PM_SUSPEND_MAX)
return -EINVAL;
#endif

__pm_stay_awake(autosleep_ws);

mutex_lock(&autosleep_lock);

autosleep_state = state;

__pm_relax(autosleep_ws);

if (state > PM_SUSPEND_ON)
queue_up_suspend_work();

mutex_unlock(&autosleep_lock);
return 0;
}

int __init pm_autosleep_init(void)
{
autosleep_ws = wakeup_source_register("autosleep");
if (!autosleep_ws)
return -ENOMEM;

autosleep_wq = alloc_ordered_workqueue("autosleep", 0);
if (autosleep_wq)
return 0;

wakeup_source_unregister(autosleep_ws);
return -ENOMEM;
}
Loading

0 comments on commit 7483b4a

Please sign in to comment.