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