android_kernel_google_msm/drivers/base/syscore.c

132 lines
3.1 KiB
C
Raw Permalink Normal View History

/*
* 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>
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
2015-03-01 21:13:33 +00:00
#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);
}