13d0407baSopenharmony_ci/* 23d0407baSopenharmony_ci * kernel/power/wakeup_reason.c 33d0407baSopenharmony_ci * 43d0407baSopenharmony_ci * Logs the reasons which caused the kernel to resume from 53d0407baSopenharmony_ci * the suspend mode. 63d0407baSopenharmony_ci * 73d0407baSopenharmony_ci * Copyright (C) 2020 Google, Inc. 83d0407baSopenharmony_ci * This software is licensed under the terms of the GNU General Public 93d0407baSopenharmony_ci * License version 2, as published by the Free Software Foundation, and 103d0407baSopenharmony_ci * may be copied, distributed, and modified under those terms. 113d0407baSopenharmony_ci * 123d0407baSopenharmony_ci * This program is distributed in the hope that it will be useful, 133d0407baSopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of 143d0407baSopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 153d0407baSopenharmony_ci * GNU General Public License for more details. 163d0407baSopenharmony_ci */ 173d0407baSopenharmony_ci 183d0407baSopenharmony_ci#include <linux/wakeup_reason.h> 193d0407baSopenharmony_ci#include <linux/kernel.h> 203d0407baSopenharmony_ci#include <linux/irq.h> 213d0407baSopenharmony_ci#include <linux/interrupt.h> 223d0407baSopenharmony_ci#include <linux/io.h> 233d0407baSopenharmony_ci#include <linux/kobject.h> 243d0407baSopenharmony_ci#include <linux/sysfs.h> 253d0407baSopenharmony_ci#include <linux/init.h> 263d0407baSopenharmony_ci#include <linux/spinlock.h> 273d0407baSopenharmony_ci#include <linux/notifier.h> 283d0407baSopenharmony_ci#include <linux/suspend.h> 293d0407baSopenharmony_ci#include <linux/slab.h> 303d0407baSopenharmony_ci 313d0407baSopenharmony_ci/* 323d0407baSopenharmony_ci * struct wakeup_irq_node - stores data and relationships for IRQs logged as 333d0407baSopenharmony_ci * either base or nested wakeup reasons during suspend/resume flow. 343d0407baSopenharmony_ci * @siblings - for membership on leaf or parent IRQ lists 353d0407baSopenharmony_ci * @irq - the IRQ number 363d0407baSopenharmony_ci * @irq_name - the name associated with the IRQ, or a default if none 373d0407baSopenharmony_ci */ 383d0407baSopenharmony_cistruct wakeup_irq_node { 393d0407baSopenharmony_ci struct list_head siblings; 403d0407baSopenharmony_ci int irq; 413d0407baSopenharmony_ci const char *irq_name; 423d0407baSopenharmony_ci}; 433d0407baSopenharmony_ci 443d0407baSopenharmony_cienum wakeup_reason_flag { 453d0407baSopenharmony_ci RESUME_NONE = 0, 463d0407baSopenharmony_ci RESUME_IRQ, 473d0407baSopenharmony_ci RESUME_ABORT, 483d0407baSopenharmony_ci RESUME_ABNORMAL, 493d0407baSopenharmony_ci}; 503d0407baSopenharmony_ci 513d0407baSopenharmony_cistatic DEFINE_SPINLOCK(wakeup_reason_lock); 523d0407baSopenharmony_ci 533d0407baSopenharmony_cistatic LIST_HEAD(leaf_irqs); /* kept in ascending IRQ sorted order */ 543d0407baSopenharmony_cistatic LIST_HEAD(parent_irqs); /* unordered */ 553d0407baSopenharmony_ci 563d0407baSopenharmony_cistatic struct kmem_cache *wakeup_irq_nodes_cache; 573d0407baSopenharmony_ci 583d0407baSopenharmony_cistatic const char *default_irq_name = "(unnamed)"; 593d0407baSopenharmony_ci 603d0407baSopenharmony_cistatic struct kobject *kobj; 613d0407baSopenharmony_ci 623d0407baSopenharmony_cistatic bool capture_reasons; 633d0407baSopenharmony_cistatic int wakeup_reason; 643d0407baSopenharmony_cistatic char non_irq_wake_reason[MAX_SUSPEND_ABORT_LEN]; 653d0407baSopenharmony_ci 663d0407baSopenharmony_cistatic ktime_t last_monotime; /* monotonic time before last suspend */ 673d0407baSopenharmony_cistatic ktime_t curr_monotime; /* monotonic time after last suspend */ 683d0407baSopenharmony_cistatic ktime_t last_stime; /* monotonic boottime offset before last suspend */ 693d0407baSopenharmony_cistatic ktime_t curr_stime; /* monotonic boottime offset after last suspend */ 703d0407baSopenharmony_ci 713d0407baSopenharmony_cistatic void init_node(struct wakeup_irq_node *p, int irq) 723d0407baSopenharmony_ci{ 733d0407baSopenharmony_ci struct irq_desc *desc; 743d0407baSopenharmony_ci 753d0407baSopenharmony_ci INIT_LIST_HEAD(&p->siblings); 763d0407baSopenharmony_ci 773d0407baSopenharmony_ci p->irq = irq; 783d0407baSopenharmony_ci desc = irq_to_desc(irq); 793d0407baSopenharmony_ci if (desc && desc->action && desc->action->name) { 803d0407baSopenharmony_ci p->irq_name = desc->action->name; 813d0407baSopenharmony_ci } else { 823d0407baSopenharmony_ci p->irq_name = default_irq_name; 833d0407baSopenharmony_ci } 843d0407baSopenharmony_ci} 853d0407baSopenharmony_ci 863d0407baSopenharmony_cistatic struct wakeup_irq_node *create_node(int irq) 873d0407baSopenharmony_ci{ 883d0407baSopenharmony_ci struct wakeup_irq_node *result; 893d0407baSopenharmony_ci 903d0407baSopenharmony_ci result = kmem_cache_alloc(wakeup_irq_nodes_cache, GFP_ATOMIC); 913d0407baSopenharmony_ci if (unlikely(!result)) { 923d0407baSopenharmony_ci pr_warn("Failed to log wakeup IRQ %d\n", irq); 933d0407baSopenharmony_ci } else { 943d0407baSopenharmony_ci init_node(result, irq); 953d0407baSopenharmony_ci } 963d0407baSopenharmony_ci 973d0407baSopenharmony_ci return result; 983d0407baSopenharmony_ci} 993d0407baSopenharmony_ci 1003d0407baSopenharmony_cistatic void delete_list(struct list_head *head) 1013d0407baSopenharmony_ci{ 1023d0407baSopenharmony_ci struct wakeup_irq_node *n; 1033d0407baSopenharmony_ci 1043d0407baSopenharmony_ci while (!list_empty(head)) { 1053d0407baSopenharmony_ci n = list_first_entry(head, struct wakeup_irq_node, siblings); 1063d0407baSopenharmony_ci list_del(&n->siblings); 1073d0407baSopenharmony_ci kmem_cache_free(wakeup_irq_nodes_cache, n); 1083d0407baSopenharmony_ci } 1093d0407baSopenharmony_ci} 1103d0407baSopenharmony_ci 1113d0407baSopenharmony_cistatic bool add_sibling_node_sorted(struct list_head *head, int irq) 1123d0407baSopenharmony_ci{ 1133d0407baSopenharmony_ci struct wakeup_irq_node *n = NULL; 1143d0407baSopenharmony_ci struct list_head *predecessor = head; 1153d0407baSopenharmony_ci 1163d0407baSopenharmony_ci if (unlikely(WARN_ON(!head))) { 1173d0407baSopenharmony_ci return NULL; 1183d0407baSopenharmony_ci } 1193d0407baSopenharmony_ci 1203d0407baSopenharmony_ci if (!list_empty(head)) { 1213d0407baSopenharmony_ci list_for_each_entry(n, head, siblings) 1223d0407baSopenharmony_ci { 1233d0407baSopenharmony_ci if (n->irq < irq) { 1243d0407baSopenharmony_ci predecessor = &n->siblings; 1253d0407baSopenharmony_ci } else if (n->irq == irq) { 1263d0407baSopenharmony_ci return true; 1273d0407baSopenharmony_ci } else { 1283d0407baSopenharmony_ci break; 1293d0407baSopenharmony_ci } 1303d0407baSopenharmony_ci } 1313d0407baSopenharmony_ci } 1323d0407baSopenharmony_ci 1333d0407baSopenharmony_ci n = create_node(irq); 1343d0407baSopenharmony_ci if (n) { 1353d0407baSopenharmony_ci list_add(&n->siblings, predecessor); 1363d0407baSopenharmony_ci return true; 1373d0407baSopenharmony_ci } 1383d0407baSopenharmony_ci 1393d0407baSopenharmony_ci return false; 1403d0407baSopenharmony_ci} 1413d0407baSopenharmony_ci 1423d0407baSopenharmony_cistatic struct wakeup_irq_node *find_node_in_list(struct list_head *head, int irq) 1433d0407baSopenharmony_ci{ 1443d0407baSopenharmony_ci struct wakeup_irq_node *n; 1453d0407baSopenharmony_ci 1463d0407baSopenharmony_ci if (unlikely(WARN_ON(!head))) { 1473d0407baSopenharmony_ci return NULL; 1483d0407baSopenharmony_ci } 1493d0407baSopenharmony_ci 1503d0407baSopenharmony_ci list_for_each_entry(n, head, siblings) if (n->irq == irq) return n; 1513d0407baSopenharmony_ci 1523d0407baSopenharmony_ci return NULL; 1533d0407baSopenharmony_ci} 1543d0407baSopenharmony_ci 1553d0407baSopenharmony_civoid log_irq_wakeup_reason(int irq) 1563d0407baSopenharmony_ci{ 1573d0407baSopenharmony_ci unsigned long flags; 1583d0407baSopenharmony_ci 1593d0407baSopenharmony_ci spin_lock_irqsave(&wakeup_reason_lock, flags); 1603d0407baSopenharmony_ci if (wakeup_reason == RESUME_ABNORMAL || wakeup_reason == RESUME_ABORT) { 1613d0407baSopenharmony_ci spin_unlock_irqrestore(&wakeup_reason_lock, flags); 1623d0407baSopenharmony_ci return; 1633d0407baSopenharmony_ci } 1643d0407baSopenharmony_ci 1653d0407baSopenharmony_ci if (!capture_reasons) { 1663d0407baSopenharmony_ci spin_unlock_irqrestore(&wakeup_reason_lock, flags); 1673d0407baSopenharmony_ci return; 1683d0407baSopenharmony_ci } 1693d0407baSopenharmony_ci 1703d0407baSopenharmony_ci if (find_node_in_list(&parent_irqs, irq) == NULL) { 1713d0407baSopenharmony_ci add_sibling_node_sorted(&leaf_irqs, irq); 1723d0407baSopenharmony_ci } 1733d0407baSopenharmony_ci 1743d0407baSopenharmony_ci wakeup_reason = RESUME_IRQ; 1753d0407baSopenharmony_ci spin_unlock_irqrestore(&wakeup_reason_lock, flags); 1763d0407baSopenharmony_ci} 1773d0407baSopenharmony_ci 1783d0407baSopenharmony_civoid log_threaded_irq_wakeup_reason(int irq, int parent_irq) 1793d0407baSopenharmony_ci{ 1803d0407baSopenharmony_ci struct wakeup_irq_node *parent; 1813d0407baSopenharmony_ci unsigned long flags; 1823d0407baSopenharmony_ci 1833d0407baSopenharmony_ci /* 1843d0407baSopenharmony_ci * Intentionally unsynchronized. Calls that come in after we have 1853d0407baSopenharmony_ci * resumed should have a fast exit path since there's no work to be 1863d0407baSopenharmony_ci * done, any any coherence issue that could cause a wrong value here is 1873d0407baSopenharmony_ci * both highly improbable - given the set/clear timing - and very low 1883d0407baSopenharmony_ci * impact (parent IRQ gets logged instead of the specific child). 1893d0407baSopenharmony_ci */ 1903d0407baSopenharmony_ci if (!capture_reasons) { 1913d0407baSopenharmony_ci return; 1923d0407baSopenharmony_ci } 1933d0407baSopenharmony_ci 1943d0407baSopenharmony_ci spin_lock_irqsave(&wakeup_reason_lock, flags); 1953d0407baSopenharmony_ci 1963d0407baSopenharmony_ci if (wakeup_reason == RESUME_ABNORMAL || wakeup_reason == RESUME_ABORT) { 1973d0407baSopenharmony_ci spin_unlock_irqrestore(&wakeup_reason_lock, flags); 1983d0407baSopenharmony_ci return; 1993d0407baSopenharmony_ci } 2003d0407baSopenharmony_ci 2013d0407baSopenharmony_ci if (!capture_reasons || (find_node_in_list(&leaf_irqs, irq) != NULL)) { 2023d0407baSopenharmony_ci spin_unlock_irqrestore(&wakeup_reason_lock, flags); 2033d0407baSopenharmony_ci return; 2043d0407baSopenharmony_ci } 2053d0407baSopenharmony_ci 2063d0407baSopenharmony_ci parent = find_node_in_list(&parent_irqs, parent_irq); 2073d0407baSopenharmony_ci if (parent != NULL) { 2083d0407baSopenharmony_ci add_sibling_node_sorted(&leaf_irqs, irq); 2093d0407baSopenharmony_ci } else { 2103d0407baSopenharmony_ci parent = find_node_in_list(&leaf_irqs, parent_irq); 2113d0407baSopenharmony_ci if (parent != NULL) { 2123d0407baSopenharmony_ci list_del_init(&parent->siblings); 2133d0407baSopenharmony_ci list_add_tail(&parent->siblings, &parent_irqs); 2143d0407baSopenharmony_ci add_sibling_node_sorted(&leaf_irqs, irq); 2153d0407baSopenharmony_ci } 2163d0407baSopenharmony_ci } 2173d0407baSopenharmony_ci 2183d0407baSopenharmony_ci spin_unlock_irqrestore(&wakeup_reason_lock, flags); 2193d0407baSopenharmony_ci} 2203d0407baSopenharmony_ciEXPORT_SYMBOL_GPL(log_threaded_irq_wakeup_reason); 2213d0407baSopenharmony_ci 2223d0407baSopenharmony_cistatic void _log_abort_or_abnormal_wake(bool abort, const char *fmt, va_list args) 2233d0407baSopenharmony_ci{ 2243d0407baSopenharmony_ci unsigned long flags; 2253d0407baSopenharmony_ci 2263d0407baSopenharmony_ci spin_lock_irqsave(&wakeup_reason_lock, flags); 2273d0407baSopenharmony_ci 2283d0407baSopenharmony_ci /* Suspend abort or abnormal wake reason has already been logged. */ 2293d0407baSopenharmony_ci if (wakeup_reason != RESUME_NONE) { 2303d0407baSopenharmony_ci spin_unlock_irqrestore(&wakeup_reason_lock, flags); 2313d0407baSopenharmony_ci return; 2323d0407baSopenharmony_ci } 2333d0407baSopenharmony_ci 2343d0407baSopenharmony_ci if (abort) { 2353d0407baSopenharmony_ci wakeup_reason = RESUME_ABORT; 2363d0407baSopenharmony_ci } else { 2373d0407baSopenharmony_ci wakeup_reason = RESUME_ABNORMAL; 2383d0407baSopenharmony_ci } 2393d0407baSopenharmony_ci 2403d0407baSopenharmony_ci (void)vsnprintf(non_irq_wake_reason, MAX_SUSPEND_ABORT_LEN, fmt, args); 2413d0407baSopenharmony_ci 2423d0407baSopenharmony_ci spin_unlock_irqrestore(&wakeup_reason_lock, flags); 2433d0407baSopenharmony_ci} 2443d0407baSopenharmony_ci 2453d0407baSopenharmony_civoid log_suspend_abort_reason(const char *fmt, ...) 2463d0407baSopenharmony_ci{ 2473d0407baSopenharmony_ci va_list args; 2483d0407baSopenharmony_ci 2493d0407baSopenharmony_ci va_start(args, fmt); 2503d0407baSopenharmony_ci _log_abort_or_abnormal_wake(true, fmt, args); 2513d0407baSopenharmony_ci va_end(args); 2523d0407baSopenharmony_ci} 2533d0407baSopenharmony_ciEXPORT_SYMBOL_GPL(log_suspend_abort_reason); 2543d0407baSopenharmony_ci 2553d0407baSopenharmony_civoid log_abnormal_wakeup_reason(const char *fmt, ...) 2563d0407baSopenharmony_ci{ 2573d0407baSopenharmony_ci va_list args; 2583d0407baSopenharmony_ci 2593d0407baSopenharmony_ci va_start(args, fmt); 2603d0407baSopenharmony_ci _log_abort_or_abnormal_wake(false, fmt, args); 2613d0407baSopenharmony_ci va_end(args); 2623d0407baSopenharmony_ci} 2633d0407baSopenharmony_ciEXPORT_SYMBOL_GPL(log_abnormal_wakeup_reason); 2643d0407baSopenharmony_ci 2653d0407baSopenharmony_civoid clear_wakeup_reasons(void) 2663d0407baSopenharmony_ci{ 2673d0407baSopenharmony_ci unsigned long flags; 2683d0407baSopenharmony_ci 2693d0407baSopenharmony_ci spin_lock_irqsave(&wakeup_reason_lock, flags); 2703d0407baSopenharmony_ci 2713d0407baSopenharmony_ci delete_list(&leaf_irqs); 2723d0407baSopenharmony_ci delete_list(&parent_irqs); 2733d0407baSopenharmony_ci wakeup_reason = RESUME_NONE; 2743d0407baSopenharmony_ci capture_reasons = true; 2753d0407baSopenharmony_ci 2763d0407baSopenharmony_ci spin_unlock_irqrestore(&wakeup_reason_lock, flags); 2773d0407baSopenharmony_ci} 2783d0407baSopenharmony_ci 2793d0407baSopenharmony_cistatic void print_wakeup_sources(void) 2803d0407baSopenharmony_ci{ 2813d0407baSopenharmony_ci struct wakeup_irq_node *n; 2823d0407baSopenharmony_ci unsigned long flags; 2833d0407baSopenharmony_ci 2843d0407baSopenharmony_ci spin_lock_irqsave(&wakeup_reason_lock, flags); 2853d0407baSopenharmony_ci 2863d0407baSopenharmony_ci capture_reasons = false; 2873d0407baSopenharmony_ci 2883d0407baSopenharmony_ci if (wakeup_reason == RESUME_ABORT) { 2893d0407baSopenharmony_ci pr_info("Abort: %s\n", non_irq_wake_reason); 2903d0407baSopenharmony_ci spin_unlock_irqrestore(&wakeup_reason_lock, flags); 2913d0407baSopenharmony_ci return; 2923d0407baSopenharmony_ci } 2933d0407baSopenharmony_ci 2943d0407baSopenharmony_ci if (wakeup_reason == RESUME_IRQ && !list_empty(&leaf_irqs)) { 2953d0407baSopenharmony_ci list_for_each_entry(n, &leaf_irqs, siblings) pr_info("Resume caused by IRQ %d, %s\n", n->irq, n->irq_name); 2963d0407baSopenharmony_ci } else if (wakeup_reason == RESUME_ABNORMAL) { 2973d0407baSopenharmony_ci pr_info("Resume caused by %s\n", non_irq_wake_reason); 2983d0407baSopenharmony_ci } else { 2993d0407baSopenharmony_ci pr_info("Resume cause unknown\n"); 3003d0407baSopenharmony_ci } 3013d0407baSopenharmony_ci 3023d0407baSopenharmony_ci spin_unlock_irqrestore(&wakeup_reason_lock, flags); 3033d0407baSopenharmony_ci} 3043d0407baSopenharmony_ci 3053d0407baSopenharmony_cistatic ssize_t last_resume_reason_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) 3063d0407baSopenharmony_ci{ 3073d0407baSopenharmony_ci ssize_t buf_offset = 0; 3083d0407baSopenharmony_ci struct wakeup_irq_node *n; 3093d0407baSopenharmony_ci unsigned long flags; 3103d0407baSopenharmony_ci 3113d0407baSopenharmony_ci spin_lock_irqsave(&wakeup_reason_lock, flags); 3123d0407baSopenharmony_ci 3133d0407baSopenharmony_ci if (wakeup_reason == RESUME_ABORT) { 3143d0407baSopenharmony_ci buf_offset = scnprintf(buf, PAGE_SIZE, "Abort: %s", non_irq_wake_reason); 3153d0407baSopenharmony_ci spin_unlock_irqrestore(&wakeup_reason_lock, flags); 3163d0407baSopenharmony_ci return buf_offset; 3173d0407baSopenharmony_ci } 3183d0407baSopenharmony_ci 3193d0407baSopenharmony_ci if (wakeup_reason == RESUME_IRQ && !list_empty(&leaf_irqs)) { 3203d0407baSopenharmony_ci list_for_each_entry(n, &leaf_irqs, siblings) buf_offset += 3213d0407baSopenharmony_ci scnprintf(buf + buf_offset, PAGE_SIZE - buf_offset, "%d %s\n", n->irq, n->irq_name); 3223d0407baSopenharmony_ci } else if (wakeup_reason == RESUME_ABNORMAL) { 3233d0407baSopenharmony_ci buf_offset = scnprintf(buf, PAGE_SIZE, "-1 %s", non_irq_wake_reason); 3243d0407baSopenharmony_ci } 3253d0407baSopenharmony_ci 3263d0407baSopenharmony_ci spin_unlock_irqrestore(&wakeup_reason_lock, flags); 3273d0407baSopenharmony_ci 3283d0407baSopenharmony_ci return buf_offset; 3293d0407baSopenharmony_ci} 3303d0407baSopenharmony_ci 3313d0407baSopenharmony_cistatic ssize_t last_suspend_time_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) 3323d0407baSopenharmony_ci{ 3333d0407baSopenharmony_ci struct timespec64 sleep_time; 3343d0407baSopenharmony_ci struct timespec64 total_time; 3353d0407baSopenharmony_ci struct timespec64 suspend_resume_time; 3363d0407baSopenharmony_ci 3373d0407baSopenharmony_ci /* 3383d0407baSopenharmony_ci * total_time is calculated from monotonic bootoffsets because 3393d0407baSopenharmony_ci * unlike CLOCK_MONOTONIC it include the time spent in suspend state. 3403d0407baSopenharmony_ci */ 3413d0407baSopenharmony_ci total_time = ktime_to_timespec64(ktime_sub(curr_stime, last_stime)); 3423d0407baSopenharmony_ci 3433d0407baSopenharmony_ci /* 3443d0407baSopenharmony_ci * suspend_resume_time is calculated as monotonic (CLOCK_MONOTONIC) 3453d0407baSopenharmony_ci * time interval before entering suspend and post suspend. 3463d0407baSopenharmony_ci */ 3473d0407baSopenharmony_ci suspend_resume_time = ktime_to_timespec64(ktime_sub(curr_monotime, last_monotime)); 3483d0407baSopenharmony_ci 3493d0407baSopenharmony_ci /* sleep_time = total_time - suspend_resume_time */ 3503d0407baSopenharmony_ci sleep_time = timespec64_sub(total_time, suspend_resume_time); 3513d0407baSopenharmony_ci 3523d0407baSopenharmony_ci /* Export suspend_resume_time and sleep_time in pair here. */ 3533d0407baSopenharmony_ci return sprintf(buf, "%llu.%09lu %llu.%09lu\n", (unsigned long long)suspend_resume_time.tv_sec, 3543d0407baSopenharmony_ci suspend_resume_time.tv_nsec, (unsigned long long)sleep_time.tv_sec, sleep_time.tv_nsec); 3553d0407baSopenharmony_ci} 3563d0407baSopenharmony_ci 3573d0407baSopenharmony_cistatic struct kobj_attribute resume_reason = __ATTR_RO(last_resume_reason); 3583d0407baSopenharmony_cistatic struct kobj_attribute suspend_time = __ATTR_RO(last_suspend_time); 3593d0407baSopenharmony_ci 3603d0407baSopenharmony_cistatic struct attribute *attrs[] = { 3613d0407baSopenharmony_ci &resume_reason.attr, 3623d0407baSopenharmony_ci &suspend_time.attr, 3633d0407baSopenharmony_ci NULL, 3643d0407baSopenharmony_ci}; 3653d0407baSopenharmony_cistatic struct attribute_group attr_group = { 3663d0407baSopenharmony_ci .attrs = attrs, 3673d0407baSopenharmony_ci}; 3683d0407baSopenharmony_ci 3693d0407baSopenharmony_ci/* Detects a suspend and clears all the previous wake up reasons */ 3703d0407baSopenharmony_cistatic int wakeup_reason_pm_event(struct notifier_block *notifier, unsigned long pm_event, void *unused) 3713d0407baSopenharmony_ci{ 3723d0407baSopenharmony_ci switch (pm_event) { 3733d0407baSopenharmony_ci case PM_SUSPEND_PREPARE: 3743d0407baSopenharmony_ci /* monotonic time since boot */ 3753d0407baSopenharmony_ci last_monotime = ktime_get(); 3763d0407baSopenharmony_ci /* monotonic time since boot including the time spent in suspend */ 3773d0407baSopenharmony_ci last_stime = ktime_get_boottime(); 3783d0407baSopenharmony_ci clear_wakeup_reasons(); 3793d0407baSopenharmony_ci break; 3803d0407baSopenharmony_ci case PM_POST_SUSPEND: 3813d0407baSopenharmony_ci /* monotonic time since boot */ 3823d0407baSopenharmony_ci curr_monotime = ktime_get(); 3833d0407baSopenharmony_ci /* monotonic time since boot including the time spent in suspend */ 3843d0407baSopenharmony_ci curr_stime = ktime_get_boottime(); 3853d0407baSopenharmony_ci print_wakeup_sources(); 3863d0407baSopenharmony_ci break; 3873d0407baSopenharmony_ci default: 3883d0407baSopenharmony_ci break; 3893d0407baSopenharmony_ci } 3903d0407baSopenharmony_ci return NOTIFY_DONE; 3913d0407baSopenharmony_ci} 3923d0407baSopenharmony_ci 3933d0407baSopenharmony_cistatic struct notifier_block wakeup_reason_pm_notifier_block = { 3943d0407baSopenharmony_ci .notifier_call = wakeup_reason_pm_event, 3953d0407baSopenharmony_ci}; 3963d0407baSopenharmony_ci 3973d0407baSopenharmony_cistatic int __init wakeup_reason_init(void) 3983d0407baSopenharmony_ci{ 3993d0407baSopenharmony_ci if (register_pm_notifier(&wakeup_reason_pm_notifier_block)) { 4003d0407baSopenharmony_ci pr_warn("[%s] failed to register PM notifier\n", __func__); 4013d0407baSopenharmony_ci goto fail; 4023d0407baSopenharmony_ci } 4033d0407baSopenharmony_ci 4043d0407baSopenharmony_ci kobj = kobject_create_and_add("wakeup_reasons", kernel_kobj); 4053d0407baSopenharmony_ci if (!kobj) { 4063d0407baSopenharmony_ci pr_warn("[%s] failed to create a sysfs kobject\n", __func__); 4073d0407baSopenharmony_ci goto fail_unregister_pm_notifier; 4083d0407baSopenharmony_ci } 4093d0407baSopenharmony_ci 4103d0407baSopenharmony_ci if (sysfs_create_group(kobj, &attr_group)) { 4113d0407baSopenharmony_ci pr_warn("[%s] failed to create a sysfs group\n", __func__); 4123d0407baSopenharmony_ci goto fail_kobject_put; 4133d0407baSopenharmony_ci } 4143d0407baSopenharmony_ci 4153d0407baSopenharmony_ci wakeup_irq_nodes_cache = kmem_cache_create("wakeup_irq_node_cache", sizeof(struct wakeup_irq_node), 0, 0, NULL); 4163d0407baSopenharmony_ci if (!wakeup_irq_nodes_cache) { 4173d0407baSopenharmony_ci goto fail_remove_group; 4183d0407baSopenharmony_ci } 4193d0407baSopenharmony_ci 4203d0407baSopenharmony_ci return 0; 4213d0407baSopenharmony_ci 4223d0407baSopenharmony_cifail_remove_group: 4233d0407baSopenharmony_ci sysfs_remove_group(kobj, &attr_group); 4243d0407baSopenharmony_cifail_kobject_put: 4253d0407baSopenharmony_ci kobject_put(kobj); 4263d0407baSopenharmony_cifail_unregister_pm_notifier: 4273d0407baSopenharmony_ci unregister_pm_notifier(&wakeup_reason_pm_notifier_block); 4283d0407baSopenharmony_cifail: 4293d0407baSopenharmony_ci return 1; 4303d0407baSopenharmony_ci} 4313d0407baSopenharmony_ci 4323d0407baSopenharmony_cilate_initcall(wakeup_reason_init); 433