Blame view
kernel/power/wakeup_reason.c
11.2 KB
bc17c02a8 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 ANDROID: power: w... |
7 |
* Copyright (C) 2020 Google, Inc. |
bc17c02a8 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 ANDROID: power: w... |
29 |
#include <linux/slab.h> |
bc17c02a8 ANDROID: power: w... |
30 |
|
b19f0cc3f 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 ANDROID: power: w... |
43 44 45 46 47 48 |
enum wakeup_reason_flag { RESUME_NONE = 0, RESUME_IRQ, RESUME_ABORT, RESUME_ABNORMAL, }; |
b19f0cc3f 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 ANDROID: power: w... |
53 |
|
b19f0cc3f 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 ANDROID: power: w... |
61 |
static int wakeup_reason; |
b19f0cc3f ANDROID: power: w... |
62 |
static char non_irq_wake_reason[MAX_SUSPEND_ABORT_LEN]; |
bc17c02a8 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 ANDROID: power: w... |
68 |
static void init_node(struct wakeup_irq_node *p, int irq) |
bc17c02a8 ANDROID: power: w... |
69 |
{ |
bc17c02a8 ANDROID: power: w... |
70 |
struct irq_desc *desc; |
b19f0cc3f 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 ANDROID: fix wake... |
109 |
struct wakeup_irq_node *n = NULL; |
b19f0cc3f 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 ANDROID: power: w... |
121 |
else |
b19f0cc3f ANDROID: power: w... |
122 |
break; |
bc17c02a8 ANDROID: power: w... |
123 |
} |
b19f0cc3f ANDROID: power: w... |
124 125 126 127 128 |
n = create_node(irq); if (n) { list_add(&n->siblings, predecessor); return true; |
bc17c02a8 ANDROID: power: w... |
129 |
} |
b19f0cc3f 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 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 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 ANDROID: power: w... |
166 |
wakeup_reason = RESUME_IRQ; |
b19f0cc3f 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 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 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 ANDROID: power: w... |
209 |
EXPORT_SYMBOL_GPL(log_threaded_irq_wakeup_reason); |
b19f0cc3f ANDROID: power: w... |
210 |
|
f46fe356e ANDROID: fix wake... |
211 212 |
static void __log_abort_or_abnormal_wake(bool abort, const char *fmt, va_list args) |
b19f0cc3f 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 ANDROID: power: w... |
219 |
if (wakeup_reason != RESUME_NONE) { |
b19f0cc3f ANDROID: power: w... |
220 221 222 |
spin_unlock_irqrestore(&wakeup_reason_lock, flags); return; } |
8f61f09d3 ANDROID: power: w... |
223 224 225 226 |
if (abort) wakeup_reason = RESUME_ABORT; else wakeup_reason = RESUME_ABNORMAL; |
b19f0cc3f 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 ANDROID: power: E... |
240 |
EXPORT_SYMBOL_GPL(log_suspend_abort_reason); |
b19f0cc3f 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 ANDROID: power: E... |
250 |
EXPORT_SYMBOL_GPL(log_abnormal_wakeup_reason); |
b19f0cc3f 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 ANDROID: power: w... |
260 |
wakeup_reason = RESUME_NONE; |
b19f0cc3f 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 ANDROID: power: w... |
274 |
if (wakeup_reason == RESUME_ABORT) { |
b19f0cc3f 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 ANDROID: power: w... |
280 |
if (wakeup_reason == RESUME_IRQ && !list_empty(&leaf_irqs)) |
b19f0cc3f 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 ANDROID: power: w... |
285 |
else if (wakeup_reason == RESUME_ABNORMAL) |
b19f0cc3f 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 ANDROID: power: w... |
303 |
if (wakeup_reason == RESUME_ABORT) { |
b19f0cc3f 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 ANDROID: power: w... |
309 |
if (wakeup_reason == RESUME_IRQ && !list_empty(&leaf_irqs)) |
b19f0cc3f 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 ANDROID: power: w... |
315 |
else if (wakeup_reason == RESUME_ABNORMAL) |
b19f0cc3f 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 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 Merge branch 'lin... |
326 327 328 |
struct timespec64 sleep_time; struct timespec64 total_time; struct timespec64 suspend_resume_time; |
bc17c02a8 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 Merge branch 'lin... |
334 |
total_time = ktime_to_timespec64(ktime_sub(curr_stime, last_stime)); |
bc17c02a8 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 Merge branch 'lin... |
340 341 |
suspend_resume_time = ktime_to_timespec64(ktime_sub(curr_monotime, last_monotime)); |
bc17c02a8 ANDROID: power: w... |
342 343 |
/* sleep_time = total_time - suspend_resume_time */ |
0f2cb7cf8 Merge branch 'lin... |
344 |
sleep_time = timespec64_sub(total_time, suspend_resume_time); |
bc17c02a8 ANDROID: power: w... |
345 346 |
/* Export suspend_resume_time and sleep_time in pair here. */ |
0f2cb7cf8 Merge branch 'lin... |
347 348 |
return sprintf(buf, "%llu.%09lu %llu.%09lu ", |
f46fe356e 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 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 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 ANDROID: power: w... |
370 371 |
switch (pm_event) { case PM_SUSPEND_PREPARE: |
bc17c02a8 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 ANDROID: power: w... |
376 |
clear_wakeup_reasons(); |
bc17c02a8 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 ANDROID: power: w... |
383 |
print_wakeup_sources(); |
bc17c02a8 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 ANDROID: fix wake... |
394 |
static int __init wakeup_reason_init(void) |
bc17c02a8 ANDROID: power: w... |
395 |
{ |
b19f0cc3f 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 ANDROID: power: w... |
400 |
} |
b19f0cc3f 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 ANDROID: power: w... |
407 |
} |
b19f0cc3f 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 ANDROID: power: w... |
420 |
return 0; |
b19f0cc3f 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 ANDROID: power: w... |
430 431 432 |
} late_initcall(wakeup_reason_init); |