diff options
Diffstat (limited to 'kernel/power')
| -rw-r--r-- | kernel/power/consoleearlysuspend.c | 112 | ||||
| -rw-r--r-- | kernel/power/earlysuspend.c | 183 | ||||
| -rw-r--r-- | kernel/power/fbearlysuspend.c | 153 | ||||
| -rw-r--r-- | kernel/power/userwakelock.c | 236 | ||||
| -rw-r--r-- | kernel/power/wakelock.c | 637 | 
5 files changed, 1321 insertions, 0 deletions
| diff --git a/kernel/power/consoleearlysuspend.c b/kernel/power/consoleearlysuspend.c new file mode 100644 index 000000000000..b6c47d88584a --- /dev/null +++ b/kernel/power/consoleearlysuspend.c @@ -0,0 +1,112 @@ +/* kernel/power/consoleearlysuspend.c + * + * Copyright (C) 2005-2008 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/console.h> +#include <linux/earlysuspend.h> +#include <linux/kbd_kern.h> +#include <linux/module.h> +#include <linux/vt_kern.h> +#include <linux/wait.h> + +#define EARLY_SUSPEND_CONSOLE	(MAX_NR_CONSOLES-1) + +static int orig_fgconsole; +static void console_early_suspend(struct early_suspend *h) +{ +	acquire_console_sem(); +	orig_fgconsole = fg_console; +	if (vc_allocate(EARLY_SUSPEND_CONSOLE)) +		goto err; +	if (set_console(EARLY_SUSPEND_CONSOLE)) +		goto err; +	release_console_sem(); + +	if (vt_waitactive(EARLY_SUSPEND_CONSOLE)) +		pr_warning("console_early_suspend: Can't switch VCs.\n"); +	return; +      err: +	pr_warning("console_early_suspend: Can't set console\n"); +	release_console_sem(); +} + +static void console_late_resume(struct early_suspend *h) +{ +	int ret; +	acquire_console_sem(); +	ret = set_console(orig_fgconsole); +	release_console_sem(); +	if (ret) { +		pr_warning("console_late_resume: Can't set console.\n"); +		return; +	} + +	if (vt_waitactive(orig_fgconsole)) +		pr_warning("console_late_resume: Can't switch VCs.\n"); +} + +static struct early_suspend console_early_suspend_desc = { +	.level = EARLY_SUSPEND_LEVEL_STOP_DRAWING, +	.suspend = console_early_suspend, +	.resume = console_late_resume, +}; + +static ssize_t console_switch_show(struct kobject *kobj, +				   struct kobj_attribute *attr, char *buf) +{ +	return 0; +} + +#define power_ro_attr(_name) \ +	static struct kobj_attribute _name##_attr = {	\ +			.attr	= {				\ +						.name = __stringify(_name),	\ +						.mode = 0444,			\ +					},					\ +			.show	= _name##_show,			\ +			.store	= NULL,		\ +	} + +power_ro_attr(console_switch); + +static struct attribute *g[] = { +	&console_switch_attr.attr, +	NULL, +}; + +static struct attribute_group attr_group = { +	.attrs = g, +}; + +static int __init console_early_suspend_init(void) +{ +	int ret; + +	ret = sysfs_create_group(power_kobj, &attr_group); +	if (ret) { +		pr_err("android_power_init: sysfs_create_group failed\n"); +		return ret; +	} +	register_early_suspend(&console_early_suspend_desc); +	return 0; +} + +static void __exit console_early_suspend_exit(void) +{ +	unregister_early_suspend(&console_early_suspend_desc); +	sysfs_remove_group(power_kobj, &attr_group); +} + +module_init(console_early_suspend_init); +module_exit(console_early_suspend_exit); diff --git a/kernel/power/earlysuspend.c b/kernel/power/earlysuspend.c new file mode 100644 index 000000000000..88af6ebb2c22 --- /dev/null +++ b/kernel/power/earlysuspend.c @@ -0,0 +1,183 @@ +/* kernel/power/earlysuspend.c + * + * Copyright (C) 2005-2008 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/earlysuspend.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/rtc.h> +#include <linux/syscalls.h> /* sys_sync */ +#include <linux/wakelock.h> +#include <linux/workqueue.h> + +#include "power.h" + +enum { +	DEBUG_USER_STATE = 1U << 0, +	DEBUG_SUSPEND = 1U << 2, +}; +static int debug_mask = DEBUG_USER_STATE; +module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); + +static DEFINE_MUTEX(early_suspend_lock); +static LIST_HEAD(early_suspend_handlers); +static void early_suspend(struct work_struct *work); +static void late_resume(struct work_struct *work); +static DECLARE_WORK(early_suspend_work, early_suspend); +static DECLARE_WORK(late_resume_work, late_resume); +static DEFINE_SPINLOCK(state_lock); +enum { +	SUSPEND_REQUESTED = 0x1, +	SUSPENDED = 0x2, +	SUSPEND_REQUESTED_AND_SUSPENDED = SUSPEND_REQUESTED | SUSPENDED, +}; +static int state; + +void register_early_suspend(struct early_suspend *handler) +{ +	struct list_head *pos; + +	mutex_lock(&early_suspend_lock); +	list_for_each(pos, &early_suspend_handlers) { +		struct early_suspend *e; +		e = list_entry(pos, struct early_suspend, link); +		if (e->level > handler->level) +			break; +	} +	list_add_tail(&handler->link, pos); +	if ((state & SUSPENDED) && handler->suspend) +		handler->suspend(handler); +	mutex_unlock(&early_suspend_lock); +} +EXPORT_SYMBOL(register_early_suspend); + +void unregister_early_suspend(struct early_suspend *handler) +{ +	mutex_lock(&early_suspend_lock); +	list_del(&handler->link); +	mutex_unlock(&early_suspend_lock); +} +EXPORT_SYMBOL(unregister_early_suspend); + +static void early_suspend(struct work_struct *work) +{ +	struct early_suspend *pos; +	unsigned long irqflags; +	int abort = 0; + +	mutex_lock(&early_suspend_lock); +	spin_lock_irqsave(&state_lock, irqflags); +	if (state == SUSPEND_REQUESTED) +		state |= SUSPENDED; +	else +		abort = 1; +	spin_unlock_irqrestore(&state_lock, irqflags); + +	if (abort) { +		if (debug_mask & DEBUG_SUSPEND) +			pr_info("early_suspend: abort, state %d\n", state); +		mutex_unlock(&early_suspend_lock); +		goto abort; +	} + +	if (debug_mask & DEBUG_SUSPEND) +		pr_info("early_suspend: call handlers\n"); +	list_for_each_entry(pos, &early_suspend_handlers, link) { +		if (pos->suspend != NULL) +			pos->suspend(pos); +	} + +	wake_lock_suspend(); +	mutex_unlock(&early_suspend_lock); + +	if (debug_mask & DEBUG_SUSPEND) +		pr_info("early_suspend: sync\n"); + +	sys_sync(); +abort: +	spin_lock_irqsave(&state_lock, irqflags); +	if (state == SUSPEND_REQUESTED_AND_SUSPENDED) +		wake_unlock(&main_wake_lock); +	spin_unlock_irqrestore(&state_lock, irqflags); +} + +static void late_resume(struct work_struct *work) +{ +	struct early_suspend *pos; +	unsigned long irqflags; +	int abort = 0; + +	mutex_lock(&early_suspend_lock); +	spin_lock_irqsave(&state_lock, irqflags); +	if (state == SUSPENDED) +		state &= ~SUSPENDED; +	else +		abort = 1; +	spin_unlock_irqrestore(&state_lock, irqflags); + +	if (abort) { +		if (debug_mask & DEBUG_SUSPEND) +			pr_info("late_resume: abort, state %d\n", state); +		goto abort; +	} +	if (debug_mask & DEBUG_SUSPEND) +		pr_info("late_resume: call handlers\n"); + +	wake_lock_resume(); + +	list_for_each_entry_reverse(pos, &early_suspend_handlers, link) +		if (pos->resume != NULL) +			pos->resume(pos); +	if (debug_mask & DEBUG_SUSPEND) +		pr_info("late_resume: done\n"); +abort: +	mutex_unlock(&early_suspend_lock); +} + +void request_suspend_state(suspend_state_t new_state) +{ +	unsigned long irqflags; +	int old_sleep; + +	spin_lock_irqsave(&state_lock, irqflags); +	old_sleep = state & SUSPEND_REQUESTED; +	if (debug_mask & DEBUG_USER_STATE) { +		struct timespec ts; +		struct rtc_time tm; +		getnstimeofday(&ts); +		rtc_time_to_tm(ts.tv_sec, &tm); +		pr_info("request_suspend_state: %s (%d->%d) at %lld " +			"(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n", +			new_state != PM_SUSPEND_ON ? "sleep" : "wakeup", +			requested_suspend_state, new_state, +			ktime_to_ns(ktime_get()), +			tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, +			tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec); +	} +	if (!old_sleep && new_state != PM_SUSPEND_ON) { +		state |= SUSPEND_REQUESTED; +		queue_work(suspend_work_queue, &early_suspend_work); +	} else if (old_sleep && new_state == PM_SUSPEND_ON) { +		state &= ~SUSPEND_REQUESTED; +		wake_lock(&main_wake_lock); +		queue_work(suspend_work_queue, &late_resume_work); +	} +	requested_suspend_state = new_state; +	spin_unlock_irqrestore(&state_lock, irqflags); +} + +suspend_state_t get_suspend_state(void) +{ +	return requested_suspend_state; +} diff --git a/kernel/power/fbearlysuspend.c b/kernel/power/fbearlysuspend.c new file mode 100644 index 000000000000..15137650149c --- /dev/null +++ b/kernel/power/fbearlysuspend.c @@ -0,0 +1,153 @@ +/* kernel/power/fbearlysuspend.c + * + * Copyright (C) 2005-2008 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/earlysuspend.h> +#include <linux/module.h> +#include <linux/wait.h> + +#include "power.h" + +static wait_queue_head_t fb_state_wq; +static DEFINE_SPINLOCK(fb_state_lock); +static enum { +	FB_STATE_STOPPED_DRAWING, +	FB_STATE_REQUEST_STOP_DRAWING, +	FB_STATE_DRAWING_OK, +} fb_state; + +/* tell userspace to stop drawing, wait for it to stop */ +static void stop_drawing_early_suspend(struct early_suspend *h) +{ +	int ret; +	unsigned long irq_flags; + +	spin_lock_irqsave(&fb_state_lock, irq_flags); +	fb_state = FB_STATE_REQUEST_STOP_DRAWING; +	spin_unlock_irqrestore(&fb_state_lock, irq_flags); + +	wake_up_all(&fb_state_wq); +	ret = wait_event_timeout(fb_state_wq, +				 fb_state == FB_STATE_STOPPED_DRAWING, +				 HZ); +	if (unlikely(fb_state != FB_STATE_STOPPED_DRAWING)) +		pr_warning("stop_drawing_early_suspend: timeout waiting for " +			   "userspace to stop drawing\n"); +} + +/* tell userspace to start drawing */ +static void start_drawing_late_resume(struct early_suspend *h) +{ +	unsigned long irq_flags; + +	spin_lock_irqsave(&fb_state_lock, irq_flags); +	fb_state = FB_STATE_DRAWING_OK; +	spin_unlock_irqrestore(&fb_state_lock, irq_flags); +	wake_up(&fb_state_wq); +} + +static struct early_suspend stop_drawing_early_suspend_desc = { +	.level = EARLY_SUSPEND_LEVEL_STOP_DRAWING, +	.suspend = stop_drawing_early_suspend, +	.resume = start_drawing_late_resume, +}; + +static ssize_t wait_for_fb_sleep_show(struct kobject *kobj, +				      struct kobj_attribute *attr, char *buf) +{ +	char *s = buf; +	int ret; + +	ret = wait_event_interruptible(fb_state_wq, +				       fb_state != FB_STATE_DRAWING_OK); +	if (ret && fb_state == FB_STATE_DRAWING_OK) +		return ret; +	else +		s += sprintf(buf, "sleeping"); +	return s - buf; +} + +static ssize_t wait_for_fb_wake_show(struct kobject *kobj, +				     struct kobj_attribute *attr, char *buf) +{ +	char *s = buf; +	int ret; +	unsigned long irq_flags; + +	spin_lock_irqsave(&fb_state_lock, irq_flags); +	if (fb_state == FB_STATE_REQUEST_STOP_DRAWING) { +		fb_state = FB_STATE_STOPPED_DRAWING; +		wake_up(&fb_state_wq); +	} +	spin_unlock_irqrestore(&fb_state_lock, irq_flags); + +	ret = wait_event_interruptible(fb_state_wq, +				       fb_state == FB_STATE_DRAWING_OK); +	if (ret && fb_state != FB_STATE_DRAWING_OK) +		return ret; +	else +		s += sprintf(buf, "awake"); + +	return s - buf; +} + +#define power_ro_attr(_name) \ +static struct kobj_attribute _name##_attr = {	\ +	.attr	= {				\ +		.name = __stringify(_name),	\ +		.mode = 0444,			\ +	},					\ +	.show	= _name##_show,			\ +	.store	= NULL,		\ +} + +power_ro_attr(wait_for_fb_sleep); +power_ro_attr(wait_for_fb_wake); + +static struct attribute *g[] = { +	&wait_for_fb_sleep_attr.attr, +	&wait_for_fb_wake_attr.attr, +	NULL, +}; + +static struct attribute_group attr_group = { +	.attrs = g, +}; + +static int __init android_power_init(void) +{ +	int ret; + +	init_waitqueue_head(&fb_state_wq); +	fb_state = FB_STATE_DRAWING_OK; + +	ret = sysfs_create_group(power_kobj, &attr_group); +	if (ret) { +		pr_err("android_power_init: sysfs_create_group failed\n"); +		return ret; +	} + +	register_early_suspend(&stop_drawing_early_suspend_desc); +	return 0; +} + +static void  __exit android_power_exit(void) +{ +	unregister_early_suspend(&stop_drawing_early_suspend_desc); +	sysfs_remove_group(power_kobj, &attr_group); +} + +module_init(android_power_init); +module_exit(android_power_exit); + diff --git a/kernel/power/userwakelock.c b/kernel/power/userwakelock.c new file mode 100644 index 000000000000..60a5607295e4 --- /dev/null +++ b/kernel/power/userwakelock.c @@ -0,0 +1,236 @@ +/* kernel/power/userwakelock.c + * + * Copyright (C) 2005-2008 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/ctype.h> +#include <linux/module.h> +#include <linux/wakelock.h> +#include <linux/earlysuspend.h> + +#include "power.h" + +enum { +	DEBUG_FAILURE	= BIT(0), +	DEBUG_ERROR	= BIT(1), +	DEBUG_NEW	= BIT(2), +	DEBUG_ACCESS	= BIT(3), +	DEBUG_LOOKUP	= BIT(4), +}; +static int debug_mask = DEBUG_FAILURE; +module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); + +static DEFINE_MUTEX(tree_lock); + +struct user_wake_lock { +	struct rb_node		node; +	struct wake_lock	wake_lock; +	char			name[0]; +}; +struct rb_root user_wake_locks; + +static struct user_wake_lock *lookup_wake_lock_name( +	const char *buf, int allocate, long *timeoutptr) +{ +	struct rb_node **p = &user_wake_locks.rb_node; +	struct rb_node *parent = NULL; +	struct user_wake_lock *l; +	int diff; +	u64 timeout; +	int name_len; +	const char *arg; + +	/* Find length of lock name and start of optional timeout string */ +	arg = buf; +	while (*arg && !isspace(*arg)) +		arg++; +	name_len = arg - buf; +	if (!name_len) +		goto bad_arg; +	while (isspace(*arg)) +		arg++; + +	/* Process timeout string */ +	if (timeoutptr && *arg) { +		timeout = simple_strtoull(arg, (char **)&arg, 0); +		while (isspace(*arg)) +			arg++; +		if (*arg) +			goto bad_arg; +		/* convert timeout from nanoseconds to jiffies > 0 */ +		timeout += (NSEC_PER_SEC / HZ) - 1; +		do_div(timeout, (NSEC_PER_SEC / HZ)); +		if (timeout <= 0) +			timeout = 1; +		*timeoutptr = timeout; +	} else if (*arg) +		goto bad_arg; +	else if (timeoutptr) +		*timeoutptr = 0; + +	/* Lookup wake lock in rbtree */ +	while (*p) { +		parent = *p; +		l = rb_entry(parent, struct user_wake_lock, node); +		diff = strncmp(buf, l->name, name_len); +		if (!diff && l->name[name_len]) +			diff = -1; +		if (debug_mask & DEBUG_ERROR) +			pr_info("lookup_wake_lock_name: compare %.*s %s %d\n", +				name_len, buf, l->name, diff); + +		if (diff < 0) +			p = &(*p)->rb_left; +		else if (diff > 0) +			p = &(*p)->rb_right; +		else +			return l; +	} + +	/* Allocate and add new wakelock to rbtree */ +	if (!allocate) { +		if (debug_mask & DEBUG_ERROR) +			pr_info("lookup_wake_lock_name: %.*s not found\n", +				name_len, buf); +		return ERR_PTR(-EINVAL); +	} +	l = kzalloc(sizeof(*l) + name_len + 1, GFP_KERNEL); +	if (l == NULL) { +		if (debug_mask & DEBUG_FAILURE) +			pr_err("lookup_wake_lock_name: failed to allocate " +				"memory for %.*s\n", name_len, buf); +		return ERR_PTR(-ENOMEM); +	} +	memcpy(l->name, buf, name_len); +	if (debug_mask & DEBUG_NEW) +		pr_info("lookup_wake_lock_name: new wake lock %s\n", l->name); +	wake_lock_init(&l->wake_lock, WAKE_LOCK_SUSPEND, l->name); +	rb_link_node(&l->node, parent, p); +	rb_insert_color(&l->node, &user_wake_locks); +	return l; + +bad_arg: +	if (debug_mask & DEBUG_ERROR) +		pr_info("lookup_wake_lock_name: wake lock, %.*s, bad arg, %s\n", +			name_len, buf, arg); +	return ERR_PTR(-EINVAL); +} + +ssize_t wake_lock_show( +	struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ +	char *s = buf; +	char *end = buf + PAGE_SIZE; +	struct rb_node *n; +	struct user_wake_lock *l; + +	mutex_lock(&tree_lock); + +	for (n = rb_first(&user_wake_locks); n != NULL; n = rb_next(n)) { +		l = rb_entry(n, struct user_wake_lock, node); +		if (wake_lock_active(&l->wake_lock)) +			s += scnprintf(s, end - s, "%s ", l->name); +	} +	s += scnprintf(s, end - s, "\n"); + +	mutex_unlock(&tree_lock); +	return (s - buf); +} + +ssize_t wake_lock_store( +	struct kobject *kobj, struct kobj_attribute *attr, +	const char *buf, size_t n) +{ +	long timeout; +	struct user_wake_lock *l; + +	mutex_lock(&tree_lock); +	l = lookup_wake_lock_name(buf, 1, &timeout); +	if (IS_ERR(l)) { +		n = PTR_ERR(l); +		goto bad_name; +	} + +	if (debug_mask & DEBUG_ACCESS) +		pr_info("wake_lock_store: %s, timeout %ld\n", l->name, timeout); + +	if (timeout) +		wake_lock_timeout(&l->wake_lock, timeout); +	else +		wake_lock(&l->wake_lock); +bad_name: +	mutex_unlock(&tree_lock); +	return n; +} + +int register_lock_handle(const char *lock_name, struct early_suspend *handler) +{ +	struct user_wake_lock *l; + +	mutex_lock(&tree_lock); +	l = lookup_wake_lock_name(lock_name, 1, NULL); +	if (IS_ERR(l)) { +		mutex_unlock(&tree_lock); +		return -1; +	} + +	list_add(&handler->link, &(l->wake_lock.wake_lock_suspend)); +	mutex_unlock(&tree_lock); + +	return 0; +} +EXPORT_SYMBOL(register_lock_handle); + +ssize_t wake_unlock_show( +	struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ +	char *s = buf; +	char *end = buf + PAGE_SIZE; +	struct rb_node *n; +	struct user_wake_lock *l; + +	mutex_lock(&tree_lock); + +	for (n = rb_first(&user_wake_locks); n != NULL; n = rb_next(n)) { +		l = rb_entry(n, struct user_wake_lock, node); +		if (!wake_lock_active(&l->wake_lock)) +			s += scnprintf(s, end - s, "%s ", l->name); +	} +	s += scnprintf(s, end - s, "\n"); + +	mutex_unlock(&tree_lock); +	return (s - buf); +} + +ssize_t wake_unlock_store( +	struct kobject *kobj, struct kobj_attribute *attr, +	const char *buf, size_t n) +{ +	struct user_wake_lock *l; + +	mutex_lock(&tree_lock); +	l = lookup_wake_lock_name(buf, 0, NULL); +	if (IS_ERR(l)) { +		n = PTR_ERR(l); +		goto not_found; +	} + +	if (debug_mask & DEBUG_ACCESS) +		pr_info("wake_unlock_store: %s\n", l->name); + +	wake_unlock(&l->wake_lock); +not_found: +	mutex_unlock(&tree_lock); +	return n; +} + diff --git a/kernel/power/wakelock.c b/kernel/power/wakelock.c new file mode 100644 index 000000000000..22a30439945e --- /dev/null +++ b/kernel/power/wakelock.c @@ -0,0 +1,637 @@ +/* kernel/power/wakelock.c + * + * Copyright (C) 2005-2008 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/platform_device.h> +#include <linux/rtc.h> +#include <linux/suspend.h> +#include <linux/syscalls.h> /* sys_sync */ +#include <linux/wakelock.h> +#include <linux/earlysuspend.h> +#ifdef CONFIG_WAKELOCK_STAT +#include <linux/proc_fs.h> +#endif +#include "power.h" + +enum { +	DEBUG_EXIT_SUSPEND = 1U << 0, +	DEBUG_WAKEUP = 1U << 1, +	DEBUG_SUSPEND = 1U << 2, +	DEBUG_EXPIRE = 1U << 3, +	DEBUG_WAKE_LOCK = 1U << 4, +}; +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) + +#define TOO_MAY_LOCKS_WARNING		"\n\ntoo many wakelocks!!!\n" + +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; +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; + +#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) +{ +	struct timespec ts; +	struct timespec kt; +	struct timespec tomono; +	struct timespec delta; +	unsigned long seq; +	long timeout; + +	if (!(lock->flags & WAKE_LOCK_AUTO_EXPIRE)) +		return 0; +	do { +		seq = read_seqbegin(&xtime_lock); +		timeout = lock->expires - jiffies; +		if (timeout > 0) +			return 0; +		kt = current_kernel_time(); +		tomono = wall_to_monotonic; +	} while (read_seqretry(&xtime_lock, seq)); +	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; +} + + +static int print_lock_stat(char *buf, int len, 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; +	int n; + +	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; +	} + +	n = snprintf(buf, len, +		     "\"%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)); + +	return n > len ? len : n; +} + + +static int wakelocks_read_proc(char *page, char **start, off_t off, +			       int count, int *eof, void *data) +{ +	unsigned long irqflags; +	struct wake_lock *lock; +	int len = 0; +	int type; + +	spin_lock_irqsave(&list_lock, irqflags); + +	len += snprintf(page + len, count - len, +			"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) { +		len += print_lock_stat(page + len, count - len, lock); +	} +	for (type = 0; type < WAKE_LOCK_TYPE_COUNT; type++) { +		list_for_each_entry(lock, &active_wake_locks[type], link) +			len += print_lock_stat(page + len, count - len, lock); +	} +	spin_unlock_irqrestore(&list_lock, irqflags); + +	if (len == count) +		memcpy(page + len - strlen(TOO_MAY_LOCKS_WARNING), +		       TOO_MAY_LOCKS_WARNING, +		       strlen(TOO_MAY_LOCKS_WARNING)); + +	*eof = 1; + +	return len; +} + +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); +			else +				add = elapsed; +			lock->stat.prevent_suspend_time = ktime_add( +				lock->stat.prevent_suspend_time, add); +		} +		if (done || expired) +			lock->flags &= ~WAKE_LOCK_PREVENTING_SUSPEND; +		else +			lock->flags |= WAKE_LOCK_PREVENTING_SUSPEND; +	} +	last_sleep_time_update = now; +} +#endif + +void wake_lock_suspend() +{ +	struct wake_lock *lock; +	struct early_suspend *pos; + +	pr_debug("wake lock suspend............start\n"); +	list_for_each_entry(lock, &active_wake_locks[WAKE_LOCK_SUSPEND], link) { +		list_for_each_entry(pos, &lock->wake_lock_suspend, link) { +			pr_debug("wake lock %s suspend\n", lock->name); +			if (pos->suspend != NULL) +				pos->suspend(pos); +		} +	} +} +EXPORT_SYMBOL(wake_lock_suspend); + +void wake_lock_resume() +{ +	struct wake_lock *lock; +	struct early_suspend *pos; + +	pr_debug("wake lock resume............start\n"); +	list_for_each_entry_reverse(lock, &active_wake_locks[WAKE_LOCK_SUSPEND], link) { +		list_for_each_entry_reverse(pos, &lock->wake_lock_resume, link) { +			pr_debug("wake lock %s resume\n", lock->name); +			if (pos->suspend != NULL) +				pos->resume(pos); +		} +	} +} +EXPORT_SYMBOL(wake_lock_resume); + + +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); +} + +/* Caller must acquire the list_lock spinlock */ +static void print_active_locks(int type) +{ +	struct wake_lock *lock; + +	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("wake lock %s, expired\n", lock->name); +			else +				pr_info("active wake lock %s, time left %ld\n", +					lock->name, timeout); +		} else +			pr_info("active wake lock %s\n", lock->name); +	} +} + +static long has_wake_lock_locked(int type) +{ +	struct wake_lock *lock, *n; +	long max_timeout = 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; +	} +	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); +	spin_unlock_irqrestore(&list_lock, irqflags); +	return ret; +} + +static void suspend(struct work_struct *work) +{ +	int ret; +	int entry_event_num; + +	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; +	sys_sync(); +	if (debug_mask & DEBUG_SUSPEND) +		pr_info("suspend: enter suspend\n"); +	ret = pm_suspend(requested_suspend_state); +	if (debug_mask & DEBUG_EXIT_SUSPEND) { +		struct timespec ts; +		struct rtc_time tm; +		getnstimeofday(&ts); +		rtc_time_to_tm(ts.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.tv_nsec); +	} +	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); +	} +} +static DECLARE_WORK(suspend_work, suspend); + +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 platform_device *pdev, pm_message_t state) +{ +	int ret = has_wake_lock(WAKE_LOCK_SUSPEND) ? -EAGAIN : 0; +#ifdef CONFIG_WAKELOCK_STAT +	wait_for_wakeup = 1; +#endif +	if (debug_mask & DEBUG_SUSPEND) +		pr_info("power_suspend_late return %d\n", ret); +	return ret; +} + +static struct platform_driver power_driver = { +	.driver.name = "power", +	.suspend_late = power_suspend_late, +}; +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); +	INIT_LIST_HEAD(&lock->wake_lock_suspend); +	INIT_LIST_HEAD(&lock->wake_lock_resume); +	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 __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"); + +	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; +	} + +	suspend_work_queue = create_singlethread_workqueue("suspend"); +	if (suspend_work_queue == NULL) { +		ret = -ENOMEM; +		goto err_suspend_work_queue; +	} + +#ifdef CONFIG_WAKELOCK_STAT +	create_proc_read_entry("wakelocks", S_IRUGO, NULL, +				wakelocks_read_proc, NULL); +#endif + +	return 0; + +err_suspend_work_queue: +	platform_driver_unregister(&power_driver); +err_platform_driver_register: +	platform_device_unregister(&power_device); +err_platform_device_register: +	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); +	platform_driver_unregister(&power_driver); +	platform_device_unregister(&power_device); +	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); | 
