Blame view

kernel/power/wakelock.c 5.46 KB
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
1
2
3
4
5
6
7
8
9
10
  /*
   * kernel/power/wakelock.c
   *
   * User space wakeup sources support.
   *
   * Copyright (C) 2012 Rafael J. Wysocki <rjw@sisk.pl>
   *
   * This code is based on the analogous interface allowing user space to
   * manipulate wakelocks on Android.
   */
11388c87d   Rafael J. Wysocki   PM / Sleep: Requi...
11
  #include <linux/capability.h>
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
12
13
14
15
16
17
18
  #include <linux/ctype.h>
  #include <linux/device.h>
  #include <linux/err.h>
  #include <linux/hrtimer.h>
  #include <linux/list.h>
  #include <linux/rbtree.h>
  #include <linux/slab.h>
6c5be2916   Rashika Kheria   PM / wakeup: Incl...
19
  #include "power.h"
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
20
21
22
23
24
25
  static DEFINE_MUTEX(wakelocks_lock);
  
  struct wakelock {
  	char			*name;
  	struct rb_node		node;
  	struct wakeup_source	ws;
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
26
  #ifdef CONFIG_PM_WAKELOCKS_GC
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
27
  	struct list_head	lru;
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
28
  #endif
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
29
30
31
  };
  
  static struct rb_root wakelocks_tree = RB_ROOT;
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
  
  ssize_t pm_show_wakelocks(char *buf, bool show_active)
  {
  	struct rb_node *node;
  	struct wakelock *wl;
  	char *str = buf;
  	char *end = buf + PAGE_SIZE;
  
  	mutex_lock(&wakelocks_lock);
  
  	for (node = rb_first(&wakelocks_tree); node; node = rb_next(node)) {
  		wl = rb_entry(node, struct wakelock, node);
  		if (wl->ws.active == show_active)
  			str += scnprintf(str, end - str, "%s ", wl->name);
  	}
  	if (str > buf)
  		str--;
  
  	str += scnprintf(str, end - str, "
  ");
  
  	mutex_unlock(&wakelocks_lock);
  	return (str - buf);
  }
c73893e2c   Rafael J. Wysocki   PM / Sleep: Make ...
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
  #if CONFIG_PM_WAKELOCKS_LIMIT > 0
  static unsigned int number_of_wakelocks;
  
  static inline bool wakelocks_limit_exceeded(void)
  {
  	return number_of_wakelocks > CONFIG_PM_WAKELOCKS_LIMIT;
  }
  
  static inline void increment_wakelocks_number(void)
  {
  	number_of_wakelocks++;
  }
  
  static inline void decrement_wakelocks_number(void)
  {
  	number_of_wakelocks--;
  }
  #else /* CONFIG_PM_WAKELOCKS_LIMIT = 0 */
  static inline bool wakelocks_limit_exceeded(void) { return false; }
  static inline void increment_wakelocks_number(void) {}
  static inline void decrement_wakelocks_number(void) {}
  #endif /* CONFIG_PM_WAKELOCKS_LIMIT */
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
  #ifdef CONFIG_PM_WAKELOCKS_GC
  #define WL_GC_COUNT_MAX	100
  #define WL_GC_TIME_SEC	300
  
  static LIST_HEAD(wakelocks_lru_list);
  static unsigned int wakelocks_gc_count;
  
  static inline void wakelocks_lru_add(struct wakelock *wl)
  {
  	list_add(&wl->lru, &wakelocks_lru_list);
  }
  
  static inline void wakelocks_lru_most_recent(struct wakelock *wl)
  {
  	list_move(&wl->lru, &wakelocks_lru_list);
  }
  
  static void wakelocks_gc(void)
  {
  	struct wakelock *wl, *aux;
  	ktime_t now;
  
  	if (++wakelocks_gc_count <= WL_GC_COUNT_MAX)
  		return;
  
  	now = ktime_get();
  	list_for_each_entry_safe_reverse(wl, aux, &wakelocks_lru_list, lru) {
  		u64 idle_time_ns;
  		bool active;
  
  		spin_lock_irq(&wl->ws.lock);
  		idle_time_ns = ktime_to_ns(ktime_sub(now, wl->ws.last_time));
  		active = wl->ws.active;
  		spin_unlock_irq(&wl->ws.lock);
  
  		if (idle_time_ns < ((u64)WL_GC_TIME_SEC * NSEC_PER_SEC))
  			break;
  
  		if (!active) {
  			wakeup_source_remove(&wl->ws);
  			rb_erase(&wl->node, &wakelocks_tree);
  			list_del(&wl->lru);
  			kfree(wl->name);
  			kfree(wl);
  			decrement_wakelocks_number();
  		}
  	}
  	wakelocks_gc_count = 0;
  }
  #else /* !CONFIG_PM_WAKELOCKS_GC */
  static inline void wakelocks_lru_add(struct wakelock *wl) {}
  static inline void wakelocks_lru_most_recent(struct wakelock *wl) {}
  static inline void wakelocks_gc(void) {}
  #endif /* !CONFIG_PM_WAKELOCKS_GC */
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
  static struct wakelock *wakelock_lookup_add(const char *name, size_t len,
  					    bool add_if_not_found)
  {
  	struct rb_node **node = &wakelocks_tree.rb_node;
  	struct rb_node *parent = *node;
  	struct wakelock *wl;
  
  	while (*node) {
  		int diff;
  
  		parent = *node;
  		wl = rb_entry(*node, struct wakelock, node);
  		diff = strncmp(name, wl->name, len);
  		if (diff == 0) {
  			if (wl->name[len])
  				diff = -1;
  			else
  				return wl;
  		}
  		if (diff < 0)
  			node = &(*node)->rb_left;
  		else
  			node = &(*node)->rb_right;
  	}
  	if (!add_if_not_found)
  		return ERR_PTR(-EINVAL);
c73893e2c   Rafael J. Wysocki   PM / Sleep: Make ...
158
  	if (wakelocks_limit_exceeded())
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
  		return ERR_PTR(-ENOSPC);
  
  	/* Not found, we have to add a new one. */
  	wl = kzalloc(sizeof(*wl), GFP_KERNEL);
  	if (!wl)
  		return ERR_PTR(-ENOMEM);
  
  	wl->name = kstrndup(name, len, GFP_KERNEL);
  	if (!wl->name) {
  		kfree(wl);
  		return ERR_PTR(-ENOMEM);
  	}
  	wl->ws.name = wl->name;
  	wakeup_source_add(&wl->ws);
  	rb_link_node(&wl->node, parent, node);
  	rb_insert_color(&wl->node, &wakelocks_tree);
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
175
  	wakelocks_lru_add(wl);
c73893e2c   Rafael J. Wysocki   PM / Sleep: Make ...
176
  	increment_wakelocks_number();
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
177
178
179
180
181
182
183
184
185
186
  	return wl;
  }
  
  int pm_wake_lock(const char *buf)
  {
  	const char *str = buf;
  	struct wakelock *wl;
  	u64 timeout_ns = 0;
  	size_t len;
  	int ret = 0;
11388c87d   Rafael J. Wysocki   PM / Sleep: Requi...
187
188
  	if (!capable(CAP_BLOCK_SUSPEND))
  		return -EPERM;
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
  	while (*str && !isspace(*str))
  		str++;
  
  	len = str - buf;
  	if (!len)
  		return -EINVAL;
  
  	if (*str && *str != '
  ') {
  		/* Find out if there's a valid timeout string appended. */
  		ret = kstrtou64(skip_spaces(str), 10, &timeout_ns);
  		if (ret)
  			return -EINVAL;
  	}
  
  	mutex_lock(&wakelocks_lock);
  
  	wl = wakelock_lookup_add(buf, len, true);
  	if (IS_ERR(wl)) {
  		ret = PTR_ERR(wl);
  		goto out;
  	}
  	if (timeout_ns) {
  		u64 timeout_ms = timeout_ns + NSEC_PER_MSEC - 1;
  
  		do_div(timeout_ms, NSEC_PER_MSEC);
  		__pm_wakeup_event(&wl->ws, timeout_ms);
  	} else {
  		__pm_stay_awake(&wl->ws);
  	}
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
219
  	wakelocks_lru_most_recent(wl);
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
220
221
222
223
224
  
   out:
  	mutex_unlock(&wakelocks_lock);
  	return ret;
  }
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
225
226
227
228
229
  int pm_wake_unlock(const char *buf)
  {
  	struct wakelock *wl;
  	size_t len;
  	int ret = 0;
11388c87d   Rafael J. Wysocki   PM / Sleep: Requi...
230
231
  	if (!capable(CAP_BLOCK_SUSPEND))
  		return -EPERM;
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
  	len = strlen(buf);
  	if (!len)
  		return -EINVAL;
  
  	if (buf[len-1] == '
  ')
  		len--;
  
  	if (!len)
  		return -EINVAL;
  
  	mutex_lock(&wakelocks_lock);
  
  	wl = wakelock_lookup_add(buf, len, false);
  	if (IS_ERR(wl)) {
  		ret = PTR_ERR(wl);
  		goto out;
  	}
  	__pm_relax(&wl->ws);
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
251
252
253
  
  	wakelocks_lru_most_recent(wl);
  	wakelocks_gc();
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
254
255
256
257
258
  
   out:
  	mutex_unlock(&wakelocks_lock);
  	return ret;
  }