diff options
author | Leonard Crestez <leonard.crestez@nxp.com> | 2017-07-04 20:52:26 +0300 |
---|---|---|
committer | Leonard Crestez <leonard.crestez@nxp.com> | 2018-08-24 12:41:33 +0300 |
commit | bca40f2f16d507177d73fa8c2f8f4dca48d3a69a (patch) | |
tree | d94fef5242a164f1fd4652f63b52c612bf9fc54e /arch | |
parent | fd1252b4c44c8898bdd1adc4b6361f07073adbb6 (diff) |
MLK15034: ARM: cpuidle imx7d: Check IPIs manually before LPI
The GPC will wake us on peripheral interrupts but not IPIs. So check
them manually by reading the GIC's GICD_SPENDSGIR* registers and
aborting idle if something is pending.
We do this only for the last cpu and after taking the required locks.
We know that at this stage the other cpu is in WFI itself or waiting for
the imx_pen_lock and can't trigger any additional IPIs. This means that
the check is not racy.
This fixes occasional lost IPIs causing tasks to get stuck in the
TASK_WAKING 'W' state for long periods. This eventually manifested as
rcu stalls.
Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/arm/mach-imx/cpuidle-imx7d.c | 26 |
1 files changed, 26 insertions, 0 deletions
diff --git a/arch/arm/mach-imx/cpuidle-imx7d.c b/arch/arm/mach-imx/cpuidle-imx7d.c index 204065d27a2d..740cbb2278ba 100644 --- a/arch/arm/mach-imx/cpuidle-imx7d.c +++ b/arch/arm/mach-imx/cpuidle-imx7d.c @@ -84,6 +84,9 @@ static atomic_t master_wait = ATOMIC_INIT(0); static void (*imx7d_wfi_in_iram_fn)(void __iomem *iram_vbase); static struct imx7_cpuidle_pm_info *cpuidle_pm_info; +/* Mapped for the kernel, unlike cpuidle_pm_info->gic_dist_base.vbase */ +static void __iomem *imx7d_cpuidle_gic_base; + static void imx_pen_lock(int cpu) { if (cpu == 0) { @@ -122,6 +125,16 @@ static int imx7d_idle_finish(unsigned long val) return 0; } +static bool imx7d_gic_sgis_pending(void) +{ + void __iomem *sgip_base = imx7d_cpuidle_gic_base + 0x1f20; + + return (readl_relaxed(sgip_base + 0x0) | + readl_relaxed(sgip_base + 0x4) | + readl_relaxed(sgip_base + 0x8) | + readl_relaxed(sgip_base + 0xc)); +} + static int imx7d_enter_low_power_idle(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index) { @@ -141,6 +154,16 @@ static int imx7d_enter_low_power_idle(struct cpuidle_device *dev, cpu_pm_enter(); if (atomic_inc_return(&master_lpi) == num_online_cpus() && cpuidle_pm_info->last_cpu == -1) { + /* + * GPC will not wake on SGIs so check for them + * manually here. At this point we know the other cpu + * is in wfi or waiting for the lock and can't send + * any additional IPIs. + */ + if (imx7d_gic_sgis_pending()) { + index = -1; + goto skip_lpi_flow; + } imx_gpcv2_set_lpm_mode(WAIT_UNCLOCKED); imx_gpcv2_set_cpu_power_gate_in_idle(true); cpu_cluster_pm_enter(); @@ -162,6 +185,7 @@ static int imx7d_enter_low_power_idle(struct cpuidle_device *dev, if (cpuidle_pm_info->last_cpu == dev->cpu) cpuidle_pm_info->last_cpu = -1; +skip_lpi_flow: cpu_pm_exit(); imx_pen_unlock(dev->cpu); } @@ -328,6 +352,8 @@ int __init imx7d_cpuidle_init(void) cpuidle_pm_info->gic_dist_base.vbase = (void __iomem *)IMX_IO_P2V(MX7D_GIC_BASE_ADDR); + imx7d_cpuidle_gic_base = ioremap(MX7D_GIC_BASE_ADDR, MX7D_GIC_SIZE); + imx7d_enable_rcosc(); #ifdef CONFIG_HOTPLUG_CPU |