Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b430f7c4 authored by Peter Zijlstra's avatar Peter Zijlstra Committed by Ingo Molnar
Browse files

perf/x86: Fix Intel shared extra MSR allocation



Zheng Yan reported that event group validation can wreck event state
when Intel extra_reg allocation changes event state.

Validation shouldn't change any persistent state. Cloning events in
validate_{event,group}() isn't really pretty either, so add a few
special cases to avoid modifying the event state.

The code is restructured to minimize the special case impact.

Reported-by: default avatarZheng Yan <zheng.z.yan@linux.intel.com>
Acked-by: default avatarStephane Eranian <eranian@google.com>
Signed-off-by: default avatarPeter Zijlstra <a.p.zijlstra@chello.nl>
Link: http://lkml.kernel.org/r/1338903031.28282.175.camel@twins


Signed-off-by: default avatarIngo Molnar <mingo@kernel.org>
parent 436d03fa
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -1496,6 +1496,7 @@ static struct cpu_hw_events *allocate_fake_cpuc(void)
		if (!cpuc->shared_regs)
		if (!cpuc->shared_regs)
			goto error;
			goto error;
	}
	}
	cpuc->is_fake = 1;
	return cpuc;
	return cpuc;
error:
error:
	free_fake_cpuc(cpuc);
	free_fake_cpuc(cpuc);
+1 −0
Original line number Original line Diff line number Diff line
@@ -117,6 +117,7 @@ struct cpu_hw_events {
	struct perf_event	*event_list[X86_PMC_IDX_MAX]; /* in enabled order */
	struct perf_event	*event_list[X86_PMC_IDX_MAX]; /* in enabled order */


	unsigned int		group_flag;
	unsigned int		group_flag;
	int			is_fake;


	/*
	/*
	 * Intel DebugStore bits
	 * Intel DebugStore bits
+64 −28
Original line number Original line Diff line number Diff line
@@ -1119,27 +1119,33 @@ intel_bts_constraints(struct perf_event *event)
	return NULL;
	return NULL;
}
}


static bool intel_try_alt_er(struct perf_event *event, int orig_idx)
static int intel_alt_er(int idx)
{
{
	if (!(x86_pmu.er_flags & ERF_HAS_RSP_1))
	if (!(x86_pmu.er_flags & ERF_HAS_RSP_1))
		return false;
		return idx;


	if (event->hw.extra_reg.idx == EXTRA_REG_RSP_0) {
	if (idx == EXTRA_REG_RSP_0)
		event->hw.config &= ~INTEL_ARCH_EVENT_MASK;
		return EXTRA_REG_RSP_1;
		event->hw.config |= 0x01bb;

		event->hw.extra_reg.idx = EXTRA_REG_RSP_1;
	if (idx == EXTRA_REG_RSP_1)
		event->hw.extra_reg.reg = MSR_OFFCORE_RSP_1;
		return EXTRA_REG_RSP_0;
	} else if (event->hw.extra_reg.idx == EXTRA_REG_RSP_1) {

	return idx;
}

static void intel_fixup_er(struct perf_event *event, int idx)
{
	event->hw.extra_reg.idx = idx;

	if (idx == EXTRA_REG_RSP_0) {
		event->hw.config &= ~INTEL_ARCH_EVENT_MASK;
		event->hw.config &= ~INTEL_ARCH_EVENT_MASK;
		event->hw.config |= 0x01b7;
		event->hw.config |= 0x01b7;
		event->hw.extra_reg.idx = EXTRA_REG_RSP_0;
		event->hw.extra_reg.reg = MSR_OFFCORE_RSP_0;
		event->hw.extra_reg.reg = MSR_OFFCORE_RSP_0;
	} else if (idx == EXTRA_REG_RSP_1) {
		event->hw.config &= ~INTEL_ARCH_EVENT_MASK;
		event->hw.config |= 0x01bb;
		event->hw.extra_reg.reg = MSR_OFFCORE_RSP_1;
	}
	}

	if (event->hw.extra_reg.idx == orig_idx)
		return false;

	return true;
}
}


/*
/*
@@ -1157,14 +1163,18 @@ __intel_shared_reg_get_constraints(struct cpu_hw_events *cpuc,
	struct event_constraint *c = &emptyconstraint;
	struct event_constraint *c = &emptyconstraint;
	struct er_account *era;
	struct er_account *era;
	unsigned long flags;
	unsigned long flags;
	int orig_idx = reg->idx;
	int idx = reg->idx;


	/* already allocated shared msr */
	/*
	if (reg->alloc)
	 * reg->alloc can be set due to existing state, so for fake cpuc we
	 * need to ignore this, otherwise we might fail to allocate proper fake
	 * state for this extra reg constraint. Also see the comment below.
	 */
	if (reg->alloc && !cpuc->is_fake)
		return NULL; /* call x86_get_event_constraint() */
		return NULL; /* call x86_get_event_constraint() */


again:
again:
	era = &cpuc->shared_regs->regs[reg->idx];
	era = &cpuc->shared_regs->regs[idx];
	/*
	/*
	 * we use spin_lock_irqsave() to avoid lockdep issues when
	 * we use spin_lock_irqsave() to avoid lockdep issues when
	 * passing a fake cpuc
	 * passing a fake cpuc
@@ -1173,6 +1183,29 @@ again:


	if (!atomic_read(&era->ref) || era->config == reg->config) {
	if (!atomic_read(&era->ref) || era->config == reg->config) {


		/*
		 * If its a fake cpuc -- as per validate_{group,event}() we
		 * shouldn't touch event state and we can avoid doing so
		 * since both will only call get_event_constraints() once
		 * on each event, this avoids the need for reg->alloc.
		 *
		 * Not doing the ER fixup will only result in era->reg being
		 * wrong, but since we won't actually try and program hardware
		 * this isn't a problem either.
		 */
		if (!cpuc->is_fake) {
			if (idx != reg->idx)
				intel_fixup_er(event, idx);

			/*
			 * x86_schedule_events() can call get_event_constraints()
			 * multiple times on events in the case of incremental
			 * scheduling(). reg->alloc ensures we only do the ER
			 * allocation once.
			 */
			reg->alloc = 1;
		}

		/* lock in msr value */
		/* lock in msr value */
		era->config = reg->config;
		era->config = reg->config;
		era->reg = reg->reg;
		era->reg = reg->reg;
@@ -1180,18 +1213,18 @@ again:
		/* one more user */
		/* one more user */
		atomic_inc(&era->ref);
		atomic_inc(&era->ref);


		/* no need to reallocate during incremental event scheduling */
		reg->alloc = 1;

		/*
		/*
		 * need to call x86_get_event_constraint()
		 * need to call x86_get_event_constraint()
		 * to check if associated event has constraints
		 * to check if associated event has constraints
		 */
		 */
		c = NULL;
		c = NULL;
	} else if (intel_try_alt_er(event, orig_idx)) {
	} else {
		idx = intel_alt_er(idx);
		if (idx != reg->idx) {
			raw_spin_unlock_irqrestore(&era->lock, flags);
			raw_spin_unlock_irqrestore(&era->lock, flags);
			goto again;
			goto again;
		}
		}
	}
	raw_spin_unlock_irqrestore(&era->lock, flags);
	raw_spin_unlock_irqrestore(&era->lock, flags);


	return c;
	return c;
@@ -1204,11 +1237,14 @@ __intel_shared_reg_put_constraints(struct cpu_hw_events *cpuc,
	struct er_account *era;
	struct er_account *era;


	/*
	/*
	 * only put constraint if extra reg was actually
	 * Only put constraint if extra reg was actually allocated. Also takes
	 * allocated. Also takes care of event which do
	 * care of event which do not use an extra shared reg.
	 * not use an extra shared reg
	 *
	 * Also, if this is a fake cpuc we shouldn't touch any event state
	 * (reg->alloc) and we don't care about leaving inconsistent cpuc state
	 * either since it'll be thrown out.
	 */
	 */
	if (!reg->alloc)
	if (!reg->alloc || cpuc->is_fake)
		return;
		return;


	era = &cpuc->shared_regs->regs[reg->idx];
	era = &cpuc->shared_regs->regs[reg->idx];