[PATCH 1/8] PM: Add suspend block api.
From: Arve HjÃnnevÃg
Date: Fri May 14 2010 - 00:11:35 EST
Adds /sys/power/policy that selects the behaviour of /sys/power/state.
After setting the policy to opportunistic, writes to /sys/power/state
become non-blocking requests that specify which suspend state to enter
when no suspend blockers are active. A special state, "on", stops the
process by activating the "main" suspend blocker.
Signed-off-by: Arve HjÃnnevÃg <arve@xxxxxxxxxxx>
---
Documentation/power/opportunistic-suspend.txt | 129 +++++++++++
include/linux/suspend.h | 1 +
include/linux/suspend_blocker.h | 74 +++++++
kernel/power/Kconfig | 16 ++
kernel/power/Makefile | 1 +
kernel/power/main.c | 128 +++++++++++-
kernel/power/opportunistic_suspend.c | 284 +++++++++++++++++++++++++
kernel/power/power.h | 9 +
kernel/power/suspend.c | 3 +-
9 files changed, 637 insertions(+), 8 deletions(-)
create mode 100644 Documentation/power/opportunistic-suspend.txt
create mode 100755 include/linux/suspend_blocker.h
create mode 100644 kernel/power/opportunistic_suspend.c
diff --git a/Documentation/power/opportunistic-suspend.txt b/Documentation/power/opportunistic-suspend.txt
new file mode 100644
index 0000000..4bee7bc
--- /dev/null
+++ b/Documentation/power/opportunistic-suspend.txt
@@ -0,0 +1,129 @@
+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);
diff --git a/include/linux/suspend.h b/include/linux/suspend.h
index 5e781d8..07023d3 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 100755
index 0000000..8788302
--- /dev/null
+++ b/include/linux/suspend_blocker.h
@@ -0,0 +1,74 @@
+/* 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>
+
+/**
+ * struct suspend_blocker - the basic suspend_blocker structure
+ * @link: List entry for active or inactive list.
+ * @flags: Tracks initialized and active state.
+ * @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;
+#endif
+};
+
+#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);
+
+#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; }
+#endif
+
+#endif
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index 5c36ea9..6d11a45 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -130,6 +130,22 @@ 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 HIBERNATION_NVS
bool
diff --git a/kernel/power/Makefile b/kernel/power/Makefile
index 4319181..95d8e6d 100644
--- a/kernel/power/Makefile
+++ b/kernel/power/Makefile
@@ -7,6 +7,7 @@ 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_PM_TEST_SUSPEND) += suspend_test.o
obj-$(CONFIG_HIBERNATION) += hibernate.o snapshot.o swap.o user.o
obj-$(CONFIG_HIBERNATION_NVS) += hibernate_nvs.o
diff --git a/kernel/power/main.c b/kernel/power/main.c
index b58800b..afbb4dd 100644
--- a/kernel/power/main.c
+++ b/kernel/power/main.c
@@ -20,6 +20,58 @@ 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; }
+
+static inline bool pm_state_valid(int state_idx)
+{
+ return pm_states[state_idx] && valid_state(state_idx);
+}
+
+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 +198,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 +213,13 @@ 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
if (s != buf)
/* convert the last space to a newline */
@@ -173,7 +232,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 +244,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 +255,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 +264,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 +346,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 +360,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 +379,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 0000000..acc1651
--- /dev/null
+++ b/kernel/power/opportunistic_suspend.c
@@ -0,0 +1,284 @@
+/*
+ * 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 "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);
+
+#define SB_INITIALIZED (1U << 8)
+#define SB_ACTIVE (1U << 9)
+
+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;
+
+#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 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");
+ queue_work(pm_wq, work);
+ }
+
+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);
+
+ 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.
+ *
+ * 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);
+ 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);
+
+ blocker->flags |= SB_ACTIVE;
+ list_move(&blocker->link, &active_blockers);
+
+ current_event_num++;
+
+ 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);
+
+ 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 ((debug_mask & DEBUG_SUSPEND) && blocker == &main_suspend_blocker)
+ print_active_suspend_blockers();
+
+ 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);
+}
diff --git a/kernel/power/power.h b/kernel/power/power.h
index 46c5a26..2e9cfd5 100644
--- a/kernel/power/power.h
+++ b/kernel/power/power.h
@@ -236,3 +236,12 @@ 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
diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c
index 56e7dbb..9eb3876 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,7 +158,7 @@ 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))
error = suspend_ops->enter(state);
sysdev_resume();
}
--
1.6.5.1
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/