Blame view

kernel/power/wakelock.c 5.89 KB
b24413180   Greg Kroah-Hartman   License cleanup: ...
1
  // SPDX-License-Identifier: GPL-2.0
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
2
3
4
5
6
7
8
9
10
11
  /*
   * 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...
12
  #include <linux/capability.h>
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
13
14
15
16
17
18
19
  #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>
6ce12a977   SungEun Kim   PM / autosleep: U...
20
  #include <linux/workqueue.h>
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
21

6c5be2916   Rashika Kheria   PM / wakeup: Incl...
22
  #include "power.h"
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
23
24
25
26
27
  static DEFINE_MUTEX(wakelocks_lock);
  
  struct wakelock {
  	char			*name;
  	struct rb_node		node;
2434aea58   Tri Vo   PM / wakeup: Use ...
28
  	struct wakeup_source	*ws;
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
29
  #ifdef CONFIG_PM_WAKELOCKS_GC
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
30
  	struct list_head	lru;
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
31
  #endif
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
32
33
34
  };
  
  static struct rb_root wakelocks_tree = RB_ROOT;
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
35
36
37
38
39
40
41
42
43
44
45
46
  
  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);
2434aea58   Tri Vo   PM / wakeup: Use ...
47
  		if (wl->ws->active == show_active)
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
48
49
50
51
52
53
54
55
56
57
58
  			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 ...
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
  #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 ...
81
82
83
  #ifdef CONFIG_PM_WAKELOCKS_GC
  #define WL_GC_COUNT_MAX	100
  #define WL_GC_TIME_SEC	300
6ce12a977   SungEun Kim   PM / autosleep: U...
84
  static void __wakelocks_gc(struct work_struct *work);
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
85
  static LIST_HEAD(wakelocks_lru_list);
6ce12a977   SungEun Kim   PM / autosleep: U...
86
  static DECLARE_WORK(wakelock_work, __wakelocks_gc);
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
87
88
89
90
91
92
93
94
95
96
97
  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);
  }
6ce12a977   SungEun Kim   PM / autosleep: U...
98
  static void __wakelocks_gc(struct work_struct *work)
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
99
100
101
  {
  	struct wakelock *wl, *aux;
  	ktime_t now;
6ce12a977   SungEun Kim   PM / autosleep: U...
102
  	mutex_lock(&wakelocks_lock);
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
103
104
105
106
107
  
  	now = ktime_get();
  	list_for_each_entry_safe_reverse(wl, aux, &wakelocks_lru_list, lru) {
  		u64 idle_time_ns;
  		bool active;
2434aea58   Tri Vo   PM / wakeup: Use ...
108
109
110
111
  		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);
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
112
113
114
115
116
  
  		if (idle_time_ns < ((u64)WL_GC_TIME_SEC * NSEC_PER_SEC))
  			break;
  
  		if (!active) {
2434aea58   Tri Vo   PM / wakeup: Use ...
117
  			wakeup_source_unregister(wl->ws);
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
118
119
120
121
122
123
124
125
  			rb_erase(&wl->node, &wakelocks_tree);
  			list_del(&wl->lru);
  			kfree(wl->name);
  			kfree(wl);
  			decrement_wakelocks_number();
  		}
  	}
  	wakelocks_gc_count = 0;
6ce12a977   SungEun Kim   PM / autosleep: U...
126
127
128
129
130
131
132
133
134
135
  
  	mutex_unlock(&wakelocks_lock);
  }
  
  static void wakelocks_gc(void)
  {
  	if (++wakelocks_gc_count <= WL_GC_COUNT_MAX)
  		return;
  
  	schedule_work(&wakelock_work);
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
136
137
138
139
140
141
  }
  #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...
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
  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 ...
168
  	if (wakelocks_limit_exceeded())
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
169
170
171
172
173
174
175
176
177
178
179
180
  		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);
  	}
2434aea58   Tri Vo   PM / wakeup: Use ...
181

c8377adfa   Tri Vo   PM / wakeup: Show...
182
  	wl->ws = wakeup_source_register(NULL, wl->name);
2434aea58   Tri Vo   PM / wakeup: Use ...
183
184
185
186
187
188
  	if (!wl->ws) {
  		kfree(wl->name);
  		kfree(wl);
  		return ERR_PTR(-ENOMEM);
  	}
  	wl->ws->last_time = ktime_get();
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
189
190
  	rb_link_node(&wl->node, parent, node);
  	rb_insert_color(&wl->node, &wakelocks_tree);
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
191
  	wakelocks_lru_add(wl);
c73893e2c   Rafael J. Wysocki   PM / Sleep: Make ...
192
  	increment_wakelocks_number();
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
193
194
195
196
197
198
199
200
201
202
  	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...
203
204
  	if (!capable(CAP_BLOCK_SUSPEND))
  		return -EPERM;
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
  	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);
2434aea58   Tri Vo   PM / wakeup: Use ...
231
  		__pm_wakeup_event(wl->ws, timeout_ms);
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
232
  	} else {
2434aea58   Tri Vo   PM / wakeup: Use ...
233
  		__pm_stay_awake(wl->ws);
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
234
  	}
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
235
  	wakelocks_lru_most_recent(wl);
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
236
237
238
239
240
  
   out:
  	mutex_unlock(&wakelocks_lock);
  	return ret;
  }
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
241
242
243
244
245
  int pm_wake_unlock(const char *buf)
  {
  	struct wakelock *wl;
  	size_t len;
  	int ret = 0;
11388c87d   Rafael J. Wysocki   PM / Sleep: Requi...
246
247
  	if (!capable(CAP_BLOCK_SUSPEND))
  		return -EPERM;
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
  	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;
  	}
2434aea58   Tri Vo   PM / wakeup: Use ...
266
  	__pm_relax(wl->ws);
4e585d25e   Rafael J. Wysocki   PM / Sleep: User ...
267
268
269
  
  	wakelocks_lru_most_recent(wl);
  	wakelocks_gc();
b86ff9820   Rafael J. Wysocki   PM / Sleep: Add u...
270
271
272
273
274
  
   out:
  	mutex_unlock(&wakelocks_lock);
  	return ret;
  }