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>
This commit is contained in:
@@ -269,8 +269,7 @@ static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
return (s - buf);
|
||||
}
|
||||
|
||||
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
static suspend_state_t decode_state(const char *buf, size_t n)
|
||||
{
|
||||
#ifdef CONFIG_SUSPEND
|
||||
suspend_state_t state = PM_SUSPEND_STANDBY;
|
||||
@@ -278,27 +277,48 @@ static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
#endif
|
||||
char *p;
|
||||
int len;
|
||||
int error = -EINVAL;
|
||||
|
||||
p = memchr(buf, '\n', n);
|
||||
len = p ? p - buf : n;
|
||||
|
||||
/* First, check if we are requested to hibernate */
|
||||
if (len == 4 && !strncmp(buf, "disk", len)) {
|
||||
error = hibernate();
|
||||
goto Exit;
|
||||
}
|
||||
/* Check hibernation first. */
|
||||
if (len == 4 && !strncmp(buf, "disk", len))
|
||||
return PM_SUSPEND_MAX;
|
||||
|
||||
#ifdef CONFIG_SUSPEND
|
||||
for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) {
|
||||
if (*s && len == strlen(*s) && !strncmp(buf, *s, len)) {
|
||||
error = pm_suspend(state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++)
|
||||
if (*s && len == strlen(*s) && !strncmp(buf, *s, len))
|
||||
return state;
|
||||
#endif
|
||||
|
||||
Exit:
|
||||
return PM_SUSPEND_ON;
|
||||
}
|
||||
|
||||
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
suspend_state_t state;
|
||||
int error;
|
||||
|
||||
error = pm_autosleep_lock();
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
if (pm_autosleep_state() > PM_SUSPEND_ON) {
|
||||
error = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
state = decode_state(buf, n);
|
||||
if (state < PM_SUSPEND_MAX)
|
||||
error = pm_suspend(state);
|
||||
else if (state == PM_SUSPEND_MAX)
|
||||
error = hibernate();
|
||||
else
|
||||
error = -EINVAL;
|
||||
|
||||
out:
|
||||
pm_autosleep_unlock();
|
||||
return error ? error : n;
|
||||
}
|
||||
|
||||
@@ -339,7 +359,8 @@ static ssize_t wakeup_count_show(struct kobject *kobj,
|
||||
{
|
||||
unsigned int val;
|
||||
|
||||
return pm_get_wakeup_count(&val) ? sprintf(buf, "%u\n", val) : -EINTR;
|
||||
return pm_get_wakeup_count(&val, true) ?
|
||||
sprintf(buf, "%u\n", val) : -EINTR;
|
||||
}
|
||||
|
||||
static ssize_t wakeup_count_store(struct kobject *kobj,
|
||||
@@ -347,15 +368,69 @@ static ssize_t wakeup_count_store(struct kobject *kobj,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
unsigned int val;
|
||||
int error;
|
||||
|
||||
error = pm_autosleep_lock();
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
if (pm_autosleep_state() > PM_SUSPEND_ON) {
|
||||
error = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
error = -EINVAL;
|
||||
if (sscanf(buf, "%u", &val) == 1) {
|
||||
if (pm_save_wakeup_count(val))
|
||||
return n;
|
||||
error = n;
|
||||
}
|
||||
return -EINVAL;
|
||||
|
||||
out:
|
||||
pm_autosleep_unlock();
|
||||
return error;
|
||||
}
|
||||
|
||||
power_attr(wakeup_count);
|
||||
|
||||
#ifdef CONFIG_PM_AUTOSLEEP
|
||||
static ssize_t autosleep_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
suspend_state_t state = pm_autosleep_state();
|
||||
|
||||
if (state == PM_SUSPEND_ON)
|
||||
return sprintf(buf, "off\n");
|
||||
|
||||
#ifdef CONFIG_SUSPEND
|
||||
if (state < PM_SUSPEND_MAX)
|
||||
return sprintf(buf, "%s\n", valid_state(state) ?
|
||||
pm_states[state] : "error");
|
||||
#endif
|
||||
#ifdef CONFIG_HIBERNATION
|
||||
return sprintf(buf, "disk\n");
|
||||
#else
|
||||
return sprintf(buf, "error");
|
||||
#endif
|
||||
}
|
||||
|
||||
static ssize_t autosleep_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
suspend_state_t state = decode_state(buf, n);
|
||||
int error;
|
||||
|
||||
if (state == PM_SUSPEND_ON
|
||||
&& !(strncmp(buf, "off", 3) && strncmp(buf, "off\n", 4)))
|
||||
return -EINVAL;
|
||||
|
||||
error = pm_autosleep_set_state(state);
|
||||
return error ? error : n;
|
||||
}
|
||||
|
||||
power_attr(autosleep);
|
||||
#endif /* CONFIG_PM_AUTOSLEEP */
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
|
||||
#ifdef CONFIG_PM_TRACE
|
||||
@@ -409,6 +484,9 @@ static struct attribute * g[] = {
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
&pm_async_attr.attr,
|
||||
&wakeup_count_attr.attr,
|
||||
#ifdef CONFIG_PM_AUTOSLEEP
|
||||
&autosleep_attr.attr,
|
||||
#endif
|
||||
#ifdef CONFIG_PM_DEBUG
|
||||
&pm_test_attr.attr,
|
||||
#endif
|
||||
@@ -444,7 +522,10 @@ static int __init pm_init(void)
|
||||
power_kobj = kobject_create_and_add("power", NULL);
|
||||
if (!power_kobj)
|
||||
return -ENOMEM;
|
||||
return sysfs_create_group(power_kobj, &attr_group);
|
||||
error = sysfs_create_group(power_kobj, &attr_group);
|
||||
if (error)
|
||||
return error;
|
||||
return pm_autosleep_init();
|
||||
}
|
||||
|
||||
core_initcall(pm_init);
|
||||
|
Reference in New Issue
Block a user