• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * kernel/power/wakeup_reason.c
3  *
4  * Logs the reasons which caused the kernel to resume from
5  * the suspend mode.
6  *
7  * Copyright (C) 2014 Google, Inc.
8  * This software is licensed under the terms of the GNU General Public
9  * License version 2, as published by the Free Software Foundation, and
10  * may be copied, distributed, and modified under those terms.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  */
17 
18 #include <linux/wakeup_reason.h>
19 #include <linux/kernel.h>
20 #include <linux/irq.h>
21 #include <linux/interrupt.h>
22 #include <linux/io.h>
23 #include <linux/kobject.h>
24 #include <linux/sysfs.h>
25 #include <linux/init.h>
26 #include <linux/spinlock.h>
27 #include <linux/notifier.h>
28 #include <linux/suspend.h>
29 
30 
31 #define MAX_WAKEUP_REASON_IRQS 32
32 static int irq_list[MAX_WAKEUP_REASON_IRQS];
33 static int irqcount;
34 static bool suspend_abort;
35 static char abort_reason[MAX_SUSPEND_ABORT_LEN];
36 static struct kobject *wakeup_reason;
37 static DEFINE_SPINLOCK(resume_reason_lock);
38 
39 static struct timespec last_xtime; /* wall time before last suspend */
40 static struct timespec curr_xtime; /* wall time after last suspend */
41 static struct timespec last_stime; /* total_sleep_time before last suspend */
42 static struct timespec curr_stime; /* total_sleep_time after last suspend */
43 
last_resume_reason_show(struct kobject * kobj,struct kobj_attribute * attr,char * buf)44 static ssize_t last_resume_reason_show(struct kobject *kobj, struct kobj_attribute *attr,
45 		char *buf)
46 {
47 	int irq_no, buf_offset = 0;
48 	struct irq_desc *desc;
49 	spin_lock(&resume_reason_lock);
50 	if (suspend_abort) {
51 		buf_offset = sprintf(buf, "Abort: %s", abort_reason);
52 	} else {
53 		for (irq_no = 0; irq_no < irqcount; irq_no++) {
54 			desc = irq_to_desc(irq_list[irq_no]);
55 			if (desc && desc->action && desc->action->name)
56 				buf_offset += sprintf(buf + buf_offset, "%d %s\n",
57 						irq_list[irq_no], desc->action->name);
58 			else
59 				buf_offset += sprintf(buf + buf_offset, "%d\n",
60 						irq_list[irq_no]);
61 		}
62 	}
63 	spin_unlock(&resume_reason_lock);
64 	return buf_offset;
65 }
66 
last_suspend_time_show(struct kobject * kobj,struct kobj_attribute * attr,char * buf)67 static ssize_t last_suspend_time_show(struct kobject *kobj,
68 			struct kobj_attribute *attr, char *buf)
69 {
70 	struct timespec sleep_time;
71 	struct timespec total_time;
72 	struct timespec suspend_resume_time;
73 
74 	sleep_time = timespec_sub(curr_stime, last_stime);
75 	total_time = timespec_sub(curr_xtime, last_xtime);
76 	suspend_resume_time = timespec_sub(total_time, sleep_time);
77 
78 	/*
79 	 * suspend_resume_time is calculated from sleep_time. Userspace would
80 	 * always need both. Export them in pair here.
81 	 */
82 	return sprintf(buf, "%lu.%09lu %lu.%09lu\n",
83 				suspend_resume_time.tv_sec, suspend_resume_time.tv_nsec,
84 				sleep_time.tv_sec, sleep_time.tv_nsec);
85 }
86 
87 static struct kobj_attribute resume_reason = __ATTR_RO(last_resume_reason);
88 static struct kobj_attribute suspend_time = __ATTR_RO(last_suspend_time);
89 
90 static struct attribute *attrs[] = {
91 	&resume_reason.attr,
92 	&suspend_time.attr,
93 	NULL,
94 };
95 static struct attribute_group attr_group = {
96 	.attrs = attrs,
97 };
98 
99 /*
100  * logs all the wake up reasons to the kernel
101  * stores the irqs to expose them to the userspace via sysfs
102  */
log_wakeup_reason(int irq)103 void log_wakeup_reason(int irq)
104 {
105 	struct irq_desc *desc;
106 	desc = irq_to_desc(irq);
107 	if (desc && desc->action && desc->action->name)
108 		printk(KERN_INFO "Resume caused by IRQ %d, %s\n", irq,
109 				desc->action->name);
110 	else
111 		printk(KERN_INFO "Resume caused by IRQ %d\n", irq);
112 
113 	spin_lock(&resume_reason_lock);
114 	if (irqcount == MAX_WAKEUP_REASON_IRQS) {
115 		spin_unlock(&resume_reason_lock);
116 		printk(KERN_WARNING "Resume caused by more than %d IRQs\n",
117 				MAX_WAKEUP_REASON_IRQS);
118 		return;
119 	}
120 
121 	irq_list[irqcount++] = irq;
122 	spin_unlock(&resume_reason_lock);
123 }
124 
check_wakeup_reason(int irq)125 int check_wakeup_reason(int irq)
126 {
127 	int irq_no;
128 	int ret = false;
129 
130 	spin_lock(&resume_reason_lock);
131 	for (irq_no = 0; irq_no < irqcount; irq_no++)
132 		if (irq_list[irq_no] == irq) {
133 			ret = true;
134 			break;
135 	}
136 	spin_unlock(&resume_reason_lock);
137 	return ret;
138 }
139 
log_suspend_abort_reason(const char * fmt,...)140 void log_suspend_abort_reason(const char *fmt, ...)
141 {
142 	va_list args;
143 
144 	spin_lock(&resume_reason_lock);
145 
146 	//Suspend abort reason has already been logged.
147 	if (suspend_abort) {
148 		spin_unlock(&resume_reason_lock);
149 		return;
150 	}
151 
152 	suspend_abort = true;
153 	va_start(args, fmt);
154 	vsnprintf(abort_reason, MAX_SUSPEND_ABORT_LEN, fmt, args);
155 	va_end(args);
156 	spin_unlock(&resume_reason_lock);
157 }
158 
159 /* Detects a suspend and clears all the previous wake up reasons*/
wakeup_reason_pm_event(struct notifier_block * notifier,unsigned long pm_event,void * unused)160 static int wakeup_reason_pm_event(struct notifier_block *notifier,
161 		unsigned long pm_event, void *unused)
162 {
163 	struct timespec xtom; /* wall_to_monotonic, ignored */
164 
165 	switch (pm_event) {
166 	case PM_SUSPEND_PREPARE:
167 		spin_lock(&resume_reason_lock);
168 		irqcount = 0;
169 		suspend_abort = false;
170 		spin_unlock(&resume_reason_lock);
171 
172 		get_xtime_and_monotonic_and_sleep_offset(&last_xtime, &xtom,
173 			&last_stime);
174 		break;
175 	case PM_POST_SUSPEND:
176 		get_xtime_and_monotonic_and_sleep_offset(&curr_xtime, &xtom,
177 			&curr_stime);
178 		break;
179 	default:
180 		break;
181 	}
182 	return NOTIFY_DONE;
183 }
184 
185 static struct notifier_block wakeup_reason_pm_notifier_block = {
186 	.notifier_call = wakeup_reason_pm_event,
187 };
188 
189 /* Initializes the sysfs parameter
190  * registers the pm_event notifier
191  */
wakeup_reason_init(void)192 int __init wakeup_reason_init(void)
193 {
194 	int retval;
195 
196 	retval = register_pm_notifier(&wakeup_reason_pm_notifier_block);
197 	if (retval)
198 		printk(KERN_WARNING "[%s] failed to register PM notifier %d\n",
199 				__func__, retval);
200 
201 	wakeup_reason = kobject_create_and_add("wakeup_reasons", kernel_kobj);
202 	if (!wakeup_reason) {
203 		printk(KERN_WARNING "[%s] failed to create a sysfs kobject\n",
204 				__func__);
205 		return 1;
206 	}
207 	retval = sysfs_create_group(wakeup_reason, &attr_group);
208 	if (retval) {
209 		kobject_put(wakeup_reason);
210 		printk(KERN_WARNING "[%s] failed to create a sysfs group %d\n",
211 				__func__, retval);
212 	}
213 	return 0;
214 }
215 
216 late_initcall(wakeup_reason_init);
217