perf_counter: Add P6 PMU support

Add basic P6 PMU support. The P6 uses the EVNTSEL0 EN bit to
enable/disable both its counters. We use this for the
global enable/disable, and clear all config bits (except EN)
to disable individual counters.

Actual ia32 hardware doesn't support lfence, so use a locked
op without side-effect to implement a full barrier.

perf stat and perf record seem to function correctly.

[a.p.zijlstra@chello.nl: cleanups and complete the enable/disable code]

Signed-off-by: Vince Weaver <vince@deater.net>
Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
LKML-Reference: <Pine.LNX.4.64.0907081718450.2715@pianoman.cluster.toy>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
This commit is contained in:
Vince Weaver 2009-07-08 17:46:14 -04:00 committed by Ingo Molnar
parent 9590b7ba3f
commit 11d1578f94
2 changed files with 220 additions and 15 deletions

View file

@ -65,6 +65,44 @@ static DEFINE_PER_CPU(struct cpu_hw_counters, cpu_hw_counters) = {
.enabled = 1, .enabled = 1,
}; };
/*
* Not sure about some of these
*/
static const u64 p6_perfmon_event_map[] =
{
[PERF_COUNT_HW_CPU_CYCLES] = 0x0079,
[PERF_COUNT_HW_INSTRUCTIONS] = 0x00c0,
[PERF_COUNT_HW_CACHE_REFERENCES] = 0x0000,
[PERF_COUNT_HW_CACHE_MISSES] = 0x0000,
[PERF_COUNT_HW_BRANCH_INSTRUCTIONS] = 0x00c4,
[PERF_COUNT_HW_BRANCH_MISSES] = 0x00c5,
[PERF_COUNT_HW_BUS_CYCLES] = 0x0062,
};
static u64 p6_pmu_event_map(int event)
{
return p6_perfmon_event_map[event];
}
static u64 p6_pmu_raw_event(u64 event)
{
#define P6_EVNTSEL_EVENT_MASK 0x000000FFULL
#define P6_EVNTSEL_UNIT_MASK 0x0000FF00ULL
#define P6_EVNTSEL_EDGE_MASK 0x00040000ULL
#define P6_EVNTSEL_INV_MASK 0x00800000ULL
#define P6_EVNTSEL_COUNTER_MASK 0xFF000000ULL
#define P6_EVNTSEL_MASK \
(P6_EVNTSEL_EVENT_MASK | \
P6_EVNTSEL_UNIT_MASK | \
P6_EVNTSEL_EDGE_MASK | \
P6_EVNTSEL_INV_MASK | \
P6_EVNTSEL_COUNTER_MASK)
return event & P6_EVNTSEL_MASK;
}
/* /*
* Intel PerfMon v3. Used on Core2 and later. * Intel PerfMon v3. Used on Core2 and later.
*/ */
@ -726,6 +764,23 @@ static int __hw_perf_counter_init(struct perf_counter *counter)
return 0; return 0;
} }
static void p6_pmu_disable_all(void)
{
struct cpu_hw_counters *cpuc = &__get_cpu_var(cpu_hw_counters);
unsigned long val;
if (!cpuc->enabled)
return;
cpuc->enabled = 0;
barrier();
/* p6 only has one enable register */
rdmsrl(MSR_P6_EVNTSEL0, val);
val &= ~ARCH_PERFMON_EVENTSEL0_ENABLE;
wrmsrl(MSR_P6_EVNTSEL0, val);
}
static void intel_pmu_disable_all(void) static void intel_pmu_disable_all(void)
{ {
wrmsrl(MSR_CORE_PERF_GLOBAL_CTRL, 0); wrmsrl(MSR_CORE_PERF_GLOBAL_CTRL, 0);
@ -767,6 +822,23 @@ void hw_perf_disable(void)
return x86_pmu.disable_all(); return x86_pmu.disable_all();
} }
static void p6_pmu_enable_all(void)
{
struct cpu_hw_counters *cpuc = &__get_cpu_var(cpu_hw_counters);
unsigned long val;
if (cpuc->enabled)
return;
cpuc->enabled = 1;
barrier();
/* p6 only has one enable register */
rdmsrl(MSR_P6_EVNTSEL0, val);
val |= ARCH_PERFMON_EVENTSEL0_ENABLE;
wrmsrl(MSR_P6_EVNTSEL0, val);
}
static void intel_pmu_enable_all(void) static void intel_pmu_enable_all(void)
{ {
wrmsrl(MSR_CORE_PERF_GLOBAL_CTRL, x86_pmu.intel_ctrl); wrmsrl(MSR_CORE_PERF_GLOBAL_CTRL, x86_pmu.intel_ctrl);
@ -819,16 +891,13 @@ static inline void intel_pmu_ack_status(u64 ack)
static inline void x86_pmu_enable_counter(struct hw_perf_counter *hwc, int idx) static inline void x86_pmu_enable_counter(struct hw_perf_counter *hwc, int idx)
{ {
int err; (void)checking_wrmsrl(hwc->config_base + idx,
err = checking_wrmsrl(hwc->config_base + idx,
hwc->config | ARCH_PERFMON_EVENTSEL0_ENABLE); hwc->config | ARCH_PERFMON_EVENTSEL0_ENABLE);
} }
static inline void x86_pmu_disable_counter(struct hw_perf_counter *hwc, int idx) static inline void x86_pmu_disable_counter(struct hw_perf_counter *hwc, int idx)
{ {
int err; (void)checking_wrmsrl(hwc->config_base + idx, hwc->config);
err = checking_wrmsrl(hwc->config_base + idx,
hwc->config);
} }
static inline void static inline void
@ -836,13 +905,24 @@ intel_pmu_disable_fixed(struct hw_perf_counter *hwc, int __idx)
{ {
int idx = __idx - X86_PMC_IDX_FIXED; int idx = __idx - X86_PMC_IDX_FIXED;
u64 ctrl_val, mask; u64 ctrl_val, mask;
int err;
mask = 0xfULL << (idx * 4); mask = 0xfULL << (idx * 4);
rdmsrl(hwc->config_base, ctrl_val); rdmsrl(hwc->config_base, ctrl_val);
ctrl_val &= ~mask; ctrl_val &= ~mask;
err = checking_wrmsrl(hwc->config_base, ctrl_val); (void)checking_wrmsrl(hwc->config_base, ctrl_val);
}
static inline void
p6_pmu_disable_counter(struct hw_perf_counter *hwc, int idx)
{
struct cpu_hw_counters *cpuc = &__get_cpu_var(cpu_hw_counters);
unsigned long val = ARCH_PERFMON_EVENTSEL0_ENABLE;
if (!cpuc->enabled)
val = 0;
(void)checking_wrmsrl(hwc->config_base + idx, val);
} }
static inline void static inline void
@ -943,6 +1023,17 @@ intel_pmu_enable_fixed(struct hw_perf_counter *hwc, int __idx)
err = checking_wrmsrl(hwc->config_base, ctrl_val); err = checking_wrmsrl(hwc->config_base, ctrl_val);
} }
static void p6_pmu_enable_counter(struct hw_perf_counter *hwc, int idx)
{
struct cpu_hw_counters *cpuc = &__get_cpu_var(cpu_hw_counters);
if (cpuc->enabled)
x86_pmu_enable_counter(hwc, idx);
else
x86_pmu_disable_counter(hwc, idx);
}
static void intel_pmu_enable_counter(struct hw_perf_counter *hwc, int idx) static void intel_pmu_enable_counter(struct hw_perf_counter *hwc, int idx)
{ {
if (unlikely(hwc->config_base == MSR_ARCH_PERFMON_FIXED_CTR_CTRL)) { if (unlikely(hwc->config_base == MSR_ARCH_PERFMON_FIXED_CTR_CTRL)) {
@ -1176,6 +1267,49 @@ static void intel_pmu_reset(void)
local_irq_restore(flags); local_irq_restore(flags);
} }
static int p6_pmu_handle_irq(struct pt_regs *regs)
{
struct perf_sample_data data;
struct cpu_hw_counters *cpuc;
struct perf_counter *counter;
struct hw_perf_counter *hwc;
int idx, handled = 0;
u64 val;
data.regs = regs;
data.addr = 0;
cpuc = &__get_cpu_var(cpu_hw_counters);
for (idx = 0; idx < x86_pmu.num_counters; idx++) {
if (!test_bit(idx, cpuc->active_mask))
continue;
counter = cpuc->counters[idx];
hwc = &counter->hw;
val = x86_perf_counter_update(counter, hwc, idx);
if (val & (1ULL << (x86_pmu.counter_bits - 1)))
continue;
/*
* counter overflow
*/
handled = 1;
data.period = counter->hw.last_period;
if (!x86_perf_counter_set_period(counter, hwc, idx))
continue;
if (perf_counter_overflow(counter, 1, &data))
p6_pmu_disable_counter(hwc, idx);
}
if (handled)
inc_irq_stat(apic_perf_irqs);
return handled;
}
/* /*
* This handler is triggered by the local APIC, so the APIC IRQ handling * This handler is triggered by the local APIC, so the APIC IRQ handling
@ -1185,14 +1319,13 @@ static int intel_pmu_handle_irq(struct pt_regs *regs)
{ {
struct perf_sample_data data; struct perf_sample_data data;
struct cpu_hw_counters *cpuc; struct cpu_hw_counters *cpuc;
int bit, cpu, loops; int bit, loops;
u64 ack, status; u64 ack, status;
data.regs = regs; data.regs = regs;
data.addr = 0; data.addr = 0;
cpu = smp_processor_id(); cpuc = &__get_cpu_var(cpu_hw_counters);
cpuc = &per_cpu(cpu_hw_counters, cpu);
perf_disable(); perf_disable();
status = intel_pmu_get_status(); status = intel_pmu_get_status();
@ -1249,14 +1382,13 @@ static int amd_pmu_handle_irq(struct pt_regs *regs)
struct cpu_hw_counters *cpuc; struct cpu_hw_counters *cpuc;
struct perf_counter *counter; struct perf_counter *counter;
struct hw_perf_counter *hwc; struct hw_perf_counter *hwc;
int cpu, idx, handled = 0; int idx, handled = 0;
u64 val; u64 val;
data.regs = regs; data.regs = regs;
data.addr = 0; data.addr = 0;
cpu = smp_processor_id(); cpuc = &__get_cpu_var(cpu_hw_counters);
cpuc = &per_cpu(cpu_hw_counters, cpu);
for (idx = 0; idx < x86_pmu.num_counters; idx++) { for (idx = 0; idx < x86_pmu.num_counters; idx++) {
if (!test_bit(idx, cpuc->active_mask)) if (!test_bit(idx, cpuc->active_mask))
@ -1353,6 +1485,32 @@ static __read_mostly struct notifier_block perf_counter_nmi_notifier = {
.priority = 1 .priority = 1
}; };
static struct x86_pmu p6_pmu = {
.name = "p6",
.handle_irq = p6_pmu_handle_irq,
.disable_all = p6_pmu_disable_all,
.enable_all = p6_pmu_enable_all,
.enable = p6_pmu_enable_counter,
.disable = p6_pmu_disable_counter,
.eventsel = MSR_P6_EVNTSEL0,
.perfctr = MSR_P6_PERFCTR0,
.event_map = p6_pmu_event_map,
.raw_event = p6_pmu_raw_event,
.max_events = ARRAY_SIZE(p6_perfmon_event_map),
.max_period = (1ULL << 31) - 1,
.version = 0,
.num_counters = 2,
/*
* Counters have 40 bits implemented. However they are designed such
* that bits [32-39] are sign extensions of bit 31. As such the
* effective width of a counter for P6-like PMU is 32 bits only.
*
* See IA-32 Intel Architecture Software developer manual Vol 3B
*/
.counter_bits = 32,
.counter_mask = (1ULL << 32) - 1,
};
static struct x86_pmu intel_pmu = { static struct x86_pmu intel_pmu = {
.name = "Intel", .name = "Intel",
.handle_irq = intel_pmu_handle_irq, .handle_irq = intel_pmu_handle_irq,
@ -1392,6 +1550,41 @@ static struct x86_pmu amd_pmu = {
.max_period = (1ULL << 47) - 1, .max_period = (1ULL << 47) - 1,
}; };
static int p6_pmu_init(void)
{
int high, low;
switch (boot_cpu_data.x86_model) {
case 1:
case 3: /* Pentium Pro */
case 5:
case 6: /* Pentium II */
case 7:
case 8:
case 11: /* Pentium III */
break;
case 9:
case 13:
/* for Pentium M, we need to check if PMU exist */
rdmsr(MSR_IA32_MISC_ENABLE, low, high);
if (low & MSR_IA32_MISC_ENABLE_EMON)
break;
default:
pr_cont("unsupported p6 CPU model %d ",
boot_cpu_data.x86_model);
return -ENODEV;
}
if (!cpu_has_apic) {
pr_info("no Local APIC, try rebooting with lapic");
return -ENODEV;
}
x86_pmu = p6_pmu;
return 0;
}
static int intel_pmu_init(void) static int intel_pmu_init(void)
{ {
union cpuid10_edx edx; union cpuid10_edx edx;
@ -1400,8 +1593,14 @@ static int intel_pmu_init(void)
unsigned int ebx; unsigned int ebx;
int version; int version;
if (!cpu_has(&boot_cpu_data, X86_FEATURE_ARCH_PERFMON)) if (!cpu_has(&boot_cpu_data, X86_FEATURE_ARCH_PERFMON)) {
/* check for P6 processor family */
if (boot_cpu_data.x86 == 6) {
return p6_pmu_init();
} else {
return -ENODEV; return -ENODEV;
}
}
/* /*
* Check whether the Architectural PerfMon supports * Check whether the Architectural PerfMon supports

View file

@ -1,7 +1,13 @@
#ifndef _PERF_PERF_H #ifndef _PERF_PERF_H
#define _PERF_PERF_H #define _PERF_PERF_H
#if defined(__x86_64__) || defined(__i386__) #if defined(__i386__)
#include "../../arch/x86/include/asm/unistd.h"
#define rmb() asm volatile("lock; addl $0,0(%%esp)" ::: "memory")
#define cpu_relax() asm volatile("rep; nop" ::: "memory");
#endif
#if defined(__x86_64__)
#include "../../arch/x86/include/asm/unistd.h" #include "../../arch/x86/include/asm/unistd.h"
#define rmb() asm volatile("lfence" ::: "memory") #define rmb() asm volatile("lfence" ::: "memory")
#define cpu_relax() asm volatile("rep; nop" ::: "memory"); #define cpu_relax() asm volatile("rep; nop" ::: "memory");