perf/x86/intel/bts: Fix confused ordering of PMU callbacks
The intel_bts driver is using a CPU-local 'started' variable to order callbacks and PMIs and make sure that AUX transactions don't get messed up. However, the ordering rules in regard to this variable is a complete mess, which recently resulted in perf_fuzzer-triggered warnings and panics. The general ordering rule that is patch is enforcing is that this cpu-local variable be set only when the cpu-local AUX transaction is active; consequently, this variable is to be checked before the AUX related bits can be touched. Reported-by: Vince Weaver <vincent.weaver@maine.edu> Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Cc: Arnaldo Carvalho de Melo <acme@infradead.org> Cc: Arnaldo Carvalho de Melo <acme@redhat.com> Cc: Jiri Olsa <jolsa@redhat.com> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Stephane Eranian <eranian@google.com> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: vince@deater.net Link: http://lkml.kernel.org/r/20160906132353.19887-4-alexander.shishkin@linux.intel.com Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
parent
b79ccadd6b
commit
a9a94401c2
@ -31,7 +31,17 @@
|
||||
struct bts_ctx {
|
||||
struct perf_output_handle handle;
|
||||
struct debug_store ds_back;
|
||||
int started;
|
||||
int state;
|
||||
};
|
||||
|
||||
/* BTS context states: */
|
||||
enum {
|
||||
/* no ongoing AUX transactions */
|
||||
BTS_STATE_STOPPED = 0,
|
||||
/* AUX transaction is on, BTS tracing is disabled */
|
||||
BTS_STATE_INACTIVE,
|
||||
/* AUX transaction is on, BTS tracing is running */
|
||||
BTS_STATE_ACTIVE,
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct bts_ctx, bts_ctx);
|
||||
@ -204,6 +214,15 @@ static void bts_update(struct bts_ctx *bts)
|
||||
static int
|
||||
bts_buffer_reset(struct bts_buffer *buf, struct perf_output_handle *handle);
|
||||
|
||||
/*
|
||||
* Ordering PMU callbacks wrt themselves and the PMI is done by means
|
||||
* of bts::state, which:
|
||||
* - is set when bts::handle::event is valid, that is, between
|
||||
* perf_aux_output_begin() and perf_aux_output_end();
|
||||
* - is zero otherwise;
|
||||
* - is ordered against bts::handle::event with a compiler barrier.
|
||||
*/
|
||||
|
||||
static void __bts_event_start(struct perf_event *event)
|
||||
{
|
||||
struct bts_ctx *bts = this_cpu_ptr(&bts_ctx);
|
||||
@ -221,10 +240,13 @@ static void __bts_event_start(struct perf_event *event)
|
||||
|
||||
/*
|
||||
* local barrier to make sure that ds configuration made it
|
||||
* before we enable BTS
|
||||
* before we enable BTS and bts::state goes ACTIVE
|
||||
*/
|
||||
wmb();
|
||||
|
||||
/* INACTIVE/STOPPED -> ACTIVE */
|
||||
WRITE_ONCE(bts->state, BTS_STATE_ACTIVE);
|
||||
|
||||
intel_pmu_enable_bts(config);
|
||||
|
||||
}
|
||||
@ -251,9 +273,6 @@ static void bts_event_start(struct perf_event *event, int flags)
|
||||
|
||||
__bts_event_start(event);
|
||||
|
||||
/* PMI handler: this counter is running and likely generating PMIs */
|
||||
ACCESS_ONCE(bts->started) = 1;
|
||||
|
||||
return;
|
||||
|
||||
fail_end_stop:
|
||||
@ -263,30 +282,34 @@ fail_stop:
|
||||
event->hw.state = PERF_HES_STOPPED;
|
||||
}
|
||||
|
||||
static void __bts_event_stop(struct perf_event *event)
|
||||
static void __bts_event_stop(struct perf_event *event, int state)
|
||||
{
|
||||
struct bts_ctx *bts = this_cpu_ptr(&bts_ctx);
|
||||
|
||||
/* ACTIVE -> INACTIVE(PMI)/STOPPED(->stop()) */
|
||||
WRITE_ONCE(bts->state, state);
|
||||
|
||||
/*
|
||||
* No extra synchronization is mandated by the documentation to have
|
||||
* BTS data stores globally visible.
|
||||
*/
|
||||
intel_pmu_disable_bts();
|
||||
|
||||
if (event->hw.state & PERF_HES_STOPPED)
|
||||
return;
|
||||
|
||||
ACCESS_ONCE(event->hw.state) |= PERF_HES_STOPPED;
|
||||
}
|
||||
|
||||
static void bts_event_stop(struct perf_event *event, int flags)
|
||||
{
|
||||
struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
|
||||
struct bts_ctx *bts = this_cpu_ptr(&bts_ctx);
|
||||
struct bts_buffer *buf = perf_get_aux(&bts->handle);
|
||||
struct bts_buffer *buf = NULL;
|
||||
int state = READ_ONCE(bts->state);
|
||||
|
||||
/* PMI handler: don't restart this counter */
|
||||
ACCESS_ONCE(bts->started) = 0;
|
||||
if (state == BTS_STATE_ACTIVE)
|
||||
__bts_event_stop(event, BTS_STATE_STOPPED);
|
||||
|
||||
__bts_event_stop(event);
|
||||
if (state != BTS_STATE_STOPPED)
|
||||
buf = perf_get_aux(&bts->handle);
|
||||
|
||||
event->hw.state |= PERF_HES_STOPPED;
|
||||
|
||||
if (flags & PERF_EF_UPDATE) {
|
||||
bts_update(bts);
|
||||
@ -296,6 +319,7 @@ static void bts_event_stop(struct perf_event *event, int flags)
|
||||
bts->handle.head =
|
||||
local_xchg(&buf->data_size,
|
||||
buf->nr_pages << PAGE_SHIFT);
|
||||
|
||||
perf_aux_output_end(&bts->handle, local_xchg(&buf->data_size, 0),
|
||||
!!local_xchg(&buf->lost, 0));
|
||||
}
|
||||
@ -310,8 +334,20 @@ static void bts_event_stop(struct perf_event *event, int flags)
|
||||
void intel_bts_enable_local(void)
|
||||
{
|
||||
struct bts_ctx *bts = this_cpu_ptr(&bts_ctx);
|
||||
int state = READ_ONCE(bts->state);
|
||||
|
||||
if (bts->handle.event && bts->started)
|
||||
/*
|
||||
* Here we transition from INACTIVE to ACTIVE;
|
||||
* if we instead are STOPPED from the interrupt handler,
|
||||
* stay that way. Can't be ACTIVE here though.
|
||||
*/
|
||||
if (WARN_ON_ONCE(state == BTS_STATE_ACTIVE))
|
||||
return;
|
||||
|
||||
if (state == BTS_STATE_STOPPED)
|
||||
return;
|
||||
|
||||
if (bts->handle.event)
|
||||
__bts_event_start(bts->handle.event);
|
||||
}
|
||||
|
||||
@ -319,8 +355,15 @@ void intel_bts_disable_local(void)
|
||||
{
|
||||
struct bts_ctx *bts = this_cpu_ptr(&bts_ctx);
|
||||
|
||||
/*
|
||||
* Here we transition from ACTIVE to INACTIVE;
|
||||
* do nothing for STOPPED or INACTIVE.
|
||||
*/
|
||||
if (READ_ONCE(bts->state) != BTS_STATE_ACTIVE)
|
||||
return;
|
||||
|
||||
if (bts->handle.event)
|
||||
__bts_event_stop(bts->handle.event);
|
||||
__bts_event_stop(bts->handle.event, BTS_STATE_INACTIVE);
|
||||
}
|
||||
|
||||
static int
|
||||
@ -407,9 +450,13 @@ int intel_bts_interrupt(void)
|
||||
struct perf_event *event = bts->handle.event;
|
||||
struct bts_buffer *buf;
|
||||
s64 old_head;
|
||||
int err;
|
||||
int err = -ENOSPC;
|
||||
|
||||
if (!event || !bts->started)
|
||||
/*
|
||||
* this is wrapped in intel_bts_enable_local/intel_bts_disable_local,
|
||||
* so we can only be INACTIVE or STOPPED
|
||||
*/
|
||||
if (READ_ONCE(bts->state) == BTS_STATE_STOPPED)
|
||||
return 0;
|
||||
|
||||
buf = perf_get_aux(&bts->handle);
|
||||
@ -432,12 +479,21 @@ int intel_bts_interrupt(void)
|
||||
!!local_xchg(&buf->lost, 0));
|
||||
|
||||
buf = perf_aux_output_begin(&bts->handle, event);
|
||||
if (!buf)
|
||||
return 1;
|
||||
if (buf)
|
||||
err = bts_buffer_reset(buf, &bts->handle);
|
||||
|
||||
err = bts_buffer_reset(buf, &bts->handle);
|
||||
if (err)
|
||||
perf_aux_output_end(&bts->handle, 0, false);
|
||||
if (err) {
|
||||
WRITE_ONCE(bts->state, BTS_STATE_STOPPED);
|
||||
|
||||
if (buf) {
|
||||
/*
|
||||
* BTS_STATE_STOPPED should be visible before
|
||||
* cleared handle::event
|
||||
*/
|
||||
barrier();
|
||||
perf_aux_output_end(&bts->handle, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user