x86/perf/amd: Resolve race condition when disabling PMC
[linux-2.6-microblaze.git] / arch / x86 / events / amd / core.c
index 7d2d7c8..c09ee88 100644 (file)
@@ -3,6 +3,7 @@
 #include <linux/types.h>
 #include <linux/init.h>
 #include <linux/slab.h>
+#include <linux/delay.h>
 #include <asm/apicdef.h>
 
 #include "../perf_event.h"
@@ -429,6 +430,64 @@ static void amd_pmu_cpu_dead(int cpu)
        }
 }
 
+/*
+ * When a PMC counter overflows, an NMI is used to process the event and
+ * reset the counter. NMI latency can result in the counter being updated
+ * before the NMI can run, which can result in what appear to be spurious
+ * NMIs. This function is intended to wait for the NMI to run and reset
+ * the counter to avoid possible unhandled NMI messages.
+ */
+#define OVERFLOW_WAIT_COUNT    50
+
+static void amd_pmu_wait_on_overflow(int idx)
+{
+       unsigned int i;
+       u64 counter;
+
+       /*
+        * Wait for the counter to be reset if it has overflowed. This loop
+        * should exit very, very quickly, but just in case, don't wait
+        * forever...
+        */
+       for (i = 0; i < OVERFLOW_WAIT_COUNT; i++) {
+               rdmsrl(x86_pmu_event_addr(idx), counter);
+               if (counter & (1ULL << (x86_pmu.cntval_bits - 1)))
+                       break;
+
+               /* Might be in IRQ context, so can't sleep */
+               udelay(1);
+       }
+}
+
+static void amd_pmu_disable_all(void)
+{
+       struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
+       int idx;
+
+       x86_pmu_disable_all();
+
+       /*
+        * This shouldn't be called from NMI context, but add a safeguard here
+        * to return, since if we're in NMI context we can't wait for an NMI
+        * to reset an overflowed counter value.
+        */
+       if (in_nmi())
+               return;
+
+       /*
+        * Check each counter for overflow and wait for it to be reset by the
+        * NMI if it has overflowed. This relies on the fact that all active
+        * counters are always enabled when this function is caled and
+        * ARCH_PERFMON_EVENTSEL_INT is always set.
+        */
+       for (idx = 0; idx < x86_pmu.num_counters; idx++) {
+               if (!test_bit(idx, cpuc->active_mask))
+                       continue;
+
+               amd_pmu_wait_on_overflow(idx);
+       }
+}
+
 static struct event_constraint *
 amd_get_event_constraints(struct cpu_hw_events *cpuc, int idx,
                          struct perf_event *event)
@@ -622,7 +681,7 @@ static ssize_t amd_event_sysfs_show(char *page, u64 config)
 static __initconst const struct x86_pmu amd_pmu = {
        .name                   = "AMD",
        .handle_irq             = x86_pmu_handle_irq,
-       .disable_all            = x86_pmu_disable_all,
+       .disable_all            = amd_pmu_disable_all,
        .enable_all             = x86_pmu_enable_all,
        .enable                 = x86_pmu_enable_event,
        .disable                = x86_pmu_disable_event,
@@ -732,7 +791,7 @@ void amd_pmu_enable_virt(void)
        cpuc->perf_ctr_virt_mask = 0;
 
        /* Reload all events */
-       x86_pmu_disable_all();
+       amd_pmu_disable_all();
        x86_pmu_enable_all(0);
 }
 EXPORT_SYMBOL_GPL(amd_pmu_enable_virt);
@@ -750,7 +809,7 @@ void amd_pmu_disable_virt(void)
        cpuc->perf_ctr_virt_mask = AMD64_EVENTSEL_HOSTONLY;
 
        /* Reload all events */
-       x86_pmu_disable_all();
+       amd_pmu_disable_all();
        x86_pmu_enable_all(0);
 }
 EXPORT_SYMBOL_GPL(amd_pmu_disable_virt);