blob: a28a8db41468f3e38ecd4a21466069e0401ca556 [file] [log] [blame]
/* 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/slab.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;
}
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;
}