Blame view

kernel/power/wakeup_reason.c 11.2 KB
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
1
2
3
4
5
6
  /*
   * kernel/power/wakeup_reason.c
   *
   * Logs the reasons which caused the kernel to resume from
   * the suspend mode.
   *
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
7
   * Copyright (C) 2020 Google, Inc.
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
   * 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/wakeup_reason.h>
  #include <linux/kernel.h>
  #include <linux/irq.h>
  #include <linux/interrupt.h>
  #include <linux/io.h>
  #include <linux/kobject.h>
  #include <linux/sysfs.h>
  #include <linux/init.h>
  #include <linux/spinlock.h>
  #include <linux/notifier.h>
  #include <linux/suspend.h>
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
29
  #include <linux/slab.h>
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
30

b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
31
32
33
34
35
36
37
38
39
40
41
42
  /*
   * struct wakeup_irq_node - stores data and relationships for IRQs logged as
   * either base or nested wakeup reasons during suspend/resume flow.
   * @siblings - for membership on leaf or parent IRQ lists
   * @irq      - the IRQ number
   * @irq_name - the name associated with the IRQ, or a default if none
   */
  struct wakeup_irq_node {
  	struct list_head siblings;
  	int irq;
  	const char *irq_name;
  };
8f61f09d3   claude.yen   ANDROID: power: w...
43
44
45
46
47
48
  enum wakeup_reason_flag {
  	RESUME_NONE = 0,
  	RESUME_IRQ,
  	RESUME_ABORT,
  	RESUME_ABNORMAL,
  };
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
49
50
51
52
  static DEFINE_SPINLOCK(wakeup_reason_lock);
  
  static LIST_HEAD(leaf_irqs);   /* kept in ascending IRQ sorted order */
  static LIST_HEAD(parent_irqs); /* unordered */
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
53

b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
54
55
56
57
58
59
60
  static struct kmem_cache *wakeup_irq_nodes_cache;
  
  static const char *default_irq_name = "(unnamed)";
  
  static struct kobject *kobj;
  
  static bool capture_reasons;
8f61f09d3   claude.yen   ANDROID: power: w...
61
  static int wakeup_reason;
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
62
  static char non_irq_wake_reason[MAX_SUSPEND_ABORT_LEN];
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
63
64
65
66
67
  
  static ktime_t last_monotime; /* monotonic time before last suspend */
  static ktime_t curr_monotime; /* monotonic time after last suspend */
  static ktime_t last_stime; /* monotonic boottime offset before last suspend */
  static ktime_t curr_stime; /* monotonic boottime offset after last suspend */
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
68
  static void init_node(struct wakeup_irq_node *p, int irq)
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
69
  {
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
70
  	struct irq_desc *desc;
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
71
72
73
74
75
76
77
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
  
  	INIT_LIST_HEAD(&p->siblings);
  
  	p->irq = irq;
  	desc = irq_to_desc(irq);
  	if (desc && desc->action && desc->action->name)
  		p->irq_name = desc->action->name;
  	else
  		p->irq_name = default_irq_name;
  }
  
  static struct wakeup_irq_node *create_node(int irq)
  {
  	struct wakeup_irq_node *result;
  
  	result = kmem_cache_alloc(wakeup_irq_nodes_cache, GFP_ATOMIC);
  	if (unlikely(!result))
  		pr_warn("Failed to log wakeup IRQ %d
  ", irq);
  	else
  		init_node(result, irq);
  
  	return result;
  }
  
  static void delete_list(struct list_head *head)
  {
  	struct wakeup_irq_node *n;
  
  	while (!list_empty(head)) {
  		n = list_first_entry(head, struct wakeup_irq_node, siblings);
  		list_del(&n->siblings);
  		kmem_cache_free(wakeup_irq_nodes_cache, n);
  	}
  }
  
  static bool add_sibling_node_sorted(struct list_head *head, int irq)
  {
f46fe356e   Kelly Rossmoyer   ANDROID: fix wake...
109
  	struct wakeup_irq_node *n = NULL;
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
110
111
112
113
114
115
116
117
118
119
120
  	struct list_head *predecessor = head;
  
  	if (unlikely(WARN_ON(!head)))
  		return NULL;
  
  	if (!list_empty(head))
  		list_for_each_entry(n, head, siblings) {
  			if (n->irq < irq)
  				predecessor = &n->siblings;
  			else if (n->irq == irq)
  				return true;
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
121
  			else
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
122
  				break;
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
123
  		}
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
124
125
126
127
128
  
  	n = create_node(irq);
  	if (n) {
  		list_add(&n->siblings, predecessor);
  		return true;
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
129
  	}
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
  
  	return false;
  }
  
  static struct wakeup_irq_node *find_node_in_list(struct list_head *head,
  						 int irq)
  {
  	struct wakeup_irq_node *n;
  
  	if (unlikely(WARN_ON(!head)))
  		return NULL;
  
  	list_for_each_entry(n, head, siblings)
  		if (n->irq == irq)
  			return n;
  
  	return NULL;
  }
  
  void log_irq_wakeup_reason(int irq)
  {
  	unsigned long flags;
  
  	spin_lock_irqsave(&wakeup_reason_lock, flags);
8f61f09d3   claude.yen   ANDROID: power: w...
154
155
156
157
  	if (wakeup_reason == RESUME_ABNORMAL || wakeup_reason == RESUME_ABORT) {
  		spin_unlock_irqrestore(&wakeup_reason_lock, flags);
  		return;
  	}
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
158
159
160
161
162
163
164
165
  
  	if (!capture_reasons) {
  		spin_unlock_irqrestore(&wakeup_reason_lock, flags);
  		return;
  	}
  
  	if (find_node_in_list(&parent_irqs, irq) == NULL)
  		add_sibling_node_sorted(&leaf_irqs, irq);
8f61f09d3   claude.yen   ANDROID: power: w...
166
  	wakeup_reason = RESUME_IRQ;
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
  	spin_unlock_irqrestore(&wakeup_reason_lock, flags);
  }
  
  void log_threaded_irq_wakeup_reason(int irq, int parent_irq)
  {
  	struct wakeup_irq_node *parent;
  	unsigned long flags;
  
  	/*
  	 * Intentionally unsynchronized.  Calls that come in after we have
  	 * resumed should have a fast exit path since there's no work to be
  	 * done, any any coherence issue that could cause a wrong value here is
  	 * both highly improbable - given the set/clear timing - and very low
  	 * impact (parent IRQ gets logged instead of the specific child).
  	 */
  	if (!capture_reasons)
  		return;
  
  	spin_lock_irqsave(&wakeup_reason_lock, flags);
8f61f09d3   claude.yen   ANDROID: power: w...
186
187
188
189
  	if (wakeup_reason == RESUME_ABNORMAL || wakeup_reason == RESUME_ABORT) {
  		spin_unlock_irqrestore(&wakeup_reason_lock, flags);
  		return;
  	}
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
  	if (!capture_reasons || (find_node_in_list(&leaf_irqs, irq) != NULL)) {
  		spin_unlock_irqrestore(&wakeup_reason_lock, flags);
  		return;
  	}
  
  	parent = find_node_in_list(&parent_irqs, parent_irq);
  	if (parent != NULL)
  		add_sibling_node_sorted(&leaf_irqs, irq);
  	else {
  		parent = find_node_in_list(&leaf_irqs, parent_irq);
  		if (parent != NULL) {
  			list_del_init(&parent->siblings);
  			list_add_tail(&parent->siblings, &parent_irqs);
  			add_sibling_node_sorted(&leaf_irqs, irq);
  		}
  	}
  
  	spin_unlock_irqrestore(&wakeup_reason_lock, flags);
  }
2488b4d45   claude.yen   ANDROID: power: w...
209
  EXPORT_SYMBOL_GPL(log_threaded_irq_wakeup_reason);
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
210

f46fe356e   Kelly Rossmoyer   ANDROID: fix wake...
211
212
  static void __log_abort_or_abnormal_wake(bool abort, const char *fmt,
  					 va_list args)
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
213
214
215
216
217
218
  {
  	unsigned long flags;
  
  	spin_lock_irqsave(&wakeup_reason_lock, flags);
  
  	/* Suspend abort or abnormal wake reason has already been logged. */
8f61f09d3   claude.yen   ANDROID: power: w...
219
  	if (wakeup_reason != RESUME_NONE) {
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
220
221
222
  		spin_unlock_irqrestore(&wakeup_reason_lock, flags);
  		return;
  	}
8f61f09d3   claude.yen   ANDROID: power: w...
223
224
225
226
  	if (abort)
  		wakeup_reason = RESUME_ABORT;
  	else
  		wakeup_reason = RESUME_ABNORMAL;
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
227
228
229
230
231
232
233
234
235
236
237
238
239
  	vsnprintf(non_irq_wake_reason, MAX_SUSPEND_ABORT_LEN, fmt, args);
  
  	spin_unlock_irqrestore(&wakeup_reason_lock, flags);
  }
  
  void log_suspend_abort_reason(const char *fmt, ...)
  {
  	va_list args;
  
  	va_start(args, fmt);
  	__log_abort_or_abnormal_wake(true, fmt, args);
  	va_end(args);
  }
7a626b43e   Daniel Mentz   ANDROID: power: E...
240
  EXPORT_SYMBOL_GPL(log_suspend_abort_reason);
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
241
242
243
244
245
246
247
248
249
  
  void log_abnormal_wakeup_reason(const char *fmt, ...)
  {
  	va_list args;
  
  	va_start(args, fmt);
  	__log_abort_or_abnormal_wake(false, fmt, args);
  	va_end(args);
  }
7a626b43e   Daniel Mentz   ANDROID: power: E...
250
  EXPORT_SYMBOL_GPL(log_abnormal_wakeup_reason);
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
251
252
253
254
255
256
257
258
259
  
  void clear_wakeup_reasons(void)
  {
  	unsigned long flags;
  
  	spin_lock_irqsave(&wakeup_reason_lock, flags);
  
  	delete_list(&leaf_irqs);
  	delete_list(&parent_irqs);
8f61f09d3   claude.yen   ANDROID: power: w...
260
  	wakeup_reason = RESUME_NONE;
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
261
262
263
264
265
266
267
268
269
270
271
272
273
  	capture_reasons = true;
  
  	spin_unlock_irqrestore(&wakeup_reason_lock, flags);
  }
  
  static void print_wakeup_sources(void)
  {
  	struct wakeup_irq_node *n;
  	unsigned long flags;
  
  	spin_lock_irqsave(&wakeup_reason_lock, flags);
  
  	capture_reasons = false;
8f61f09d3   claude.yen   ANDROID: power: w...
274
  	if (wakeup_reason == RESUME_ABORT) {
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
275
276
277
278
279
  		pr_info("Abort: %s
  ", non_irq_wake_reason);
  		spin_unlock_irqrestore(&wakeup_reason_lock, flags);
  		return;
  	}
8f61f09d3   claude.yen   ANDROID: power: w...
280
  	if (wakeup_reason == RESUME_IRQ && !list_empty(&leaf_irqs))
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
281
282
283
284
  		list_for_each_entry(n, &leaf_irqs, siblings)
  			pr_info("Resume caused by IRQ %d, %s
  ", n->irq,
  				n->irq_name);
8f61f09d3   claude.yen   ANDROID: power: w...
285
  	else if (wakeup_reason == RESUME_ABNORMAL)
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
  		pr_info("Resume caused by %s
  ", non_irq_wake_reason);
  	else
  		pr_info("Resume cause unknown
  ");
  
  	spin_unlock_irqrestore(&wakeup_reason_lock, flags);
  }
  
  static ssize_t last_resume_reason_show(struct kobject *kobj,
  				       struct kobj_attribute *attr, char *buf)
  {
  	ssize_t buf_offset = 0;
  	struct wakeup_irq_node *n;
  	unsigned long flags;
  
  	spin_lock_irqsave(&wakeup_reason_lock, flags);
8f61f09d3   claude.yen   ANDROID: power: w...
303
  	if (wakeup_reason == RESUME_ABORT) {
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
304
305
306
307
308
  		buf_offset = scnprintf(buf, PAGE_SIZE, "Abort: %s",
  				       non_irq_wake_reason);
  		spin_unlock_irqrestore(&wakeup_reason_lock, flags);
  		return buf_offset;
  	}
8f61f09d3   claude.yen   ANDROID: power: w...
309
  	if (wakeup_reason == RESUME_IRQ && !list_empty(&leaf_irqs))
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
310
311
312
313
314
  		list_for_each_entry(n, &leaf_irqs, siblings)
  			buf_offset += scnprintf(buf + buf_offset,
  						PAGE_SIZE - buf_offset,
  						"%d %s
  ", n->irq, n->irq_name);
8f61f09d3   claude.yen   ANDROID: power: w...
315
  	else if (wakeup_reason == RESUME_ABNORMAL)
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
316
317
318
319
  		buf_offset = scnprintf(buf, PAGE_SIZE, "-1 %s",
  				       non_irq_wake_reason);
  
  	spin_unlock_irqrestore(&wakeup_reason_lock, flags);
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
320
321
322
323
324
325
  	return buf_offset;
  }
  
  static ssize_t last_suspend_time_show(struct kobject *kobj,
  			struct kobj_attribute *attr, char *buf)
  {
0f2cb7cf8   Todd Kjos   Merge branch 'lin...
326
327
328
  	struct timespec64 sleep_time;
  	struct timespec64 total_time;
  	struct timespec64 suspend_resume_time;
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
329
330
331
332
333
  
  	/*
  	 * total_time is calculated from monotonic bootoffsets because
  	 * unlike CLOCK_MONOTONIC it include the time spent in suspend state.
  	 */
0f2cb7cf8   Todd Kjos   Merge branch 'lin...
334
  	total_time = ktime_to_timespec64(ktime_sub(curr_stime, last_stime));
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
335
336
337
338
339
  
  	/*
  	 * suspend_resume_time is calculated as monotonic (CLOCK_MONOTONIC)
  	 * time interval before entering suspend and post suspend.
  	 */
0f2cb7cf8   Todd Kjos   Merge branch 'lin...
340
341
  	suspend_resume_time =
  		ktime_to_timespec64(ktime_sub(curr_monotime, last_monotime));
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
342
343
  
  	/* sleep_time = total_time - suspend_resume_time */
0f2cb7cf8   Todd Kjos   Merge branch 'lin...
344
  	sleep_time = timespec64_sub(total_time, suspend_resume_time);
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
345
346
  
  	/* Export suspend_resume_time and sleep_time in pair here. */
0f2cb7cf8   Todd Kjos   Merge branch 'lin...
347
348
  	return sprintf(buf, "%llu.%09lu %llu.%09lu
  ",
f46fe356e   Kelly Rossmoyer   ANDROID: fix wake...
349
350
351
352
  		       (unsigned long long)suspend_resume_time.tv_sec,
  		       suspend_resume_time.tv_nsec,
  		       (unsigned long long)sleep_time.tv_sec,
  		       sleep_time.tv_nsec);
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
353
354
355
356
357
358
359
360
361
362
363
364
365
  }
  
  static struct kobj_attribute resume_reason = __ATTR_RO(last_resume_reason);
  static struct kobj_attribute suspend_time = __ATTR_RO(last_suspend_time);
  
  static struct attribute *attrs[] = {
  	&resume_reason.attr,
  	&suspend_time.attr,
  	NULL,
  };
  static struct attribute_group attr_group = {
  	.attrs = attrs,
  };
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
366
367
368
369
  /* Detects a suspend and clears all the previous wake up reasons*/
  static int wakeup_reason_pm_event(struct notifier_block *notifier,
  		unsigned long pm_event, void *unused)
  {
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
370
371
  	switch (pm_event) {
  	case PM_SUSPEND_PREPARE:
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
372
373
374
375
  		/* monotonic time since boot */
  		last_monotime = ktime_get();
  		/* monotonic time since boot including the time spent in suspend */
  		last_stime = ktime_get_boottime();
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
376
  		clear_wakeup_reasons();
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
377
378
379
380
381
382
  		break;
  	case PM_POST_SUSPEND:
  		/* monotonic time since boot */
  		curr_monotime = ktime_get();
  		/* monotonic time since boot including the time spent in suspend */
  		curr_stime = ktime_get_boottime();
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
383
  		print_wakeup_sources();
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
384
385
386
387
388
389
390
391
392
393
  		break;
  	default:
  		break;
  	}
  	return NOTIFY_DONE;
  }
  
  static struct notifier_block wakeup_reason_pm_notifier_block = {
  	.notifier_call = wakeup_reason_pm_event,
  };
f46fe356e   Kelly Rossmoyer   ANDROID: fix wake...
394
  static int __init wakeup_reason_init(void)
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
395
  {
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
396
397
398
399
  	if (register_pm_notifier(&wakeup_reason_pm_notifier_block)) {
  		pr_warn("[%s] failed to register PM notifier
  ", __func__);
  		goto fail;
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
400
  	}
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
401
402
403
404
405
406
  
  	kobj = kobject_create_and_add("wakeup_reasons", kernel_kobj);
  	if (!kobj) {
  		pr_warn("[%s] failed to create a sysfs kobject
  ", __func__);
  		goto fail_unregister_pm_notifier;
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
407
  	}
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
408
409
410
411
412
413
414
415
416
417
418
419
  
  	if (sysfs_create_group(kobj, &attr_group)) {
  		pr_warn("[%s] failed to create a sysfs group
  ", __func__);
  		goto fail_kobject_put;
  	}
  
  	wakeup_irq_nodes_cache =
  		kmem_cache_create("wakeup_irq_node_cache",
  				  sizeof(struct wakeup_irq_node), 0, 0, NULL);
  	if (!wakeup_irq_nodes_cache)
  		goto fail_remove_group;
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
420
  	return 0;
b19f0cc3f   Kelly Rossmoyer   ANDROID: power: w...
421
422
423
424
425
426
427
428
429
  
  fail_remove_group:
  	sysfs_remove_group(kobj, &attr_group);
  fail_kobject_put:
  	kobject_put(kobj);
  fail_unregister_pm_notifier:
  	unregister_pm_notifier(&wakeup_reason_pm_notifier_block);
  fail:
  	return 1;
bc17c02a8   Ruchi Kandoi   ANDROID: power: w...
430
431
432
  }
  
  late_initcall(wakeup_reason_init);