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

Commit 98fb1807 authored by Paul Mackerras's avatar Paul Mackerras Committed by Ingo Molnar
Browse files

perf_counter: powerpc: Make powerpc perf_counter code safe for 32-bit kernels



This abstracts a few things in arch/powerpc/kernel/perf_counter.c
that are specific to 64-bit kernels, and provides definitions for
32-bit kernels.  In particular,

* Only 64-bit has MMCRA and the bits in it that give information
  about a PMU interrupt (sampled PR, HV, slot number etc.)
* Only 64-bit has the lppaca and the lppaca->pmcregs_in_use field
* Use of SDAR is confined to 64-bit for now
* Only 64-bit has soft/lazy interrupt disable and therefore
  pseudo-NMIs (interrupts that occur while interrupts are soft-disabled)
* Only 64-bit has PMC7 and PMC8
* Only 64-bit has the MSR_HV bit.

This also fixes the types used in a couple of places, where we were
using long types for things that need to be 64-bit.

Signed-off-by: default avatarPaul Mackerras <paulus@samba.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: linuxppc-dev@ozlabs.org
Cc: benh@kernel.crashing.org
LKML-Reference: <19000.55590.634126.876084@cargo.ozlabs.ibm.com>
Signed-off-by: default avatarIngo Molnar <mingo@elte.hu>
parent 079b3c56
Loading
Loading
Loading
Loading
+133 −60
Original line number Diff line number Diff line
@@ -46,6 +46,115 @@ struct power_pmu *ppmu;
 */
static unsigned int freeze_counters_kernel = MMCR0_FCS;

/*
 * 32-bit doesn't have MMCRA but does have an MMCR2,
 * and a few other names are different.
 */
#ifdef CONFIG_PPC32

#define MMCR0_FCHV		0
#define MMCR0_PMCjCE		MMCR0_PMCnCE

#define SPRN_MMCRA		SPRN_MMCR2
#define MMCRA_SAMPLE_ENABLE	0

static inline unsigned long perf_ip_adjust(struct pt_regs *regs)
{
	return 0;
}
static inline void perf_set_pmu_inuse(int inuse) { }
static inline void perf_get_data_addr(struct pt_regs *regs, u64 *addrp) { }
static inline u32 perf_get_misc_flags(struct pt_regs *regs)
{
	return 0;
}
static inline void perf_read_regs(struct pt_regs *regs) { }
static inline int perf_intr_is_nmi(struct pt_regs *regs)
{
	return 0;
}

#endif /* CONFIG_PPC32 */

/*
 * Things that are specific to 64-bit implementations.
 */
#ifdef CONFIG_PPC64

static inline unsigned long perf_ip_adjust(struct pt_regs *regs)
{
	unsigned long mmcra = regs->dsisr;

	if ((mmcra & MMCRA_SAMPLE_ENABLE) && !(ppmu->flags & PPMU_ALT_SIPR)) {
		unsigned long slot = (mmcra & MMCRA_SLOT) >> MMCRA_SLOT_SHIFT;
		if (slot > 1)
			return 4 * (slot - 1);
	}
	return 0;
}

static inline void perf_set_pmu_inuse(int inuse)
{
	get_lppaca()->pmcregs_in_use = inuse;
}

/*
 * The user wants a data address recorded.
 * If we're not doing instruction sampling, give them the SDAR
 * (sampled data address).  If we are doing instruction sampling, then
 * only give them the SDAR if it corresponds to the instruction
 * pointed to by SIAR; this is indicated by the [POWER6_]MMCRA_SDSYNC
 * bit in MMCRA.
 */
static inline void perf_get_data_addr(struct pt_regs *regs, u64 *addrp)
{
	unsigned long mmcra = regs->dsisr;
	unsigned long sdsync = (ppmu->flags & PPMU_ALT_SIPR) ?
		POWER6_MMCRA_SDSYNC : MMCRA_SDSYNC;

	if (!(mmcra & MMCRA_SAMPLE_ENABLE) || (mmcra & sdsync))
		*addrp = mfspr(SPRN_SDAR);
}

static inline u32 perf_get_misc_flags(struct pt_regs *regs)
{
	unsigned long mmcra = regs->dsisr;

	if (TRAP(regs) != 0xf00)
		return 0;	/* not a PMU interrupt */

	if (ppmu->flags & PPMU_ALT_SIPR) {
		if (mmcra & POWER6_MMCRA_SIHV)
			return PERF_EVENT_MISC_HYPERVISOR;
		return (mmcra & POWER6_MMCRA_SIPR) ?
			PERF_EVENT_MISC_USER : PERF_EVENT_MISC_KERNEL;
	}
	if (mmcra & MMCRA_SIHV)
		return PERF_EVENT_MISC_HYPERVISOR;
	return (mmcra & MMCRA_SIPR) ? PERF_EVENT_MISC_USER :
		PERF_EVENT_MISC_KERNEL;
}

/*
 * Overload regs->dsisr to store MMCRA so we only need to read it once
 * on each interrupt.
 */
static inline void perf_read_regs(struct pt_regs *regs)
{
	regs->dsisr = mfspr(SPRN_MMCRA);
}

/*
 * If interrupts were soft-disabled when a PMU interrupt occurs, treat
 * it as an NMI.
 */
static inline int perf_intr_is_nmi(struct pt_regs *regs)
{
	return !regs->softe;
}

#endif /* CONFIG_PPC64 */

static void perf_counter_interrupt(struct pt_regs *regs);

void perf_counter_print_debug(void)
@@ -78,12 +187,14 @@ static unsigned long read_pmc(int idx)
	case 6:
		val = mfspr(SPRN_PMC6);
		break;
#ifdef CONFIG_PPC64
	case 7:
		val = mfspr(SPRN_PMC7);
		break;
	case 8:
		val = mfspr(SPRN_PMC8);
		break;
#endif /* CONFIG_PPC64 */
	default:
		printk(KERN_ERR "oops trying to read PMC%d\n", idx);
		val = 0;
@@ -115,12 +226,14 @@ static void write_pmc(int idx, unsigned long val)
	case 6:
		mtspr(SPRN_PMC6, val);
		break;
#ifdef CONFIG_PPC64
	case 7:
		mtspr(SPRN_PMC7, val);
		break;
	case 8:
		mtspr(SPRN_PMC8, val);
		break;
#endif /* CONFIG_PPC64 */
	default:
		printk(KERN_ERR "oops trying to write PMC%d\n", idx);
	}
@@ -283,7 +396,7 @@ static int check_excludes(struct perf_counter **ctrs, unsigned int cflags[],

static void power_pmu_read(struct perf_counter *counter)
{
	long val, delta, prev;
	s64 val, delta, prev;

	if (!counter->hw.idx)
		return;
@@ -477,7 +590,7 @@ void hw_perf_enable(void)
		mtspr(SPRN_MMCRA, cpuhw->mmcr[2] & ~MMCRA_SAMPLE_ENABLE);
		mtspr(SPRN_MMCR1, cpuhw->mmcr[1]);
		if (cpuhw->n_counters == 0)
			get_lppaca()->pmcregs_in_use = 0;
			perf_set_pmu_inuse(0);
		goto out_enable;
	}

@@ -510,7 +623,7 @@ void hw_perf_enable(void)
	 * bit set and set the hardware counters to their initial values.
	 * Then unfreeze the counters.
	 */
	get_lppaca()->pmcregs_in_use = 1;
	perf_set_pmu_inuse(1);
	mtspr(SPRN_MMCRA, cpuhw->mmcr[2] & ~MMCRA_SAMPLE_ENABLE);
	mtspr(SPRN_MMCR1, cpuhw->mmcr[1]);
	mtspr(SPRN_MMCR0, (cpuhw->mmcr[0] & ~(MMCR0_PMC1CE | MMCR0_PMCjCE))
@@ -1007,11 +1120,10 @@ const struct pmu *hw_perf_counter_init(struct perf_counter *counter)
 * things if requested.  Note that interrupts are hard-disabled
 * here so there is no possibility of being interrupted.
 */
static void record_and_restart(struct perf_counter *counter, long val,
static void record_and_restart(struct perf_counter *counter, unsigned long val,
			       struct pt_regs *regs, int nmi)
{
	u64 period = counter->hw.sample_period;
	unsigned long mmcra, sdsync;
	s64 prev, delta, left;
	int record = 0;

@@ -1033,8 +1145,8 @@ static void record_and_restart(struct perf_counter *counter, long val,
				left = period;
			record = 1;
		}
		if (left < 0x80000000L)
			val = 0x80000000L - left;
		if (left < 0x80000000LL)
			val = 0x80000000LL - left;
	}

	/*
@@ -1047,22 +1159,9 @@ static void record_and_restart(struct perf_counter *counter, long val,
			.period	= counter->hw.last_period,
		};

		if (counter->attr.sample_type & PERF_SAMPLE_ADDR) {
			/*
			 * The user wants a data address recorded.
			 * If we're not doing instruction sampling,
			 * give them the SDAR (sampled data address).
			 * If we are doing instruction sampling, then only
			 * give them the SDAR if it corresponds to the
			 * instruction pointed to by SIAR; this is indicated
			 * by the [POWER6_]MMCRA_SDSYNC bit in MMCRA.
			 */
			mmcra = regs->dsisr;
			sdsync = (ppmu->flags & PPMU_ALT_SIPR) ?
				POWER6_MMCRA_SDSYNC : MMCRA_SDSYNC;
			if (!(mmcra & MMCRA_SAMPLE_ENABLE) || (mmcra & sdsync))
				data.addr = mfspr(SPRN_SDAR);
		}
		if (counter->attr.sample_type & PERF_SAMPLE_ADDR)
			perf_get_data_addr(regs, &data.addr);

		if (perf_counter_overflow(counter, nmi, &data)) {
			/*
			 * Interrupts are coming too fast - throttle them
@@ -1088,47 +1187,26 @@ static void record_and_restart(struct perf_counter *counter, long val,
 */
unsigned long perf_misc_flags(struct pt_regs *regs)
{
	unsigned long mmcra;
	u32 flags = perf_get_misc_flags(regs);

	if (TRAP(regs) != 0xf00) {
		/* not a PMU interrupt */
	if (flags)
		return flags;
	return user_mode(regs) ? PERF_EVENT_MISC_USER :
		PERF_EVENT_MISC_KERNEL;
}

	mmcra = regs->dsisr;
	if (ppmu->flags & PPMU_ALT_SIPR) {
		if (mmcra & POWER6_MMCRA_SIHV)
			return PERF_EVENT_MISC_HYPERVISOR;
		return (mmcra & POWER6_MMCRA_SIPR) ? PERF_EVENT_MISC_USER :
			PERF_EVENT_MISC_KERNEL;
	}
	if (mmcra & MMCRA_SIHV)
		return PERF_EVENT_MISC_HYPERVISOR;
	return (mmcra & MMCRA_SIPR) ? PERF_EVENT_MISC_USER :
			PERF_EVENT_MISC_KERNEL;
}

/*
 * Called from generic code to get the instruction pointer
 * for an event.
 */
unsigned long perf_instruction_pointer(struct pt_regs *regs)
{
	unsigned long mmcra;
	unsigned long ip;
	unsigned long slot;

	if (TRAP(regs) != 0xf00)
		return regs->nip;	/* not a PMU interrupt */

	ip = mfspr(SPRN_SIAR);
	mmcra = regs->dsisr;
	if ((mmcra & MMCRA_SAMPLE_ENABLE) && !(ppmu->flags & PPMU_ALT_SIPR)) {
		slot = (mmcra & MMCRA_SLOT) >> MMCRA_SLOT_SHIFT;
		if (slot > 1)
			ip += 4 * (slot - 1);
	}
	ip = mfspr(SPRN_SIAR) + perf_ip_adjust(regs);
	return ip;
}

@@ -1140,7 +1218,7 @@ static void perf_counter_interrupt(struct pt_regs *regs)
	int i;
	struct cpu_hw_counters *cpuhw = &__get_cpu_var(cpu_hw_counters);
	struct perf_counter *counter;
	long val;
	unsigned long val;
	int found = 0;
	int nmi;

@@ -1148,16 +1226,9 @@ static void perf_counter_interrupt(struct pt_regs *regs)
		freeze_limited_counters(cpuhw, mfspr(SPRN_PMC5),
					mfspr(SPRN_PMC6));

	/*
	 * Overload regs->dsisr to store MMCRA so we only need to read it once.
	 */
	regs->dsisr = mfspr(SPRN_MMCRA);
	perf_read_regs(regs);

	/*
	 * If interrupts were soft-disabled when this PMU interrupt
	 * occurred, treat it as an NMI.
	 */
	nmi = !regs->softe;
	nmi = perf_intr_is_nmi(regs);
	if (nmi)
		nmi_enter();
	else
@@ -1223,11 +1294,13 @@ int register_power_pmu(struct power_pmu *pmu)
	pr_info("%s performance monitor hardware support registered\n",
		pmu->name);

#ifdef MSR_HV
	/*
	 * Use FCHV to ignore kernel events if MSR.HV is set.
	 */
	if (mfmsr() & MSR_HV)
		freeze_counters_kernel = MMCR0_FCHV;
#endif /* CONFIG_PPC64 */

	return 0;
}