[PATCH 10/25] perf: arm_pmu: Add PMU/CPU affinity tracking

Marc Zyngier
2025-09-08T16:31:50Z

The ARM PMU code is built around the assumption that different CPU
affinities (either a single CPU in the case of an SPI, or a group
of CPUs for a PPI) must result in different IRQ numbers.

This scheme is about to be removed, so the driver must be able to match
the new behaviour.

For this purpose, add a new per-CPU variable that tracks the PMU
affinities on a per-CPU basis, so that the driver can, from any CPU,
find out which other CPUs it is sharing an interrupt number with.

It is likely that some simplifications could result from this scheme,
but this is good enough to get started.

Signed-off-by: Marc Zyngier <maz@kernel.org>
---
drivers/perf/arm_pmu.c | 37 +++++++++++++++++++++++++--------
drivers/perf/arm_pmu_acpi.c | 5 +++++
drivers/perf/arm_pmu_platform.c | 4 ++++
include/linux/perf/arm_pmu.h | 1 +
4 files changed, 38 insertions(+), 9 deletions(-)

diff --git a/drivers/perf/arm_pmu.c b/drivers/perf/arm_pmu.c
index 5c310e803dd78..6e3c72e9b41cd 100644
--- a/drivers/perf/arm_pmu.c
+++ b/drivers/perf/arm_pmu.c
@@ -26,7 +26,7 @@

#include <asm/irq_regs.h>

-static int armpmu_count_irq_users(const int irq);
+static int armpmu_count_irq_users(const int this_cpu, const int irq);

struct pmu_irq_ops {
void (*enable_pmuirq)(unsigned int irq);
@@ -64,7 +64,7 @@ static void armpmu_enable_percpu_pmuirq(unsigned int irq)
static void armpmu_free_percpu_pmuirq(unsigned int irq, int cpu,
void __percpu *devid)
{
- if (armpmu_count_irq_users(irq) == 1)
+ if (armpmu_count_irq_users(cpu, irq) == 1)
free_percpu_irq(irq, devid);
}

@@ -89,7 +89,7 @@ static void armpmu_disable_percpu_pmunmi(unsigned int irq)
static void armpmu_free_percpu_pmunmi(unsigned int irq, int cpu,
void __percpu *devid)
{
- if (armpmu_count_irq_users(irq) == 1)
+ if (armpmu_count_irq_users(cpu, irq) == 1)
free_percpu_nmi(irq, devid);
}

@@ -100,11 +100,20 @@ static const struct pmu_irq_ops percpu_pmunmi_ops = {
};

DEFINE_PER_CPU(struct arm_pmu *, cpu_armpmu);
+static DEFINE_PER_CPU(const struct cpumask *, pmu_affinity);
static DEFINE_PER_CPU(int, cpu_irq);
static DEFINE_PER_CPU(const struct pmu_irq_ops *, cpu_irq_ops);

static bool has_nmi;

+void armpmu_register_affinity_group(const struct cpumask *aff_grp)
+{
+ int cpu;
+
+ for_each_cpu(cpu, aff_grp)
+ per_cpu(pmu_affinity, cpu) = aff_grp;
+}
+
static inline u64 arm_pmu_event_max_period(struct perf_event *event)
{
if (event->hw.flags & ARMPMU_EVT_64BIT)
@@ -580,11 +589,16 @@ static const struct attribute_group armpmu_common_attr_group = {
.attrs = armpmu_common_attrs,
};

-static int armpmu_count_irq_users(const int irq)
+static int armpmu_count_irq_users(const int this_cpu, const int irq)
{
+ const struct cpumask *affinity;
int cpu, count = 0;

- for_each_possible_cpu(cpu) {
+ affinity = per_cpu(pmu_affinity, this_cpu);
+ if (WARN_ON(!affinity))
+ return 0;
+
+ for_each_cpu(cpu, affinity) {
if (per_cpu(cpu_irq, cpu) == irq)
count++;
}
@@ -592,12 +606,17 @@ static int armpmu_count_irq_users(const int irq)
return count;
}

-static const struct pmu_irq_ops *armpmu_find_irq_ops(int irq)
+static const struct pmu_irq_ops *armpmu_find_irq_ops(int this_cpu, int irq)
{
const struct pmu_irq_ops *ops = NULL;
+ const struct cpumask *affinity;
int cpu;

- for_each_possible_cpu(cpu) {
+ affinity = per_cpu(pmu_affinity, this_cpu);
+ if (!affinity)
+ return NULL;
+
+ for_each_cpu(cpu, affinity) {
if (per_cpu(cpu_irq, cpu) != irq)
continue;

@@ -658,7 +677,7 @@ int armpmu_request_irq(int irq, int cpu)
has_nmi = true;
irq_ops = &pmunmi_ops;
}
- } else if (armpmu_count_irq_users(irq) == 0) {
+ } else if (armpmu_count_irq_users(cpu, irq) == 0) {
err = request_percpu_nmi(irq, handler, "arm-pmu", &cpu_armpmu);

/* If cannot get an NMI, get a normal interrupt */
@@ -672,7 +691,7 @@ int armpmu_request_irq(int irq, int cpu)
}
} else {
/* Per cpudevid irq was already requested by another CPU */
- irq_ops = armpmu_find_irq_ops(irq);
+ irq_ops = armpmu_find_irq_ops(cpu, irq);

if (WARN_ON(!irq_ops))
err = -EINVAL;
diff --git a/drivers/perf/arm_pmu_acpi.c b/drivers/perf/arm_pmu_acpi.c
index 05dda19c5359a..a5956fa728070 100644
--- a/drivers/perf/arm_pmu_acpi.c
+++ b/drivers/perf/arm_pmu_acpi.c
@@ -212,6 +212,11 @@ static int arm_pmu_acpi_parse_irqs(void)
pr_warn("No ACPI PMU IRQ for CPU%d\n", cpu);
}

+ if (irq_is_percpu_devid(irq))
+ armpmu_register_affinity_group(cpu_possible_mask);
+ else
+ armpmu_register_affinity_group(cpumask_of(cpu));
+
/*
* Log and request the IRQ so the core arm_pmu code can manage
* it. We'll have to sanity-check IRQs later when we associate
diff --git a/drivers/perf/arm_pmu_platform.c b/drivers/perf/arm_pmu_platform.c
index 9c0494d8a867a..0c7d5065e9074 100644
--- a/drivers/perf/arm_pmu_platform.c
+++ b/drivers/perf/arm_pmu_platform.c
@@ -53,6 +53,8 @@ static int pmu_parse_percpu_irq(struct arm_pmu *pmu, int irq,
for_each_cpu(cpu, &pmu->supported_cpus)
per_cpu(hw_events->irq, cpu) = irq;

+ armpmu_register_affinity_group(&pmu->supported_cpus);
+
return 0;
}

@@ -152,6 +154,8 @@ static int pmu_parse_irqs(struct arm_pmu *pmu)
cpumask_set_cpu(cpu, &pmu->supported_cpus);
}

+ armpmu_register_affinity_group(&pmu->supported_cpus);
+
return 0;
}

diff --git a/include/linux/perf/arm_pmu.h b/include/linux/perf/arm_pmu.h
index 93c9a26492fcf..7bf548678b288 100644
--- a/include/linux/perf/arm_pmu.h
+++ b/include/linux/perf/arm_pmu.h
@@ -171,6 +171,7 @@ struct pmu_probe_info {
int arm_pmu_device_probe(struct platform_device *pdev,
const struct of_device_id *of_table,
const struct pmu_probe_info *probe_table);
+void armpmu_register_affinity_group(const struct cpumask *);

#ifdef CONFIG_ACPI
int arm_pmu_acpi_probe(armpmu_init_fn init_fn);
--
2.39.2