1*4882a593Smuzhiyun /*
2*4882a593Smuzhiyun * kernel/power/wakeup_reason.c
3*4882a593Smuzhiyun *
4*4882a593Smuzhiyun * Logs the reasons which caused the kernel to resume from
5*4882a593Smuzhiyun * the suspend mode.
6*4882a593Smuzhiyun *
7*4882a593Smuzhiyun * Copyright (C) 2020 Google, Inc.
8*4882a593Smuzhiyun * This software is licensed under the terms of the GNU General Public
9*4882a593Smuzhiyun * License version 2, as published by the Free Software Foundation, and
10*4882a593Smuzhiyun * may be copied, distributed, and modified under those terms.
11*4882a593Smuzhiyun *
12*4882a593Smuzhiyun * This program is distributed in the hope that it will be useful,
13*4882a593Smuzhiyun * but WITHOUT ANY WARRANTY; without even the implied warranty of
14*4882a593Smuzhiyun * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15*4882a593Smuzhiyun * GNU General Public License for more details.
16*4882a593Smuzhiyun */
17*4882a593Smuzhiyun
18*4882a593Smuzhiyun #include <linux/wakeup_reason.h>
19*4882a593Smuzhiyun #include <linux/kernel.h>
20*4882a593Smuzhiyun #include <linux/irq.h>
21*4882a593Smuzhiyun #include <linux/interrupt.h>
22*4882a593Smuzhiyun #include <linux/io.h>
23*4882a593Smuzhiyun #include <linux/kobject.h>
24*4882a593Smuzhiyun #include <linux/sysfs.h>
25*4882a593Smuzhiyun #include <linux/init.h>
26*4882a593Smuzhiyun #include <linux/spinlock.h>
27*4882a593Smuzhiyun #include <linux/notifier.h>
28*4882a593Smuzhiyun #include <linux/suspend.h>
29*4882a593Smuzhiyun #include <linux/slab.h>
30*4882a593Smuzhiyun
31*4882a593Smuzhiyun /*
32*4882a593Smuzhiyun * struct wakeup_irq_node - stores data and relationships for IRQs logged as
33*4882a593Smuzhiyun * either base or nested wakeup reasons during suspend/resume flow.
34*4882a593Smuzhiyun * @siblings - for membership on leaf or parent IRQ lists
35*4882a593Smuzhiyun * @irq - the IRQ number
36*4882a593Smuzhiyun * @irq_name - the name associated with the IRQ, or a default if none
37*4882a593Smuzhiyun */
38*4882a593Smuzhiyun struct wakeup_irq_node {
39*4882a593Smuzhiyun struct list_head siblings;
40*4882a593Smuzhiyun int irq;
41*4882a593Smuzhiyun const char *irq_name;
42*4882a593Smuzhiyun };
43*4882a593Smuzhiyun
44*4882a593Smuzhiyun enum wakeup_reason_flag {
45*4882a593Smuzhiyun RESUME_NONE = 0,
46*4882a593Smuzhiyun RESUME_IRQ,
47*4882a593Smuzhiyun RESUME_ABORT,
48*4882a593Smuzhiyun RESUME_ABNORMAL,
49*4882a593Smuzhiyun };
50*4882a593Smuzhiyun
51*4882a593Smuzhiyun static DEFINE_SPINLOCK(wakeup_reason_lock);
52*4882a593Smuzhiyun
53*4882a593Smuzhiyun static LIST_HEAD(leaf_irqs); /* kept in ascending IRQ sorted order */
54*4882a593Smuzhiyun static LIST_HEAD(parent_irqs); /* unordered */
55*4882a593Smuzhiyun
56*4882a593Smuzhiyun static struct kmem_cache *wakeup_irq_nodes_cache;
57*4882a593Smuzhiyun
58*4882a593Smuzhiyun static const char *default_irq_name = "(unnamed)";
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun static struct kobject *kobj;
61*4882a593Smuzhiyun
62*4882a593Smuzhiyun static bool capture_reasons;
63*4882a593Smuzhiyun static int wakeup_reason;
64*4882a593Smuzhiyun static char non_irq_wake_reason[MAX_SUSPEND_ABORT_LEN];
65*4882a593Smuzhiyun
66*4882a593Smuzhiyun static ktime_t last_monotime; /* monotonic time before last suspend */
67*4882a593Smuzhiyun static ktime_t curr_monotime; /* monotonic time after last suspend */
68*4882a593Smuzhiyun static ktime_t last_stime; /* monotonic boottime offset before last suspend */
69*4882a593Smuzhiyun static ktime_t curr_stime; /* monotonic boottime offset after last suspend */
70*4882a593Smuzhiyun
init_node(struct wakeup_irq_node * p,int irq)71*4882a593Smuzhiyun static void init_node(struct wakeup_irq_node *p, int irq)
72*4882a593Smuzhiyun {
73*4882a593Smuzhiyun struct irq_desc *desc;
74*4882a593Smuzhiyun
75*4882a593Smuzhiyun INIT_LIST_HEAD(&p->siblings);
76*4882a593Smuzhiyun
77*4882a593Smuzhiyun p->irq = irq;
78*4882a593Smuzhiyun desc = irq_to_desc(irq);
79*4882a593Smuzhiyun if (desc && desc->action && desc->action->name)
80*4882a593Smuzhiyun p->irq_name = desc->action->name;
81*4882a593Smuzhiyun else
82*4882a593Smuzhiyun p->irq_name = default_irq_name;
83*4882a593Smuzhiyun }
84*4882a593Smuzhiyun
create_node(int irq)85*4882a593Smuzhiyun static struct wakeup_irq_node *create_node(int irq)
86*4882a593Smuzhiyun {
87*4882a593Smuzhiyun struct wakeup_irq_node *result;
88*4882a593Smuzhiyun
89*4882a593Smuzhiyun result = kmem_cache_alloc(wakeup_irq_nodes_cache, GFP_ATOMIC);
90*4882a593Smuzhiyun if (unlikely(!result))
91*4882a593Smuzhiyun pr_warn("Failed to log wakeup IRQ %d\n", irq);
92*4882a593Smuzhiyun else
93*4882a593Smuzhiyun init_node(result, irq);
94*4882a593Smuzhiyun
95*4882a593Smuzhiyun return result;
96*4882a593Smuzhiyun }
97*4882a593Smuzhiyun
delete_list(struct list_head * head)98*4882a593Smuzhiyun static void delete_list(struct list_head *head)
99*4882a593Smuzhiyun {
100*4882a593Smuzhiyun struct wakeup_irq_node *n;
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun while (!list_empty(head)) {
103*4882a593Smuzhiyun n = list_first_entry(head, struct wakeup_irq_node, siblings);
104*4882a593Smuzhiyun list_del(&n->siblings);
105*4882a593Smuzhiyun kmem_cache_free(wakeup_irq_nodes_cache, n);
106*4882a593Smuzhiyun }
107*4882a593Smuzhiyun }
108*4882a593Smuzhiyun
add_sibling_node_sorted(struct list_head * head,int irq)109*4882a593Smuzhiyun static bool add_sibling_node_sorted(struct list_head *head, int irq)
110*4882a593Smuzhiyun {
111*4882a593Smuzhiyun struct wakeup_irq_node *n = NULL;
112*4882a593Smuzhiyun struct list_head *predecessor = head;
113*4882a593Smuzhiyun
114*4882a593Smuzhiyun if (unlikely(WARN_ON(!head)))
115*4882a593Smuzhiyun return NULL;
116*4882a593Smuzhiyun
117*4882a593Smuzhiyun if (!list_empty(head))
118*4882a593Smuzhiyun list_for_each_entry(n, head, siblings) {
119*4882a593Smuzhiyun if (n->irq < irq)
120*4882a593Smuzhiyun predecessor = &n->siblings;
121*4882a593Smuzhiyun else if (n->irq == irq)
122*4882a593Smuzhiyun return true;
123*4882a593Smuzhiyun else
124*4882a593Smuzhiyun break;
125*4882a593Smuzhiyun }
126*4882a593Smuzhiyun
127*4882a593Smuzhiyun n = create_node(irq);
128*4882a593Smuzhiyun if (n) {
129*4882a593Smuzhiyun list_add(&n->siblings, predecessor);
130*4882a593Smuzhiyun return true;
131*4882a593Smuzhiyun }
132*4882a593Smuzhiyun
133*4882a593Smuzhiyun return false;
134*4882a593Smuzhiyun }
135*4882a593Smuzhiyun
find_node_in_list(struct list_head * head,int irq)136*4882a593Smuzhiyun static struct wakeup_irq_node *find_node_in_list(struct list_head *head,
137*4882a593Smuzhiyun int irq)
138*4882a593Smuzhiyun {
139*4882a593Smuzhiyun struct wakeup_irq_node *n;
140*4882a593Smuzhiyun
141*4882a593Smuzhiyun if (unlikely(WARN_ON(!head)))
142*4882a593Smuzhiyun return NULL;
143*4882a593Smuzhiyun
144*4882a593Smuzhiyun list_for_each_entry(n, head, siblings)
145*4882a593Smuzhiyun if (n->irq == irq)
146*4882a593Smuzhiyun return n;
147*4882a593Smuzhiyun
148*4882a593Smuzhiyun return NULL;
149*4882a593Smuzhiyun }
150*4882a593Smuzhiyun
log_irq_wakeup_reason(int irq)151*4882a593Smuzhiyun void log_irq_wakeup_reason(int irq)
152*4882a593Smuzhiyun {
153*4882a593Smuzhiyun unsigned long flags;
154*4882a593Smuzhiyun
155*4882a593Smuzhiyun spin_lock_irqsave(&wakeup_reason_lock, flags);
156*4882a593Smuzhiyun if (wakeup_reason == RESUME_ABNORMAL || wakeup_reason == RESUME_ABORT) {
157*4882a593Smuzhiyun spin_unlock_irqrestore(&wakeup_reason_lock, flags);
158*4882a593Smuzhiyun return;
159*4882a593Smuzhiyun }
160*4882a593Smuzhiyun
161*4882a593Smuzhiyun if (!capture_reasons) {
162*4882a593Smuzhiyun spin_unlock_irqrestore(&wakeup_reason_lock, flags);
163*4882a593Smuzhiyun return;
164*4882a593Smuzhiyun }
165*4882a593Smuzhiyun
166*4882a593Smuzhiyun if (find_node_in_list(&parent_irqs, irq) == NULL)
167*4882a593Smuzhiyun add_sibling_node_sorted(&leaf_irqs, irq);
168*4882a593Smuzhiyun
169*4882a593Smuzhiyun wakeup_reason = RESUME_IRQ;
170*4882a593Smuzhiyun spin_unlock_irqrestore(&wakeup_reason_lock, flags);
171*4882a593Smuzhiyun }
172*4882a593Smuzhiyun
log_threaded_irq_wakeup_reason(int irq,int parent_irq)173*4882a593Smuzhiyun void log_threaded_irq_wakeup_reason(int irq, int parent_irq)
174*4882a593Smuzhiyun {
175*4882a593Smuzhiyun struct wakeup_irq_node *parent;
176*4882a593Smuzhiyun unsigned long flags;
177*4882a593Smuzhiyun
178*4882a593Smuzhiyun /*
179*4882a593Smuzhiyun * Intentionally unsynchronized. Calls that come in after we have
180*4882a593Smuzhiyun * resumed should have a fast exit path since there's no work to be
181*4882a593Smuzhiyun * done, any any coherence issue that could cause a wrong value here is
182*4882a593Smuzhiyun * both highly improbable - given the set/clear timing - and very low
183*4882a593Smuzhiyun * impact (parent IRQ gets logged instead of the specific child).
184*4882a593Smuzhiyun */
185*4882a593Smuzhiyun if (!capture_reasons)
186*4882a593Smuzhiyun return;
187*4882a593Smuzhiyun
188*4882a593Smuzhiyun spin_lock_irqsave(&wakeup_reason_lock, flags);
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun if (wakeup_reason == RESUME_ABNORMAL || wakeup_reason == RESUME_ABORT) {
191*4882a593Smuzhiyun spin_unlock_irqrestore(&wakeup_reason_lock, flags);
192*4882a593Smuzhiyun return;
193*4882a593Smuzhiyun }
194*4882a593Smuzhiyun
195*4882a593Smuzhiyun if (!capture_reasons || (find_node_in_list(&leaf_irqs, irq) != NULL)) {
196*4882a593Smuzhiyun spin_unlock_irqrestore(&wakeup_reason_lock, flags);
197*4882a593Smuzhiyun return;
198*4882a593Smuzhiyun }
199*4882a593Smuzhiyun
200*4882a593Smuzhiyun parent = find_node_in_list(&parent_irqs, parent_irq);
201*4882a593Smuzhiyun if (parent != NULL)
202*4882a593Smuzhiyun add_sibling_node_sorted(&leaf_irqs, irq);
203*4882a593Smuzhiyun else {
204*4882a593Smuzhiyun parent = find_node_in_list(&leaf_irqs, parent_irq);
205*4882a593Smuzhiyun if (parent != NULL) {
206*4882a593Smuzhiyun list_del_init(&parent->siblings);
207*4882a593Smuzhiyun list_add_tail(&parent->siblings, &parent_irqs);
208*4882a593Smuzhiyun add_sibling_node_sorted(&leaf_irqs, irq);
209*4882a593Smuzhiyun }
210*4882a593Smuzhiyun }
211*4882a593Smuzhiyun
212*4882a593Smuzhiyun spin_unlock_irqrestore(&wakeup_reason_lock, flags);
213*4882a593Smuzhiyun }
214*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(log_threaded_irq_wakeup_reason);
215*4882a593Smuzhiyun
__log_abort_or_abnormal_wake(bool abort,const char * fmt,va_list args)216*4882a593Smuzhiyun static void __log_abort_or_abnormal_wake(bool abort, const char *fmt,
217*4882a593Smuzhiyun va_list args)
218*4882a593Smuzhiyun {
219*4882a593Smuzhiyun unsigned long flags;
220*4882a593Smuzhiyun
221*4882a593Smuzhiyun spin_lock_irqsave(&wakeup_reason_lock, flags);
222*4882a593Smuzhiyun
223*4882a593Smuzhiyun /* Suspend abort or abnormal wake reason has already been logged. */
224*4882a593Smuzhiyun if (wakeup_reason != RESUME_NONE) {
225*4882a593Smuzhiyun spin_unlock_irqrestore(&wakeup_reason_lock, flags);
226*4882a593Smuzhiyun return;
227*4882a593Smuzhiyun }
228*4882a593Smuzhiyun
229*4882a593Smuzhiyun if (abort)
230*4882a593Smuzhiyun wakeup_reason = RESUME_ABORT;
231*4882a593Smuzhiyun else
232*4882a593Smuzhiyun wakeup_reason = RESUME_ABNORMAL;
233*4882a593Smuzhiyun
234*4882a593Smuzhiyun vsnprintf(non_irq_wake_reason, MAX_SUSPEND_ABORT_LEN, fmt, args);
235*4882a593Smuzhiyun
236*4882a593Smuzhiyun spin_unlock_irqrestore(&wakeup_reason_lock, flags);
237*4882a593Smuzhiyun }
238*4882a593Smuzhiyun
log_suspend_abort_reason(const char * fmt,...)239*4882a593Smuzhiyun void log_suspend_abort_reason(const char *fmt, ...)
240*4882a593Smuzhiyun {
241*4882a593Smuzhiyun va_list args;
242*4882a593Smuzhiyun
243*4882a593Smuzhiyun va_start(args, fmt);
244*4882a593Smuzhiyun __log_abort_or_abnormal_wake(true, fmt, args);
245*4882a593Smuzhiyun va_end(args);
246*4882a593Smuzhiyun }
247*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(log_suspend_abort_reason);
248*4882a593Smuzhiyun
log_abnormal_wakeup_reason(const char * fmt,...)249*4882a593Smuzhiyun void log_abnormal_wakeup_reason(const char *fmt, ...)
250*4882a593Smuzhiyun {
251*4882a593Smuzhiyun va_list args;
252*4882a593Smuzhiyun
253*4882a593Smuzhiyun va_start(args, fmt);
254*4882a593Smuzhiyun __log_abort_or_abnormal_wake(false, fmt, args);
255*4882a593Smuzhiyun va_end(args);
256*4882a593Smuzhiyun }
257*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(log_abnormal_wakeup_reason);
258*4882a593Smuzhiyun
clear_wakeup_reasons(void)259*4882a593Smuzhiyun void clear_wakeup_reasons(void)
260*4882a593Smuzhiyun {
261*4882a593Smuzhiyun unsigned long flags;
262*4882a593Smuzhiyun
263*4882a593Smuzhiyun spin_lock_irqsave(&wakeup_reason_lock, flags);
264*4882a593Smuzhiyun
265*4882a593Smuzhiyun delete_list(&leaf_irqs);
266*4882a593Smuzhiyun delete_list(&parent_irqs);
267*4882a593Smuzhiyun wakeup_reason = RESUME_NONE;
268*4882a593Smuzhiyun capture_reasons = true;
269*4882a593Smuzhiyun
270*4882a593Smuzhiyun spin_unlock_irqrestore(&wakeup_reason_lock, flags);
271*4882a593Smuzhiyun }
272*4882a593Smuzhiyun
print_wakeup_sources(void)273*4882a593Smuzhiyun static void print_wakeup_sources(void)
274*4882a593Smuzhiyun {
275*4882a593Smuzhiyun struct wakeup_irq_node *n;
276*4882a593Smuzhiyun unsigned long flags;
277*4882a593Smuzhiyun
278*4882a593Smuzhiyun spin_lock_irqsave(&wakeup_reason_lock, flags);
279*4882a593Smuzhiyun
280*4882a593Smuzhiyun capture_reasons = false;
281*4882a593Smuzhiyun
282*4882a593Smuzhiyun if (wakeup_reason == RESUME_ABORT) {
283*4882a593Smuzhiyun pr_info("Abort: %s\n", non_irq_wake_reason);
284*4882a593Smuzhiyun spin_unlock_irqrestore(&wakeup_reason_lock, flags);
285*4882a593Smuzhiyun return;
286*4882a593Smuzhiyun }
287*4882a593Smuzhiyun
288*4882a593Smuzhiyun if (wakeup_reason == RESUME_IRQ && !list_empty(&leaf_irqs))
289*4882a593Smuzhiyun list_for_each_entry(n, &leaf_irqs, siblings)
290*4882a593Smuzhiyun pr_info("Resume caused by IRQ %d, %s\n", n->irq,
291*4882a593Smuzhiyun n->irq_name);
292*4882a593Smuzhiyun else if (wakeup_reason == RESUME_ABNORMAL)
293*4882a593Smuzhiyun pr_info("Resume caused by %s\n", non_irq_wake_reason);
294*4882a593Smuzhiyun else
295*4882a593Smuzhiyun pr_info("Resume cause unknown\n");
296*4882a593Smuzhiyun
297*4882a593Smuzhiyun spin_unlock_irqrestore(&wakeup_reason_lock, flags);
298*4882a593Smuzhiyun }
299*4882a593Smuzhiyun
last_resume_reason_show(struct kobject * kobj,struct kobj_attribute * attr,char * buf)300*4882a593Smuzhiyun static ssize_t last_resume_reason_show(struct kobject *kobj,
301*4882a593Smuzhiyun struct kobj_attribute *attr, char *buf)
302*4882a593Smuzhiyun {
303*4882a593Smuzhiyun ssize_t buf_offset = 0;
304*4882a593Smuzhiyun struct wakeup_irq_node *n;
305*4882a593Smuzhiyun unsigned long flags;
306*4882a593Smuzhiyun
307*4882a593Smuzhiyun spin_lock_irqsave(&wakeup_reason_lock, flags);
308*4882a593Smuzhiyun
309*4882a593Smuzhiyun if (wakeup_reason == RESUME_ABORT) {
310*4882a593Smuzhiyun buf_offset = scnprintf(buf, PAGE_SIZE, "Abort: %s",
311*4882a593Smuzhiyun non_irq_wake_reason);
312*4882a593Smuzhiyun spin_unlock_irqrestore(&wakeup_reason_lock, flags);
313*4882a593Smuzhiyun return buf_offset;
314*4882a593Smuzhiyun }
315*4882a593Smuzhiyun
316*4882a593Smuzhiyun if (wakeup_reason == RESUME_IRQ && !list_empty(&leaf_irqs))
317*4882a593Smuzhiyun list_for_each_entry(n, &leaf_irqs, siblings)
318*4882a593Smuzhiyun buf_offset += scnprintf(buf + buf_offset,
319*4882a593Smuzhiyun PAGE_SIZE - buf_offset,
320*4882a593Smuzhiyun "%d %s\n", n->irq, n->irq_name);
321*4882a593Smuzhiyun else if (wakeup_reason == RESUME_ABNORMAL)
322*4882a593Smuzhiyun buf_offset = scnprintf(buf, PAGE_SIZE, "-1 %s",
323*4882a593Smuzhiyun non_irq_wake_reason);
324*4882a593Smuzhiyun
325*4882a593Smuzhiyun spin_unlock_irqrestore(&wakeup_reason_lock, flags);
326*4882a593Smuzhiyun
327*4882a593Smuzhiyun return buf_offset;
328*4882a593Smuzhiyun }
329*4882a593Smuzhiyun
last_suspend_time_show(struct kobject * kobj,struct kobj_attribute * attr,char * buf)330*4882a593Smuzhiyun static ssize_t last_suspend_time_show(struct kobject *kobj,
331*4882a593Smuzhiyun struct kobj_attribute *attr, char *buf)
332*4882a593Smuzhiyun {
333*4882a593Smuzhiyun struct timespec64 sleep_time;
334*4882a593Smuzhiyun struct timespec64 total_time;
335*4882a593Smuzhiyun struct timespec64 suspend_resume_time;
336*4882a593Smuzhiyun
337*4882a593Smuzhiyun /*
338*4882a593Smuzhiyun * total_time is calculated from monotonic bootoffsets because
339*4882a593Smuzhiyun * unlike CLOCK_MONOTONIC it include the time spent in suspend state.
340*4882a593Smuzhiyun */
341*4882a593Smuzhiyun total_time = ktime_to_timespec64(ktime_sub(curr_stime, last_stime));
342*4882a593Smuzhiyun
343*4882a593Smuzhiyun /*
344*4882a593Smuzhiyun * suspend_resume_time is calculated as monotonic (CLOCK_MONOTONIC)
345*4882a593Smuzhiyun * time interval before entering suspend and post suspend.
346*4882a593Smuzhiyun */
347*4882a593Smuzhiyun suspend_resume_time =
348*4882a593Smuzhiyun ktime_to_timespec64(ktime_sub(curr_monotime, last_monotime));
349*4882a593Smuzhiyun
350*4882a593Smuzhiyun /* sleep_time = total_time - suspend_resume_time */
351*4882a593Smuzhiyun sleep_time = timespec64_sub(total_time, suspend_resume_time);
352*4882a593Smuzhiyun
353*4882a593Smuzhiyun /* Export suspend_resume_time and sleep_time in pair here. */
354*4882a593Smuzhiyun return sprintf(buf, "%llu.%09lu %llu.%09lu\n",
355*4882a593Smuzhiyun (unsigned long long)suspend_resume_time.tv_sec,
356*4882a593Smuzhiyun suspend_resume_time.tv_nsec,
357*4882a593Smuzhiyun (unsigned long long)sleep_time.tv_sec,
358*4882a593Smuzhiyun sleep_time.tv_nsec);
359*4882a593Smuzhiyun }
360*4882a593Smuzhiyun
361*4882a593Smuzhiyun static struct kobj_attribute resume_reason = __ATTR_RO(last_resume_reason);
362*4882a593Smuzhiyun static struct kobj_attribute suspend_time = __ATTR_RO(last_suspend_time);
363*4882a593Smuzhiyun
364*4882a593Smuzhiyun static struct attribute *attrs[] = {
365*4882a593Smuzhiyun &resume_reason.attr,
366*4882a593Smuzhiyun &suspend_time.attr,
367*4882a593Smuzhiyun NULL,
368*4882a593Smuzhiyun };
369*4882a593Smuzhiyun static struct attribute_group attr_group = {
370*4882a593Smuzhiyun .attrs = attrs,
371*4882a593Smuzhiyun };
372*4882a593Smuzhiyun
373*4882a593Smuzhiyun /* 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)374*4882a593Smuzhiyun static int wakeup_reason_pm_event(struct notifier_block *notifier,
375*4882a593Smuzhiyun unsigned long pm_event, void *unused)
376*4882a593Smuzhiyun {
377*4882a593Smuzhiyun switch (pm_event) {
378*4882a593Smuzhiyun case PM_SUSPEND_PREPARE:
379*4882a593Smuzhiyun /* monotonic time since boot */
380*4882a593Smuzhiyun last_monotime = ktime_get();
381*4882a593Smuzhiyun /* monotonic time since boot including the time spent in suspend */
382*4882a593Smuzhiyun last_stime = ktime_get_boottime();
383*4882a593Smuzhiyun clear_wakeup_reasons();
384*4882a593Smuzhiyun break;
385*4882a593Smuzhiyun case PM_POST_SUSPEND:
386*4882a593Smuzhiyun /* monotonic time since boot */
387*4882a593Smuzhiyun curr_monotime = ktime_get();
388*4882a593Smuzhiyun /* monotonic time since boot including the time spent in suspend */
389*4882a593Smuzhiyun curr_stime = ktime_get_boottime();
390*4882a593Smuzhiyun print_wakeup_sources();
391*4882a593Smuzhiyun break;
392*4882a593Smuzhiyun default:
393*4882a593Smuzhiyun break;
394*4882a593Smuzhiyun }
395*4882a593Smuzhiyun return NOTIFY_DONE;
396*4882a593Smuzhiyun }
397*4882a593Smuzhiyun
398*4882a593Smuzhiyun static struct notifier_block wakeup_reason_pm_notifier_block = {
399*4882a593Smuzhiyun .notifier_call = wakeup_reason_pm_event,
400*4882a593Smuzhiyun };
401*4882a593Smuzhiyun
wakeup_reason_init(void)402*4882a593Smuzhiyun static int __init wakeup_reason_init(void)
403*4882a593Smuzhiyun {
404*4882a593Smuzhiyun if (register_pm_notifier(&wakeup_reason_pm_notifier_block)) {
405*4882a593Smuzhiyun pr_warn("[%s] failed to register PM notifier\n", __func__);
406*4882a593Smuzhiyun goto fail;
407*4882a593Smuzhiyun }
408*4882a593Smuzhiyun
409*4882a593Smuzhiyun kobj = kobject_create_and_add("wakeup_reasons", kernel_kobj);
410*4882a593Smuzhiyun if (!kobj) {
411*4882a593Smuzhiyun pr_warn("[%s] failed to create a sysfs kobject\n", __func__);
412*4882a593Smuzhiyun goto fail_unregister_pm_notifier;
413*4882a593Smuzhiyun }
414*4882a593Smuzhiyun
415*4882a593Smuzhiyun if (sysfs_create_group(kobj, &attr_group)) {
416*4882a593Smuzhiyun pr_warn("[%s] failed to create a sysfs group\n", __func__);
417*4882a593Smuzhiyun goto fail_kobject_put;
418*4882a593Smuzhiyun }
419*4882a593Smuzhiyun
420*4882a593Smuzhiyun wakeup_irq_nodes_cache =
421*4882a593Smuzhiyun kmem_cache_create("wakeup_irq_node_cache",
422*4882a593Smuzhiyun sizeof(struct wakeup_irq_node), 0, 0, NULL);
423*4882a593Smuzhiyun if (!wakeup_irq_nodes_cache)
424*4882a593Smuzhiyun goto fail_remove_group;
425*4882a593Smuzhiyun
426*4882a593Smuzhiyun return 0;
427*4882a593Smuzhiyun
428*4882a593Smuzhiyun fail_remove_group:
429*4882a593Smuzhiyun sysfs_remove_group(kobj, &attr_group);
430*4882a593Smuzhiyun fail_kobject_put:
431*4882a593Smuzhiyun kobject_put(kobj);
432*4882a593Smuzhiyun fail_unregister_pm_notifier:
433*4882a593Smuzhiyun unregister_pm_notifier(&wakeup_reason_pm_notifier_block);
434*4882a593Smuzhiyun fail:
435*4882a593Smuzhiyun return 1;
436*4882a593Smuzhiyun }
437*4882a593Smuzhiyun
438*4882a593Smuzhiyun late_initcall(wakeup_reason_init);
439