Skip to content

Commit

Permalink
perf_counter: Rework the perf counter disable/enable
Browse files Browse the repository at this point in the history
The current disable/enable mechanism is:

	token = hw_perf_save_disable();
	...
	/* do bits */
	...
	hw_perf_restore(token);

This works well, provided that the use nests properly. Except we don't.

x86 NMI/INT throttling has non-nested use of this, breaking things. Therefore
provide a reference counter disable/enable interface, where the first disable
disables the hardware, and the last enable enables the hardware again.

[ Impact: refactor, simplify the PMU disable/enable logic ]

Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Corey Ashford <cjashfor@linux.vnet.ibm.com>
LKML-Reference: <new-submission>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
  • Loading branch information
Peter Zijlstra authored and Ingo Molnar committed May 15, 2009
1 parent 962bf7a commit 9e35ad3
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 120 deletions.
24 changes: 12 additions & 12 deletions arch/powerpc/kernel/perf_counter.c
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ static void write_mmcr0(struct cpu_hw_counters *cpuhw, unsigned long mmcr0)
* Disable all counters to prevent PMU interrupts and to allow
* counters to be added or removed.
*/
u64 hw_perf_save_disable(void)
void hw_perf_disable(void)
{
struct cpu_hw_counters *cpuhw;
unsigned long ret;
Expand Down Expand Up @@ -428,15 +428,14 @@ u64 hw_perf_save_disable(void)
mb();
}
local_irq_restore(flags);
return ret;
}

/*
* Re-enable all counters if disable == 0.
* If we were previously disabled and counters were added, then
* put the new config on the PMU.
*/
void hw_perf_restore(u64 disable)
void hw_perf_enable(void)
{
struct perf_counter *counter;
struct cpu_hw_counters *cpuhw;
Expand All @@ -448,9 +447,12 @@ void hw_perf_restore(u64 disable)
int n_lim;
int idx;

if (disable)
return;
local_irq_save(flags);
if (!cpuhw->disabled) {
local_irq_restore(flags);
return;
}

cpuhw = &__get_cpu_var(cpu_hw_counters);
cpuhw->disabled = 0;

Expand Down Expand Up @@ -649,19 +651,18 @@ int hw_perf_group_sched_in(struct perf_counter *group_leader,
/*
* Add a counter to the PMU.
* If all counters are not already frozen, then we disable and
* re-enable the PMU in order to get hw_perf_restore to do the
* re-enable the PMU in order to get hw_perf_enable to do the
* actual work of reconfiguring the PMU.
*/
static int power_pmu_enable(struct perf_counter *counter)
{
struct cpu_hw_counters *cpuhw;
unsigned long flags;
u64 pmudis;
int n0;
int ret = -EAGAIN;

local_irq_save(flags);
pmudis = hw_perf_save_disable();
perf_disable();

/*
* Add the counter to the list (if there is room)
Expand All @@ -685,7 +686,7 @@ static int power_pmu_enable(struct perf_counter *counter)

ret = 0;
out:
hw_perf_restore(pmudis);
perf_enable();
local_irq_restore(flags);
return ret;
}
Expand All @@ -697,11 +698,10 @@ static void power_pmu_disable(struct perf_counter *counter)
{
struct cpu_hw_counters *cpuhw;
long i;
u64 pmudis;
unsigned long flags;

local_irq_save(flags);
pmudis = hw_perf_save_disable();
perf_disable();

power_pmu_read(counter);

Expand Down Expand Up @@ -735,7 +735,7 @@ static void power_pmu_disable(struct perf_counter *counter)
cpuhw->mmcr[0] &= ~(MMCR0_PMXE | MMCR0_FCECE);
}

hw_perf_restore(pmudis);
perf_enable();
local_irq_restore(flags);
}

Expand Down
113 changes: 42 additions & 71 deletions arch/x86/kernel/cpu/perf_counter.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ struct cpu_hw_counters {
unsigned long used_mask[BITS_TO_LONGS(X86_PMC_IDX_MAX)];
unsigned long active_mask[BITS_TO_LONGS(X86_PMC_IDX_MAX)];
unsigned long interrupts;
u64 throttle_ctrl;
int enabled;
};

Expand All @@ -42,8 +41,8 @@ struct x86_pmu {
const char *name;
int version;
int (*handle_irq)(struct pt_regs *, int);
u64 (*save_disable_all)(void);
void (*restore_all)(u64);
void (*disable_all)(void);
void (*enable_all)(void);
void (*enable)(struct hw_perf_counter *, int);
void (*disable)(struct hw_perf_counter *, int);
unsigned eventsel;
Expand All @@ -56,6 +55,7 @@ struct x86_pmu {
int counter_bits;
u64 counter_mask;
u64 max_period;
u64 intel_ctrl;
};

static struct x86_pmu x86_pmu __read_mostly;
Expand Down Expand Up @@ -311,31 +311,26 @@ static int __hw_perf_counter_init(struct perf_counter *counter)
return 0;
}

static u64 intel_pmu_save_disable_all(void)
static void intel_pmu_disable_all(void)
{
u64 ctrl;

rdmsrl(MSR_CORE_PERF_GLOBAL_CTRL, ctrl);
wrmsrl(MSR_CORE_PERF_GLOBAL_CTRL, 0);

return ctrl;
}

static u64 amd_pmu_save_disable_all(void)
static void amd_pmu_disable_all(void)
{
struct cpu_hw_counters *cpuc = &__get_cpu_var(cpu_hw_counters);
int enabled, idx;
int idx;

if (!cpuc->enabled)
return;

enabled = cpuc->enabled;
cpuc->enabled = 0;
/*
* ensure we write the disable before we start disabling the
* counters proper, so that amd_pmu_enable_counter() does the
* right thing.
*/
barrier();
if (!enabled)
goto out;

for (idx = 0; idx < x86_pmu.num_counters; idx++) {
u64 val;
Expand All @@ -348,37 +343,31 @@ static u64 amd_pmu_save_disable_all(void)
val &= ~ARCH_PERFMON_EVENTSEL0_ENABLE;
wrmsrl(MSR_K7_EVNTSEL0 + idx, val);
}

out:
return enabled;
}

u64 hw_perf_save_disable(void)
void hw_perf_disable(void)
{
if (!x86_pmu_initialized())
return 0;
return x86_pmu.save_disable_all();
return;
return x86_pmu.disable_all();
}
/*
* Exported because of ACPI idle
*/
EXPORT_SYMBOL_GPL(hw_perf_save_disable);

static void intel_pmu_restore_all(u64 ctrl)
static void intel_pmu_enable_all(void)
{
wrmsrl(MSR_CORE_PERF_GLOBAL_CTRL, ctrl);
wrmsrl(MSR_CORE_PERF_GLOBAL_CTRL, x86_pmu.intel_ctrl);
}

static void amd_pmu_restore_all(u64 ctrl)
static void amd_pmu_enable_all(void)
{
struct cpu_hw_counters *cpuc = &__get_cpu_var(cpu_hw_counters);
int idx;

cpuc->enabled = ctrl;
barrier();
if (!ctrl)
if (cpuc->enabled)
return;

cpuc->enabled = 1;
barrier();

for (idx = 0; idx < x86_pmu.num_counters; idx++) {
u64 val;

Expand All @@ -392,16 +381,12 @@ static void amd_pmu_restore_all(u64 ctrl)
}
}

void hw_perf_restore(u64 ctrl)
void hw_perf_enable(void)
{
if (!x86_pmu_initialized())
return;
x86_pmu.restore_all(ctrl);
x86_pmu.enable_all();
}
/*
* Exported because of ACPI idle
*/
EXPORT_SYMBOL_GPL(hw_perf_restore);

static inline u64 intel_pmu_get_status(void)
{
Expand Down Expand Up @@ -735,15 +720,14 @@ static int intel_pmu_handle_irq(struct pt_regs *regs, int nmi)
int bit, cpu = smp_processor_id();
u64 ack, status;
struct cpu_hw_counters *cpuc = &per_cpu(cpu_hw_counters, cpu);
int ret = 0;

cpuc->throttle_ctrl = intel_pmu_save_disable_all();

perf_disable();
status = intel_pmu_get_status();
if (!status)
goto out;
if (!status) {
perf_enable();
return 0;
}

ret = 1;
again:
inc_irq_stat(apic_perf_irqs);
ack = status;
Expand All @@ -767,19 +751,11 @@ static int intel_pmu_handle_irq(struct pt_regs *regs, int nmi)
status = intel_pmu_get_status();
if (status)
goto again;
out:
/*
* Restore - do not reenable when global enable is off or throttled:
*/
if (cpuc->throttle_ctrl) {
if (++cpuc->interrupts < PERFMON_MAX_INTERRUPTS) {
intel_pmu_restore_all(cpuc->throttle_ctrl);
} else {
pr_info("CPU#%d: perfcounters: max interrupt rate exceeded! Throttle on.\n", smp_processor_id());
}
}

return ret;
if (++cpuc->interrupts != PERFMON_MAX_INTERRUPTS)
perf_enable();

return 1;
}

static int amd_pmu_handle_irq(struct pt_regs *regs, int nmi)
Expand All @@ -792,13 +768,11 @@ static int amd_pmu_handle_irq(struct pt_regs *regs, int nmi)
struct hw_perf_counter *hwc;
int idx, throttle = 0;

cpuc->throttle_ctrl = cpuc->enabled;
cpuc->enabled = 0;
barrier();

if (cpuc->throttle_ctrl) {
if (++cpuc->interrupts >= PERFMON_MAX_INTERRUPTS)
throttle = 1;
if (++cpuc->interrupts == PERFMON_MAX_INTERRUPTS) {
throttle = 1;
__perf_disable();
cpuc->enabled = 0;
barrier();
}

for (idx = 0; idx < x86_pmu.num_counters; idx++) {
Expand All @@ -824,9 +798,6 @@ static int amd_pmu_handle_irq(struct pt_regs *regs, int nmi)
amd_pmu_disable_counter(hwc, idx);
}

if (cpuc->throttle_ctrl && !throttle)
cpuc->enabled = 1;

return handled;
}

Expand All @@ -839,13 +810,11 @@ void perf_counter_unthrottle(void)

cpuc = &__get_cpu_var(cpu_hw_counters);
if (cpuc->interrupts >= PERFMON_MAX_INTERRUPTS) {
pr_info("CPU#%d: perfcounters: throttle off.\n", smp_processor_id());

/*
* Clear them before re-enabling irqs/NMIs again:
*/
cpuc->interrupts = 0;
hw_perf_restore(cpuc->throttle_ctrl);
perf_enable();
} else {
cpuc->interrupts = 0;
}
Expand Down Expand Up @@ -931,8 +900,8 @@ static __read_mostly struct notifier_block perf_counter_nmi_notifier = {
static struct x86_pmu intel_pmu = {
.name = "Intel",
.handle_irq = intel_pmu_handle_irq,
.save_disable_all = intel_pmu_save_disable_all,
.restore_all = intel_pmu_restore_all,
.disable_all = intel_pmu_disable_all,
.enable_all = intel_pmu_enable_all,
.enable = intel_pmu_enable_counter,
.disable = intel_pmu_disable_counter,
.eventsel = MSR_ARCH_PERFMON_EVENTSEL0,
Expand All @@ -951,8 +920,8 @@ static struct x86_pmu intel_pmu = {
static struct x86_pmu amd_pmu = {
.name = "AMD",
.handle_irq = amd_pmu_handle_irq,
.save_disable_all = amd_pmu_save_disable_all,
.restore_all = amd_pmu_restore_all,
.disable_all = amd_pmu_disable_all,
.enable_all = amd_pmu_enable_all,
.enable = amd_pmu_enable_counter,
.disable = amd_pmu_disable_counter,
.eventsel = MSR_K7_EVNTSEL0,
Expand Down Expand Up @@ -1003,6 +972,8 @@ static int intel_pmu_init(void)
x86_pmu.counter_bits = eax.split.bit_width;
x86_pmu.counter_mask = (1ULL << eax.split.bit_width) - 1;

rdmsrl(MSR_CORE_PERF_GLOBAL_CTRL, x86_pmu.intel_ctrl);

return 0;
}

Expand Down
6 changes: 2 additions & 4 deletions drivers/acpi/processor_idle.c
Original file line number Diff line number Diff line change
Expand Up @@ -763,11 +763,9 @@ static int acpi_idle_bm_check(void)
*/
static inline void acpi_idle_do_entry(struct acpi_processor_cx *cx)
{
u64 perf_flags;

/* Don't trace irqs off for idle */
stop_critical_timings();
perf_flags = hw_perf_save_disable();
perf_disable();
if (cx->entry_method == ACPI_CSTATE_FFH) {
/* Call into architectural FFH based C-state */
acpi_processor_ffh_cstate_enter(cx);
Expand All @@ -782,7 +780,7 @@ static inline void acpi_idle_do_entry(struct acpi_processor_cx *cx)
gets asserted in time to freeze execution properly. */
unused = inl(acpi_gbl_FADT.xpm_timer_block.address);
}
hw_perf_restore(perf_flags);
perf_enable();
start_critical_timings();
}

Expand Down
10 changes: 6 additions & 4 deletions include/linux/perf_counter.h
Original file line number Diff line number Diff line change
Expand Up @@ -544,8 +544,10 @@ extern void perf_counter_exit_task(struct task_struct *child);
extern void perf_counter_do_pending(void);
extern void perf_counter_print_debug(void);
extern void perf_counter_unthrottle(void);
extern u64 hw_perf_save_disable(void);
extern void hw_perf_restore(u64 ctrl);
extern void __perf_disable(void);
extern bool __perf_enable(void);
extern void perf_disable(void);
extern void perf_enable(void);
extern int perf_counter_task_disable(void);
extern int perf_counter_task_enable(void);
extern int hw_perf_group_sched_in(struct perf_counter *group_leader,
Expand Down Expand Up @@ -600,8 +602,8 @@ static inline void perf_counter_exit_task(struct task_struct *child) { }
static inline void perf_counter_do_pending(void) { }
static inline void perf_counter_print_debug(void) { }
static inline void perf_counter_unthrottle(void) { }
static inline void hw_perf_restore(u64 ctrl) { }
static inline u64 hw_perf_save_disable(void) { return 0; }
static inline void perf_disable(void) { }
static inline void perf_enable(void) { }
static inline int perf_counter_task_disable(void) { return -EINVAL; }
static inline int perf_counter_task_enable(void) { return -EINVAL; }

Expand Down
Loading

0 comments on commit 9e35ad3

Please sign in to comment.