diff options
author | Stephen Rothwell <sfr@canb.auug.org.au> | 2010-05-27 14:44:30 +1000 |
---|---|---|
committer | Stephen Rothwell <sfr@canb.auug.org.au> | 2010-05-27 14:44:30 +1000 |
commit | b93534050b8b048c3f1f0cf1fa84aa733acc207d (patch) | |
tree | 829628ec0f1a3143a97a06b28dac280affb2d50d /kernel | |
parent | 69146ae8774832d62c0b633a9a42b1711c410f7a (diff) | |
parent | dcc4aba7b6df76136f526e1b4d2c15e1fb611941 (diff) |
Merge remote branch 'suspend/linux-next'
Diffstat (limited to 'kernel')
-rw-r--r-- | kernel/power/Kconfig | 31 | ||||
-rw-r--r-- | kernel/power/Makefile | 2 | ||||
-rw-r--r-- | kernel/power/main.c | 132 | ||||
-rw-r--r-- | kernel/power/opportunistic_suspend.c | 639 | ||||
-rw-r--r-- | kernel/power/power.h | 14 | ||||
-rw-r--r-- | kernel/power/process.c | 11 | ||||
-rw-r--r-- | kernel/power/suspend.c | 5 | ||||
-rw-r--r-- | kernel/power/user_suspend_blocker.c | 143 |
8 files changed, 967 insertions, 10 deletions
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); |