diff --git a/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/testing/sysfs-power index 237c735db6c..31725ffeeb3 100644 --- a/Documentation/ABI/testing/sysfs-power +++ b/Documentation/ABI/testing/sysfs-power @@ -189,3 +189,45 @@ Description: Reading from this file causes the last string successfully written to it to be returned. + +What: /sys/power/wake_lock +Date: February 2012 +Contact: Rafael J. Wysocki +Description: + The /sys/power/wake_lock file allows user space to create + wakeup source objects and activate them on demand (if one of + those wakeup sources is active, reads from the + /sys/power/wakeup_count file block or return false). When a + string without white space is written to /sys/power/wake_lock, + it will be assumed to represent a wakeup source name. If there + is a wakeup source object with that name, it will be activated + (unless active already). Otherwise, a new wakeup source object + will be registered, assigned the given name and activated. + If a string written to /sys/power/wake_lock contains white + space, the part of the string preceding the white space will be + regarded as a wakeup source name and handled as descrived above. + The other part of the string will be regarded as a timeout (in + nanoseconds) such that the wakeup source will be automatically + deactivated after it has expired. The timeout, if present, is + set regardless of the current state of the wakeup source object + in question. + + Reads from this file return a string consisting of the names of + wakeup sources created with the help of it that are active at + the moment, separated with spaces. + + +What: /sys/power/wake_unlock +Date: February 2012 +Contact: Rafael J. Wysocki +Description: + The /sys/power/wake_unlock file allows user space to deactivate + wakeup sources created with the help of /sys/power/wake_lock. + When a string is written to /sys/power/wake_unlock, it will be + assumed to represent the name of a wakeup source to deactivate. + If a wakeup source object of that name exists and is active at + the moment, it will be deactivated. + + Reads from this file return a string consisting of the names of + wakeup sources created with the help of /sys/power/wake_lock + that are inactive at the moment, separated with spaces. diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 2595b8d8fe1..cbb463b3a75 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -133,6 +133,7 @@ void wakeup_source_add(struct wakeup_source *ws) spin_lock_init(&ws->lock); setup_timer(&ws->timer, pm_wakeup_timer_fn, (unsigned long)ws); ws->active = false; + ws->last_time = ktime_get(); spin_lock_irq(&events_lock); list_add_rcu(&ws->entry, &wakeup_sources); diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index 479b90ba443..f142239f908 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -111,6 +111,14 @@ config PM_AUTOSLEEP Allow the kernel to trigger a system transition into a global sleep state automatically whenever there are no active wakeup sources. +config PM_WAKELOCKS + bool "User space wakeup sources interface" + depends on PM_SLEEP + default n + ---help--- + Allow user space to create, activate and deactivate wakeup source + objects with the help of a sysfs-based interface. + config PM_RUNTIME bool "Run-time PM core functionality" depends on !IA64_HP_SIM diff --git a/kernel/power/Makefile b/kernel/power/Makefile index 010b2f7e148..29472bff11e 100644 --- a/kernel/power/Makefile +++ b/kernel/power/Makefile @@ -10,5 +10,6 @@ obj-$(CONFIG_PM_TEST_SUSPEND) += suspend_test.o obj-$(CONFIG_HIBERNATION) += hibernate.o snapshot.o swap.o user.o \ block_io.o obj-$(CONFIG_PM_AUTOSLEEP) += autosleep.o +obj-$(CONFIG_PM_WAKELOCKS) += wakelock.o obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o diff --git a/kernel/power/main.c b/kernel/power/main.c index 80b49ae2737..c2bfc34ea72 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c @@ -520,6 +520,43 @@ static ssize_t autosleep_store(struct kobject *kobj, power_attr(autosleep); #endif /* CONFIG_PM_AUTOSLEEP */ + +#ifdef CONFIG_PM_WAKELOCKS +static ssize_t wake_lock_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return pm_show_wakelocks(buf, true); +} + +static ssize_t wake_lock_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + int error = pm_wake_lock(buf); + return error ? error : n; +} + +power_attr(wake_lock); + +static ssize_t wake_unlock_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return pm_show_wakelocks(buf, false); +} + +static ssize_t wake_unlock_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + int error = pm_wake_unlock(buf); + return error ? error : n; +} + +power_attr(wake_unlock); + +#endif /* CONFIG_PM_WAKELOCKS */ #endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_PM_TRACE @@ -583,6 +620,10 @@ static struct attribute *g[] = { #ifdef CONFIG_PM_AUTOSLEEP &autosleep_attr.attr, #endif +#ifdef CONFIG_PM_WAKELOCKS + &wake_lock_attr.attr, + &wake_unlock_attr.attr, +#endif #ifdef CONFIG_PM_DEBUG &pm_test_attr.attr, #endif diff --git a/kernel/power/power.h b/kernel/power/power.h index 4cf80fa115d..b0bd4beaebf 100644 --- a/kernel/power/power.h +++ b/kernel/power/power.h @@ -282,3 +282,12 @@ static inline void pm_autosleep_unlock(void) {} static inline suspend_state_t pm_autosleep_state(void) { return PM_SUSPEND_ON; } #endif /* !CONFIG_PM_AUTOSLEEP */ + +#ifdef CONFIG_PM_WAKELOCKS + +/* kernel/power/wakelock.c */ +extern ssize_t pm_show_wakelocks(char *buf, bool show_active); +extern int pm_wake_lock(const char *buf); +extern int pm_wake_unlock(const char *buf); + +#endif /* !CONFIG_PM_WAKELOCKS */ diff --git a/kernel/power/wakelock.c b/kernel/power/wakelock.c index 2583856fab3..579700665e8 100644 --- a/kernel/power/wakelock.c +++ b/kernel/power/wakelock.c @@ -1,712 +1,215 @@ -/* kernel/power/wakelock.c +/* + * kernel/power/wakelock.c * - * Copyright (C) 2005-2008 Google, Inc. + * User space wakeup sources support. * - * 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. + * Copyright (C) 2012 Rafael J. Wysocki * + * This code is based on the analogous interface allowing user space to + * manipulate wakelocks on Android. */ -#include -#include -#include -#include -#include /* sys_sync */ -#include -#ifdef CONFIG_WAKELOCK_STAT -#include -#endif -#include "power.h" +#include +#include +#include +#include +#include +#include +#include -enum { - DEBUG_EXIT_SUSPEND = 1U << 0, - DEBUG_WAKEUP = 1U << 1, - DEBUG_SUSPEND = 1U << 2, - DEBUG_EXPIRE = 1U << 3, - DEBUG_WAKE_LOCK = 1U << 4, +#define WL_NUMBER_LIMIT 100 +#define WL_GC_COUNT_MAX 100 +#define WL_GC_TIME_SEC 300 + +static DEFINE_MUTEX(wakelocks_lock); + +struct wakelock { + char *name; + struct rb_node node; + struct wakeup_source ws; + struct list_head lru; }; -static int debug_mask = DEBUG_EXIT_SUSPEND | DEBUG_WAKEUP; -module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); -#define WAKE_LOCK_TYPE_MASK (0x0f) -#define WAKE_LOCK_INITIALIZED (1U << 8) -#define WAKE_LOCK_ACTIVE (1U << 9) -#define WAKE_LOCK_AUTO_EXPIRE (1U << 10) -#define WAKE_LOCK_PREVENTING_SUSPEND (1U << 11) +static struct rb_root wakelocks_tree = RB_ROOT; +static LIST_HEAD(wakelocks_lru_list); +static unsigned int number_of_wakelocks; +static unsigned int wakelocks_gc_count; -static DEFINE_SPINLOCK(list_lock); -static LIST_HEAD(inactive_locks); -static struct list_head active_wake_locks[WAKE_LOCK_TYPE_COUNT]; -static int current_event_num; -static int suspend_sys_sync_count; -static DEFINE_SPINLOCK(suspend_sys_sync_lock); -static struct workqueue_struct *suspend_sys_sync_work_queue; -static DECLARE_COMPLETION(suspend_sys_sync_comp); -struct workqueue_struct *suspend_work_queue; -struct wake_lock main_wake_lock; -suspend_state_t requested_suspend_state = PM_SUSPEND_MEM; -static struct wake_lock unknown_wakeup; -static struct wake_lock suspend_backoff_lock; - -#define SUSPEND_BACKOFF_THRESHOLD 10 -#define SUSPEND_BACKOFF_INTERVAL 10000 - -static unsigned suspend_short_count; - -#ifdef CONFIG_WAKELOCK_STAT -static struct wake_lock deleted_wake_locks; -static ktime_t last_sleep_time_update; -static int wait_for_wakeup; - -int get_expired_time(struct wake_lock *lock, ktime_t *expire_time) +ssize_t pm_show_wakelocks(char *buf, bool show_active) { - struct timespec ts; - struct timespec kt; - struct timespec tomono; - struct timespec delta; - struct timespec sleep; - long timeout; + struct rb_node *node; + struct wakelock *wl; + char *str = buf; + char *end = buf + PAGE_SIZE; - if (!(lock->flags & WAKE_LOCK_AUTO_EXPIRE)) - return 0; - get_xtime_and_monotonic_and_sleep_offset(&kt, &tomono, &sleep); - timeout = lock->expires - jiffies; - if (timeout > 0) - return 0; - jiffies_to_timespec(-timeout, &delta); - set_normalized_timespec(&ts, kt.tv_sec + tomono.tv_sec - delta.tv_sec, - kt.tv_nsec + tomono.tv_nsec - delta.tv_nsec); - *expire_time = timespec_to_ktime(ts); - return 1; -} + mutex_lock(&wakelocks_lock); - -static int print_lock_stat(struct seq_file *m, struct wake_lock *lock) -{ - int lock_count = lock->stat.count; - int expire_count = lock->stat.expire_count; - ktime_t active_time = ktime_set(0, 0); - ktime_t total_time = lock->stat.total_time; - ktime_t max_time = lock->stat.max_time; - - ktime_t prevent_suspend_time = lock->stat.prevent_suspend_time; - if (lock->flags & WAKE_LOCK_ACTIVE) { - ktime_t now, add_time; - int expired = get_expired_time(lock, &now); - if (!expired) - now = ktime_get(); - add_time = ktime_sub(now, lock->stat.last_time); - lock_count++; - if (!expired) - active_time = add_time; - else - expire_count++; - total_time = ktime_add(total_time, add_time); - if (lock->flags & WAKE_LOCK_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; + for (node = rb_first(&wakelocks_tree); node; node = rb_next(node)) { + wl = rb_entry(node, struct wakelock, node); + if (wl->ws.active == show_active) + str += scnprintf(str, end - str, "%s ", wl->name); } + if (str > buf) + str--; - return seq_printf(m, - "\"%s\"\t%d\t%d\t%d\t%lld\t%lld\t%lld\t%lld\t%lld\n", - lock->name, lock_count, expire_count, - lock->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(lock->stat.last_time)); + str += scnprintf(str, end - str, "\n"); + + mutex_unlock(&wakelocks_lock); + return (str - buf); } -static int wakelock_stats_show(struct seq_file *m, void *unused) +static struct wakelock *wakelock_lookup_add(const char *name, size_t len, + bool add_if_not_found) { - unsigned long irqflags; - struct wake_lock *lock; - int ret; - int type; + struct rb_node **node = &wakelocks_tree.rb_node; + struct rb_node *parent = *node; + struct wakelock *wl; - spin_lock_irqsave(&list_lock, irqflags); + while (*node) { + int diff; - ret = seq_puts(m, "name\tcount\texpire_count\twake_count\tactive_since" - "\ttotal_time\tsleep_time\tmax_time\tlast_change\n"); - list_for_each_entry(lock, &inactive_locks, link) - ret = print_lock_stat(m, lock); - for (type = 0; type < WAKE_LOCK_TYPE_COUNT; type++) { - list_for_each_entry(lock, &active_wake_locks[type], link) - ret = print_lock_stat(m, lock); - } - spin_unlock_irqrestore(&list_lock, irqflags); - return 0; -} - -static void wake_unlock_stat_locked(struct wake_lock *lock, int expired) -{ - ktime_t duration; - ktime_t now; - if (!(lock->flags & WAKE_LOCK_ACTIVE)) - return; - if (get_expired_time(lock, &now)) - expired = 1; - else - now = ktime_get(); - lock->stat.count++; - if (expired) - lock->stat.expire_count++; - duration = ktime_sub(now, lock->stat.last_time); - lock->stat.total_time = ktime_add(lock->stat.total_time, duration); - if (ktime_to_ns(duration) > ktime_to_ns(lock->stat.max_time)) - lock->stat.max_time = duration; - lock->stat.last_time = ktime_get(); - if (lock->flags & WAKE_LOCK_PREVENTING_SUSPEND) { - duration = ktime_sub(now, last_sleep_time_update); - lock->stat.prevent_suspend_time = ktime_add( - lock->stat.prevent_suspend_time, duration); - lock->flags &= ~WAKE_LOCK_PREVENTING_SUSPEND; - } -} - -static void update_sleep_wait_stats_locked(int done) -{ - struct wake_lock *lock; - ktime_t now, etime, elapsed, add; - int expired; - - now = ktime_get(); - elapsed = ktime_sub(now, last_sleep_time_update); - list_for_each_entry(lock, &active_wake_locks[WAKE_LOCK_SUSPEND], link) { - expired = get_expired_time(lock, &etime); - if (lock->flags & WAKE_LOCK_PREVENTING_SUSPEND) { - if (expired) - add = ktime_sub(etime, last_sleep_time_update); + parent = *node; + wl = rb_entry(*node, struct wakelock, node); + diff = strncmp(name, wl->name, len); + if (diff == 0) { + if (wl->name[len]) + diff = -1; else - add = elapsed; - lock->stat.prevent_suspend_time = ktime_add( - lock->stat.prevent_suspend_time, add); + return wl; } - if (done || expired) - lock->flags &= ~WAKE_LOCK_PREVENTING_SUSPEND; + if (diff < 0) + node = &(*node)->rb_left; else - lock->flags |= WAKE_LOCK_PREVENTING_SUSPEND; + node = &(*node)->rb_right; } - last_sleep_time_update = now; -} -#endif + if (!add_if_not_found) + return ERR_PTR(-EINVAL); + if (number_of_wakelocks > WL_NUMBER_LIMIT) + return ERR_PTR(-ENOSPC); -static void expire_wake_lock(struct wake_lock *lock) -{ -#ifdef CONFIG_WAKELOCK_STAT - wake_unlock_stat_locked(lock, 1); -#endif - lock->flags &= ~(WAKE_LOCK_ACTIVE | WAKE_LOCK_AUTO_EXPIRE); - list_del(&lock->link); - list_add(&lock->link, &inactive_locks); - if (debug_mask & (DEBUG_WAKE_LOCK | DEBUG_EXPIRE)) - pr_info("expired wake lock %s\n", lock->name); -} + /* Not found, we have to add a new one. */ + wl = kzalloc(sizeof(*wl), GFP_KERNEL); + if (!wl) + return ERR_PTR(-ENOMEM); -/* Caller must acquire the list_lock spinlock */ -static void print_active_locks(int type) -{ - struct wake_lock *lock; - bool print_expired = true; - - BUG_ON(type >= WAKE_LOCK_TYPE_COUNT); - list_for_each_entry(lock, &active_wake_locks[type], link) { - if (lock->flags & WAKE_LOCK_AUTO_EXPIRE) { - long timeout = lock->expires - jiffies; - if (timeout > 0) - pr_info("active wake lock %s, time left %ld\n", - lock->name, timeout); - else if (print_expired) - pr_info("wake lock %s, expired\n", lock->name); - } else { - pr_info("active wake lock %s\n", lock->name); - if (!(debug_mask & DEBUG_EXPIRE)) - print_expired = false; - } + wl->name = kstrndup(name, len, GFP_KERNEL); + if (!wl->name) { + kfree(wl); + return ERR_PTR(-ENOMEM); } + wl->ws.name = wl->name; + wakeup_source_add(&wl->ws); + rb_link_node(&wl->node, parent, node); + rb_insert_color(&wl->node, &wakelocks_tree); + list_add(&wl->lru, &wakelocks_lru_list); + number_of_wakelocks++; + return wl; } -static long has_wake_lock_locked(int type) +int pm_wake_lock(const char *buf) { - struct wake_lock *lock, *n; - long max_timeout = 0; + const char *str = buf; + struct wakelock *wl; + u64 timeout_ns = 0; + size_t len; + int ret = 0; - BUG_ON(type >= WAKE_LOCK_TYPE_COUNT); - list_for_each_entry_safe(lock, n, &active_wake_locks[type], link) { - if (lock->flags & WAKE_LOCK_AUTO_EXPIRE) { - long timeout = lock->expires - jiffies; - if (timeout <= 0) - expire_wake_lock(lock); - else if (timeout > max_timeout) - max_timeout = timeout; - } else - return -1; + while (*str && !isspace(*str)) + str++; + + len = str - buf; + if (!len) + return -EINVAL; + + if (*str && *str != '\n') { + /* Find out if there's a valid timeout string appended. */ + ret = kstrtou64(skip_spaces(str), 10, &timeout_ns); + if (ret) + return -EINVAL; } - return max_timeout; -} -long has_wake_lock(int type) -{ - long ret; - unsigned long irqflags; - spin_lock_irqsave(&list_lock, irqflags); - ret = has_wake_lock_locked(type); - if (ret && (debug_mask & DEBUG_WAKEUP) && type == WAKE_LOCK_SUSPEND) - print_active_locks(type); - spin_unlock_irqrestore(&list_lock, irqflags); + mutex_lock(&wakelocks_lock); + + wl = wakelock_lookup_add(buf, len, true); + if (IS_ERR(wl)) { + ret = PTR_ERR(wl); + goto out; + } + if (timeout_ns) { + u64 timeout_ms = timeout_ns + NSEC_PER_MSEC - 1; + + do_div(timeout_ms, NSEC_PER_MSEC); + __pm_wakeup_event(&wl->ws, timeout_ms); + } else { + __pm_stay_awake(&wl->ws); + } + + list_move(&wl->lru, &wakelocks_lru_list); + + out: + mutex_unlock(&wakelocks_lock); return ret; } -static void suspend_sys_sync(struct work_struct *work) +static void wakelocks_gc(void) { - if (debug_mask & DEBUG_SUSPEND) - pr_info("PM: Syncing filesystems...\n"); + struct wakelock *wl, *aux; + ktime_t now = ktime_get(); - sys_sync(); + list_for_each_entry_safe_reverse(wl, aux, &wakelocks_lru_list, lru) { + u64 idle_time_ns; + bool active; - if (debug_mask & DEBUG_SUSPEND) - pr_info("sync done.\n"); + spin_lock_irq(&wl->ws.lock); + idle_time_ns = ktime_to_ns(ktime_sub(now, wl->ws.last_time)); + active = wl->ws.active; + spin_unlock_irq(&wl->ws.lock); - spin_lock(&suspend_sys_sync_lock); - suspend_sys_sync_count--; - spin_unlock(&suspend_sys_sync_lock); -} -static DECLARE_WORK(suspend_sys_sync_work, suspend_sys_sync); + if (idle_time_ns < ((u64)WL_GC_TIME_SEC * NSEC_PER_SEC)) + break; -void suspend_sys_sync_queue(void) -{ - int ret; - - spin_lock(&suspend_sys_sync_lock); - ret = queue_work(suspend_sys_sync_work_queue, &suspend_sys_sync_work); - if (ret) - suspend_sys_sync_count++; - spin_unlock(&suspend_sys_sync_lock); -} - -static bool suspend_sys_sync_abort; -static void suspend_sys_sync_handler(unsigned long); -static DEFINE_TIMER(suspend_sys_sync_timer, suspend_sys_sync_handler, 0, 0); -/* value should be less then half of input event wake lock timeout value - * which is currently set to 5*HZ (see drivers/input/evdev.c) - */ -#define SUSPEND_SYS_SYNC_TIMEOUT (HZ/4) -static void suspend_sys_sync_handler(unsigned long arg) -{ - if (suspend_sys_sync_count == 0) { - complete(&suspend_sys_sync_comp); - } else if (has_wake_lock(WAKE_LOCK_SUSPEND)) { - suspend_sys_sync_abort = true; - complete(&suspend_sys_sync_comp); - } else { - mod_timer(&suspend_sys_sync_timer, jiffies + - SUSPEND_SYS_SYNC_TIMEOUT); - } -} - -int suspend_sys_sync_wait(void) -{ - suspend_sys_sync_abort = false; - - if (suspend_sys_sync_count != 0) { - mod_timer(&suspend_sys_sync_timer, jiffies + - SUSPEND_SYS_SYNC_TIMEOUT); - wait_for_completion(&suspend_sys_sync_comp); - } - if (suspend_sys_sync_abort) { - pr_info("suspend aborted....while waiting for sys_sync\n"); - return -EAGAIN; - } - - return 0; -} - -static void suspend_backoff(void) -{ - pr_info("suspend: too many immediate wakeups, back off\n"); - wake_lock_timeout(&suspend_backoff_lock, - msecs_to_jiffies(SUSPEND_BACKOFF_INTERVAL)); -} - -static void suspend(struct work_struct *work) -{ - int ret; - int entry_event_num; - struct timespec ts_entry, ts_exit; - - if (has_wake_lock(WAKE_LOCK_SUSPEND)) { - if (debug_mask & DEBUG_SUSPEND) - pr_info("suspend: abort suspend\n"); - return; - } - - entry_event_num = current_event_num; - suspend_sys_sync_queue(); - if (debug_mask & DEBUG_SUSPEND) - pr_info("suspend: enter suspend\n"); - getnstimeofday(&ts_entry); - ret = pm_suspend(requested_suspend_state); - getnstimeofday(&ts_exit); - - if (debug_mask & DEBUG_EXIT_SUSPEND) { - struct rtc_time tm; - rtc_time_to_tm(ts_exit.tv_sec, &tm); - pr_info("suspend: exit suspend, ret = %d " - "(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n", ret, - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec, ts_exit.tv_nsec); - } - - if (ts_exit.tv_sec - ts_entry.tv_sec <= 1) { - ++suspend_short_count; - - if (suspend_short_count == SUSPEND_BACKOFF_THRESHOLD) { - suspend_backoff(); - suspend_short_count = 0; + if (!active) { + wakeup_source_remove(&wl->ws); + rb_erase(&wl->node, &wakelocks_tree); + list_del(&wl->lru); + kfree(wl->name); + kfree(wl); + number_of_wakelocks--; } - } else { - suspend_short_count = 0; } + wakelocks_gc_count = 0; +} - if (current_event_num == entry_event_num) { - if (debug_mask & DEBUG_SUSPEND) - pr_info("suspend: pm_suspend returned with no event\n"); - wake_lock_timeout(&unknown_wakeup, HZ / 2); +int pm_wake_unlock(const char *buf) +{ + struct wakelock *wl; + size_t len; + int ret = 0; + + len = strlen(buf); + if (!len) + return -EINVAL; + + if (buf[len-1] == '\n') + len--; + + if (!len) + return -EINVAL; + + mutex_lock(&wakelocks_lock); + + wl = wakelock_lookup_add(buf, len, false); + if (IS_ERR(wl)) { + ret = PTR_ERR(wl); + goto out; } -} -static DECLARE_WORK(suspend_work, suspend); + __pm_relax(&wl->ws); + list_move(&wl->lru, &wakelocks_lru_list); + if (++wakelocks_gc_count > WL_GC_COUNT_MAX) + wakelocks_gc(); -static void expire_wake_locks(unsigned long data) -{ - long has_lock; - unsigned long irqflags; - if (debug_mask & DEBUG_EXPIRE) - pr_info("expire_wake_locks: start\n"); - spin_lock_irqsave(&list_lock, irqflags); - if (debug_mask & DEBUG_SUSPEND) - print_active_locks(WAKE_LOCK_SUSPEND); - has_lock = has_wake_lock_locked(WAKE_LOCK_SUSPEND); - if (debug_mask & DEBUG_EXPIRE) - pr_info("expire_wake_locks: done, has_lock %ld\n", has_lock); - if (has_lock == 0) - queue_work(suspend_work_queue, &suspend_work); - spin_unlock_irqrestore(&list_lock, irqflags); -} -static DEFINE_TIMER(expire_timer, expire_wake_locks, 0, 0); - -static int power_suspend_late(struct device *dev) -{ - int ret = has_wake_lock(WAKE_LOCK_SUSPEND) ? -EAGAIN : 0; -#ifdef CONFIG_WAKELOCK_STAT - wait_for_wakeup = !ret; -#endif - if (debug_mask & DEBUG_SUSPEND) - pr_info("power_suspend_late return %d\n", ret); + out: + mutex_unlock(&wakelocks_lock); return ret; } - -static struct dev_pm_ops power_driver_pm_ops = { - .suspend_noirq = power_suspend_late, -}; - -static struct platform_driver power_driver = { - .driver.name = "power", - .driver.pm = &power_driver_pm_ops, -}; -static struct platform_device power_device = { - .name = "power", -}; - -void wake_lock_init(struct wake_lock *lock, int type, const char *name) -{ - unsigned long irqflags = 0; - - if (name) - lock->name = name; - BUG_ON(!lock->name); - - if (debug_mask & DEBUG_WAKE_LOCK) - pr_info("wake_lock_init name=%s\n", lock->name); -#ifdef CONFIG_WAKELOCK_STAT - lock->stat.count = 0; - lock->stat.expire_count = 0; - lock->stat.wakeup_count = 0; - lock->stat.total_time = ktime_set(0, 0); - lock->stat.prevent_suspend_time = ktime_set(0, 0); - lock->stat.max_time = ktime_set(0, 0); - lock->stat.last_time = ktime_set(0, 0); -#endif - lock->flags = (type & WAKE_LOCK_TYPE_MASK) | WAKE_LOCK_INITIALIZED; - - INIT_LIST_HEAD(&lock->link); - spin_lock_irqsave(&list_lock, irqflags); - list_add(&lock->link, &inactive_locks); - spin_unlock_irqrestore(&list_lock, irqflags); -} -EXPORT_SYMBOL(wake_lock_init); - -void wake_lock_destroy(struct wake_lock *lock) -{ - unsigned long irqflags; - if (debug_mask & DEBUG_WAKE_LOCK) - pr_info("wake_lock_destroy name=%s\n", lock->name); - spin_lock_irqsave(&list_lock, irqflags); - lock->flags &= ~WAKE_LOCK_INITIALIZED; -#ifdef CONFIG_WAKELOCK_STAT - if (lock->stat.count) { - deleted_wake_locks.stat.count += lock->stat.count; - deleted_wake_locks.stat.expire_count += lock->stat.expire_count; - deleted_wake_locks.stat.total_time = - ktime_add(deleted_wake_locks.stat.total_time, - lock->stat.total_time); - deleted_wake_locks.stat.prevent_suspend_time = - ktime_add(deleted_wake_locks.stat.prevent_suspend_time, - lock->stat.prevent_suspend_time); - deleted_wake_locks.stat.max_time = - ktime_add(deleted_wake_locks.stat.max_time, - lock->stat.max_time); - } -#endif - list_del(&lock->link); - spin_unlock_irqrestore(&list_lock, irqflags); -} -EXPORT_SYMBOL(wake_lock_destroy); - -static void wake_lock_internal( - struct wake_lock *lock, long timeout, int has_timeout) -{ - int type; - unsigned long irqflags; - long expire_in; - - spin_lock_irqsave(&list_lock, irqflags); - type = lock->flags & WAKE_LOCK_TYPE_MASK; - BUG_ON(type >= WAKE_LOCK_TYPE_COUNT); - BUG_ON(!(lock->flags & WAKE_LOCK_INITIALIZED)); -#ifdef CONFIG_WAKELOCK_STAT - if (type == WAKE_LOCK_SUSPEND && wait_for_wakeup) { - if (debug_mask & DEBUG_WAKEUP) - pr_info("wakeup wake lock: %s\n", lock->name); - wait_for_wakeup = 0; - lock->stat.wakeup_count++; - } - if ((lock->flags & WAKE_LOCK_AUTO_EXPIRE) && - (long)(lock->expires - jiffies) <= 0) { - wake_unlock_stat_locked(lock, 0); - lock->stat.last_time = ktime_get(); - } -#endif - if (!(lock->flags & WAKE_LOCK_ACTIVE)) { - lock->flags |= WAKE_LOCK_ACTIVE; -#ifdef CONFIG_WAKELOCK_STAT - lock->stat.last_time = ktime_get(); -#endif - } - list_del(&lock->link); - if (has_timeout) { - if (debug_mask & DEBUG_WAKE_LOCK) - pr_info("wake_lock: %s, type %d, timeout %ld.%03lu\n", - lock->name, type, timeout / HZ, - (timeout % HZ) * MSEC_PER_SEC / HZ); - lock->expires = jiffies + timeout; - lock->flags |= WAKE_LOCK_AUTO_EXPIRE; - list_add_tail(&lock->link, &active_wake_locks[type]); - } else { - if (debug_mask & DEBUG_WAKE_LOCK) - pr_info("wake_lock: %s, type %d\n", lock->name, type); - lock->expires = LONG_MAX; - lock->flags &= ~WAKE_LOCK_AUTO_EXPIRE; - list_add(&lock->link, &active_wake_locks[type]); - } - if (type == WAKE_LOCK_SUSPEND) { - current_event_num++; -#ifdef CONFIG_WAKELOCK_STAT - if (lock == &main_wake_lock) - update_sleep_wait_stats_locked(1); - else if (!wake_lock_active(&main_wake_lock)) - update_sleep_wait_stats_locked(0); -#endif - if (has_timeout) - expire_in = has_wake_lock_locked(type); - else - expire_in = -1; - if (expire_in > 0) { - if (debug_mask & DEBUG_EXPIRE) - pr_info("wake_lock: %s, start expire timer, " - "%ld\n", lock->name, expire_in); - mod_timer(&expire_timer, jiffies + expire_in); - } else { - if (del_timer(&expire_timer)) - if (debug_mask & DEBUG_EXPIRE) - pr_info("wake_lock: %s, stop expire timer\n", - lock->name); - if (expire_in == 0) - queue_work(suspend_work_queue, &suspend_work); - } - } - spin_unlock_irqrestore(&list_lock, irqflags); -} - -void wake_lock(struct wake_lock *lock) -{ - wake_lock_internal(lock, 0, 0); -} -EXPORT_SYMBOL(wake_lock); - -void wake_lock_timeout(struct wake_lock *lock, long timeout) -{ - wake_lock_internal(lock, timeout, 1); -} -EXPORT_SYMBOL(wake_lock_timeout); - -void wake_unlock(struct wake_lock *lock) -{ - int type; - unsigned long irqflags; - spin_lock_irqsave(&list_lock, irqflags); - type = lock->flags & WAKE_LOCK_TYPE_MASK; -#ifdef CONFIG_WAKELOCK_STAT - wake_unlock_stat_locked(lock, 0); -#endif - if (debug_mask & DEBUG_WAKE_LOCK) - pr_info("wake_unlock: %s\n", lock->name); - lock->flags &= ~(WAKE_LOCK_ACTIVE | WAKE_LOCK_AUTO_EXPIRE); - list_del(&lock->link); - list_add(&lock->link, &inactive_locks); - if (type == WAKE_LOCK_SUSPEND) { - long has_lock = has_wake_lock_locked(type); - if (has_lock > 0) { - if (debug_mask & DEBUG_EXPIRE) - pr_info("wake_unlock: %s, start expire timer, " - "%ld\n", lock->name, has_lock); - mod_timer(&expire_timer, jiffies + has_lock); - } else { - if (del_timer(&expire_timer)) - if (debug_mask & DEBUG_EXPIRE) - pr_info("wake_unlock: %s, stop expire " - "timer\n", lock->name); - if (has_lock == 0) - queue_work(suspend_work_queue, &suspend_work); - } - if (lock == &main_wake_lock) { - if (debug_mask & DEBUG_SUSPEND) - print_active_locks(WAKE_LOCK_SUSPEND); -#ifdef CONFIG_WAKELOCK_STAT - update_sleep_wait_stats_locked(0); -#endif - } - } - spin_unlock_irqrestore(&list_lock, irqflags); -} -EXPORT_SYMBOL(wake_unlock); - -int wake_lock_active(struct wake_lock *lock) -{ - return !!(lock->flags & WAKE_LOCK_ACTIVE); -} -EXPORT_SYMBOL(wake_lock_active); - -static int wakelock_stats_open(struct inode *inode, struct file *file) -{ - return single_open(file, wakelock_stats_show, NULL); -} - -static const struct file_operations wakelock_stats_fops = { - .owner = THIS_MODULE, - .open = wakelock_stats_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int __init wakelocks_init(void) -{ - int ret; - int i; - - for (i = 0; i < ARRAY_SIZE(active_wake_locks); i++) - INIT_LIST_HEAD(&active_wake_locks[i]); - -#ifdef CONFIG_WAKELOCK_STAT - wake_lock_init(&deleted_wake_locks, WAKE_LOCK_SUSPEND, - "deleted_wake_locks"); -#endif - wake_lock_init(&main_wake_lock, WAKE_LOCK_SUSPEND, "main"); - wake_lock(&main_wake_lock); - wake_lock_init(&unknown_wakeup, WAKE_LOCK_SUSPEND, "unknown_wakeups"); - wake_lock_init(&suspend_backoff_lock, WAKE_LOCK_SUSPEND, - "suspend_backoff"); - - ret = platform_device_register(&power_device); - if (ret) { - pr_err("wakelocks_init: platform_device_register failed\n"); - goto err_platform_device_register; - } - ret = platform_driver_register(&power_driver); - if (ret) { - pr_err("wakelocks_init: platform_driver_register failed\n"); - goto err_platform_driver_register; - } - - INIT_COMPLETION(suspend_sys_sync_comp); - suspend_sys_sync_work_queue = - create_singlethread_workqueue("suspend_sys_sync"); - if (suspend_sys_sync_work_queue == NULL) { - ret = -ENOMEM; - goto err_suspend_sys_sync_work_queue; - } - - suspend_work_queue = create_singlethread_workqueue("suspend"); - if (suspend_work_queue == NULL) { - ret = -ENOMEM; - goto err_suspend_work_queue; - } - -#ifdef CONFIG_WAKELOCK_STAT - proc_create("wakelocks", S_IRUGO, NULL, &wakelock_stats_fops); -#endif - - return 0; - -err_suspend_work_queue: -err_suspend_sys_sync_work_queue: - platform_driver_unregister(&power_driver); -err_platform_driver_register: - platform_device_unregister(&power_device); -err_platform_device_register: - wake_lock_destroy(&suspend_backoff_lock); - wake_lock_destroy(&unknown_wakeup); - wake_lock_destroy(&main_wake_lock); -#ifdef CONFIG_WAKELOCK_STAT - wake_lock_destroy(&deleted_wake_locks); -#endif - return ret; -} - -static void __exit wakelocks_exit(void) -{ -#ifdef CONFIG_WAKELOCK_STAT - remove_proc_entry("wakelocks", NULL); -#endif - destroy_workqueue(suspend_work_queue); - destroy_workqueue(suspend_sys_sync_work_queue); - platform_driver_unregister(&power_driver); - platform_device_unregister(&power_device); - wake_lock_destroy(&suspend_backoff_lock); - wake_lock_destroy(&unknown_wakeup); - wake_lock_destroy(&main_wake_lock); -#ifdef CONFIG_WAKELOCK_STAT - wake_lock_destroy(&deleted_wake_locks); -#endif -} - -core_initcall(wakelocks_init); -module_exit(wakelocks_exit);