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

Commit 5b65b16f authored by Patrick Fay's avatar Patrick Fay
Browse files

perf: Add support for exclude_idle attribute



Enable the perf exclude_idle event attribute to avoid waking
possilby sleeping CPUS. The counter values are updated when CPU
enters idle. If the CPU is idle when perf reads the current
event value (of an exclude idle event) then the value from
when the CPU went idle is returned.
This commit supercedes the commits below. The context for the
commits below changed too much to enable cherry-picking.
commit 573979dee2a7 ("perf: Add support for exclude_idle attribute")
commit 54f6e4ae87be ("perf: Enable updating exclude_idle events
at idle")
commit 960dbb1751f3 ("perf: Skip permission checks on kernel owned
perf events")

Change-Id: Ib554c9fe106963ec1b42e72aeaf84fc73201bbb7
Signed-off-by: default avatarPatrick Fay <pfay@codeaurora.org>
parent 1111c4cd
Loading
Loading
Loading
Loading
+64 −3
Original line number Diff line number Diff line
@@ -867,8 +867,6 @@ static int armv8pmu_set_event_filter(struct hw_perf_event *event,
{
	unsigned long config_base = 0;

	if (attr->exclude_idle)
		return -EPERM;
	if (is_kernel_in_hyp_mode() &&
	    attr->exclude_kernel != attr->exclude_hv)
		return -EINVAL;
@@ -975,11 +973,74 @@ static void __armv8pmu_probe_pmu(void *info)
			     ARRAY_SIZE(pmceid));
}

static void armv8pmu_idle_update(struct arm_pmu *cpu_pmu)
{
	struct pmu_hw_events *hw_events;
	struct perf_event *event;
	int idx;

	if (!cpu_pmu)
		return;

	hw_events = this_cpu_ptr(cpu_pmu->hw_events);

	if (!hw_events)
		return;

	for (idx = 0; idx < cpu_pmu->num_events; ++idx) {

		if (!test_bit(idx, hw_events->used_mask))
			continue;

		event = hw_events->events[idx];

		if (!event || !event->attr.exclude_idle ||
				event->state != PERF_EVENT_STATE_ACTIVE)
			continue;

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

struct arm_pmu_and_idle_nb {
	struct arm_pmu *cpu_pmu;
	struct notifier_block perf_cpu_idle_nb;
};

static int perf_cpu_idle_notifier(struct notifier_block *nb,
				unsigned long action, void *data)
{
	struct arm_pmu_and_idle_nb *pmu_nb = container_of(nb,
				struct arm_pmu_and_idle_nb, perf_cpu_idle_nb);

	if (action == IDLE_START)
		armv8pmu_idle_update(pmu_nb->cpu_pmu);

	return NOTIFY_OK;
}

static int armv8pmu_probe_pmu(struct arm_pmu *cpu_pmu)
{
	return smp_call_function_any(&cpu_pmu->supported_cpus,
	int ret;
	struct arm_pmu_and_idle_nb *pmu_idle_nb;

	pmu_idle_nb = devm_kzalloc(&cpu_pmu->plat_device->dev,
					sizeof(*pmu_idle_nb), GFP_KERNEL);
	if (!pmu_idle_nb)
		return -ENOMEM;

	pmu_idle_nb->cpu_pmu = cpu_pmu;
	pmu_idle_nb->perf_cpu_idle_nb.notifier_call = perf_cpu_idle_notifier;
	idle_notifier_register(&pmu_idle_nb->perf_cpu_idle_nb);

	ret = smp_call_function_any(&cpu_pmu->supported_cpus,
				    __armv8pmu_probe_pmu,
				    cpu_pmu, 1);

	if (ret)
		idle_notifier_unregister(&pmu_idle_nb->perf_cpu_idle_nb);

	return ret;
}

static void armv8_pmu_init(struct arm_pmu *cpu_pmu)
+41 −8
Original line number Diff line number Diff line
@@ -372,6 +372,7 @@ static atomic_t perf_sched_count;
static DEFINE_PER_CPU(atomic_t, perf_cgroup_events);
static DEFINE_PER_CPU(int, perf_sched_cb_usages);
static DEFINE_PER_CPU(struct pmu_event_list, pmu_sb_events);
static DEFINE_PER_CPU(bool, is_idle);

static atomic_t nr_mmap_events __read_mostly;
static atomic_t nr_comm_events __read_mostly;
@@ -3605,23 +3606,31 @@ u64 perf_event_read_local(struct perf_event *event)
static int perf_event_read(struct perf_event *event, bool group)
{
	int event_cpu, ret = 0;
	bool active_event_skip_read = false;

	/*
	 * If event is enabled and currently active on a CPU, update the
	 * value in the event structure:
	 */
	event_cpu = READ_ONCE(event->oncpu);

	if (event->state == PERF_EVENT_STATE_ACTIVE) {
		if ((unsigned int)event_cpu >= nr_cpu_ids)
			return 0;
		if (cpu_isolated(event_cpu) ||
			(event->attr.exclude_idle &&
				per_cpu(is_idle, event_cpu)))
			active_event_skip_read = true;
	}

	if (event->state == PERF_EVENT_STATE_ACTIVE &&
						!cpu_isolated(event->oncpu)) {
		!active_event_skip_read) {
		struct perf_read_data data = {
			.event = event,
			.group = group,
			.ret = 0,
		};

		event_cpu = READ_ONCE(event->oncpu);
		if ((unsigned)event_cpu >= nr_cpu_ids)
			return 0;

		preempt_disable();
		event_cpu = __perf_event_read_cpu(event, event_cpu);

@@ -3635,10 +3644,12 @@ static int perf_event_read(struct perf_event *event, bool group)
		 * Therefore, either way, we'll have an up-to-date event count
		 * after this.
		 */
		(void)smp_call_function_single(event_cpu, __perf_event_read, &data, 1);
		(void)smp_call_function_single(event_cpu,
				__perf_event_read, &data, 1);
		preempt_enable();
		ret = data.ret;
	} else if (event->state == PERF_EVENT_STATE_INACTIVE) {
	} else if (event->state == PERF_EVENT_STATE_INACTIVE ||
			active_event_skip_read) {
		struct perf_event_context *ctx = event->ctx;
		unsigned long flags;

@@ -3731,7 +3742,8 @@ find_get_context(struct pmu *pmu, struct task_struct *task,

	if (!task) {
		/* Must be root to operate on a CPU event: */
		if (perf_paranoid_cpu() && !capable(CAP_SYS_ADMIN))
		if (!is_kernel_event(event) && perf_paranoid_cpu() &&
			!capable(CAP_SYS_ADMIN))
			return ERR_PTR(-EACCES);

		/*
@@ -10849,6 +10861,26 @@ static struct notifier_block perf_reboot_notifier = {
	.priority = INT_MIN,
};

static int event_idle_notif(struct notifier_block *nb, unsigned long action,
							void *data)
{
	switch (action) {
	case IDLE_START:
		__this_cpu_write(is_idle, true);
		break;
	case IDLE_END:
		__this_cpu_write(is_idle, false);
		break;
	}

	return NOTIFY_OK;
}

static struct notifier_block perf_event_idle_nb = {
	.notifier_call = event_idle_notif,
};


void __init perf_event_init(void)
{
	int ret;
@@ -10862,6 +10894,7 @@ void __init perf_event_init(void)
	perf_pmu_register(&perf_task_clock, NULL, -1);
	perf_tp_register();
	perf_event_init_cpu(smp_processor_id());
	idle_notifier_register(&perf_event_idle_nb);
	register_reboot_notifier(&perf_reboot_notifier);

	ret = init_hw_breakpoint();