From e94f38fed9df0cf95eaf5ebb98ab7539db5dfb86 Mon Sep 17 00:00:00 2001 From: Kelly Rossmoyer Date: Fri, 12 Jul 2019 11:27:22 -0700 Subject: [PATCH] cpuidle: lpm_levels: add soft watchdog for s2idle 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 [@0ctobot: Reduce MAX_S2IDLE_CPU_ATTEMPTS to 24] Signed-off-by: Adam W. Willis --- drivers/cpuidle/lpm-levels.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/drivers/cpuidle/lpm-levels.c b/drivers/cpuidle/lpm-levels.c index 46a7dae8f7fc..2f0ef9551e7e 100644 --- a/drivers/cpuidle/lpm-levels.c +++ b/drivers/cpuidle/lpm-levels.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -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; @@ -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; @@ -1477,6 +1484,27 @@ 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); @@ -1484,6 +1512,11 @@ static void lpm_cpuidle_s2idle(struct cpuidle_device *dev, 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