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

Commit 57273471 authored by Will Deacon's avatar Will Deacon Committed by Russell King
Browse files

ARM: 7354/1: perf: limit sample_period to half max_period in non-sampling mode



On ARM, the PMU does not stop counting after an overflow and therefore
IRQ latency affects the new counter value read by the kernel. This is
significant for non-sampling runs where it is possible for the new value
to overtake the previous one, causing the delta to be out by up to
max_period events.

Commit a737823d ("ARM: 6835/1: perf: ensure overflows aren't missed due
to IRQ latency") attempted to fix this problem by allowing interrupt
handlers to pass an overflow flag to the event update function, causing
the overflow calculation to assume that the counter passed through zero
when going from prev to new. Unfortunately, this doesn't work when
overflow occurs on the perf_task_tick path because we have the flag
cleared and end up computing a large negative delta.

This patch removes the overflow flag from armpmu_event_update and
instead limits the sample_period to half of the max_period for
non-sampling profiling runs.

Cc: <stable@vger.kernel.org>
Signed-off-by: default avatarMing Lei <ming.lei@canonical.com>
Signed-off-by: default avatarWill Deacon <will.deacon@arm.com>
Signed-off-by: default avatarRussell King <rmk+kernel@arm.linux.org.uk>
parent 81caaf25
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -134,7 +134,7 @@ int __init armpmu_register(struct arm_pmu *armpmu, char *name, int type);

u64 armpmu_event_update(struct perf_event *event,
			struct hw_perf_event *hwc,
			int idx, int overflow);
			int idx);

int armpmu_event_set_period(struct perf_event *event,
			    struct hw_perf_event *hwc,
+11 −11
Original line number Diff line number Diff line
@@ -180,7 +180,7 @@ armpmu_event_set_period(struct perf_event *event,
u64
armpmu_event_update(struct perf_event *event,
		    struct hw_perf_event *hwc,
		    int idx, int overflow)
		    int idx)
{
	struct arm_pmu *armpmu = to_arm_pmu(event->pmu);
	u64 delta, prev_raw_count, new_raw_count;
@@ -193,13 +193,7 @@ armpmu_event_update(struct perf_event *event,
			     new_raw_count) != prev_raw_count)
		goto again;

	new_raw_count &= armpmu->max_period;
	prev_raw_count &= armpmu->max_period;

	if (overflow)
		delta = armpmu->max_period - prev_raw_count + new_raw_count + 1;
	else
		delta = new_raw_count - prev_raw_count;
	delta = (new_raw_count - prev_raw_count) & armpmu->max_period;

	local64_add(delta, &event->count);
	local64_sub(delta, &hwc->period_left);
@@ -216,7 +210,7 @@ armpmu_read(struct perf_event *event)
	if (hwc->idx < 0)
		return;

	armpmu_event_update(event, hwc, hwc->idx, 0);
	armpmu_event_update(event, hwc, hwc->idx);
}

static void
@@ -232,7 +226,7 @@ armpmu_stop(struct perf_event *event, int flags)
	if (!(hwc->state & PERF_HES_STOPPED)) {
		armpmu->disable(hwc, hwc->idx);
		barrier(); /* why? */
		armpmu_event_update(event, hwc, hwc->idx, 0);
		armpmu_event_update(event, hwc, hwc->idx);
		hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
	}
}
@@ -518,7 +512,13 @@ __hw_perf_event_init(struct perf_event *event)
	hwc->config_base	    |= (unsigned long)mapping;

	if (!hwc->sample_period) {
		hwc->sample_period  = armpmu->max_period;
		/*
		 * For non-sampling runs, limit the sample_period to half
		 * of the counter width. That way, the new counter value
		 * is far less likely to overtake the previous one unless
		 * you have some serious IRQ latency issues.
		 */
		hwc->sample_period  = armpmu->max_period >> 1;
		hwc->last_period    = hwc->sample_period;
		local64_set(&hwc->period_left, hwc->sample_period);
	}
+1 −1
Original line number Diff line number Diff line
@@ -524,7 +524,7 @@ armv6pmu_handle_irq(int irq_num,
			continue;

		hwc = &event->hw;
		armpmu_event_update(event, hwc, idx, 1);
		armpmu_event_update(event, hwc, idx);
		data.period = event->hw.last_period;
		if (!armpmu_event_set_period(event, hwc, idx))
			continue;
+1 −1
Original line number Diff line number Diff line
@@ -963,7 +963,7 @@ static irqreturn_t armv7pmu_handle_irq(int irq_num, void *dev)
			continue;

		hwc = &event->hw;
		armpmu_event_update(event, hwc, idx, 1);
		armpmu_event_update(event, hwc, idx);
		data.period = event->hw.last_period;
		if (!armpmu_event_set_period(event, hwc, idx))
			continue;
+2 −2
Original line number Diff line number Diff line
@@ -259,7 +259,7 @@ xscale1pmu_handle_irq(int irq_num, void *dev)
			continue;

		hwc = &event->hw;
		armpmu_event_update(event, hwc, idx, 1);
		armpmu_event_update(event, hwc, idx);
		data.period = event->hw.last_period;
		if (!armpmu_event_set_period(event, hwc, idx))
			continue;
@@ -596,7 +596,7 @@ xscale2pmu_handle_irq(int irq_num, void *dev)
			continue;

		hwc = &event->hw;
		armpmu_event_update(event, hwc, idx, 1);
		armpmu_event_update(event, hwc, idx);
		data.period = event->hw.last_period;
		if (!armpmu_event_set_period(event, hwc, idx))
			continue;