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

Commit adcd5a56 authored by Neil Leeder's avatar Neil Leeder Committed by Stephen Boyd
Browse files

Perf: keep events across hotplug



Keep event list alive across a CPU hotplug so that perf
can resume when the CPU comes back online. Bring a CPU
online when exiting a perf session so it can be cleaned
up properly.

Change-Id: Ie0e4a43f751beb77afdc84e9d52b21780f279d80
Signed-off-by: default avatarNeil Leeder <nleeder@codeaurora.org>
parent fe311197
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -563,6 +563,7 @@ static void armpmu_init(struct arm_pmu *armpmu)
	armpmu->pmu.start = armpmu_start;
	armpmu->pmu.stop = armpmu_stop;
	armpmu->pmu.read = armpmu_read;
	armpmu->pmu.events_across_hotplug = 1;
}

int armpmu_register(struct arm_pmu *armpmu, int type)
+27 −25
Original line number Diff line number Diff line
@@ -186,6 +186,26 @@ static void disable_irq_callback(void *info)
	disable_percpu_irq(irq);
}

static void armpmu_update_counters(void)
{
	struct pmu_hw_events *hw_events;
	int idx;

	if (!cpu_pmu)
		return;

	hw_events = cpu_pmu->get_hw_events();

	for (idx = 0; idx <= cpu_pmu->num_events; ++idx) {
		struct perf_event *event = hw_events->events[idx];

		if (!event)
			continue;

		cpu_pmu->pmu.read(event);
	}
}

/*
 * PMU hardware loses all context when a CPU goes offline.
 * When a CPU is hotplugged back in, since some hardware registers are
@@ -196,11 +216,13 @@ static int __cpuinit cpu_pmu_notify(struct notifier_block *b,
				    unsigned long action, void *hcpu)
{
	int irq;
	struct pmu *pmu;

	if (cpu_has_active_perf((int)hcpu)) {
		switch ((action & ~CPU_TASKS_FROZEN)) {

		case CPU_DOWN_PREPARE:
			armpmu_update_counters();
			/*
			 * If this is on a multicore CPU, we need
			 * to disarm the PMU IRQ before disappearing.
@@ -213,7 +235,7 @@ static int __cpuinit cpu_pmu_notify(struct notifier_block *b,
			}
			return NOTIFY_DONE;

		case CPU_UP_PREPARE:
		case CPU_STARTING:
			/*
			 * If this is on a multicore CPU, we need
			 * to arm the PMU IRQ before appearing.
@@ -221,14 +243,14 @@ static int __cpuinit cpu_pmu_notify(struct notifier_block *b,
			if (cpu_pmu &&
				cpu_pmu->plat_device->dev.platform_data) {
				irq = platform_get_irq(cpu_pmu->plat_device, 0);
				smp_call_function_single((int)hcpu,
						enable_irq_callback, &irq, 1);
				enable_irq_callback(&irq);
			}
			return NOTIFY_DONE;

		case CPU_STARTING:
			if (cpu_pmu && cpu_pmu->reset) {
				__get_cpu_var(from_idle) = 1;
				cpu_pmu->reset(NULL);
				pmu = &cpu_pmu->pmu;
				pmu->pmu_enable(pmu);
				return NOTIFY_OK;
			}
		default:
@@ -248,26 +270,6 @@ static struct notifier_block __cpuinitdata cpu_pmu_hotplug_notifier = {
	.notifier_call = cpu_pmu_notify,
};

static void armpmu_update_counters(void)
{
	struct pmu_hw_events *hw_events;
	int idx;

	if (!cpu_pmu)
		return;

	hw_events = cpu_pmu->get_hw_events();

	for (idx = 0; idx <= cpu_pmu->num_events; ++idx) {
		struct perf_event *event = hw_events->events[idx];

		if (!event)
			continue;

		cpu_pmu->pmu.read(event);
	}
}

/*TODO: Unify with pending patch from ARM */
static int perf_cpu_pm_notifier(struct notifier_block *self, unsigned long cmd,
		void *v)
+3 −2
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
static char *descriptions =
	" 0 msm: perf: add debug patch logging framework\n"
	" 1 Perf: port perf-events to 3.10 kernel\n"
	" 2 Perf: keep events across hotplug\n"
;

static ssize_t desc_read(struct file *fp, char __user *buf,
+2 −0
Original line number Diff line number Diff line
@@ -194,6 +194,8 @@ struct pmu {
	int * __percpu			pmu_disable_count;
	struct perf_cpu_context * __percpu pmu_cpu_context;
	int				task_ctx_nr;
	u32                             events_across_hotplug:1,
					reserved:31;

	/*
	 * Fully disable/enable this PMU, can be used to protect from the PMI
+42 −7
Original line number Diff line number Diff line
@@ -1248,6 +1248,28 @@ static int __perf_remove_from_context(void *info)
	return 0;
}

#ifdef CONFIG_SMP
static void perf_retry_remove(struct perf_event *event)
{
	int up_ret;
	/*
	 * CPU was offline. Bring it online so we can
	 * gracefully exit a perf context.
	 */
	up_ret = cpu_up(event->cpu);
	if (!up_ret)
		/* Try the remove call once again. */
		cpu_function_call(event->cpu, __perf_remove_from_context,
				  event);
	else
		pr_err("Failed to bring up CPU: %d, ret: %d\n",
		       event->cpu, up_ret);
}
#else
static void perf_retry_remove(struct perf_event *event)
{
}
#endif

/*
 * Remove the event from a task's (or a CPU's) list of events.
@@ -1262,19 +1284,23 @@ static int __perf_remove_from_context(void *info)
 * When called from perf_event_exit_task, it's OK because the
 * context has been detached from its task.
 */
static void perf_remove_from_context(struct perf_event *event)
static void __ref perf_remove_from_context(struct perf_event *event)
{
	struct perf_event_context *ctx = event->ctx;
	struct task_struct *task = ctx->task;
	int ret;

	lockdep_assert_held(&ctx->mutex);

	if (!task) {
		/*
		 * Per cpu events are removed via an smp call and
		 * the removal is always successful.
		 * Per cpu events are removed via an smp call
		 */
		cpu_function_call(event->cpu, __perf_remove_from_context, event);
		ret = cpu_function_call(event->cpu, __perf_remove_from_context,
					event);
		if (ret == -ENXIO)
			perf_retry_remove(event);
		return;
	}

@@ -7417,12 +7443,21 @@ static void perf_event_exit_cpu_context(int cpu)

	idx = srcu_read_lock(&pmus_srcu);
	list_for_each_entry_rcu(pmu, &pmus, entry) {
		/*
		 * If keeping events across hotplugging is supported, do not
		 * remove the event list, but keep it alive across CPU hotplug.
		 * The context is exited via an fd close path when userspace
		 * is done and the target CPU is online.
		 */
		if (!pmu->events_across_hotplug) {
			ctx = &per_cpu_ptr(pmu->pmu_cpu_context, cpu)->ctx;

			mutex_lock(&ctx->mutex);
		smp_call_function_single(cpu, __perf_event_exit_context, ctx, 1);
			smp_call_function_single(cpu, __perf_event_exit_context,
						 ctx, 1);
			mutex_unlock(&ctx->mutex);
		}
	}
	srcu_read_unlock(&pmus_srcu, idx);
}