aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@linux.intel.com>2010-12-08 22:47:40 -0800
committerH. Peter Anvin <hpa@linux.intel.com>2010-12-14 10:48:42 -0800
commit979d4d3733258d0fe5898eed72c8741054961a4d (patch)
tree2696f4b518c6b6cacf51b36baca07804e9af5e12
parent31883443bcb4ee9320564d7b28610aba286196d9 (diff)
downloadmrst-s0i3-test-979d4d3733258d0fe5898eed72c8741054961a4d.tar.gz
mrst-s0i3-test-979d4d3733258d0fe5898eed72c8741054961a4d.tar.xz
mrst-s0i3-test-979d4d3733258d0fe5898eed72c8741054961a4d.zip
s0i3: avoid the "S0i3 hangfire" problem by demoting C6 to C4 if needed
If we tried to enter S0i3, and it failed for some reason, then we have a PMU command live which will cause C6 to be promoted to S0i3. Therefore it is unsafe to enter conventional C6 until this command has timed out, which takes about a millisecond. Accordingly, keep a state flag and demote conventional C6 to C4 during that interval. The PMU interrupt handler will clear this flag when the completion interrupt comes in.
-rw-r--r--drivers/idle/intel_idle.c8
-rw-r--r--drivers/idle/mrst_s0i3.c49
-rw-r--r--drivers/idle/mrst_s0i3.h2
3 files changed, 46 insertions, 13 deletions
diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c
index f927ad2da37..bf906c88760 100644
--- a/drivers/idle/intel_idle.c
+++ b/drivers/idle/intel_idle.c
@@ -227,17 +227,17 @@ static struct cpuidle_state mrst_cstates[MWAIT_MAX_NUM_CSTATES] = {
.flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TLB_FLUSHED,
.exit_latency = 140,
.target_residency = 560,
- .enter = &intel_idle },
+ .enter = &mrst_idle, },
#ifdef CONFIG_MRST_S0I3
{ /* MRST S0i3 */
.name = "MRST-S0i3",
- .desc = "MWAIT 0x60",
- .driver_data = (void *) 0x60,
+ .desc = "MRST S0i3",
+ .driver_data = (void *) -1UL, /* Special */
.flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TLB_FLUSHED |
CPUIDLE_FLAG_INTEL_FAKE,
.exit_latency = 300, /* XXX */
.target_residency = 1200, /* XXX */
- .enter = &mrst_s0i3_idle },
+ .enter = &mrst_idle },
#endif
};
diff --git a/drivers/idle/mrst_s0i3.c b/drivers/idle/mrst_s0i3.c
index 782d927b0b6..d44e806662a 100644
--- a/drivers/idle/mrst_s0i3.c
+++ b/drivers/idle/mrst_s0i3.c
@@ -59,28 +59,43 @@ static struct pci_dev *pmu_dev; /* South Complex PMU unit */
static u64 *wakeup_ptr;
static phys_addr_t s0i3_trampoline_phys;
static void *s0i3_trampoline_base;
+static volatile bool s0i3_pmu_command_pending;
/**
- * mrst_s0i3_idle
+ * mrst_idle
* @dev: cpuidle_device
* @state: cpuidle state
*
+ * This enters S0i3, C6 or C4 depending on what is currently permitted.
+ * C1-C4 are handled via the normal intel_idle entry.
*/
-int mrst_s0i3_idle(struct cpuidle_device *dev, struct cpuidle_state *state)
+int mrst_idle(struct cpuidle_device *dev, struct cpuidle_state *state)
{
+ unsigned long ecx = 1; /* break on interrupt flag */
+ unsigned long eax = (unsigned long)cpuidle_get_statedata(state);
ktime_t kt_before, kt_after;
s64 usec_delta;
int cpu = smp_processor_id();
local_irq_disable();
- /* We must be the only executing CPU thread for this to be legal */
- if (!pmu_reg || !cpumask_equal(cpu_online_mask, cpumask_of(cpu))) {
- local_irq_enable();
- return intel_idle(dev, state-1); /* Revert to C6 */
+ /*
+ * If there is another CPU running, the GPU is active or the PMU
+ * is uninitialized, we cannot enter S0i3
+ */
+ if (eax == -1UL &&
+ (!pmu_reg || !cpumask_equal(cpu_online_mask, cpumask_of(cpu)))) {
+ eax = 0x52; /* Demote to C6 */
}
/*
+ * If there is a pending PMU command, we cannot enter C6,
+ * demote to C4.
+ */
+ if (eax == 0x52 && s0i3_pmu_command_pending)
+ eax = 0x30; /* Demote to C4 */
+
+ /*
* leave_mm() to avoid costly and often unnecessary wakeups
* for flushing the user TLB's associated with the active mm.
*/
@@ -93,8 +108,18 @@ int mrst_s0i3_idle(struct cpuidle_device *dev, struct cpuidle_state *state)
stop_critical_timings();
- if (!need_resched())
- do_s0i3();
+ if (!need_resched()) {
+ if (eax == -1UL) {
+ do_s0i3();
+ } else {
+ /* Conventional MWAIT */
+
+ __monitor((void *)&current_thread_info()->flags, 0, 0);
+ smp_mb();
+ if (!need_resched())
+ __mwait(eax, ecx);
+ }
+ }
start_critical_timings();
@@ -257,6 +282,9 @@ noinline static void s0i3_prep_pmu(void)
/* Clock gate Langwell */
writel(SUB_SYS_D0I2_VALUE, &pmu_reg->pm_ssc[0]);
+
+ /* Avoid entering conventional C6 until the PMU command has cleared */
+ s0i3_pmu_command_pending = true;
}
static inline void s0i3_update_wake_pointer(void)
@@ -276,6 +304,9 @@ static noinline void do_s0i3(void)
restore_processor_state();
s0i3_restore_lapic();
+ /* The PMU command executed correctly, so no longer pending */
+ s0i3_pmu_command_pending = false;
+
/* HACK HACK HACK Enable MSI interrupts again */
writel(0x0, &pmu_reg->pm_msic);
@@ -325,6 +356,8 @@ static irqreturn_t s0i3_pmu_irq(int irq, void *dummy)
/* Clear the status */
writel(status, &pmu_reg->pm_ics);
+ s0i3_pmu_command_pending = false;
+
return IRQ_HANDLED;
}
diff --git a/drivers/idle/mrst_s0i3.h b/drivers/idle/mrst_s0i3.h
index 574626ffb15..23f7c26d6bf 100644
--- a/drivers/idle/mrst_s0i3.h
+++ b/drivers/idle/mrst_s0i3.h
@@ -29,7 +29,7 @@
#include <linux/types.h>
int intel_idle(struct cpuidle_device *dev, struct cpuidle_state *state);
-int mrst_s0i3_idle(struct cpuidle_device *dev, struct cpuidle_state *state);
+int mrst_idle(struct cpuidle_device *dev, struct cpuidle_state *state);
int mrst_s0i3_entry(u32 regval, volatile u32 *regaddr);
void mrst_s0i3_resume(void);