/* * 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 #include #include #include #include #include #include #include #include #include #include #include #define MAX_WAKEUP_REASON_IRQS 32 static bool suspend_abort; static char abort_reason[MAX_SUSPEND_ABORT_LEN]; static struct wakeup_irq_node *base_irq_nodes; static struct wakeup_irq_node *cur_irq_tree; static int cur_irq_tree_depth; static LIST_HEAD(wakeup_irqs); static struct kmem_cache *wakeup_irq_nodes_cache; static struct kobject *wakeup_reason; static spinlock_t resume_reason_lock; bool log_wakeups __read_mostly; struct completion wakeups_completion; static struct timespec last_xtime; /* wall time before last suspend */ static struct timespec curr_xtime; /* wall time after last suspend */ static struct timespec last_stime; /* sleep time before last suspend */ static struct timespec curr_stime; /* sleep time after last suspend */ static struct timespec total_xtime; /* total suspend time since boot */ static struct timespec total_stime; /* total sleep time since boot */ static struct timespec total_atime; /* total suspend abort time since boot */ static unsigned long suspend_count; /* total amount of resumes */ static unsigned long abort_count; /* total amount of suspend abort */ static void init_wakeup_irq_node(struct wakeup_irq_node *p, int irq) { p->irq = irq; p->desc = irq_to_desc(irq); p->child = NULL; p->parent = NULL; p->handled = false; INIT_LIST_HEAD(&p->siblings); INIT_LIST_HEAD(&p->next); } static struct wakeup_irq_node* alloc_irq_node(int irq) { struct wakeup_irq_node *n; n = kmem_cache_alloc(wakeup_irq_nodes_cache, GFP_ATOMIC); if (!n) { pr_warning("Failed to log chained wakeup IRQ %d\n", irq); return NULL; } init_wakeup_irq_node(n, irq); return n; } static struct wakeup_irq_node * search_siblings(struct wakeup_irq_node *root, int irq) { bool found = false; struct wakeup_irq_node *n = NULL; BUG_ON(!root); if (root->irq == irq) return root; list_for_each_entry(n, &root->siblings, siblings) { if (n->irq == irq) { found = true; break; } } return found ? n : NULL; } static struct wakeup_irq_node * add_to_siblings(struct wakeup_irq_node *root, int irq) { struct wakeup_irq_node *n; if (root) { n = search_siblings(root, irq); if (n) return n; } n = alloc_irq_node(irq); if (n && root) list_add(&n->siblings, &root->siblings); return n; } #ifdef CONFIG_DEDUCE_WAKEUP_REASONS static struct wakeup_irq_node* add_child(struct wakeup_irq_node *root, int irq) { if (!root->child) { root->child = alloc_irq_node(irq); if (!root->child) return NULL; root->child->parent = root; return root->child; } return add_to_siblings(root->child, irq); } static struct wakeup_irq_node *find_first_sibling(struct wakeup_irq_node *node) { struct wakeup_irq_node *n; if (node->parent) return node; list_for_each_entry(n, &node->siblings, siblings) { if (n->parent) return n; } return NULL; } static struct wakeup_irq_node * get_base_node(struct wakeup_irq_node *node, unsigned depth) { if (!node) return NULL; while (depth) { node = find_first_sibling(node); BUG_ON(!node); node = node->parent; depth--; } return node; } #endif /* CONFIG_DEDUCE_WAKEUP_REASONS */ static const struct list_head* get_wakeup_reasons_nosync(void); static void print_wakeup_sources(void) { struct wakeup_irq_node *n; const struct list_head *wakeups; if (suspend_abort) { pr_info("Abort: %s", abort_reason); return; } wakeups = get_wakeup_reasons_nosync(); list_for_each_entry(n, wakeups, next) { if (n->desc && n->desc->action && n->desc->action->name) pr_info("Resume caused by IRQ %d, %s\n", n->irq, n->desc->action->name); else pr_info("Resume caused by IRQ %d\n", n->irq); } } static bool walk_irq_node_tree(struct wakeup_irq_node *root, bool (*visit)(struct wakeup_irq_node *, void *), void *cookie) { struct wakeup_irq_node *n, *t; if (!root) return true; list_for_each_entry_safe(n, t, &root->siblings, siblings) { if (!walk_irq_node_tree(n->child, visit, cookie)) return false; if (!visit(n, cookie)) return false; } if (!walk_irq_node_tree(root->child, visit, cookie)) return false; return visit(root, cookie); } #ifdef CONFIG_DEDUCE_WAKEUP_REASONS static bool is_node_handled(struct wakeup_irq_node *n, void *_p) { return n->handled; } static bool base_irq_nodes_done(void) { return walk_irq_node_tree(base_irq_nodes, is_node_handled, NULL); } #endif struct buf_cookie { char *buf; int buf_offset; }; static bool print_leaf_node(struct wakeup_irq_node *n, void *_p) { struct buf_cookie *b = _p; if (!n->child) { if (n->desc && n->desc->action && n->desc->action->name) b->buf_offset += snprintf(b->buf + b->buf_offset, PAGE_SIZE - b->buf_offset, "%d %s\n", n->irq, n->desc->action->name); else b->buf_offset += snprintf(b->buf + b->buf_offset, PAGE_SIZE - b->buf_offset, "%d\n", n->irq); } return true; } static ssize_t last_resume_reason_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { unsigned long flags; struct buf_cookie b = { .buf = buf, .buf_offset = 0 }; spin_lock_irqsave(&resume_reason_lock, flags); if (suspend_abort) b.buf_offset = snprintf(buf, PAGE_SIZE, "Abort: %s", abort_reason); else walk_irq_node_tree(base_irq_nodes, print_leaf_node, &b); spin_unlock_irqrestore(&resume_reason_lock, flags); return b.buf_offset; } static ssize_t last_suspend_time_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct timespec sleep_time; struct timespec total_time; struct timespec suspend_resume_time; sleep_time = timespec_sub(curr_stime, last_stime); total_time = timespec_sub(curr_xtime, last_xtime); suspend_resume_time = timespec_sub(total_time, sleep_time); /* * suspend_resume_time is calculated from sleep_time. Userspace would * always need both. Export them in pair here. */ return sprintf(buf, "%lu.%09lu %lu.%09lu\n", suspend_resume_time.tv_sec, suspend_resume_time.tv_nsec, sleep_time.tv_sec, sleep_time.tv_nsec); } static ssize_t suspend_since_boot_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct timespec xtime; xtime = timespec_sub(total_xtime, total_stime); return sprintf(buf, "%lu %lu %lu.%09lu %lu.%09lu %lu.%09lu\n", suspend_count, abort_count, xtime.tv_sec, xtime.tv_nsec, total_atime.tv_sec, total_atime.tv_nsec, total_stime.tv_sec, total_stime.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 kobj_attribute suspend_since_boot = __ATTR_RO(suspend_since_boot); static struct attribute *attrs[] = { &resume_reason.attr, &suspend_time.attr, &suspend_since_boot.attr, NULL, }; static struct attribute_group attr_group = { .attrs = attrs, }; static inline void stop_logging_wakeup_reasons(void) { ACCESS_ONCE(log_wakeups) = false; smp_wmb(); } /* * stores the immediate wakeup irqs; these often aren't the ones seen by * the drivers that registered them, due to chained interrupt controllers, * and multiple-interrupt dispatch. */ void log_base_wakeup_reason(int irq) { /* No locking is needed, since this function is called within * syscore_resume, with both nonboot CPUs and interrupts disabled. */ base_irq_nodes = add_to_siblings(base_irq_nodes, irq); BUG_ON(!base_irq_nodes); #ifndef CONFIG_DEDUCE_WAKEUP_REASONS base_irq_nodes->handled = true; #endif } #ifdef CONFIG_DEDUCE_WAKEUP_REASONS /* This function is called by generic_handle_irq, which may call itself * recursively. This happens with interrupts disabled. Using * log_possible_wakeup_reason, we build a tree of interrupts, tracing the call * stack of generic_handle_irq, for each wakeup source containing the * interrupts actually handled. * * Most of these "trees" would either have a single node (in the event that the * wakeup source is the final interrupt), or consist of a list of two * interrupts, with the wakeup source at the root, and the final dispatched * interrupt at the leaf. * * When *all* wakeup sources have been thusly spoken for, this function will * clear the log_wakeups flag, and print the wakeup reasons. TODO: percpu */ static struct wakeup_irq_node * log_possible_wakeup_reason_start(int irq, struct irq_desc *desc, unsigned depth) { BUG_ON(!irqs_disabled()); BUG_ON((signed)depth < 0); /* This function can race with a call to stop_logging_wakeup_reasons() * from a thread context. If this happens, just exit silently, as we are no * longer interested in logging interrupts. */ if (!logging_wakeup_reasons()) return NULL; /* If suspend was aborted, the base IRQ nodes are missing, and we stop * logging interrupts immediately. */ if (!base_irq_nodes) { stop_logging_wakeup_reasons(); return NULL; } /* We assume wakeup interrupts are handlerd only by the first core. */ /* TODO: relax this by having percpu versions of the irq tree */ if (smp_processor_id() != 0) { return NULL; } if (depth == 0) { cur_irq_tree_depth = 0; cur_irq_tree = search_siblings(base_irq_nodes, irq); } else if (cur_irq_tree) { if (depth > cur_irq_tree_depth) { BUG_ON(depth - cur_irq_tree_depth > 1); cur_irq_tree = add_child(cur_irq_tree, irq); if (cur_irq_tree) cur_irq_tree_depth++; } else { cur_irq_tree = get_base_node(cur_irq_tree, cur_irq_tree_depth - depth); cur_irq_tree_depth = depth; cur_irq_tree = add_to_siblings(cur_irq_tree, irq); } } return cur_irq_tree; } static void log_possible_wakeup_reason_complete(struct wakeup_irq_node *n, unsigned depth, bool handled) { if (!n) return; n->handled = handled; if (depth == 0) { if (base_irq_nodes_done()) { stop_logging_wakeup_reasons(); complete(&wakeups_completion); print_wakeup_sources(); } } } bool log_possible_wakeup_reason(int irq, struct irq_desc *desc, bool (*handler)(unsigned int, struct irq_desc *)) { static DEFINE_PER_CPU(unsigned int, depth); struct wakeup_irq_node *n; bool handled; unsigned d; d = get_cpu_var(depth)++; put_cpu_var(depth); n = log_possible_wakeup_reason_start(irq, desc, d); handled = handler(irq, desc); d = --get_cpu_var(depth); put_cpu_var(depth); if (!handled && desc && desc->action) pr_debug("%s: irq %d action %pF not handled\n", __func__, irq, desc->action->handler); log_possible_wakeup_reason_complete(n, d, handled); return handled; } #endif /* CONFIG_DEDUCE_WAKEUP_REASONS */ void log_suspend_abort_reason(const char *fmt, ...) { va_list args; spin_lock(&resume_reason_lock); //Suspend abort reason has already been logged. if (suspend_abort) { spin_unlock(&resume_reason_lock); return; } suspend_abort = true; va_start(args, fmt); vsnprintf(abort_reason, MAX_SUSPEND_ABORT_LEN, fmt, args); va_end(args); spin_unlock(&resume_reason_lock); } static bool match_node(struct wakeup_irq_node *n, void *_p) { int irq = *((int *)_p); return n->irq != irq; } int check_wakeup_reason(int irq) { bool found; spin_lock(&resume_reason_lock); found = !walk_irq_node_tree(base_irq_nodes, match_node, &irq); spin_unlock(&resume_reason_lock); return found; } static bool build_leaf_nodes(struct wakeup_irq_node *n, void *_p) { struct list_head *wakeups = _p; if (!n->child) list_add(&n->next, wakeups); return true; } static const struct list_head* get_wakeup_reasons_nosync(void) { BUG_ON(logging_wakeup_reasons()); INIT_LIST_HEAD(&wakeup_irqs); walk_irq_node_tree(base_irq_nodes, build_leaf_nodes, &wakeup_irqs); return &wakeup_irqs; } static bool build_unfinished_nodes(struct wakeup_irq_node *n, void *_p) { struct list_head *unfinished = _p; if (!n->handled) { pr_warning("%s: wakeup irq %d was not handled\n", __func__, n->irq); list_add(&n->next, unfinished); } return true; } const struct list_head* get_wakeup_reasons(unsigned long timeout, struct list_head *unfinished) { INIT_LIST_HEAD(unfinished); if (logging_wakeup_reasons()) { unsigned long signalled = 0; if (timeout) signalled = wait_for_completion_timeout(&wakeups_completion, timeout); if (WARN_ON(!signalled)) { stop_logging_wakeup_reasons(); walk_irq_node_tree(base_irq_nodes, build_unfinished_nodes, unfinished); return NULL; } pr_info("%s: waited for %u ms\n", __func__, jiffies_to_msecs(timeout - signalled)); } return get_wakeup_reasons_nosync(); } static bool delete_node(struct wakeup_irq_node *n, void *unused) { list_del(&n->siblings); kmem_cache_free(wakeup_irq_nodes_cache, n); return true; } static void clear_wakeup_reasons_nolock(void) { BUG_ON(logging_wakeup_reasons()); walk_irq_node_tree(base_irq_nodes, delete_node, NULL); base_irq_nodes = NULL; cur_irq_tree = NULL; cur_irq_tree_depth = 0; INIT_LIST_HEAD(&wakeup_irqs); suspend_abort = false; } void clear_wakeup_reasons(void) { unsigned long flags; spin_lock_irqsave(&resume_reason_lock, flags); clear_wakeup_reasons_nolock(); 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) { struct timespec xtom; /* wall_to_monotonic, ignored */ unsigned long flags; spin_lock_irqsave(&resume_reason_lock, flags); switch (pm_event) { case PM_SUSPEND_PREPARE: clear_wakeup_reasons_nolock(); get_xtime_and_monotonic_and_sleep_offset(&last_xtime, &xtom, &last_stime); break; case PM_POST_SUSPEND: get_xtime_and_monotonic_and_sleep_offset(&curr_xtime, &xtom, &curr_stime); if (!suspend_abort) { suspend_count++; total_xtime = timespec_add(total_xtime, timespec_sub(curr_xtime, last_xtime)); total_stime = timespec_add(total_stime, timespec_sub(curr_stime, last_stime)); } else { abort_count++; total_atime = timespec_add(total_atime, timespec_sub(curr_xtime, last_xtime)); } #ifdef CONFIG_DEDUCE_WAKEUP_REASONS /* log_wakeups should have been cleared by now. */ if (WARN_ON(logging_wakeup_reasons())) { stop_logging_wakeup_reasons(); print_wakeup_sources(); } #else print_wakeup_sources(); #endif break; default: break; } spin_unlock_irqrestore(&resume_reason_lock, flags); return NOTIFY_DONE; } static struct notifier_block wakeup_reason_pm_notifier_block = { .notifier_call = wakeup_reason_pm_event, }; int __init wakeup_reason_init(void) { spin_lock_init(&resume_reason_lock); if (register_pm_notifier(&wakeup_reason_pm_notifier_block)) { pr_warning("[%s] failed to register PM notifier\n", __func__); goto fail; } wakeup_reason = kobject_create_and_add("wakeup_reasons", kernel_kobj); if (!wakeup_reason) { pr_warning("[%s] failed to create a sysfs kobject\n", __func__); goto fail_unregister_pm_notifier; } if (sysfs_create_group(wakeup_reason, &attr_group)) { pr_warning("[%s] failed to create a sysfs group\n", __func__); goto fail_kobject_put; } wakeup_irq_nodes_cache = kmem_cache_create("wakeup_irq_node_cache", sizeof(struct wakeup_irq_node), 0, 0, NULL); if (!wakeup_irq_nodes_cache) goto fail_remove_group; return 0; fail_remove_group: sysfs_remove_group(wakeup_reason, &attr_group); fail_kobject_put: kobject_put(wakeup_reason); fail_unregister_pm_notifier: unregister_pm_notifier(&wakeup_reason_pm_notifier_block); fail: return 1; } late_initcall(wakeup_reason_init);