| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
| From: Ruchi Kandoi <kandoiruchi@google.com> |
| Date: Wed, 19 Feb 2014 15:30:47 -0800 |
| Subject: ANDROID: power: wakeup_reason: add an API to log wakeup reasons |
| |
| Add API log_wakeup_reason() and expose it to userspace via sysfs path |
| /sys/kernel/wakeup_reasons/last_resume_reason |
| |
| Bug: 120445600 |
| Signed-off-by: Ruchi Kandoi <kandoiruchi@google.com> |
| [AmitP: Folded following android-4.9 commit changes into this patch |
| 1135122a192a ("ANDROID: POWER: fix compile warnings in log_wakeup_reason") |
| b4e6247778b0 ("ANDROID: Power: Changes the permission to read only for sysfs file /sys/kernel/wakeup_reasons/last_resume_reason") |
| e13dbc7c69cd ("ANDROID: power: wakeup_reason: rename irq_count to irqcount")] |
| Signed-off-by: Amit Pundir <amit.pundir@linaro.org> |
| [adelva: Folded the following changes into this patch: |
| 39d7c7fe91c0 ("ANDROID: power: wakeup_reason: Add guard condition for maximum wakeup reasons") |
| 0730434bdf49 ("ANDROID: power: wakeup_reason: Avoids bogus error messages for the suspend aborts.") |
| 4e42dceae54e ("ANDROID: power: wakeup_reason: Adds functionality to log the last suspend abort reason.") |
| f21313b70ac7 ("ANDROID: power: wakeup_reason: Report suspend times from last_suspend_time") |
| f97ec34442ac ("ANDROID: power: wakeup_reason: fix suspend time reporting") |
| cd92df73e504 ("ANDROID: power: wakeup: Add last wake up source logging for suspend abort reason.") |
| 546b6ae3c087 ("ANDROID: power: wakeup: Add the guard condition for len in pm_get_active_wakeup_sources") |
| 1453d9ffcdbe ("ANDROID: power: wakeup_reason: make logging work in an interrupt context.")] |
| Change-Id: I81addaf420f1338255c5d0638b0d244a99d777d1 |
| Signed-off-by: Alistair Delva <adelva@google.com> |
| --- |
| .../ABI/testing/sysfs-kernel-wakeup_reasons | 16 ++ |
| drivers/base/power/main.c | 5 + |
| drivers/base/power/wakeup.c | 32 +++ |
| drivers/base/syscore.c | 5 +- |
| include/linux/suspend.h | 1 + |
| include/linux/wakeup_reason.h | 30 +++ |
| kernel/power/Makefile | 1 + |
| kernel/power/process.c | 22 +- |
| kernel/power/suspend.c | 20 +- |
| kernel/power/wakeup_reason.c | 215 ++++++++++++++++++ |
| 10 files changed, 339 insertions(+), 8 deletions(-) |
| create mode 100644 Documentation/ABI/testing/sysfs-kernel-wakeup_reasons |
| create mode 100644 include/linux/wakeup_reason.h |
| create mode 100644 kernel/power/wakeup_reason.c |
| |
| diff --git a/Documentation/ABI/testing/sysfs-kernel-wakeup_reasons b/Documentation/ABI/testing/sysfs-kernel-wakeup_reasons |
| new file mode 100644 |
| index 000000000000..acb19b91c192 |
| --- /dev/null |
| +++ b/Documentation/ABI/testing/sysfs-kernel-wakeup_reasons |
| @@ -0,0 +1,16 @@ |
| +What: /sys/kernel/wakeup_reasons/last_resume_reason |
| +Date: February 2014 |
| +Contact: Ruchi Kandoi <kandoiruchi@google.com> |
| +Description: |
| + The /sys/kernel/wakeup_reasons/last_resume_reason is |
| + used to report wakeup reasons after system exited suspend. |
| + |
| +What: /sys/kernel/wakeup_reasons/last_suspend_time |
| +Date: March 2015 |
| +Contact: jinqian <jinqian@google.com> |
| +Description: |
| + The /sys/kernel/wakeup_reasons/last_suspend_time is |
| + used to report time spent in last suspend cycle. It contains |
| + two numbers (in seconds) separated by space. First number is |
| + the time spent in suspend and resume processes. Second number |
| + is the time spent in sleep state. |
| \ No newline at end of file |
| diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c |
| --- a/drivers/base/power/main.c |
| +++ b/drivers/base/power/main.c |
| @@ -34,6 +34,7 @@ |
| #include <linux/cpuidle.h> |
| #include <linux/devfreq.h> |
| #include <linux/timer.h> |
| +#include <linux/wakeup_reason.h> |
| |
| #include "../base.h" |
| #include "power.h" |
| @@ -1593,6 +1594,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) |
| pm_callback_t callback = NULL; |
| const char *info = NULL; |
| int error = 0; |
| + char suspend_abort[MAX_SUSPEND_ABORT_LEN]; |
| DECLARE_DPM_WATCHDOG_ON_STACK(wd); |
| |
| TRACE_DEVICE(dev); |
| @@ -1616,6 +1618,9 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) |
| |
| if (pm_wakeup_pending()) { |
| dev->power.direct_complete = false; |
| + pm_get_active_wakeup_sources(suspend_abort, |
| + MAX_SUSPEND_ABORT_LEN); |
| + log_suspend_abort_reason(suspend_abort); |
| async_error = -EBUSY; |
| goto Complete; |
| } |
| diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c |
| --- a/drivers/base/power/wakeup.c |
| +++ b/drivers/base/power/wakeup.c |
| @@ -15,6 +15,7 @@ |
| #include <linux/seq_file.h> |
| #include <linux/debugfs.h> |
| #include <linux/pm_wakeirq.h> |
| +#include <linux/types.h> |
| #include <trace/events/power.h> |
| |
| #include "power.h" |
| @@ -872,6 +873,37 @@ void pm_wakeup_dev_event(struct device *dev, unsigned int msec, bool hard) |
| } |
| EXPORT_SYMBOL_GPL(pm_wakeup_dev_event); |
| |
| +void pm_get_active_wakeup_sources(char *pending_wakeup_source, size_t max) |
| +{ |
| + struct wakeup_source *ws, *last_active_ws = NULL; |
| + int len = 0; |
| + bool active = false; |
| + |
| + rcu_read_lock(); |
| + list_for_each_entry_rcu(ws, &wakeup_sources, entry) { |
| + if (ws->active && len < max) { |
| + if (!active) |
| + len += scnprintf(pending_wakeup_source, max, |
| + "Pending Wakeup Sources: "); |
| + len += scnprintf(pending_wakeup_source + len, max - len, |
| + "%s ", ws->name); |
| + active = true; |
| + } else if (!active && |
| + (!last_active_ws || |
| + ktime_to_ns(ws->last_time) > |
| + ktime_to_ns(last_active_ws->last_time))) { |
| + last_active_ws = ws; |
| + } |
| + } |
| + if (!active && last_active_ws) { |
| + scnprintf(pending_wakeup_source, max, |
| + "Last active Wakeup Source: %s", |
| + last_active_ws->name); |
| + } |
| + rcu_read_unlock(); |
| +} |
| +EXPORT_SYMBOL_GPL(pm_get_active_wakeup_sources); |
| + |
| void pm_print_active_wakeup_sources(void) |
| { |
| struct wakeup_source *ws; |
| diff --git a/drivers/base/syscore.c b/drivers/base/syscore.c |
| --- a/drivers/base/syscore.c |
| +++ b/drivers/base/syscore.c |
| @@ -10,6 +10,7 @@ |
| #include <linux/module.h> |
| #include <linux/suspend.h> |
| #include <trace/events/power.h> |
| +#include <linux/wakeup_reason.h> |
| |
| static LIST_HEAD(syscore_ops_list); |
| static DEFINE_MUTEX(syscore_ops_lock); |
| @@ -74,7 +75,9 @@ int syscore_suspend(void) |
| return 0; |
| |
| err_out: |
| - pr_err("PM: System core suspend callback %pS failed.\n", ops->suspend); |
| + log_suspend_abort_reason("System core suspend callback %pS failed", |
| + ops->suspend); |
| + pr_err("PM: System core suspend callback %pF failed.\n", ops->suspend); |
| |
| list_for_each_entry_continue(ops, &syscore_ops_list, node) |
| if (ops->resume) |
| diff --git a/include/linux/suspend.h b/include/linux/suspend.h |
| --- a/include/linux/suspend.h |
| +++ b/include/linux/suspend.h |
| @@ -511,6 +511,7 @@ extern bool pm_get_wakeup_count(unsigned int *count, bool block); |
| extern bool pm_save_wakeup_count(unsigned int count); |
| extern void pm_wakep_autosleep_enabled(bool set); |
| extern void pm_print_active_wakeup_sources(void); |
| +extern void pm_get_active_wakeup_sources(char *pending_sources, size_t max); |
| |
| extern void lock_system_sleep(void); |
| extern void unlock_system_sleep(void); |
| diff --git a/include/linux/wakeup_reason.h b/include/linux/wakeup_reason.h |
| new file mode 100644 |
| index 000000000000..9fbe209c7177 |
| --- /dev/null |
| +++ b/include/linux/wakeup_reason.h |
| @@ -0,0 +1,30 @@ |
| +/* |
| + * include/linux/wakeup_reason.h |
| + * |
| + * Logs the reason which caused the kernel to resume |
| + * from the suspend mode. |
| + * |
| + * Copyright (C) 2014 Google, Inc. |
| + * 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. |
| + */ |
| + |
| +#ifndef _LINUX_WAKEUP_REASON_H |
| +#define _LINUX_WAKEUP_REASON_H |
| + |
| +#define MAX_SUSPEND_ABORT_LEN 256 |
| + |
| +void log_wakeup_reason(int irq); |
| +#ifdef CONFIG_SUSPEND |
| +void log_suspend_abort_reason(const char *fmt, ...); |
| +#else |
| +static inline void log_suspend_abort_reason(const char *fmt, ...) { } |
| +#endif |
| + |
| +#endif /* _LINUX_WAKEUP_REASON_H */ |
| diff --git a/kernel/power/Makefile b/kernel/power/Makefile |
| --- a/kernel/power/Makefile |
| +++ b/kernel/power/Makefile |
| @@ -17,4 +17,5 @@ obj-$(CONFIG_PM_WAKELOCKS) += wakelock.o |
| |
| obj-$(CONFIG_MAGIC_SYSRQ) += poweroff.o |
| |
| +obj-$(CONFIG_SUSPEND) += wakeup_reason.o |
| obj-$(CONFIG_ENERGY_MODEL) += energy_model.o |
| diff --git a/kernel/power/process.c b/kernel/power/process.c |
| --- a/kernel/power/process.c |
| +++ b/kernel/power/process.c |
| @@ -22,6 +22,7 @@ |
| #include <linux/kmod.h> |
| #include <trace/events/power.h> |
| #include <linux/cpuset.h> |
| +#include <linux/wakeup_reason.h> |
| |
| /* |
| * Timeout for stopping processes |
| @@ -38,6 +39,9 @@ static int try_to_freeze_tasks(bool user_only) |
| unsigned int elapsed_msecs; |
| bool wakeup = false; |
| int sleep_usecs = USEC_PER_MSEC; |
| +#ifdef CONFIG_PM_SLEEP |
| + char suspend_abort[MAX_SUSPEND_ABORT_LEN]; |
| +#endif |
| |
| start = ktime_get_boottime(); |
| |
| @@ -67,6 +71,11 @@ static int try_to_freeze_tasks(bool user_only) |
| break; |
| |
| if (pm_wakeup_pending()) { |
| +#ifdef CONFIG_PM_SLEEP |
| + pm_get_active_wakeup_sources(suspend_abort, |
| + MAX_SUSPEND_ABORT_LEN); |
| + log_suspend_abort_reason(suspend_abort); |
| +#endif |
| wakeup = true; |
| break; |
| } |
| @@ -85,18 +94,21 @@ static int try_to_freeze_tasks(bool user_only) |
| elapsed = ktime_sub(end, start); |
| elapsed_msecs = ktime_to_ms(elapsed); |
| |
| - if (todo) { |
| + if (wakeup) { |
| pr_cont("\n"); |
| - pr_err("Freezing of tasks %s after %d.%03d seconds " |
| - "(%d tasks refusing to freeze, wq_busy=%d):\n", |
| - wakeup ? "aborted" : "failed", |
| + pr_err("Freezing of tasks aborted after %d.%03d seconds", |
| + elapsed_msecs / 1000, elapsed_msecs % 1000); |
| + } else if (todo) { |
| + pr_cont("\n"); |
| + pr_err("Freezing of tasks failed after %d.%03d seconds" |
| + " (%d tasks refusing to freeze, wq_busy=%d):\n", |
| elapsed_msecs / 1000, elapsed_msecs % 1000, |
| todo - wq_busy, wq_busy); |
| |
| if (wq_busy) |
| show_workqueue_state(); |
| |
| - if (!wakeup || pm_debug_messages_on) { |
| + if (pm_debug_messages_on) { |
| read_lock(&tasklist_lock); |
| for_each_process_thread(g, p) { |
| if (p != current && !freezer_should_skip(p) |
| diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c |
| --- a/kernel/power/suspend.c |
| +++ b/kernel/power/suspend.c |
| @@ -30,6 +30,7 @@ |
| #include <trace/events/power.h> |
| #include <linux/compiler.h> |
| #include <linux/moduleparam.h> |
| +#include <linux/wakeup_reason.h> |
| |
| #include "power.h" |
| |
| @@ -390,7 +391,8 @@ void __weak arch_suspend_enable_irqs(void) |
| */ |
| static int suspend_enter(suspend_state_t state, bool *wakeup) |
| { |
| - int error; |
| + char suspend_abort[MAX_SUSPEND_ABORT_LEN]; |
| + int error, last_dev; |
| |
| error = platform_suspend_prepare(state); |
| if (error) |
| @@ -398,7 +400,11 @@ static int suspend_enter(suspend_state_t state, bool *wakeup) |
| |
| error = dpm_suspend_late(PMSG_SUSPEND); |
| if (error) { |
| + last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1; |
| + last_dev %= REC_FAILED_NUM; |
| pr_err("late suspend of devices failed\n"); |
| + log_suspend_abort_reason("%s device failed to power down", |
| + suspend_stats.failed_devs[last_dev]); |
| goto Platform_finish; |
| } |
| error = platform_suspend_prepare_late(state); |
| @@ -407,7 +413,11 @@ static int suspend_enter(suspend_state_t state, bool *wakeup) |
| |
| error = dpm_suspend_noirq(PMSG_SUSPEND); |
| if (error) { |
| + last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1; |
| + last_dev %= REC_FAILED_NUM; |
| pr_err("noirq suspend of devices failed\n"); |
| + log_suspend_abort_reason("noirq suspend of %s device failed", |
| + suspend_stats.failed_devs[last_dev]); |
| goto Platform_early_resume; |
| } |
| error = platform_suspend_prepare_noirq(state); |
| @@ -423,8 +433,10 @@ static int suspend_enter(suspend_state_t state, bool *wakeup) |
| } |
| |
| error = suspend_disable_secondary_cpus(); |
| - if (error || suspend_test(TEST_CPUS)) |
| + if (error || suspend_test(TEST_CPUS)) { |
| + log_suspend_abort_reason("Disabling non-boot cpus failed"); |
| goto Enable_cpus; |
| + } |
| |
| arch_suspend_disable_irqs(); |
| BUG_ON(!irqs_disabled()); |
| @@ -441,6 +453,9 @@ static int suspend_enter(suspend_state_t state, bool *wakeup) |
| trace_suspend_resume(TPS("machine_suspend"), |
| state, false); |
| } else if (*wakeup) { |
| + pm_get_active_wakeup_sources(suspend_abort, |
| + MAX_SUSPEND_ABORT_LEN); |
| + log_suspend_abort_reason(suspend_abort); |
| error = -EBUSY; |
| } |
| syscore_resume(); |
| @@ -495,6 +510,7 @@ int suspend_devices_and_enter(suspend_state_t state) |
| error = dpm_suspend_start(PMSG_SUSPEND); |
| if (error) { |
| pr_err("Some devices failed to suspend, or early wake event detected\n"); |
| + log_suspend_abort_reason("Some devices failed to suspend, or early wake event detected"); |
| goto Recover_platform; |
| } |
| suspend_test_finish("suspend devices"); |
| diff --git a/kernel/power/wakeup_reason.c b/kernel/power/wakeup_reason.c |
| new file mode 100644 |
| index 000000000000..3ce5c902e3d3 |
| --- /dev/null |
| +++ b/kernel/power/wakeup_reason.c |
| @@ -0,0 +1,215 @@ |
| +/* |
| + * kernel/power/wakeup_reason.c |
| + * |
| + * Logs the reasons which caused the kernel to resume from |
| + * the suspend mode. |
| + * |
| + * Copyright (C) 2014 Google, Inc. |
| + * 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> |
| + |
| + |
| +#define MAX_WAKEUP_REASON_IRQS 32 |
| +static int irq_list[MAX_WAKEUP_REASON_IRQS]; |
| +static int irqcount; |
| +static bool suspend_abort; |
| +static char abort_reason[MAX_SUSPEND_ABORT_LEN]; |
| +static struct kobject *wakeup_reason; |
| +static spinlock_t resume_reason_lock; |
| + |
| +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 */ |
| + |
| +static ssize_t last_resume_reason_show(struct kobject *kobj, struct kobj_attribute *attr, |
| + char *buf) |
| +{ |
| + int irq_no, buf_offset = 0; |
| + struct irq_desc *desc; |
| + unsigned long flags; |
| + spin_lock_irqsave(&resume_reason_lock, flags); |
| + if (suspend_abort) { |
| + buf_offset = sprintf(buf, "Abort: %s", abort_reason); |
| + } else { |
| + for (irq_no = 0; irq_no < irqcount; irq_no++) { |
| + desc = irq_to_desc(irq_list[irq_no]); |
| + if (desc && desc->action && desc->action->name) |
| + buf_offset += sprintf(buf + buf_offset, "%d %s\n", |
| + irq_list[irq_no], desc->action->name); |
| + else |
| + buf_offset += sprintf(buf + buf_offset, "%d\n", |
| + irq_list[irq_no]); |
| + } |
| + } |
| + spin_unlock_irqrestore(&resume_reason_lock, flags); |
| + return buf_offset; |
| +} |
| + |
| +static ssize_t last_suspend_time_show(struct kobject *kobj, |
| + struct kobj_attribute *attr, char *buf) |
| +{ |
| + struct timespec64 sleep_time; |
| + struct timespec64 total_time; |
| + struct timespec64 suspend_resume_time; |
| + |
| + /* |
| + * total_time is calculated from monotonic bootoffsets because |
| + * unlike CLOCK_MONOTONIC it include the time spent in suspend state. |
| + */ |
| + total_time = ktime_to_timespec64(ktime_sub(curr_stime, last_stime)); |
| + |
| + /* |
| + * suspend_resume_time is calculated as monotonic (CLOCK_MONOTONIC) |
| + * time interval before entering suspend and post suspend. |
| + */ |
| + suspend_resume_time = |
| + ktime_to_timespec64(ktime_sub(curr_monotime, last_monotime)); |
| + |
| + /* sleep_time = total_time - suspend_resume_time */ |
| + sleep_time = timespec64_sub(total_time, suspend_resume_time); |
| + |
| + /* Export suspend_resume_time and sleep_time in pair here. */ |
| + return sprintf(buf, "%llu.%09lu %llu.%09lu\n", |
| + suspend_resume_time.tv_sec, suspend_resume_time.tv_nsec, |
| + sleep_time.tv_sec, sleep_time.tv_nsec); |
| +} |
| + |
| +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, |
| +}; |
| + |
| +/* |
| + * logs all the wake up reasons to the kernel |
| + * stores the irqs to expose them to the userspace via sysfs |
| + */ |
| +void log_wakeup_reason(int irq) |
| +{ |
| + struct irq_desc *desc; |
| + unsigned long flags; |
| + desc = irq_to_desc(irq); |
| + if (desc && desc->action && desc->action->name) |
| + printk(KERN_INFO "Resume caused by IRQ %d, %s\n", irq, |
| + desc->action->name); |
| + else |
| + printk(KERN_INFO "Resume caused by IRQ %d\n", irq); |
| + |
| + spin_lock_irqsave(&resume_reason_lock, flags); |
| + if (irqcount == MAX_WAKEUP_REASON_IRQS) { |
| + spin_unlock_irqrestore(&resume_reason_lock, flags); |
| + printk(KERN_WARNING "Resume caused by more than %d IRQs\n", |
| + MAX_WAKEUP_REASON_IRQS); |
| + return; |
| + } |
| + |
| + irq_list[irqcount++] = irq; |
| + spin_unlock_irqrestore(&resume_reason_lock, flags); |
| +} |
| + |
| +void log_suspend_abort_reason(const char *fmt, ...) |
| +{ |
| + unsigned long flags; |
| + va_list args; |
| + |
| + spin_lock_irqsave(&resume_reason_lock, flags); |
| + |
| + //Suspend abort reason has already been logged. |
| + if (suspend_abort) { |
| + spin_unlock_irqrestore(&resume_reason_lock, flags); |
| + return; |
| + } |
| + |
| + suspend_abort = true; |
| + va_start(args, fmt); |
| + vsnprintf(abort_reason, MAX_SUSPEND_ABORT_LEN, fmt, args); |
| + va_end(args); |
| + spin_unlock_irqrestore(&resume_reason_lock, flags); |
| +} |
| + |
| +/* 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) |
| +{ |
| + unsigned long flags; |
| + switch (pm_event) { |
| + case PM_SUSPEND_PREPARE: |
| + spin_lock_irqsave(&resume_reason_lock, flags); |
| + irqcount = 0; |
| + suspend_abort = false; |
| + spin_unlock_irqrestore(&resume_reason_lock, flags); |
| + /* monotonic time since boot */ |
| + last_monotime = ktime_get(); |
| + /* monotonic time since boot including the time spent in suspend */ |
| + last_stime = ktime_get_boottime(); |
| + 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(); |
| + break; |
| + default: |
| + break; |
| + } |
| + return NOTIFY_DONE; |
| +} |
| + |
| +static struct notifier_block wakeup_reason_pm_notifier_block = { |
| + .notifier_call = wakeup_reason_pm_event, |
| +}; |
| + |
| +/* Initializes the sysfs parameter |
| + * registers the pm_event notifier |
| + */ |
| +int __init wakeup_reason_init(void) |
| +{ |
| + int retval; |
| + spin_lock_init(&resume_reason_lock); |
| + retval = register_pm_notifier(&wakeup_reason_pm_notifier_block); |
| + if (retval) |
| + printk(KERN_WARNING "[%s] failed to register PM notifier %d\n", |
| + __func__, retval); |
| + |
| + wakeup_reason = kobject_create_and_add("wakeup_reasons", kernel_kobj); |
| + if (!wakeup_reason) { |
| + printk(KERN_WARNING "[%s] failed to create a sysfs kobject\n", |
| + __func__); |
| + return 1; |
| + } |
| + retval = sysfs_create_group(wakeup_reason, &attr_group); |
| + if (retval) { |
| + kobject_put(wakeup_reason); |
| + printk(KERN_WARNING "[%s] failed to create a sysfs group %d\n", |
| + __func__, retval); |
| + } |
| + return 0; |
| +} |
| + |
| +late_initcall(wakeup_reason_init); |