/* 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; }