power: Manipulate wake mask during s0ix timeouts

When S0ix failure detection is enabled and a timeout occurs such that
the SLP_S0 line never actually toggles, then s0ix_transition_timeout()
sets the HANG_DETECT event bit. This doesn't quite work in this scenario,
since the wake mask is only enabled when the power state transitions to
S0ix, which happens when the SLP_S0 line toggles. So the AP never sees the
event, since it's not in the wake mask and so never causes the EC->AP
interrupt line to change.

Detect this situation in the timeout function, and explicitly move the
wake mask to its S0ix value so that when the event bit is set, (if it
is in the wake mask), the system will wake up.

Doing this forcefully gets the wake mask out of sync with the power state.
So upon resume, explicitly restore the wake mask to its S0 state.

BUG=b:131434497
BRANCH=none
TEST=suspend_stress_test -c1 --suspend_min=60 with a firmware where
S0ix fails.

Change-Id: Id2e67c6933a7895fba85ccfdff9b336629eabf24
Signed-off-by: Evan Green <evgreen@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1592469
Reviewed-by: Furquan Shaikh <furquan@chromium.org>
This commit is contained in:
Evan Green 2019-05-01 16:46:36 -07:00 committed by chrome-bot
parent 65fde9d10e
commit fb2a7efc26
3 changed files with 39 additions and 12 deletions

View File

@ -145,6 +145,11 @@ void power_set_state(enum power_state new_state);
*/
enum power_state power_get_state(void);
/*
* Set the wake mask according to the current power state.
*/
void power_update_wake_mask(void);
/**
* Chipset-specific initialization
*

View File

@ -221,30 +221,22 @@ enum power_state power_get_state(void)
/* If host doesn't program s0ix lazy wake mask, use default s0ix mask */
#define DEFAULT_WAKE_MASK_S0IX (EC_HOST_EVENT_MASK(EC_HOST_EVENT_LID_OPEN) | \
EC_HOST_EVENT_MASK(EC_HOST_EVENT_MODE_CHANGE))
/*
* Set wake mask after power state has stabilized (5ms after power state
* change):
* Set the wake mask according to the current power state:
* 1. On transition to S0, wake mask is reset.
* 2. In non-S0 states, active mask set by host gets a higher preference.
* 3. If host has not set any active mask, then check if a lazy mask exists
* for the current power state.
* 4. If state is S0ix and no lazy or active wake mask is set, then use default
* S0ix mask to be compatible with older BIOS versions.
*
* Reason for making this a deferred call is to avoid race conditions occurring
* from S0ix periodic wakes on the SoC.
*/
static void power_update_wake_mask_deferred(void);
DECLARE_DEFERRED(power_update_wake_mask_deferred);
static void power_update_wake_mask_deferred(void)
void power_update_wake_mask(void)
{
host_event_t wake_mask;
enum power_state state;
hook_call_deferred(&power_update_wake_mask_deferred_data, -1);
state = power_get_state();
if (state == POWER_S0)
@ -260,6 +252,20 @@ static void power_update_wake_mask_deferred(void)
lpc_set_host_event_mask(LPC_HOST_EVENT_WAKE, wake_mask);
}
/*
* Set wake mask after power state has stabilized, 5ms after power state
* change. The reason for making this a deferred call is to avoid race
* conditions occurring from S0ix periodic wakes on the SoC.
*/
static void power_update_wake_mask_deferred(void);
DECLARE_DEFERRED(power_update_wake_mask_deferred);
static void power_update_wake_mask_deferred(void)
{
hook_call_deferred(&power_update_wake_mask_deferred_data, -1);
power_update_wake_mask();
}
static void power_set_active_wake_mask(void)
{

View File

@ -271,8 +271,17 @@ static void s0ix_transition_timeout(void)
/*
* Wake up the AP so they don't just chill in a non-suspended state and
* burn power. Overload a vaguely related event bit since event bits are
* at a premium.
* at a premium. If the system never entered S0ix, then manually set the
* wake mask to pretend it did, so that the hang detect event wakes the
* system.
*/
if (power_get_state() == POWER_S0) {
host_event_t s0ix_wake_mask;
get_lazy_wake_mask(POWER_S0ix, &s0ix_wake_mask);
lpc_set_host_event_mask(LPC_HOST_EVENT_WAKE, s0ix_wake_mask);
}
host_set_single_event(EC_HOST_EVENT_HANG_DETECT);
}
@ -300,6 +309,13 @@ static void s0ix_complete_resume(struct host_sleep_event_context *ctx)
{
hook_call_deferred(&s0ix_transition_timeout_data, -1);
ctx->sleep_transitions = slp_s0ix_transitions;
/*
* If s0ix timed out and never transitioned, then the wake mask was
* modified to its s0ix state, so that the event wakes the system.
* Explicitly restore the wake mask to its S0 state now.
*/
power_update_wake_mask();
}
static void s0ix_reset_tracking(void)