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