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