PM: extend suspend_again mechanism to use partialresume

The old platform suspend_again callback overrides drivers' votes, such that if
it implemented and returns false, then we do not call the partialresume
handlers.  When it doesn't exists or returns true, then we also query the
registered drivers for consensus.

When a device resumes from suspend, the suspend/resume code invokes
partialresume to check to see if the set of wakeup interrupts all have matching
handlers. If this is not the case, the PM subsystem can continue to resume as
before.  If all of the wakeup sources have matching handlers, then those are
invoked in turn (and can block), and if all of them reach consensus that the
reason for the wakeup can be ignored, they say so to the PM subsystem, which
goes right back into suspend.

Signed-off-by: Iliyan Malchev <malchev@google.com>
Change-Id: Iaeb9ed78c4b5fb815c6e9c701233e703f481f962
This commit is contained in:
Iliyan Malchev 2015-03-25 16:38:36 -07:00 committed by Artem Borisov
parent 4e0c8780cc
commit 953a4840dd
1 changed files with 61 additions and 3 deletions

View File

@ -25,6 +25,8 @@
#include <linux/suspend.h>
#include <linux/syscore_ops.h>
#include <linux/rtc.h>
#include <linux/wakeup_reason.h>
#include <linux/partialresume.h>
#include <trace/events/power.h>
#include <linux/wakeup_reason.h>
@ -187,6 +189,8 @@ static int suspend_enter(suspend_state_t state, bool *wakeup)
MAX_SUSPEND_ABORT_LEN);
log_suspend_abort_reason(suspend_abort);
}
start_logging_wakeup_reasons();
syscore_resume();
}
@ -209,6 +213,59 @@ static int suspend_enter(suspend_state_t state, bool *wakeup)
return error;
}
#ifdef CONFIG_PARTIALRESUME
static bool suspend_again(bool *drivers_resumed)
{
const struct list_head *irqs;
struct list_head unfinished;
*drivers_resumed = false;
/* If a platform suspend_again handler is defined, when it decides to
* not suspend again, this takes precedence over drivers. If a
* platform's suspend_again callback returns true, then we proceed to
* check the drivers as well.
*/
if (suspend_ops->suspend_again && !suspend_ops->suspend_again())
return false;
/* TODO: resume only the drivers associated with the wakeup interrupts!
*/
dpm_resume_end(PMSG_RESUME);
*drivers_resumed = true;
/* Thaw kernel threads opportunistically, to allow get_wakeup_reasons
* to block while the wakeup interrupt list is being assembled. Calls
* schedule() internally.
*/
thaw_kernel_threads();
/* Look for a match between the wakeup reasons and the registered
* callbacks. Don't bother thawing the kernel threads if a match is
* not found.
*/
irqs = get_wakeup_reasons(HZ, &unfinished);
if (!suspend_again_match(irqs, &unfinished))
return false;
if (suspend_again_consensus() &&
!freeze_kernel_threads()) {
clear_wakeup_reasons();
dpm_suspend_start(PMSG_SUSPEND);
*drivers_resumed = false;
return true;
}
return false;
}
#else
static __always_inline bool
suspend_again(bool *drivers_resumed __attribute__((unused)))
{
return suspend_ops->suspend_again && suspend_ops->suspend_again();
}
#endif /* CONFIG_PARTIALRESUME */
/**
* suspend_devices_and_enter - Suspend devices and enter system sleep state.
* @state: System sleep state to enter.
@ -217,6 +274,7 @@ int suspend_devices_and_enter(suspend_state_t state)
{
int error;
bool wakeup = false;
bool resumed = false;
if (!suspend_ops)
return -ENOSYS;
@ -241,12 +299,12 @@ int suspend_devices_and_enter(suspend_state_t state)
do {
error = suspend_enter(state, &wakeup);
} while (!error && !wakeup
&& suspend_ops->suspend_again && suspend_ops->suspend_again());
} while (!error && !wakeup && suspend_again(&resumed));
Resume_devices:
suspend_test_start();
dpm_resume_end(PMSG_RESUME);
if (!resumed)
dpm_resume_end(PMSG_RESUME);
suspend_test_finish("resume devices");
resume_console();
Close: