summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Williams <scwilliams@nvidia.com>2011-08-11 13:57:49 -0700
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:47:04 -0800
commit8c2d2cd9c82333bdf813c28675353e3c93a0b7d2 (patch)
tree36fb8bca383d46c0214f7cb8afd410728963945b
parent48e1e48251c6be3f9610ffe33dec9a684f2cf9f0 (diff)
ARM: tegra2: power: Fix reset race condition between the CPUs
During LP2 for CPU idle on Tegra2, there could be a race condition between the CPUs. CPU1 cannot autonomously shut itself down (put itself into reset). CPU1 must be reset by CPU0 but only when it has no outstanding memory or I/O transactions going on (i.e., it is in the WFI state). CPU1 indicates its readiness to be reset by setting status in a PMC scratch register. If CPU1 wakes up and CPU0 sees CPU1's ready to be reset status before CPU1 can clear it CPU1 could be reset at inappropriate times resulting in loss of cache coherency and ultimately a kernel panic. Eliminate the race condition by ensuring that: - CPU1's reset ready status is cleared as early as possible before CPU1 rejoins the coherent world. - Use writel when updating the IRAM LP2 status flags to ensure the IRAM and coherent memory views of the flags are consistent. - If there is not enough time remaining for CPU1 to be in LP2 for the minimum residency time, clear CPU1's reset status flag before entering WFI so that CPU0 will not wait for CPU1 to be ready to reset (since it won't be if there is insufficient time). Change-Id: I20dc5c6406b1521f20852294d48ce6d67f0926b9 Signed-off-by: Scott Williams <scwilliams@nvidia.com> Rebase-Id: Rd485f696126d7ca019d15651b839d4f2fc595848
-rw-r--r--arch/arm/mach-tegra/cpuidle-t2.c34
-rw-r--r--arch/arm/mach-tegra/headsmp.S8
-rw-r--r--arch/arm/mach-tegra/pm.c6
-rw-r--r--arch/arm/mach-tegra/sleep-t2.S29
-rw-r--r--arch/arm/mach-tegra/sleep.h1
5 files changed, 61 insertions, 17 deletions
diff --git a/arch/arm/mach-tegra/cpuidle-t2.c b/arch/arm/mach-tegra/cpuidle-t2.c
index 034c8bbce1a8..14fde6f7c4ab 100644
--- a/arch/arm/mach-tegra/cpuidle-t2.c
+++ b/arch/arm/mach-tegra/cpuidle-t2.c
@@ -71,6 +71,9 @@ static inline unsigned int time_to_bin(unsigned int time)
#ifdef CONFIG_SMP
+#define CLK_RST_CONTROLLER_CLK_CPU_CMPLX 0x4C
+#define CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR 0x344
+
static void __iomem *clk_rst = IO_ADDRESS(TEGRA_CLK_RESET_BASE);
static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
static s64 tegra_cpu1_wake_by_time = LLONG_MAX;
@@ -79,6 +82,7 @@ static int tegra2_reset_sleeping_cpu(int cpu)
{
int ret = 0;
+ BUG_ON(cpu == 0);
BUG_ON(cpu == smp_processor_id());
tegra_pen_lock();
@@ -96,38 +100,51 @@ static void tegra2_wake_reset_cpu(int cpu)
{
u32 reg;
+ BUG_ON(cpu == 0);
+ BUG_ON(cpu == smp_processor_id());
+
+ tegra_pen_lock();
+
+ tegra2_cpu_clear_resettable();
+
/* enable cpu clock on cpu */
reg = readl(clk_rst + 0x4c);
- writel(reg & ~(1 << (8 + cpu)), clk_rst + 0x4c);
+ writel(reg & ~(1 << (8 + cpu)),
+ clk_rst + CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
/* take the CPU out of reset */
reg = 0x1111 << cpu;
- writel(reg, clk_rst + 0x344);
+ writel(reg, clk_rst +
+ CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR);
/* unhalt the cpu */
flowctrl_writel(0, FLOW_CTRL_HALT_CPU(1));
+
+ tegra_pen_unlock();
}
static int tegra2_reset_other_cpus(int cpu)
{
int i;
- int abort = -1;
+ int ret = 0;
+
+ BUG_ON(cpu != 0);
for_each_online_cpu(i) {
if (i != cpu) {
if (tegra2_reset_sleeping_cpu(i)) {
- abort = i;
+ ret = -EBUSY;
break;
}
}
}
- if (abort >= 0) {
+ if (ret) {
for_each_online_cpu(i) {
- if (i != cpu && i < abort)
+ if (i != cpu)
tegra2_wake_reset_cpu(i);
}
- return -EINVAL;
+ return ret;
}
return 0;
@@ -249,6 +266,7 @@ static void tegra2_idle_lp2_cpu_1(struct cpuidle_device *dev,
/*
* Not enough time left to enter LP2
*/
+ tegra2_cpu_clear_resettable();
tegra_cpu_wfi();
return;
}
@@ -263,6 +281,8 @@ static void tegra2_idle_lp2_cpu_1(struct cpuidle_device *dev,
tegra2_sleep_wfi(PLAT_PHYS_OFFSET - PAGE_OFFSET);
+ tegra2_cpu_clear_resettable();
+
tegra_cpu1_wake_by_time = LLONG_MAX;
tegra_twd_resume(&twd_context);
diff --git a/arch/arm/mach-tegra/headsmp.S b/arch/arm/mach-tegra/headsmp.S
index caf38171f03e..ec6d24169697 100644
--- a/arch/arm/mach-tegra/headsmp.S
+++ b/arch/arm/mach-tegra/headsmp.S
@@ -184,6 +184,14 @@ ENTRY(__tegra_cpu_reset_handler)
bleq __die @ CPU not present (to OS)
#endif
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+ /* If CPU1, don't let CPU0 reset CPU1 now that CPU1 is coming up. */
+ mov32 r6, TEGRA_PMC_BASE
+ mov r0, #0
+ cmp r10, #0
+ strne r0, [r6, #PMC_SCRATCH41]
+#endif
+
#ifdef CONFIG_PM_SLEEP
/* Waking up from LP1? */
ldr r8, [r12, #RESET_DATA(MASK_LP1)]
diff --git a/arch/arm/mach-tegra/pm.c b/arch/arm/mach-tegra/pm.c
index d24b60586cf4..77e9d6ffeba8 100644
--- a/arch/arm/mach-tegra/pm.c
+++ b/arch/arm/mach-tegra/pm.c
@@ -470,7 +470,8 @@ void tegra_clear_cpu_in_lp2(int cpu)
can't use used directly by cpumask_clear_cpu() because it uses
LDREX/STREX which requires the addressed location to be inner
cacheable and sharable which IRAM isn't. */
- *iram_cpu_lp2_mask = tegra_in_lp2;
+ writel(tegra_in_lp2.bits[0], iram_cpu_lp2_mask);
+ dsb();
spin_unlock(&tegra_lp2_lock);
}
@@ -487,7 +488,8 @@ bool tegra_set_cpu_in_lp2(int cpu)
can't use used directly by cpumask_set_cpu() because it uses
LDREX/STREX which requires the addressed location to be inner
cacheable and sharable which IRAM isn't. */
- *iram_cpu_lp2_mask = tegra_in_lp2;
+ writel(tegra_in_lp2.bits[0], iram_cpu_lp2_mask);
+ dsb();
if ((cpu == 0) && cpumask_equal(&tegra_in_lp2, cpu_online_mask))
last_cpu = true;
diff --git a/arch/arm/mach-tegra/sleep-t2.S b/arch/arm/mach-tegra/sleep-t2.S
index 3144368c23f0..e4bf9223e635 100644
--- a/arch/arm/mach-tegra/sleep-t2.S
+++ b/arch/arm/mach-tegra/sleep-t2.S
@@ -107,8 +107,7 @@ ENTRY(tegra2_cpu_reset)
cmp r0, #0
moveq pc, lr @ must not be called for CPU 0
- mov32 r3, TEGRA_PMC_VIRT
- add r1, r3, #PMC_SCRATCH41
+ mov32 r1, TEGRA_PMC_VIRT + PMC_SCRATCH41
mov r12, #CPU_RESETTABLE
str r12, [r1]
@@ -134,14 +133,26 @@ ENDPROC(tegra2_cpu_reset)
#ifdef CONFIG_PM_SLEEP
/*
+ * tegra2_cpu_clear_resettable(void)
+ *
+ * Called to clear the "resettable soon" flag in PMC_SCRATCH41 when
+ * it is expected that the secondary CPU will be idle soon.
+ */
+ENTRY(tegra2_cpu_clear_resettable)
+ mov32 r1, TEGRA_PMC_VIRT + PMC_SCRATCH41
+ mov r12, #CPU_NOT_RESETTABLE
+ str r12, [r1]
+ mov pc, lr
+ENDPROC(tegra2_cpu_clear_resettable)
+
+/*
* tegra2_cpu_set_resettable_soon(void)
*
* Called to set the "resettable soon" flag in PMC_SCRATCH41 when
* it is expected that the secondary CPU will be idle soon.
*/
ENTRY(tegra2_cpu_set_resettable_soon)
- mov32 r3, TEGRA_PMC_VIRT
- add r1, r3, #PMC_SCRATCH41
+ mov32 r1, TEGRA_PMC_VIRT + PMC_SCRATCH41
mov r12, #CPU_RESETTABLE_SOON
str r12, [r1]
mov pc, lr
@@ -154,8 +165,7 @@ ENDPROC(tegra2_cpu_set_resettable_soon)
* set because it is expected that the secondary CPU will be idle soon.
*/
ENTRY(tegra2_cpu_is_resettable_soon)
- mov32 r3, TEGRA_PMC_VIRT
- add r1, r3, #PMC_SCRATCH41
+ mov32 r1, TEGRA_PMC_VIRT + PMC_SCRATCH41
ldr r12, [r1]
cmp r12, #CPU_RESETTABLE_SOON
moveq r0, #1
@@ -189,13 +199,16 @@ ENTRY(tegra2_sleep_wfi)
b tegra_cpu_save
mov r11, r2
- mov32 r3, TEGRA_PMC_VIRT
- add r0, r3, #PMC_SCRATCH41
+ mov32 r0, TEGRA_PMC_VIRT + PMC_SCRATCH41
mov r3, #CPU_RESETTABLE
str r3, [r0]
bl tegra_cpu_wfi
+ mov32 r0, TEGRA_PMC_VIRT + PMC_SCRATCH41
+ mov r3, #CPU_NOT_RESETTABLE
+ str r3, [r0]
+
/*
* cpu may be reset while in wfi, which will return through
* tegra_resume to tegra_cpu_resume_phys to tegra_cpu_resume
diff --git a/arch/arm/mach-tegra/sleep.h b/arch/arm/mach-tegra/sleep.h
index fcc6503d7e64..8f9926fe7172 100644
--- a/arch/arm/mach-tegra/sleep.h
+++ b/arch/arm/mach-tegra/sleep.h
@@ -206,6 +206,7 @@ extern void tegra2_iram_end;
int tegra2_cpu_is_resettable_soon(void);
void tegra2_cpu_reset(int cpu);
void tegra2_cpu_set_resettable_soon(void);
+void tegra2_cpu_clear_resettable(void);
void tegra2_sleep_core(unsigned long v2p);
void tegra2_hotplug_shutdown(void);
void tegra2_sleep_wfi(unsigned long v2p);