android_kernel_google_msm/drivers/base/syscore.c
Iliyan Malchev 7e87a4dc87 PM: wakeup_reason: correctly deduce wakeup interrupts
The wakeup_reason driver works by having a callback log_wakeup_reason(), be
called by the resume path once for each wakeup interrupt, with the irq number
as argument.  It then saves this interrupt in an array, and reports it when
requested (via /sys/kernel/wakeup_reasons/last_resume_reason) and also prints
the information out in kmsg.

This approach works, but it has the deficiency that often the reported wakeup
interrupt, while correct, is not the interrupt truly responsible for the
wakeup.  The reason for this is due to chained interrupt controllers (whether
in hardware or simulated in software).  It could be, for example, that the
power button is wired to a GPIO handled by a single interrupt for all GPIOs,
which interrupt then determines the GPIO and maps this to a software interrupt.
Whether this is done in software, or by chaining interrupt controllers, the end
result is that the wakeup reason will show not the interrupt associated with
the power button, but the base-GPIO interrupt instead.

This patch reworks the wakeup_sources driver such that it reports those final
interrupts we are interested in, and not the intermediate (and not the base)
ones.  It does so as follows:

-- The assumption is that generic_handle_irq() is called to dispatch all
   interrupts; due to this, chained interrupts result in recursive calls of
   generic_handle_irq().
-- We reconstruct the chains of interrupts that originate with the base wakeup
   interrupt and terminate with the interrupt we are interested in by tracing
   the calls to generic_handle_irq()
-- The tracing works by maitaining a per-cpu counter that is incremented with
   each call to generic_handle_irq(); that counter is reported to the
   wakeup_sources driver by a pair of functions, called
   log_possible_wakeup_reason_start() and log_possible_wakeup_reason_complete().
   The former is called before generic_handle_irq() handles the interrupt
   (thereby potentially calling itself recusively) and the latter afterward.
-- The two functions mentioned above are complemented by log_base_wake_reason()
   (renamed from log_wakeup_reason()), which is used to report the base wakeup
   interrupts to the wakeup_reason driver.
-- The three functions work together to build a set of trees, one per base
   wakeup reason, the leaves of which correspond to the interrupts we are
   interesed in; these trees can be arbitratily complex, though in reality they
   most often are a single node, or a chain of two nodes.  The complexity
   supports arbitrarily involved interrupt dispatch.
-- On resume, we build the tree; once the tree is completed, we walk it
   recursively, and print out to kmesg the (more useful) list of wakeup
   sources; simiarly, we walk the tree and print the leaves when
   /sys/kernel/wakeup_reasons/last_resume_reason is read.

Signed-off-by: Iliyan Malchev <malchev@google.com>
Change-Id: If8acb2951b61d2c6bcf4d011fe04d7f91057d139
2017-10-15 17:05:07 +03:00

131 lines
3.1 KiB
C

/*
* syscore.c - Execution of system core operations.
*
* Copyright (C) 2011 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
*
* This file is released under the GPLv2.
*/
#include <linux/syscore_ops.h>
#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/wakeup_reason.h>
#include <linux/irq.h>
static LIST_HEAD(syscore_ops_list);
static DEFINE_MUTEX(syscore_ops_lock);
/**
* register_syscore_ops - Register a set of system core operations.
* @ops: System core operations to register.
*/
void register_syscore_ops(struct syscore_ops *ops)
{
mutex_lock(&syscore_ops_lock);
list_add_tail(&ops->node, &syscore_ops_list);
mutex_unlock(&syscore_ops_lock);
}
EXPORT_SYMBOL_GPL(register_syscore_ops);
/**
* unregister_syscore_ops - Unregister a set of system core operations.
* @ops: System core operations to unregister.
*/
void unregister_syscore_ops(struct syscore_ops *ops)
{
mutex_lock(&syscore_ops_lock);
list_del(&ops->node);
mutex_unlock(&syscore_ops_lock);
}
EXPORT_SYMBOL_GPL(unregister_syscore_ops);
#ifdef CONFIG_PM_SLEEP
/**
* syscore_suspend - Execute all the registered system core suspend callbacks.
*
* This function is executed with one CPU on-line and disabled interrupts.
*/
int syscore_suspend(void)
{
struct syscore_ops *ops;
int ret = 0;
pr_debug("Checking wakeup interrupts\n");
/* Return error code if there are any wakeup interrupts pending. */
ret = check_wakeup_irqs();
if (ret)
return ret;
WARN_ONCE(!irqs_disabled(),
"Interrupts enabled before system core suspend.\n");
list_for_each_entry_reverse(ops, &syscore_ops_list, node)
if (ops->suspend) {
if (initcall_debug)
pr_info("PM: Calling %pF\n", ops->suspend);
ret = ops->suspend();
if (ret)
goto err_out;
WARN_ONCE(!irqs_disabled(),
"Interrupts enabled after %pF\n", ops->suspend);
}
return 0;
err_out:
log_suspend_abort_reason("System core suspend callback %pF 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)
ops->resume();
return ret;
}
EXPORT_SYMBOL_GPL(syscore_suspend);
/**
* syscore_resume - Execute all the registered system core resume callbacks.
*
* This function is executed with one CPU on-line and disabled interrupts.
*/
void syscore_resume(void)
{
struct syscore_ops *ops;
WARN_ONCE(!irqs_disabled(),
"Interrupts enabled before system core resume.\n");
list_for_each_entry(ops, &syscore_ops_list, node)
if (ops->resume) {
if (initcall_debug)
pr_info("PM: Calling %pF\n", ops->resume);
ops->resume();
WARN_ONCE(!irqs_disabled(),
"Interrupts enabled after %pF\n", ops->resume);
}
}
EXPORT_SYMBOL_GPL(syscore_resume);
#endif /* CONFIG_PM_SLEEP */
/**
* syscore_shutdown - Execute all the registered system core shutdown callbacks.
*/
void syscore_shutdown(void)
{
struct syscore_ops *ops;
mutex_lock(&syscore_ops_lock);
list_for_each_entry_reverse(ops, &syscore_ops_list, node)
if (ops->shutdown) {
if (initcall_debug)
pr_info("PM: Calling %pF\n", ops->shutdown);
ops->shutdown();
}
mutex_unlock(&syscore_ops_lock);
}