aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/power/wakelock.c
diff options
context:
space:
mode:
authorRafael J. Wysocki <rjw@sisk.pl>2012-04-29 16:53:42 -0400
committerRafael J. Wysocki <rjw@sisk.pl>2012-05-01 15:26:05 -0400
commitb86ff9820fd5df69295273b9aa68e58786ffc23f (patch)
treee8af5745652c926b9a82b3b7531dc455564efdfb /kernel/power/wakelock.c
parent55850945e872531644f31fefd217d61dd15dcab8 (diff)
PM / Sleep: Add user space interface for manipulating wakeup sources, v3
Android allows user space to manipulate wakelocks using two sysfs file located in /sys/power/, wake_lock and wake_unlock. Writing a wakelock name and optionally a timeout to the wake_lock file causes the wakelock whose name was written to be acquired (it is created before is necessary), optionally with the given timeout. Writing the name of a wakelock to wake_unlock causes that wakelock to be released. Implement an analogous interface for user space using wakeup sources. Add the /sys/power/wake_lock and /sys/power/wake_unlock files allowing user space to create, activate and deactivate wakeup sources, such that writing a name and optionally a timeout to wake_lock causes the wakeup source of that name to be activated, optionally with the given timeout. If that wakeup source doesn't exist, it will be created and then activated. Writing a name to wake_unlock causes the wakeup source of that name, if there is one, to be deactivated. Wakeup sources created with the help of wake_lock that haven't been used for more than 5 minutes are garbage collected and destroyed. Moreover, there can be only WL_NUMBER_LIMIT wakeup sources created with the help of wake_lock present at a time. The data type used to track wakeup sources created by user space is called "struct wakelock" to indicate the origins of this feature. This version of the patch includes an rbtree manipulation fix from John Stultz. Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Reviewed-by: NeilBrown <neilb@suse.de>
Diffstat (limited to 'kernel/power/wakelock.c')
-rw-r--r--kernel/power/wakelock.c215
1 files changed, 215 insertions, 0 deletions
diff --git a/kernel/power/wakelock.c b/kernel/power/wakelock.c
new file mode 100644
index 000000000000..579700665e8c
--- /dev/null
+++ b/kernel/power/wakelock.c
@@ -0,0 +1,215 @@
1/*
2 * kernel/power/wakelock.c
3 *
4 * User space wakeup sources support.
5 *
6 * Copyright (C) 2012 Rafael J. Wysocki <rjw@sisk.pl>
7 *
8 * This code is based on the analogous interface allowing user space to
9 * manipulate wakelocks on Android.
10 */
11
12#include <linux/ctype.h>
13#include <linux/device.h>
14#include <linux/err.h>
15#include <linux/hrtimer.h>
16#include <linux/list.h>
17#include <linux/rbtree.h>
18#include <linux/slab.h>
19
20#define WL_NUMBER_LIMIT 100
21#define WL_GC_COUNT_MAX 100
22#define WL_GC_TIME_SEC 300
23
24static DEFINE_MUTEX(wakelocks_lock);
25
26struct wakelock {
27 char *name;
28 struct rb_node node;
29 struct wakeup_source ws;
30 struct list_head lru;
31};
32
33static struct rb_root wakelocks_tree = RB_ROOT;
34static LIST_HEAD(wakelocks_lru_list);
35static unsigned int number_of_wakelocks;
36static unsigned int wakelocks_gc_count;
37
38ssize_t pm_show_wakelocks(char *buf, bool show_active)
39{
40 struct rb_node *node;
41 struct wakelock *wl;
42 char *str = buf;
43 char *end = buf + PAGE_SIZE;
44
45 mutex_lock(&wakelocks_lock);
46
47 for (node = rb_first(&wakelocks_tree); node; node = rb_next(node)) {
48 wl = rb_entry(node, struct wakelock, node);
49 if (wl->ws.active == show_active)
50 str += scnprintf(str, end - str, "%s ", wl->name);
51 }
52 if (str > buf)
53 str--;
54
55 str += scnprintf(str, end - str, "\n");
56
57 mutex_unlock(&wakelocks_lock);
58 return (str - buf);
59}
60
61static struct wakelock *wakelock_lookup_add(const char *name, size_t len,
62 bool add_if_not_found)
63{
64 struct rb_node **node = &wakelocks_tree.rb_node;
65 struct rb_node *parent = *node;
66 struct wakelock *wl;
67
68 while (*node) {
69 int diff;
70
71 parent = *node;
72 wl = rb_entry(*node, struct wakelock, node);
73 diff = strncmp(name, wl->name, len);
74 if (diff == 0) {
75 if (wl->name[len])
76 diff = -1;
77 else
78 return wl;
79 }
80 if (diff < 0)
81 node = &(*node)->rb_left;
82 else
83 node = &(*node)->rb_right;
84 }
85 if (!add_if_not_found)
86 return ERR_PTR(-EINVAL);
87
88 if (number_of_wakelocks > WL_NUMBER_LIMIT)
89 return ERR_PTR(-ENOSPC);
90
91 /* Not found, we have to add a new one. */
92 wl = kzalloc(sizeof(*wl), GFP_KERNEL);
93 if (!wl)
94 return ERR_PTR(-ENOMEM);
95
96 wl->name = kstrndup(name, len, GFP_KERNEL);
97 if (!wl->name) {
98 kfree(wl);
99 return ERR_PTR(-ENOMEM);
100 }
101 wl->ws.name = wl->name;
102 wakeup_source_add(&wl->ws);
103 rb_link_node(&wl->node, parent, node);
104 rb_insert_color(&wl->node, &wakelocks_tree);
105 list_add(&wl->lru, &wakelocks_lru_list);
106 number_of_wakelocks++;
107 return wl;
108}
109
110int pm_wake_lock(const char *buf)
111{
112 const char *str = buf;
113 struct wakelock *wl;
114 u64 timeout_ns = 0;
115 size_t len;
116 int ret = 0;
117
118 while (*str && !isspace(*str))
119 str++;
120
121 len = str - buf;
122 if (!len)
123 return -EINVAL;
124
125 if (*str && *str != '\n') {
126 /* Find out if there's a valid timeout string appended. */
127 ret = kstrtou64(skip_spaces(str), 10, &timeout_ns);
128 if (ret)
129 return -EINVAL;
130 }
131
132 mutex_lock(&wakelocks_lock);
133
134 wl = wakelock_lookup_add(buf, len, true);
135 if (IS_ERR(wl)) {
136 ret = PTR_ERR(wl);
137 goto out;
138 }
139 if (timeout_ns) {
140 u64 timeout_ms = timeout_ns + NSEC_PER_MSEC - 1;
141
142 do_div(timeout_ms, NSEC_PER_MSEC);
143 __pm_wakeup_event(&wl->ws, timeout_ms);
144 } else {
145 __pm_stay_awake(&wl->ws);
146 }
147
148 list_move(&wl->lru, &wakelocks_lru_list);
149
150 out:
151 mutex_unlock(&wakelocks_lock);
152 return ret;
153}
154
155static void wakelocks_gc(void)
156{
157 struct wakelock *wl, *aux;
158 ktime_t now = ktime_get();
159
160 list_for_each_entry_safe_reverse(wl, aux, &wakelocks_lru_list, lru) {
161 u64 idle_time_ns;
162 bool active;
163
164 spin_lock_irq(&wl->ws.lock);
165 idle_time_ns = ktime_to_ns(ktime_sub(now, wl->ws.last_time));
166 active = wl->ws.active;
167 spin_unlock_irq(&wl->ws.lock);
168
169 if (idle_time_ns < ((u64)WL_GC_TIME_SEC * NSEC_PER_SEC))
170 break;
171
172 if (!active) {
173 wakeup_source_remove(&wl->ws);
174 rb_erase(&wl->node, &wakelocks_tree);
175 list_del(&wl->lru);
176 kfree(wl->name);
177 kfree(wl);
178 number_of_wakelocks--;
179 }
180 }
181 wakelocks_gc_count = 0;
182}
183
184int pm_wake_unlock(const char *buf)
185{
186 struct wakelock *wl;
187 size_t len;
188 int ret = 0;
189
190 len = strlen(buf);
191 if (!len)
192 return -EINVAL;
193
194 if (buf[len-1] == '\n')
195 len--;
196
197 if (!len)
198 return -EINVAL;
199
200 mutex_lock(&wakelocks_lock);
201
202 wl = wakelock_lookup_add(buf, len, false);
203 if (IS_ERR(wl)) {
204 ret = PTR_ERR(wl);
205 goto out;
206 }
207 __pm_relax(&wl->ws);
208 list_move(&wl->lru, &wakelocks_lru_list);
209 if (++wakelocks_gc_count > WL_GC_COUNT_MAX)
210 wakelocks_gc();
211
212 out:
213 mutex_unlock(&wakelocks_lock);
214 return ret;
215}