Skip to content
This repository has been archived by the owner on Jul 7, 2021. It is now read-only.

Commit

Permalink
cpuidle: lpm_levels: add soft watchdog for s2idle
Browse files Browse the repository at this point in the history
When cpus are suspended under s2idle, non-wakeup interrupts can lead
to cascading behavior where cpus come out of suspend, get work
scheduled, return to suspend only to find another cpu has left suspend,
etc.  This, in turn, can lead to non-secure watchdog timeouts that crash
the system, since the watchdog will continue to count down while 1 or
more cpus are running but no patting occurs since the system is still
mostly suspended.

This change implements a "soft watchdog" mechanism that will detect when
cpus are cycling in and out of suspend under a single s2idle system
suspend effort and - if the number of cycles exceeds a threshold - abort
the suspend.

Bug: 136698892
Testing: Ran with debug logging throughout lpm-levels s2idle code that
was causing similar cpu scheduling/suspended behavior.
Ran with a hard 1s delay in s2idle_enter, which was identified as
another repro for producing a failure similar to ramdumps in the field.
Change-Id: I75d4be507f71719d74cf58cdd5a49d1f5cbd6c06
Signed-off-by: Kelly Rossmoyer <krossmo@google.com>
[@0ctobot: Reduce MAX_S2IDLE_CPU_ATTEMPTS to 24]
Signed-off-by: Adam W. Willis <return.of.octobot@gmail.com>
  • Loading branch information
krossmo authored and 0ctobot committed Mar 21, 2020
1 parent ad98b1c commit e94f38f
Showing 1 changed file with 33 additions and 0 deletions.
33 changes: 33 additions & 0 deletions drivers/cpuidle/lpm-levels.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include <linux/tick.h>
#include <linux/wakeup_reason.h>
#include <linux/suspend.h>
#include <linux/pm_qos.h>
#include <linux/of_platform.h>
Expand Down Expand Up @@ -59,6 +60,8 @@
#define PSCI_AFFINITY_LEVEL(lvl) ((lvl & 0x3) << 24)
#define BIAS_HYST (bias_hyst * NSEC_PER_MSEC)

#define MAX_S2IDLE_CPU_ATTEMPTS 24 /* divide by # cpus for max suspends */

static struct system_pm_ops *sys_pm_ops;


Expand Down Expand Up @@ -1464,6 +1467,10 @@ static int lpm_cpuidle_enter(struct cpuidle_device *dev,
static void lpm_cpuidle_s2idle(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int idx)
{
static DEFINE_SPINLOCK(s2idle_lock);
static struct cpumask idling_cpus;
static int s2idle_sleep_attempts;
static bool s2idle_aborted;
struct lpm_cpu *cpu = per_cpu(cpu_lpm, dev->cpu);
const struct cpumask *cpumask = get_cpu_mask(dev->cpu);
bool success = false;
Expand All @@ -1477,13 +1484,39 @@ static void lpm_cpuidle_s2idle(struct cpuidle_device *dev,
return;
}

spin_lock(&s2idle_lock);
if (cpumask_empty(&idling_cpus)) {
s2idle_sleep_attempts = 0;
s2idle_aborted = false;
} else if (s2idle_aborted) {
spin_unlock(&s2idle_lock);
return;
}

cpumask_or(&idling_cpus, &idling_cpus, cpumask);
if (++s2idle_sleep_attempts > MAX_S2IDLE_CPU_ATTEMPTS) {
s2idle_aborted = true;
}
spin_unlock(&s2idle_lock);

if (s2idle_aborted) {
pr_err("Aborting s2idle suspend: too many iterations\n");
pm_system_wakeup();
goto exit;
}

cpu_prepare(cpu, idx, true);
cluster_prepare(cpu->parent, cpumask, idx, false, 0);

success = psci_enter_sleep(cpu, idx, false);

cluster_unprepare(cpu->parent, cpumask, idx, false, 0, success);
cpu_unprepare(cpu, idx, true);

exit:
spin_lock(&s2idle_lock);
cpumask_andnot(&idling_cpus, &idling_cpus, cpumask);
spin_unlock(&s2idle_lock);
}

#ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS
Expand Down

0 comments on commit e94f38f

Please sign in to comment.