Add API log_wakeup_reason() and expose it to userspace via sysfs path
/sys/kernel/wakeup_reasons/last_resume_reason
This is useful for power management diagnostic purposes.

Signed-off-by: Ruchi Kandoi <kandoiru...@google.com>
Signed-off-by: Greg Hackmann <ghackm...@google.com>
---
 include/linux/wakeup_reason.h |  23 +++++++
 kernel/power/Makefile         |   2 +-
 kernel/power/wakeup_reason.c  | 140 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 164 insertions(+), 1 deletion(-)
 create mode 100644 include/linux/wakeup_reason.h
 create mode 100644 kernel/power/wakeup_reason.c

diff --git a/include/linux/wakeup_reason.h b/include/linux/wakeup_reason.h
new file mode 100644
index 0000000..7ce50f0
--- /dev/null
+++ b/include/linux/wakeup_reason.h
@@ -0,0 +1,23 @@
+/*
+ * 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
+
+void log_wakeup_reason(int irq);
+
+#endif /* _LINUX_WAKEUP_REASON_H */
diff --git a/kernel/power/Makefile b/kernel/power/Makefile
index 29472bf..f98f021 100644
--- a/kernel/power/Makefile
+++ b/kernel/power/Makefile
@@ -5,7 +5,7 @@ obj-y                           += qos.o
 obj-$(CONFIG_PM)               += main.o
 obj-$(CONFIG_VT_CONSOLE_SLEEP) += console.o
 obj-$(CONFIG_FREEZER)          += process.o
-obj-$(CONFIG_SUSPEND)          += suspend.o
+obj-$(CONFIG_SUSPEND)          += suspend.o wakeup_reason.o
 obj-$(CONFIG_PM_TEST_SUSPEND)  += suspend_test.o
 obj-$(CONFIG_HIBERNATION)      += hibernate.o snapshot.o swap.o user.o \
                                   block_io.o
diff --git a/kernel/power/wakeup_reason.c b/kernel/power/wakeup_reason.c
new file mode 100644
index 0000000..188a6bf
--- /dev/null
+++ b/kernel/power/wakeup_reason.c
@@ -0,0 +1,140 @@
+/*
+ * 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 struct kobject *wakeup_reason;
+static spinlock_t resume_reason_lock;
+
+static ssize_t reason_show(struct kobject *kobj, struct kobj_attribute *attr,
+               char *buf)
+{
+       int irq_no, buf_offset = 0;
+       struct irq_desc *desc;
+       spin_lock(&resume_reason_lock);
+       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(&resume_reason_lock);
+       return buf_offset;
+}
+
+static struct kobj_attribute resume_reason = __ATTR(last_resume_reason, 0666,
+               reason_show, NULL);
+
+static struct attribute *attrs[] = {
+       &resume_reason.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;
+       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(&resume_reason_lock);
+       if (irqcount == MAX_WAKEUP_REASON_IRQS) {
+               spin_unlock(&resume_reason_lock);
+               printk(KERN_WARNING "Resume caused by more than %d IRQs\n",
+                               MAX_WAKEUP_REASON_IRQS);
+               return;
+       }
+
+       irq_list[irqcount++] = irq;
+       spin_unlock(&resume_reason_lock);
+}
+
+/* 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)
+{
+       switch (pm_event) {
+       case PM_SUSPEND_PREPARE:
+               spin_lock(&resume_reason_lock);
+               irqcount = 0;
+               spin_unlock(&resume_reason_lock);
+               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);
-- 
1.9.0.279.gdc9e3eb

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to