summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Rothwell <sfr@canb.auug.org.au>2010-05-28 13:32:18 +1000
committerStephen Rothwell <sfr@canb.auug.org.au>2010-05-28 13:32:18 +1000
commitdb0b26fb8af80b17967a632b299ab722c163f1d5 (patch)
tree6df6ee0bea963d5741db1ac6fcee79b1ca9b1d13
parent018ff51bb9697b782777febbff3ddd18a554d30a (diff)
parentdcc4aba7b6df76136f526e1b4d2c15e1fb611941 (diff)
Merge remote branch 'suspend/linux-next'
-rw-r--r--Documentation/ioctl/ioctl-number.txt3
-rw-r--r--Documentation/power/opportunistic-suspend.txt156
-rw-r--r--include/linux/suspend.h1
-rw-r--r--include/linux/suspend_blocker.h166
-rw-r--r--include/linux/suspend_ioctls.h4
-rw-r--r--kernel/power/Kconfig31
-rw-r--r--kernel/power/Makefile2
-rw-r--r--kernel/power/main.c132
-rw-r--r--kernel/power/opportunistic_suspend.c639
-rw-r--r--kernel/power/power.h14
-rw-r--r--kernel/power/process.c11
-rw-r--r--kernel/power/suspend.c5
-rw-r--r--kernel/power/user_suspend_blocker.c143
13 files changed, 1296 insertions, 11 deletions
diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt
index dd5806f4fcc4..e2458f73e3e2 100644
--- a/Documentation/ioctl/ioctl-number.txt
+++ b/Documentation/ioctl/ioctl-number.txt
@@ -254,7 +254,8 @@ Code Seq#(hex) Include File Comments
'q' 80-FF linux/telephony.h Internet PhoneJACK, Internet LineJACK
linux/ixjuser.h <http://www.quicknet.net>
'r' 00-1F linux/msdos_fs.h and fs/fat/dir.c
-'s' all linux/cdk.h
+'s' all linux/cdk.h conflict!
+'s' all linux/suspend_block_dev.h conflict!
't' 00-7F linux/if_ppp.h
't' 80-8F linux/isdn_ppp.h
't' 90 linux/toshiba.h
diff --git a/Documentation/power/opportunistic-suspend.txt b/Documentation/power/opportunistic-suspend.txt
new file mode 100644
index 000000000000..93f4c246253c
--- /dev/null
+++ b/Documentation/power/opportunistic-suspend.txt
@@ -0,0 +1,156 @@
+Opportunistic Suspend
+=====================
+
+Opportunistic suspend is a feature allowing the system to be suspended (ie. put
+into one of the available sleep states) automatically whenever it is regarded
+as idle. The suspend blockers framework described below is used to determine
+when that happens.
+
+The /sys/power/policy sysfs attribute is used to switch the system between the
+opportunistic and "forced" suspend behavior, where in the latter case the
+system is only suspended if a specific value, corresponding to one of the
+available system sleep states, is written into /sys/power/state. However, in
+the former, opportunistic, case the system is put into the sleep state
+corresponding to the value written to /sys/power/state whenever there are no
+active suspend blockers. The default policy is "forced". Also, suspend blockers
+do not affect sleep states entered from idle.
+
+When the policy is "opportunisic", there is a special value, "on", that can be
+written to /sys/power/state. This will block the automatic sleep request, as if
+a suspend blocker was used by a device driver. This way the opportunistic
+suspend may be blocked by user space whithout switching back to the "forced"
+mode.
+
+A suspend blocker is an object used to inform the PM subsystem when the system
+can or cannot be suspended in the "opportunistic" mode (the "forced" mode
+ignores suspend blockers). To use it, a device driver creates a struct
+suspend_blocker that must be initialized with suspend_blocker_init(). Before
+freeing the suspend_blocker structure or its name, suspend_blocker_unregister()
+must be called on it.
+
+A suspend blocker is activated using suspend_block(), which prevents the PM
+subsystem from putting the system into the requested sleep state in the
+"opportunistic" mode until the suspend blocker is deactivated with
+suspend_unblock(). Multiple suspend blockers may be active simultaneously, and
+the system will not suspend as long as at least one of them is active.
+
+If opportunistic suspend is already in progress when suspend_block() is called,
+it will abort the suspend, unless suspend_ops->enter has already been
+executed. If suspend is aborted this way, the system is usually not fully
+operational at that point. The suspend callbacks of some drivers may still be
+running and it usually takes time to restore the system to the fully operational
+state.
+
+Here's an example showing how a cell phone or other embedded system can handle
+keystrokes (or other input events) in the presence of suspend blockers. Use
+set_irq_wake or a platform specific API to make sure the keypad interrupt wakes
+up the cpu. Once the keypad driver has resumed, the sequence of events can look
+like this:
+
+- The Keypad driver gets an interrupt. It then calls suspend_block on the
+ keypad-scan suspend_blocker and starts scanning the keypad matrix.
+- The keypad-scan code detects a key change and reports it to the input-event
+ driver.
+- The input-event driver sees the key change, enqueues an event, and calls
+ suspend_block on the input-event-queue suspend_blocker.
+- The keypad-scan code detects that no keys are held and calls suspend_unblock
+ on the keypad-scan suspend_blocker.
+- The user-space input-event thread returns from select/poll, calls
+ suspend_block on the process-input-events suspend_blocker and then calls read
+ on the input-event device.
+- The input-event driver dequeues the key-event and, since the queue is now
+ empty, it calls suspend_unblock on the input-event-queue suspend_blocker.
+- The user-space input-event thread returns from read. If it determines that
+ the key should be ignored, it calls suspend_unblock on the
+ process_input_events suspend_blocker and then calls select or poll. The
+ system will automatically suspend again, since now no suspend blockers are
+ active.
+
+If the key that was pressed instead should preform a simple action (for example,
+adjusting the volume), this action can be performed right before calling
+suspend_unblock on the process_input_events suspend_blocker. However, if the key
+triggers a longer-running action, that action needs its own suspend_blocker and
+suspend_block must be called on that suspend blocker before calling
+suspend_unblock on the process_input_events suspend_blocker.
+
+ Key pressed Key released
+ | |
+keypad-scan ++++++++++++++++++
+input-event-queue +++ +++
+process-input-events +++ +++
+
+
+Driver API
+==========
+
+A driver can use the suspend block API by adding a suspend_blocker variable to
+its state and calling suspend_blocker_init(). For instance:
+
+struct state {
+ struct suspend_blocker suspend_blocker;
+}
+
+init() {
+ suspend_blocker_init(&state->suspend_blocker, name);
+}
+
+If the suspend_blocker variable is allocated statically,
+DEFINE_SUSPEND_BLOCKER() should be used to initialize it, for example:
+
+static DEFINE_SUSPEND_BLOCKER(blocker, name);
+
+and suspend_blocker_register(&blocker) has to be called to make the suspend
+blocker usable.
+
+Before freeing the memory in which a suspend_blocker variable is located,
+suspend_blocker_unregister() must be called, for instance:
+
+uninit() {
+ suspend_blocker_unregister(&state->suspend_blocker);
+}
+
+When the driver determines that it needs to run (usually in an interrupt
+handler) it calls suspend_block():
+
+ suspend_block(&state->suspend_blocker);
+
+When it no longer needs to run it calls suspend_unblock():
+
+ suspend_unblock(&state->suspend_blocker);
+
+Calling suspend_block() when the suspend blocker is active or suspend_unblock()
+when it is not active has no effect (i.e., these functions don't nest). This
+allows drivers to update their state and call suspend suspend_block() or
+suspend_unblock() based on the result. For instance:
+
+if (list_empty(&state->pending_work))
+ suspend_unblock(&state->suspend_blocker);
+else
+ suspend_block(&state->suspend_blocker);
+
+User space API
+==============
+
+To create a suspend blocker from user space, open the suspend_blocker special
+device file:
+
+ fd = open("/dev/suspend_blocker", O_RDWR | O_CLOEXEC);
+
+then optionally call:
+
+ ioctl(fd, SUSPEND_BLOCKER_IOCTL_SET_NAME(strlen(name)), name);
+
+To activate the suspend blocker call:
+
+ ioctl(fd, SUSPEND_BLOCKER_IOCTL_BLOCK);
+
+To deactivate it call:
+
+ ioctl(fd, SUSPEND_BLOCKER_IOCTL_UNBLOCK);
+
+To destroy the suspend blocker, close the device:
+
+ close(fd);
+
+If the first ioctl called is not SUSPEND_BLOCKER_IOCTL_SET_NAME the suspend
+blocker will get the default name "(userspace)".
diff --git a/include/linux/suspend.h b/include/linux/suspend.h
index 5e781d824e6d..07023d373e4f 100644
--- a/include/linux/suspend.h
+++ b/include/linux/suspend.h
@@ -6,6 +6,7 @@
#include <linux/init.h>
#include <linux/pm.h>
#include <linux/mm.h>
+#include <linux/suspend_blocker.h>
#include <asm/errno.h>
#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_VT) && defined(CONFIG_VT_CONSOLE)
diff --git a/include/linux/suspend_blocker.h b/include/linux/suspend_blocker.h
new file mode 100644
index 000000000000..bb90b453afcc
--- /dev/null
+++ b/include/linux/suspend_blocker.h
@@ -0,0 +1,166 @@
+/* include/linux/suspend_blocker.h
+ *
+ * Copyright (C) 2007-2010 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _LINUX_SUSPEND_BLOCKER_H
+#define _LINUX_SUSPEND_BLOCKER_H
+
+#include <linux/list.h>
+#include <linux/ktime.h>
+#include <linux/workqueue.h>
+
+/**
+ * struct suspend_blocker_stats - statistics for a suspend blocker
+ *
+ * @count: Number of times this blocker has been deacivated.
+ * @wakeup_count: Number of times this blocker was the first to block suspend
+ * after resume.
+ * @total_time: Total time this suspend blocker has prevented suspend.
+ * @prevent_suspend_time: Time this suspend blocker has prevented suspend while
+ * user-space requested suspend.
+ * @max_time: Max time this suspend blocker has been continuously active.
+ * @last_time: Monotonic clock when the active state last changed.
+ */
+struct suspend_blocker_stats {
+#ifdef CONFIG_SUSPEND_BLOCKER_STATS
+ unsigned int count;
+ unsigned int wakeup_count;
+ ktime_t total_time;
+ ktime_t prevent_suspend_time;
+ ktime_t max_time;
+ ktime_t last_time;
+#endif
+};
+
+/**
+ * struct suspend_blocker - the basic suspend_blocker structure
+ * @link: List entry for active or inactive list.
+ * @flags: Tracks initialized and active state and statistics.
+ * @name: Suspend blocker name used for debugging.
+ *
+ * When a suspend_blocker is active it prevents the system from entering
+ * opportunistic suspend.
+ *
+ * The suspend_blocker structure must be initialized by suspend_blocker_init()
+ */
+struct suspend_blocker {
+#ifdef CONFIG_OPPORTUNISTIC_SUSPEND
+ struct list_head link;
+ int flags;
+ const char *name;
+ struct suspend_blocker_stats stat;
+#endif
+};
+
+/**
+ * struct suspend_blocking_work - the basic suspend_blocking_work structure
+ * @work: Standard work struct.
+ * @suspend_blocker: Suspend blocker.
+ * @func: Callback.
+ * @lock: Spinlock protecting pending and running state.
+ * @active: Number of cpu workqueues where work is pending or
+ * callback is running.
+ *
+ * When suspend blocking work is pending or its callback is running it prevents
+ * the system from entering opportunistic suspend.
+ *
+ * The suspend_blocking_work structure must be initialized by
+ * suspend_blocking_work_init().
+ */
+
+struct suspend_blocking_work {
+ struct work_struct work;
+#ifdef CONFIG_OPPORTUNISTIC_SUSPEND
+ struct suspend_blocker suspend_blocker;
+ work_func_t func;
+ spinlock_t lock;
+ int active;
+#endif
+};
+
+static inline struct suspend_blocking_work *to_suspend_blocking_work(
+ struct work_struct *work)
+{
+ return container_of(work, struct suspend_blocking_work, work);
+}
+
+#ifdef CONFIG_OPPORTUNISTIC_SUSPEND
+#define __SUSPEND_BLOCKER_INITIALIZER(blocker_name) \
+ { .name = #blocker_name, }
+
+#define DEFINE_SUSPEND_BLOCKER(blocker, name) \
+ struct suspend_blocker blocker = __SUSPEND_BLOCKER_INITIALIZER(name)
+
+extern void suspend_blocker_register(struct suspend_blocker *blocker);
+extern void suspend_blocker_init(struct suspend_blocker *blocker,
+ const char *name);
+extern void suspend_blocker_unregister(struct suspend_blocker *blocker);
+extern void suspend_block(struct suspend_blocker *blocker);
+extern void suspend_unblock(struct suspend_blocker *blocker);
+extern bool suspend_blocker_is_active(struct suspend_blocker *blocker);
+extern bool suspend_is_blocked(void);
+
+void suspend_blocking_work_init(struct suspend_blocking_work *work,
+ work_func_t func, const char *name);
+void suspend_blocking_work_destroy(struct suspend_blocking_work *work);
+int queue_suspend_blocking_work(struct workqueue_struct *wq,
+ struct suspend_blocking_work *work);
+int schedule_suspend_blocking_work(struct suspend_blocking_work *work);
+int cancel_suspend_blocking_work_sync(struct suspend_blocking_work *work);
+
+#else
+
+#define DEFINE_SUSPEND_BLOCKER(blocker, name) \
+ struct suspend_blocker blocker
+
+static inline void suspend_blocker_register(struct suspend_blocker *bl) {}
+static inline void suspend_blocker_init(struct suspend_blocker *bl,
+ const char *n) {}
+static inline void suspend_blocker_unregister(struct suspend_blocker *bl) {}
+static inline void suspend_block(struct suspend_blocker *bl) {}
+static inline void suspend_unblock(struct suspend_blocker *bl) {}
+static inline bool suspend_blocker_is_active(struct suspend_blocker *bl)
+{
+ return false;
+}
+static inline bool suspend_is_blocked(void) { return false; }
+
+static inline void suspend_blocking_work_init(
+ struct suspend_blocking_work *work, work_func_t func, const char *name)
+{
+ INIT_WORK(&work->work, func);
+}
+static inline void suspend_blocking_work_destroy(
+ struct suspend_blocking_work *work)
+{
+ cancel_work_sync(&work->work);
+}
+static inline int queue_suspend_blocking_work(
+ struct workqueue_struct *wq, struct suspend_blocking_work *work)
+{
+ return queue_work(wq, &work->work);
+}
+static inline int schedule_suspend_blocking_work(
+ struct suspend_blocking_work *work)
+{
+ return schedule_work(&work->work);
+}
+static inline int cancel_suspend_blocking_work_sync(
+ struct suspend_blocking_work *work)
+{
+ return cancel_work_sync(&work->work);
+}
+#endif
+
+#endif
diff --git a/include/linux/suspend_ioctls.h b/include/linux/suspend_ioctls.h
index 0b30382984fe..b95a6b2bd973 100644
--- a/include/linux/suspend_ioctls.h
+++ b/include/linux/suspend_ioctls.h
@@ -30,4 +30,8 @@ struct resume_swap_area {
#define SNAPSHOT_ALLOC_SWAP_PAGE _IOR(SNAPSHOT_IOC_MAGIC, 20, __kernel_loff_t)
#define SNAPSHOT_IOC_MAXNR 20
+#define SUSPEND_BLOCKER_IOCTL_SET_NAME(len) _IOC(_IOC_WRITE, 's', 0, len)
+#define SUSPEND_BLOCKER_IOCTL_BLOCK _IO('s', 1)
+#define SUSPEND_BLOCKER_IOCTL_UNBLOCK _IO('s', 2)
+
#endif /* _LINUX_SUSPEND_IOCTLS_H */
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index 5c36ea9d55d2..16a25703f1c5 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -130,6 +130,37 @@ config SUSPEND_FREEZER
Turning OFF this setting is NOT recommended! If in doubt, say Y.
+config OPPORTUNISTIC_SUSPEND
+ bool "Opportunistic suspend"
+ depends on SUSPEND
+ select RTC_LIB
+ default n
+ ---help---
+ Opportunistic sleep support. Allows the system to be put into a sleep
+ state opportunistically, if it doesn't do any useful work at the
+ moment. The PM subsystem is switched into this mode of operation by
+ writing "opportunistic" into /sys/power/policy, while writing
+ "forced" to this file turns the opportunistic suspend feature off.
+ In the "opportunistic" mode suspend blockers are used to determine
+ when to suspend the system and the value written to /sys/power/state
+ determines the sleep state the system will be put into when there are
+ no active suspend blockers.
+
+config SUSPEND_BLOCKER_STATS
+ bool "Suspend blockers statistics"
+ depends on OPPORTUNISTIC_SUSPEND
+ default y
+ ---help---
+ Use /sys/kernel/debug/suspend_blockers to report suspend blockers
+ statistics.
+
+config USER_SUSPEND_BLOCKERS
+ bool "User space suspend blockers"
+ depends on OPPORTUNISTIC_SUSPEND
+ ---help---
+ User space suspend blockers API. Creates a misc device allowing user
+ space to create, use and destroy suspend blockers.
+
config HIBERNATION_NVS
bool
diff --git a/kernel/power/Makefile b/kernel/power/Makefile
index 524e058dcf06..76568fec37be 100644
--- a/kernel/power/Makefile
+++ b/kernel/power/Makefile
@@ -7,6 +7,8 @@ obj-$(CONFIG_PM) += main.o
obj-$(CONFIG_PM_SLEEP) += console.o
obj-$(CONFIG_FREEZER) += process.o
obj-$(CONFIG_SUSPEND) += suspend.o
+obj-$(CONFIG_OPPORTUNISTIC_SUSPEND) += opportunistic_suspend.o
+obj-$(CONFIG_USER_SUSPEND_BLOCKERS) += user_suspend_blocker.o
obj-$(CONFIG_PM_TEST_SUSPEND) += suspend_test.o
obj-$(CONFIG_HIBERNATION) += hibernate.o snapshot.o swap.o user.o \
block_io.o
diff --git a/kernel/power/main.c b/kernel/power/main.c
index b58800b21fc0..8b0a1c50c331 100644
--- a/kernel/power/main.c
+++ b/kernel/power/main.c
@@ -20,6 +20,60 @@ DEFINE_MUTEX(pm_mutex);
unsigned int pm_flags;
EXPORT_SYMBOL(pm_flags);
+#ifdef CONFIG_OPPORTUNISTIC_SUSPEND
+struct pm_policy {
+ const char *name;
+ bool (*valid_state)(suspend_state_t state);
+ int (*set_state)(suspend_state_t state);
+};
+
+static struct pm_policy policies[] = {
+ {
+ .name = "forced",
+ .valid_state = valid_state,
+ .set_state = enter_state,
+ },
+ {
+ .name = "opportunistic",
+ .valid_state = opportunistic_suspend_valid_state,
+ .set_state = opportunistic_suspend_state,
+ },
+};
+
+static int policy;
+
+static inline bool hibernation_supported(void)
+{
+ return !strncmp(policies[policy].name, "forced", 6);
+}
+
+static inline bool pm_state_valid(int state_idx)
+{
+ return pm_states[state_idx] && policies[policy].valid_state(state_idx);
+}
+
+static inline int pm_enter_state(int state_idx)
+{
+ return policies[policy].set_state(state_idx);
+}
+
+#else
+
+static inline bool hibernation_supported(void) { return true; }
+
+#ifdef CONFIG_SUSPEND
+static inline bool pm_state_valid(int state_idx)
+{
+ return pm_states[state_idx] && valid_state(state_idx);
+}
+#endif /* CONFIG_SUSPEND */
+
+static inline int pm_enter_state(int state_idx)
+{
+ return enter_state(state_idx);
+}
+#endif /* CONFIG_OPPORTUNISTIC_SUSPEND */
+
#ifdef CONFIG_PM_SLEEP
/* Routines for PM-transition notifications */
@@ -146,6 +200,12 @@ struct kobject *power_kobj;
*
* store() accepts one of those strings, translates it into the
* proper enumerated value, and initiates a suspend transition.
+ *
+ * If policy is set to opportunistic, store() does not block until the
+ * system resumes, and it will try to re-enter the state until another
+ * state is requested. Suspend blockers are respected and the requested
+ * state will only be entered when no suspend blockers are active.
+ * Write "on" to disable.
*/
static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
@@ -155,12 +215,15 @@ static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr,
int i;
for (i = 0; i < PM_SUSPEND_MAX; i++) {
- if (pm_states[i] && valid_state(i))
+ if (pm_state_valid(i))
s += sprintf(s,"%s ", pm_states[i]);
}
#endif
#ifdef CONFIG_HIBERNATION
- s += sprintf(s, "%s\n", "disk");
+ if (hibernation_supported())
+ s += sprintf(s, "%s\n", "disk");
+ else
+ s += sprintf(s, "\n");
#else
if (s != buf)
/* convert the last space to a newline */
@@ -173,7 +236,7 @@ static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t n)
{
#ifdef CONFIG_SUSPEND
- suspend_state_t state = PM_SUSPEND_STANDBY;
+ suspend_state_t state = PM_SUSPEND_ON;
const char * const *s;
#endif
char *p;
@@ -185,8 +248,9 @@ static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
/* First, check if we are requested to hibernate */
if (len == 4 && !strncmp(buf, "disk", len)) {
- error = hibernate();
- goto Exit;
+ if (hibernation_supported())
+ error = hibernate();
+ goto Exit;
}
#ifdef CONFIG_SUSPEND
@@ -195,7 +259,7 @@ static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
break;
}
if (state < PM_SUSPEND_MAX && *s)
- error = enter_state(state);
+ error = pm_enter_state(state);
#endif
Exit:
@@ -204,6 +268,56 @@ static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
power_attr(state);
+#ifdef CONFIG_OPPORTUNISTIC_SUSPEND
+/**
+ * policy - set policy for state
+ */
+static ssize_t policy_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ char *s = buf;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(policies); i++) {
+ if (i == policy)
+ s += sprintf(s, "[%s] ", policies[i].name);
+ else
+ s += sprintf(s, "%s ", policies[i].name);
+ }
+ if (s != buf)
+ /* convert the last space to a newline */
+ *(s-1) = '\n';
+ return (s - buf);
+}
+
+static ssize_t policy_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t n)
+{
+ const char *s;
+ char *p;
+ int len;
+ int i;
+
+ p = memchr(buf, '\n', n);
+ len = p ? p - buf : n;
+
+ for (i = 0; i < ARRAY_SIZE(policies); i++) {
+ s = policies[i].name;
+ if (s && len == strlen(s) && !strncmp(buf, s, len)) {
+ mutex_lock(&pm_mutex);
+ policies[policy].set_state(PM_SUSPEND_ON);
+ policy = i;
+ mutex_unlock(&pm_mutex);
+ return n;
+ }
+ }
+ return -EINVAL;
+}
+
+power_attr(policy);
+#endif /* CONFIG_OPPORTUNISTIC_SUSPEND */
+
#ifdef CONFIG_PM_TRACE
int pm_trace_enabled;
@@ -236,6 +350,9 @@ static struct attribute * g[] = {
#endif
#ifdef CONFIG_PM_SLEEP
&pm_async_attr.attr,
+#ifdef CONFIG_OPPORTUNISTIC_SUSPEND
+ &policy_attr.attr,
+#endif
#ifdef CONFIG_PM_DEBUG
&pm_test_attr.attr,
#endif
@@ -247,7 +364,7 @@ static struct attribute_group attr_group = {
.attrs = g,
};
-#ifdef CONFIG_PM_RUNTIME
+#if defined(CONFIG_PM_RUNTIME) || defined(CONFIG_OPPORTUNISTIC_SUSPEND)
struct workqueue_struct *pm_wq;
EXPORT_SYMBOL_GPL(pm_wq);
@@ -266,6 +383,7 @@ static int __init pm_init(void)
int error = pm_start_workqueue();
if (error)
return error;
+ opportunistic_suspend_init();
power_kobj = kobject_create_and_add("power", NULL);
if (!power_kobj)
return -ENOMEM;
diff --git a/kernel/power/opportunistic_suspend.c b/kernel/power/opportunistic_suspend.c
new file mode 100644
index 000000000000..3af0ed318d4d
--- /dev/null
+++ b/kernel/power/opportunistic_suspend.c
@@ -0,0 +1,639 @@
+/*
+ * kernel/power/opportunistic_suspend.c
+ *
+ * Copyright (C) 2005-2010 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/rtc.h>
+#include <linux/suspend.h>
+#include <linux/debugfs.h>
+
+#include "power.h"
+
+extern struct workqueue_struct *pm_wq;
+
+enum {
+ DEBUG_EXIT_SUSPEND = 1U << 0,
+ DEBUG_WAKEUP = 1U << 1,
+ DEBUG_USER_STATE = 1U << 2,
+ DEBUG_SUSPEND = 1U << 3,
+ DEBUG_SUSPEND_BLOCKER = 1U << 4,
+};
+static int debug_mask = DEBUG_EXIT_SUSPEND | DEBUG_WAKEUP | DEBUG_USER_STATE;
+module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP);
+
+static int unknown_wakeup_delay_msecs = 500;
+module_param_named(unknown_wakeup_delay_msecs, unknown_wakeup_delay_msecs, int,
+ S_IRUGO | S_IWUSR | S_IWGRP);
+
+#define SB_INITIALIZED (1U << 8)
+#define SB_ACTIVE (1U << 9)
+#define SB_PREVENTING_SUSPEND (1U << 10)
+
+DEFINE_SUSPEND_BLOCKER(main_suspend_blocker, main);
+
+static DEFINE_SPINLOCK(list_lock);
+static DEFINE_SPINLOCK(state_lock);
+static LIST_HEAD(inactive_blockers);
+static LIST_HEAD(active_blockers);
+static int current_event_num;
+static suspend_state_t requested_suspend_state = PM_SUSPEND_MEM;
+static bool enable_suspend_blockers;
+static DEFINE_SUSPEND_BLOCKER(unknown_wakeup, unknown_wakeups);
+
+#ifdef CONFIG_SUSPEND_BLOCKER_STATS
+static struct suspend_blocker_stats dropped_suspend_blockers;
+static ktime_t last_sleep_time_update;
+static bool wait_for_wakeup;
+
+static void suspend_blocker_stat_init(struct suspend_blocker_stats *stat)
+{
+ stat->count = 0;
+ stat->wakeup_count = 0;
+ stat->total_time = ktime_set(0, 0);
+ stat->prevent_suspend_time = ktime_set(0, 0);
+ stat->max_time = ktime_set(0, 0);
+ stat->last_time = ktime_set(0, 0);
+}
+
+static void init_dropped_suspend_blockers(void)
+{
+ suspend_blocker_stat_init(&dropped_suspend_blockers);
+}
+
+static void suspend_blocker_stat_drop(struct suspend_blocker_stats *stat)
+{
+ if (!stat->count)
+ return;
+
+ dropped_suspend_blockers.count += stat->count;
+ dropped_suspend_blockers.total_time = ktime_add(
+ dropped_suspend_blockers.total_time, stat->total_time);
+ dropped_suspend_blockers.prevent_suspend_time = ktime_add(
+ dropped_suspend_blockers.prevent_suspend_time,
+ stat->prevent_suspend_time);
+ dropped_suspend_blockers.max_time = ktime_add(
+ dropped_suspend_blockers.max_time, stat->max_time);
+}
+
+static void suspend_unblock_stat(struct suspend_blocker *blocker)
+{
+ struct suspend_blocker_stats *stat = &blocker->stat;
+ ktime_t duration;
+ ktime_t now;
+
+ if (!(blocker->flags & SB_ACTIVE))
+ return;
+
+ now = ktime_get();
+ stat->count++;
+ duration = ktime_sub(now, stat->last_time);
+ stat->total_time = ktime_add(stat->total_time, duration);
+ if (ktime_to_ns(duration) > ktime_to_ns(stat->max_time))
+ stat->max_time = duration;
+
+ stat->last_time = ktime_get();
+ if (blocker->flags & SB_PREVENTING_SUSPEND) {
+ duration = ktime_sub(now, last_sleep_time_update);
+ stat->prevent_suspend_time = ktime_add(
+ stat->prevent_suspend_time, duration);
+ blocker->flags &= ~SB_PREVENTING_SUSPEND;
+ }
+}
+
+static void suspend_block_stat(struct suspend_blocker *blocker)
+{
+ if (wait_for_wakeup) {
+ if (debug_mask & DEBUG_WAKEUP)
+ pr_info("wakeup suspend blocker: %s\n", blocker->name);
+
+ wait_for_wakeup = false;
+ blocker->stat.wakeup_count++;
+ }
+ if (!(blocker->flags & SB_ACTIVE))
+ blocker->stat.last_time = ktime_get();
+}
+
+static void update_sleep_wait_stats(bool done)
+{
+ struct suspend_blocker *blocker;
+ ktime_t now, elapsed, add;
+
+ now = ktime_get();
+ elapsed = ktime_sub(now, last_sleep_time_update);
+ list_for_each_entry(blocker, &active_blockers, link) {
+ struct suspend_blocker_stats *stat = &blocker->stat;
+
+ if (blocker->flags & SB_PREVENTING_SUSPEND) {
+ add = elapsed;
+ stat->prevent_suspend_time = ktime_add(
+ stat->prevent_suspend_time, add);
+ }
+ if (done)
+ blocker->flags &= ~SB_PREVENTING_SUSPEND;
+ else
+ blocker->flags |= SB_PREVENTING_SUSPEND;
+ }
+ last_sleep_time_update = now;
+}
+
+void about_to_enter_suspend(void)
+{
+ wait_for_wakeup = true;
+}
+
+#else /* !CONFIG_SUSPEND_BLOCKER_STATS */
+
+static inline void init_dropped_suspend_blockers(void) {}
+static inline void suspend_blocker_stat_init(struct suspend_blocker_stats *s) {}
+static inline void suspend_blocker_stat_drop(struct suspend_blocker_stats *s) {}
+static inline void suspend_unblock_stat(struct suspend_blocker *blocker) {}
+static inline void suspend_block_stat(struct suspend_blocker *blocker) {}
+static inline void update_sleep_wait_stats(bool done) {}
+#endif /* !CONFIG_SUSPEND_BLOCKER_STATS */
+
+#define pr_info_time(fmt, args...) \
+ do { \
+ struct timespec ts; \
+ struct rtc_time tm; \
+ getnstimeofday(&ts); \
+ rtc_time_to_tm(ts.tv_sec, &tm); \
+ pr_info(fmt "(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n" , \
+ args, \
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, \
+ tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec); \
+ } while (0);
+
+static void print_active_suspend_blockers(void)
+{
+ struct suspend_blocker *blocker;
+
+ list_for_each_entry(blocker, &active_blockers, link)
+ pr_info("PM: Active suspend blocker %s\n", blocker->name);
+}
+
+/**
+ * suspend_is_blocked - Check if there are active suspend blockers.
+ *
+ * Return true if suspend blockers are enabled and there are active suspend
+ * blockers, in which case the system cannot be put to sleep opportunistically.
+ */
+bool suspend_is_blocked(void)
+{
+ return enable_suspend_blockers && !list_empty(&active_blockers);
+}
+
+static void expire_unknown_wakeup(unsigned long data)
+{
+ suspend_unblock(&unknown_wakeup);
+}
+static DEFINE_TIMER(expire_unknown_wakeup_timer, expire_unknown_wakeup, 0, 0);
+
+static void suspend_worker(struct work_struct *work)
+{
+ int ret;
+ int entry_event_num;
+
+ enable_suspend_blockers = true;
+
+ if (suspend_is_blocked()) {
+ if (debug_mask & DEBUG_SUSPEND)
+ pr_info("PM: Automatic suspend aborted\n");
+ goto abort;
+ }
+
+ entry_event_num = current_event_num;
+
+ if (debug_mask & DEBUG_SUSPEND)
+ pr_info("PM: Automatic suspend\n");
+
+ ret = pm_suspend(requested_suspend_state);
+
+ if (debug_mask & DEBUG_EXIT_SUSPEND)
+ pr_info_time("PM: Automatic suspend exit, ret = %d ", ret);
+
+ if (current_event_num == entry_event_num) {
+ if (debug_mask & DEBUG_SUSPEND)
+ pr_info("PM: pm_suspend() returned with no event\n");
+ suspend_block(&unknown_wakeup);
+ mod_timer(&expire_unknown_wakeup_timer,
+ msecs_to_jiffies(unknown_wakeup_delay_msecs));
+ }
+
+abort:
+ enable_suspend_blockers = false;
+}
+static DECLARE_WORK(suspend_work, suspend_worker);
+
+/**
+ * suspend_blocker_register - Prepare a suspend blocker for being used.
+ * @blocker: Suspend blocker to handle.
+ *
+ * The suspend blocker struct and name must not be freed before calling
+ * suspend_blocker_unregister().
+ */
+void suspend_blocker_register(struct suspend_blocker *blocker)
+{
+ unsigned long irqflags = 0;
+
+ WARN_ON(!blocker->name);
+
+ if (debug_mask & DEBUG_SUSPEND_BLOCKER)
+ pr_info("%s: Registering %s\n", __func__, blocker->name);
+
+ suspend_blocker_stat_init(&blocker->stat);
+
+ blocker->flags = SB_INITIALIZED;
+ INIT_LIST_HEAD(&blocker->link);
+
+ spin_lock_irqsave(&list_lock, irqflags);
+ list_add(&blocker->link, &inactive_blockers);
+ spin_unlock_irqrestore(&list_lock, irqflags);
+}
+EXPORT_SYMBOL(suspend_blocker_register);
+
+/**
+ * suspend_blocker_init - Initialize a suspend blocker's name and register it.
+ * @blocker: Suspend blocker to initialize.
+ * @name: The name of the suspend blocker to show in debug messages and
+ * /sys/kernel/debug/suspend_blockers.
+ *
+ * The suspend blocker struct and name must not be freed before calling
+ * suspend_blocker_unregister().
+ */
+void suspend_blocker_init(struct suspend_blocker *blocker, const char *name)
+{
+ blocker->name = name;
+ suspend_blocker_register(blocker);
+}
+EXPORT_SYMBOL(suspend_blocker_init);
+
+/**
+ * suspend_blocker_unregister - Unregister a suspend blocker.
+ * @blocker: Suspend blocker to handle.
+ */
+void suspend_blocker_unregister(struct suspend_blocker *blocker)
+{
+ unsigned long irqflags;
+
+ if (WARN_ON(!(blocker->flags & SB_INITIALIZED)))
+ return;
+
+ spin_lock_irqsave(&list_lock, irqflags);
+
+ suspend_unblock_stat(blocker);
+ suspend_blocker_stat_drop(&blocker->stat);
+
+ blocker->flags &= ~SB_INITIALIZED;
+ list_del(&blocker->link);
+ if ((blocker->flags & SB_ACTIVE) && list_empty(&active_blockers))
+ queue_work(pm_wq, &suspend_work);
+ spin_unlock_irqrestore(&list_lock, irqflags);
+
+ if (debug_mask & DEBUG_SUSPEND_BLOCKER)
+ pr_info("%s: Unregistered %s\n", __func__, blocker->name);
+}
+EXPORT_SYMBOL(suspend_blocker_unregister);
+
+/**
+ * suspend_block - Block system suspend.
+ * @blocker: Suspend blocker to use.
+ *
+ * It is safe to call this function from interrupt context.
+ */
+void suspend_block(struct suspend_blocker *blocker)
+{
+ unsigned long irqflags;
+
+ if (WARN_ON(!(blocker->flags & SB_INITIALIZED)))
+ return;
+
+ spin_lock_irqsave(&list_lock, irqflags);
+
+ if (debug_mask & DEBUG_SUSPEND_BLOCKER)
+ pr_info("%s: %s\n", __func__, blocker->name);
+
+ suspend_block_stat(blocker);
+
+ blocker->flags |= SB_ACTIVE;
+ list_move(&blocker->link, &active_blockers);
+
+ current_event_num++;
+
+ if (blocker == &main_suspend_blocker)
+ update_sleep_wait_stats(true);
+ else if (!suspend_blocker_is_active(&main_suspend_blocker))
+ update_sleep_wait_stats(false);
+
+ spin_unlock_irqrestore(&list_lock, irqflags);
+}
+EXPORT_SYMBOL(suspend_block);
+
+/**
+ * suspend_unblock - Allow system suspend to happen.
+ * @blocker: Suspend blocker to unblock.
+ *
+ * If no other suspend blockers are active, schedule suspend of the system.
+ *
+ * It is safe to call this function from interrupt context.
+ */
+void suspend_unblock(struct suspend_blocker *blocker)
+{
+ unsigned long irqflags;
+
+ if (WARN_ON(!(blocker->flags & SB_INITIALIZED)))
+ return;
+
+ spin_lock_irqsave(&list_lock, irqflags);
+
+ if (debug_mask & DEBUG_SUSPEND_BLOCKER)
+ pr_info("%s: %s\n", __func__, blocker->name);
+
+ suspend_unblock_stat(blocker);
+
+ list_move(&blocker->link, &inactive_blockers);
+ if ((blocker->flags & SB_ACTIVE) && list_empty(&active_blockers))
+ queue_work(pm_wq, &suspend_work);
+ blocker->flags &= ~(SB_ACTIVE);
+
+ if (blocker == &main_suspend_blocker) {
+ if (debug_mask & DEBUG_SUSPEND)
+ print_active_suspend_blockers();
+
+ update_sleep_wait_stats(false);
+ }
+
+ spin_unlock_irqrestore(&list_lock, irqflags);
+}
+EXPORT_SYMBOL(suspend_unblock);
+
+/**
+ * suspend_blocker_is_active - Test if a suspend blocker is blocking suspend
+ * @blocker: Suspend blocker to check.
+ *
+ * Returns true if the suspend_blocker is currently active.
+ */
+bool suspend_blocker_is_active(struct suspend_blocker *blocker)
+{
+ WARN_ON(!(blocker->flags & SB_INITIALIZED));
+
+ return !!(blocker->flags & SB_ACTIVE);
+}
+EXPORT_SYMBOL(suspend_blocker_is_active);
+
+bool opportunistic_suspend_valid_state(suspend_state_t state)
+{
+ return (state == PM_SUSPEND_ON) || valid_state(state);
+}
+
+int opportunistic_suspend_state(suspend_state_t state)
+{
+ unsigned long irqflags;
+
+ if (!opportunistic_suspend_valid_state(state))
+ return -ENODEV;
+
+ spin_lock_irqsave(&state_lock, irqflags);
+
+ if (debug_mask & DEBUG_USER_STATE)
+ pr_info_time("%s: %s (%d->%d) at %lld ", __func__,
+ state != PM_SUSPEND_ON ? "sleep" : "wakeup",
+ requested_suspend_state, state,
+ ktime_to_ns(ktime_get()));
+
+ requested_suspend_state = state;
+ if (state == PM_SUSPEND_ON)
+ suspend_block(&main_suspend_blocker);
+ else
+ suspend_unblock(&main_suspend_blocker);
+
+ spin_unlock_irqrestore(&state_lock, irqflags);
+
+ return 0;
+}
+
+void __init opportunistic_suspend_init(void)
+{
+ suspend_blocker_register(&main_suspend_blocker);
+ suspend_block(&main_suspend_blocker);
+ suspend_blocker_register(&unknown_wakeup);
+ init_dropped_suspend_blockers();
+}
+
+static struct dentry *suspend_blocker_stats_dentry;
+
+#ifdef CONFIG_SUSPEND_BLOCKER_STATS
+static int print_blocker_stats(struct seq_file *m, const char *name,
+ struct suspend_blocker_stats *stat, int flags)
+{
+ int lock_count = stat->count;
+ ktime_t active_time = ktime_set(0, 0);
+ ktime_t total_time = stat->total_time;
+ ktime_t max_time = stat->max_time;
+ ktime_t prevent_suspend_time = stat->prevent_suspend_time;
+
+ if (flags & SB_ACTIVE) {
+ ktime_t now, add_time;
+
+ now = ktime_get();
+ add_time = ktime_sub(now, stat->last_time);
+ lock_count++;
+ active_time = add_time;
+ total_time = ktime_add(total_time, add_time);
+ if (flags & SB_PREVENTING_SUSPEND)
+ prevent_suspend_time = ktime_add(prevent_suspend_time,
+ ktime_sub(now, last_sleep_time_update));
+ if (add_time.tv64 > max_time.tv64)
+ max_time = add_time;
+ }
+
+ return seq_printf(m, "\"%s\"\t%d\t%d\t%lld\t%lld\t%lld\t%lld\t%lld\n",
+ name, lock_count, stat->wakeup_count,
+ ktime_to_ns(active_time), ktime_to_ns(total_time),
+ ktime_to_ns(prevent_suspend_time),
+ ktime_to_ns(max_time),
+ ktime_to_ns(stat->last_time));
+}
+
+static int suspend_blocker_stats_show(struct seq_file *m, void *unused)
+{
+ unsigned long irqflags;
+ struct suspend_blocker *blocker;
+
+ seq_puts(m, "name\tcount\twake_count\tactive_since"
+ "\ttotal_time\tsleep_time\tmax_time\tlast_change\n");
+
+ spin_lock_irqsave(&list_lock, irqflags);
+ list_for_each_entry(blocker, &active_blockers, link)
+ print_blocker_stats(m,
+ blocker->name, &blocker->stat, blocker->flags);
+
+ list_for_each_entry(blocker, &inactive_blockers, link)
+ print_blocker_stats(m,
+ blocker->name, &blocker->stat, blocker->flags);
+
+ print_blocker_stats(m, "deleted", &dropped_suspend_blockers, 0);
+ spin_unlock_irqrestore(&list_lock, irqflags);
+ return 0;
+}
+
+#else
+
+static int suspend_blocker_stats_show(struct seq_file *m, void *unused)
+{
+ unsigned long irqflags;
+ struct suspend_blocker *blocker;
+
+ seq_puts(m, "name\tactive\n");
+ spin_lock_irqsave(&list_lock, irqflags);
+ list_for_each_entry(blocker, &inactive_blockers, link)
+ seq_printf(m, "\"%s\"\t0\n", blocker->name);
+ list_for_each_entry(blocker, &active_blockers, link)
+ seq_printf(m, "\"%s\"\t1\n", blocker->name);
+ spin_unlock_irqrestore(&list_lock, irqflags);
+ return 0;
+}
+
+#endif
+
+static int suspend_blocker_stats_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, suspend_blocker_stats_show, NULL);
+}
+
+static const struct file_operations suspend_blocker_stats_fops = {
+ .owner = THIS_MODULE,
+ .open = suspend_blocker_stats_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int __init suspend_blocker_debugfs_init(void)
+{
+ suspend_blocker_stats_dentry = debugfs_create_file("suspend_blockers",
+ S_IRUGO, NULL, NULL, &suspend_blocker_stats_fops);
+ return 0;
+}
+
+postcore_initcall(suspend_blocker_debugfs_init);
+
+static void suspend_blocking_work_complete(struct suspend_blocking_work *work)
+{
+ unsigned long flags;
+
+ WARN_ON(!work->active);
+ spin_lock_irqsave(&work->lock, flags);
+ if (!--work->active)
+ suspend_unblock(&work->suspend_blocker);
+ spin_unlock_irqrestore(&work->lock, flags);
+}
+
+static void suspend_blocking_work_func(struct work_struct *work)
+{
+ struct suspend_blocking_work *sbwork = to_suspend_blocking_work(work);
+
+ sbwork->func(work);
+ suspend_blocking_work_complete(sbwork);
+}
+
+/**
+ * suspend_blocking_work_init - Initialize a suspend-blocking work item.
+ * @work: Work item to initialize.
+ * @func: Callback.
+ * @name: Name for suspend blocker.
+ *
+ */
+void suspend_blocking_work_init(struct suspend_blocking_work *work,
+ work_func_t func, const char *name)
+{
+ INIT_WORK(&work->work, suspend_blocking_work_func);
+ suspend_blocker_init(&work->suspend_blocker, name);
+ work->func = func;
+ spin_lock_init(&work->lock);
+ work->active = 0;
+}
+EXPORT_SYMBOL_GPL(suspend_blocking_work_init);
+
+/**
+ * cancel_suspend_blocking_work_sync - Cancel a suspend-blocking work item.
+ * @work: Work item to handle.
+ */
+int cancel_suspend_blocking_work_sync(struct suspend_blocking_work *work)
+{
+ int ret;
+
+ ret = cancel_work_sync(&work->work);
+ if (ret)
+ suspend_blocking_work_complete(work);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(cancel_suspend_blocking_work_sync);
+
+/**
+ * suspend_blocking_work_destroy - Destroy a suspend-blocking work item.
+ * @work: The work item in question.
+ *
+ * If the work was ever queued on more then one workqueue all but the last
+ * workqueue must be flushed before calling suspend_blocking_work_destroy.
+ */
+void suspend_blocking_work_destroy(struct suspend_blocking_work *work)
+{
+ cancel_suspend_blocking_work_sync(work);
+ WARN_ON(work->active);
+ suspend_blocker_unregister(&work->suspend_blocker);
+}
+EXPORT_SYMBOL_GPL(suspend_blocking_work_destroy);
+
+/**
+ * queue_suspend_blocking_work - Queue a suspend-blocking work item.
+ * @wq: Workqueue to queue the work on.
+ * @work: Work item to queue.
+ */
+int queue_suspend_blocking_work(struct workqueue_struct *wq,
+ struct suspend_blocking_work *work)
+{
+ int ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&work->lock, flags);
+ ret = queue_work(wq, &work->work);
+ if (ret) {
+ suspend_block(&work->suspend_blocker);
+ work->active++;
+ }
+ spin_unlock_irqrestore(&work->lock, flags);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(queue_suspend_blocking_work);
+
+/**
+ * schedule_suspend_blocking_work - Schedule a suspend-blocking work item.
+ * @work: Work item to schedule.
+ */
+int schedule_suspend_blocking_work(struct suspend_blocking_work *work)
+{
+ int ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&work->lock, flags);
+ ret = schedule_work(&work->work);
+ if (ret) {
+ suspend_block(&work->suspend_blocker);
+ work->active++;
+ }
+ spin_unlock_irqrestore(&work->lock, flags);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(schedule_suspend_blocking_work);
diff --git a/kernel/power/power.h b/kernel/power/power.h
index 006270fe382d..9753ed1fd804 100644
--- a/kernel/power/power.h
+++ b/kernel/power/power.h
@@ -233,3 +233,17 @@ static inline void suspend_thaw_processes(void)
{
}
#endif
+
+#ifdef CONFIG_OPPORTUNISTIC_SUSPEND
+/* kernel/power/opportunistic_suspend.c */
+extern int opportunistic_suspend_state(suspend_state_t state);
+extern bool opportunistic_suspend_valid_state(suspend_state_t state);
+extern void __init opportunistic_suspend_init(void);
+#else
+static inline void opportunistic_suspend_init(void) {}
+#endif
+#ifdef CONFIG_SUSPEND_BLOCKER_STATS
+void about_to_enter_suspend(void);
+#else
+static inline void about_to_enter_suspend(void) {}
+#endif
diff --git a/kernel/power/process.c b/kernel/power/process.c
index 71ae29052ab6..27d26d3eb748 100644
--- a/kernel/power/process.c
+++ b/kernel/power/process.c
@@ -38,6 +38,7 @@ static int try_to_freeze_tasks(bool sig_only)
struct timeval start, end;
u64 elapsed_csecs64;
unsigned int elapsed_csecs;
+ bool wakeup = false;
do_gettimeofday(&start);
@@ -63,6 +64,10 @@ static int try_to_freeze_tasks(bool sig_only)
todo++;
} while_each_thread(g, p);
read_unlock(&tasklist_lock);
+ if (todo && suspend_is_blocked()) {
+ wakeup = true;
+ break;
+ }
if (!todo || time_after(jiffies, end_time))
break;
@@ -85,13 +90,15 @@ static int try_to_freeze_tasks(bool sig_only)
* but it cleans up leftover PF_FREEZE requests.
*/
printk("\n");
- printk(KERN_ERR "Freezing of tasks failed after %d.%02d seconds "
+ printk(KERN_ERR "Freezing of tasks %s after %d.%02d seconds "
"(%d tasks refusing to freeze):\n",
+ wakeup ? "aborted" : "failed",
elapsed_csecs / 100, elapsed_csecs % 100, todo);
read_lock(&tasklist_lock);
do_each_thread(g, p) {
task_lock(p);
- if (freezing(p) && !freezer_should_skip(p))
+ if (freezing(p) && !freezer_should_skip(p)
+ && elapsed_csecs > 100)
sched_show_task(p);
cancel_freezing(p);
task_unlock(p);
diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c
index 56e7dbb8b996..df694e7efa42 100644
--- a/kernel/power/suspend.c
+++ b/kernel/power/suspend.c
@@ -20,6 +20,7 @@
#include "power.h"
const char *const pm_states[PM_SUSPEND_MAX] = {
+ [PM_SUSPEND_ON] = "on",
[PM_SUSPEND_STANDBY] = "standby",
[PM_SUSPEND_MEM] = "mem",
};
@@ -157,8 +158,10 @@ static int suspend_enter(suspend_state_t state)
error = sysdev_suspend(PMSG_SUSPEND);
if (!error) {
- if (!suspend_test(TEST_CORE))
+ if (!suspend_is_blocked() && !suspend_test(TEST_CORE)) {
+ about_to_enter_suspend();
error = suspend_ops->enter(state);
+ }
sysdev_resume();
}
diff --git a/kernel/power/user_suspend_blocker.c b/kernel/power/user_suspend_blocker.c
new file mode 100644
index 000000000000..d53f939e6b22
--- /dev/null
+++ b/kernel/power/user_suspend_blocker.c
@@ -0,0 +1,143 @@
+/*
+ * kernel/power/user_suspend_blocker.c
+ *
+ * Copyright (C) 2009-2010 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/suspend_ioctls.h>
+
+enum {
+ DEBUG_FAILURE = BIT(0),
+};
+static int debug_mask = DEBUG_FAILURE;
+module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP);
+
+static DEFINE_MUTEX(ioctl_lock);
+
+#define USER_SUSPEND_BLOCKER_NAME_LEN 31
+
+struct user_suspend_blocker {
+ struct suspend_blocker blocker;
+ char name[USER_SUSPEND_BLOCKER_NAME_LEN + 1];
+ bool registered;
+};
+
+static int user_suspend_blocker_open(struct inode *inode, struct file *filp)
+{
+ struct user_suspend_blocker *blocker;
+
+ blocker = kzalloc(sizeof(*blocker), GFP_KERNEL);
+ if (!blocker)
+ return -ENOMEM;
+
+ nonseekable_open(inode, filp);
+ strcpy(blocker->name, "(userspace)");
+ blocker->blocker.name = blocker->name;
+ filp->private_data = blocker;
+
+ return 0;
+}
+
+static int suspend_blocker_set_name(struct user_suspend_blocker *blocker,
+ void __user *name, size_t name_len)
+{
+ if (blocker->registered)
+ return -EBUSY;
+
+ if (name_len > USER_SUSPEND_BLOCKER_NAME_LEN)
+ name_len = USER_SUSPEND_BLOCKER_NAME_LEN;
+
+ if (copy_from_user(blocker->name, name, name_len))
+ return -EFAULT;
+ blocker->name[name_len] = '\0';
+
+ return 0;
+}
+
+static long user_suspend_blocker_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long _arg)
+{
+ void __user *arg = (void __user *)_arg;
+ struct user_suspend_blocker *blocker = filp->private_data;
+ long ret = 0;
+
+ mutex_lock(&ioctl_lock);
+ if ((cmd & ~IOCSIZE_MASK) == SUSPEND_BLOCKER_IOCTL_SET_NAME(0)) {
+ ret = suspend_blocker_set_name(blocker, arg, _IOC_SIZE(cmd));
+ goto done;
+ }
+ if (!blocker->registered) {
+ suspend_blocker_register(&blocker->blocker);
+ blocker->registered = true;
+ }
+ switch (cmd) {
+ case SUSPEND_BLOCKER_IOCTL_BLOCK:
+ suspend_block(&blocker->blocker);
+ break;
+
+ case SUSPEND_BLOCKER_IOCTL_UNBLOCK:
+ suspend_unblock(&blocker->blocker);
+ break;
+
+ default:
+ ret = -ENOTTY;
+ }
+done:
+ if (ret && (debug_mask & DEBUG_FAILURE))
+ pr_err("user_suspend_blocker_ioctl: cmd %x failed, %ld\n",
+ cmd, ret);
+ mutex_unlock(&ioctl_lock);
+ return ret;
+}
+
+static int user_suspend_blocker_release(struct inode *inode, struct file *filp)
+{
+ struct user_suspend_blocker *blocker = filp->private_data;
+
+ if (blocker->registered)
+ suspend_blocker_unregister(&blocker->blocker);
+ kfree(blocker);
+
+ return 0;
+}
+
+const struct file_operations user_suspend_blocker_fops = {
+ .open = user_suspend_blocker_open,
+ .release = user_suspend_blocker_release,
+ .unlocked_ioctl = user_suspend_blocker_ioctl,
+};
+
+struct miscdevice user_suspend_blocker_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "suspend_blocker",
+ .fops = &user_suspend_blocker_fops,
+};
+
+static int __init user_suspend_blocker_init(void)
+{
+ return misc_register(&user_suspend_blocker_device);
+}
+
+static void __exit user_suspend_blocker_exit(void)
+{
+ misc_deregister(&user_suspend_blocker_device);
+}
+
+module_init(user_suspend_blocker_init);
+module_exit(user_suspend_blocker_exit);