[PATCH v3 2/2] perf: arm_pmu_acpi: Support heterogeneous ACPI systems
From: Johnny.Fan
Date: Mon Nov 02 2026 - 03:47:01 EST
From: "Johnny.Fan" <Johnny.Fan@xxxxxxxxxxx>
On heterogeneous ACPI systems, not all CPUs implement SPE or TRBE, and
the ones that do may use different PPIs. The MADT describes this per
GICC entry: a CPU without the feature reports a zero overflow
interrupt, and the PPTT hetero id identifies CPUs of the same
implementation.
The current code registers a single platform device and gives up if the
GICC entries disagree on the hetero id or on the interrupt, so the
feature is unavailable on such systems even though the firmware
describes it.
Group the CPUs by (hetero id, interrupt) and register one platform
device per group. A GSI may be shared between groups with
non-overlapping CPU masks, so only unregister the GSI mapping once no
successfully registered device uses it, as the mapping is not reference
counted.
Entries too short to contain the field mean that the firmware does not
describe the feature, which is what pre-ACPI 6.3 (SPE) and pre-ACPI 6.5
(TRBE) tables look like. If only some entries carry the field, the MADT
is inconsistent: warn and create no device at all.
Pass the CPU mask to the driver as platform data, as added by the
previous patch.
Tested on a CIX sky1 SoC with heterogeneous SPE support.
Signed-off-by: Johnny.Fan <Johnny.Fan@xxxxxxxxxxx>
Reviewed-by: Fugang Duan <fugang.duan@xxxxxxxxxxx>
---
drivers/perf/arm_pmu_acpi.c | 238 +++++++++++++++++++++++++-----------
1 file changed, 167 insertions(+), 71 deletions(-)
diff --git a/drivers/perf/arm_pmu_acpi.c b/drivers/perf/arm_pmu_acpi.c
index ca6bf8f86cfe..bffeeb063800 100644
--- a/drivers/perf/arm_pmu_acpi.c
+++ b/drivers/perf/arm_pmu_acpi.c
@@ -12,6 +12,8 @@
#include <linux/irqdesc.h>
#include <linux/percpu.h>
#include <linux/perf/arm_pmu.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
#include <asm/cpu.h>
#include <asm/cputype.h>
@@ -68,91 +70,196 @@ static void arm_pmu_acpi_unregister_irq(int cpu)
acpi_unregister_gsi(gsi);
}
+struct arm_pmu_acpi_group {
+ int hetid;
+ u16 gsi;
+ bool registered;
+ struct cpumask cpus;
+};
+
+static int arm_acpi_find_group(struct arm_pmu_acpi_group **groups,
+ int nr_groups, int hetid, u16 gsi)
+{
+ int i;
+
+ for (i = 0; i < nr_groups; i++) {
+ if (groups[i]->hetid == hetid && groups[i]->gsi == gsi)
+ return i;
+ }
+
+ return -1;
+}
+
+static bool arm_acpi_gsi_in_use(struct arm_pmu_acpi_group **groups,
+ int nr_groups, int idx)
+{
+ int i;
+
+ for (i = 0; i < nr_groups; i++) {
+ if (i == idx)
+ continue;
+ if (groups[i]->registered && groups[i]->gsi == groups[idx]->gsi)
+ return true;
+ }
+
+ return false;
+}
+
+static int arm_acpi_register_group(struct arm_pmu_acpi_group *group,
+ const char *name)
+{
+ struct arm_pmu_acpi_dev_data data = { };
+ struct platform_device *pdev;
+ struct resource res;
+ int irq, ret;
+
+ irq = acpi_register_gsi(NULL, group->gsi, ACPI_LEVEL_SENSITIVE,
+ ACPI_ACTIVE_HIGH);
+ if (irq < 0)
+ return irq;
+
+ pdev = platform_device_alloc(name, PLATFORM_DEVID_AUTO);
+ if (!pdev)
+ return -ENOMEM;
+
+ res = DEFINE_RES_IRQ(irq);
+ data.cpus = group->cpus;
+
+ ret = platform_device_add_resources(pdev, &res, 1);
+ if (ret)
+ goto err_put;
+
+ ret = platform_device_add_data(pdev, &data, sizeof(data));
+ if (ret)
+ goto err_put;
+
+ ret = platform_device_add(pdev);
+ if (!ret)
+ return 0;
+
+err_put:
+ platform_device_put(pdev);
+ return ret;
+}
+
static int __maybe_unused
-arm_acpi_register_pmu_device(struct platform_device *pdev, u8 len,
- u16 (*parse_gsi)(struct acpi_madt_generic_interrupt *))
+arm_acpi_register_pmu_devices(const char *name, u8 len,
+ u16 (*parse_gsi)(struct acpi_madt_generic_interrupt *))
{
- int cpu, this_hetid, hetid, irq, ret;
- u16 this_gsi = 0, gsi = 0;
+ struct arm_pmu_acpi_group **groups;
+ int cpu, i, nr_cpus = 0, nr_groups = 0, nr_devices = 0;
+ int nr_full = 0, nr_short = 0, last_err = 0, ret;
/*
- * Ensure that platform device must have IORESOURCE_IRQ
- * resource to hold gsi interrupt.
+ * The firmware describes the interrupt for every CPU on ACPI 6.3
+ * (SPE) or 6.5 (TRBE) and later, and for none of them on earlier
+ * revisions, where the field does not exist. A mix means the MADT
+ * cannot be trusted, so create no device at all.
*/
- if (pdev->num_resources != 1)
- return -ENXIO;
+ for_each_possible_cpu(cpu) {
+ struct acpi_madt_generic_interrupt *gicc;
- if (pdev->resource[0].flags != IORESOURCE_IRQ)
+ gicc = acpi_cpu_get_madt_gicc(cpu);
+ if (gicc->header.length < len) {
+ nr_short++;
+ continue;
+ }
+
+ nr_full++;
+ if (parse_gsi(gicc))
+ nr_cpus++;
+ }
+
+ if (nr_short && nr_full) {
+ pr_warn("ACPI: %s: inconsistent GICC entries\n", name);
return -ENXIO;
+ }
+
+ if (!nr_cpus)
+ return 0;
+
+ groups = kcalloc(nr_cpus, sizeof(*groups), GFP_KERNEL);
+ if (!groups)
+ return -ENOMEM;
- /*
- * Sanity check all the GICC tables for the same interrupt
- * number. For now, only support homogeneous ACPI machines.
- */
for_each_possible_cpu(cpu) {
+ struct arm_pmu_acpi_group *group;
struct acpi_madt_generic_interrupt *gicc;
+ int hetid;
+ u16 gsi;
gicc = acpi_cpu_get_madt_gicc(cpu);
- if (gicc->header.length < len)
- return gsi ? -ENXIO : 0;
-
- this_gsi = parse_gsi(gicc);
- this_hetid = find_acpi_cpu_topology_hetero_id(cpu);
- if (!gsi) {
- hetid = this_hetid;
- gsi = this_gsi;
- } else if (hetid != this_hetid || gsi != this_gsi) {
- pr_warn("ACPI: %s: must be homogeneous\n", pdev->name);
- return -ENXIO;
+ gsi = parse_gsi(gicc);
+ if (!gsi)
+ continue;
+
+ hetid = find_acpi_cpu_topology_hetero_id(cpu);
+ i = arm_acpi_find_group(groups, nr_groups, hetid, gsi);
+ if (i < 0) {
+ group = kzalloc_obj(*group, GFP_KERNEL);
+ if (!group) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ group->hetid = hetid;
+ group->gsi = gsi;
+ groups[nr_groups] = group;
+ i = nr_groups++;
}
+
+ cpumask_set_cpu(cpu, &groups[i]->cpus);
}
- if (!this_gsi)
- return 0;
+ for (i = 0; i < nr_groups; i++) {
+ ret = arm_acpi_register_group(groups[i], name);
+ if (ret) {
+ pr_warn("ACPI: %s: %*pbl: add device failed (%d)\n",
+ name, cpumask_pr_args(&groups[i]->cpus), ret);
+ last_err = ret;
+ continue;
+ }
- irq = acpi_register_gsi(NULL, gsi, ACPI_LEVEL_SENSITIVE, ACPI_ACTIVE_HIGH);
- if (irq < 0) {
- pr_warn("ACPI: %s Unable to register interrupt: %d\n", pdev->name, gsi);
- return -ENXIO;
+ groups[i]->registered = true;
+ nr_devices++;
}
- pdev->resource[0].start = irq;
- ret = platform_device_register(pdev);
- if (ret)
- acpi_unregister_gsi(gsi);
+ /*
+ * acpi_register_gsi() does not reference count the GSI mapping, so
+ * release a shared GSI only once no successfully registered device
+ * uses it any more.
+ */
+ for (i = 0; i < nr_groups; i++) {
+ if (groups[i]->registered)
+ continue;
+ if (arm_acpi_gsi_in_use(groups, nr_groups, i))
+ continue;
+ acpi_unregister_gsi(groups[i]->gsi);
+ }
+
+ ret = nr_devices ? 0 : last_err;
+
+err_free:
+ for (i = 0; i < nr_groups; i++)
+ kfree(groups[i]);
+ kfree(groups);
return ret;
}
#if IS_ENABLED(CONFIG_ARM_SPE_PMU)
-static struct resource spe_resources[] = {
- {
- /* irq */
- .flags = IORESOURCE_IRQ,
- }
-};
-
-static struct platform_device spe_dev = {
- .name = ARMV8_SPE_PDEV_NAME,
- .id = -1,
- .resource = spe_resources,
- .num_resources = ARRAY_SIZE(spe_resources)
-};
-
static u16 arm_spe_parse_gsi(struct acpi_madt_generic_interrupt *gicc)
{
return gicc->spe_interrupt;
}
-/*
- * For lack of a better place, hook the normal PMU MADT walk
- * and create a SPE device if we detect a recent MADT with
- * a homogeneous PPI mapping.
- */
static void arm_spe_acpi_register_device(void)
{
- int ret = arm_acpi_register_pmu_device(&spe_dev, ACPI_MADT_GICC_SPE,
- arm_spe_parse_gsi);
+ int ret;
+
+ ret = arm_acpi_register_pmu_devices(ARMV8_SPE_PDEV_NAME,
+ ACPI_MADT_GICC_SPE,
+ arm_spe_parse_gsi);
if (ret)
pr_warn("ACPI: SPE: Unable to register device\n");
}
@@ -163,20 +270,6 @@ static inline void arm_spe_acpi_register_device(void)
#endif /* CONFIG_ARM_SPE_PMU */
#if IS_ENABLED(CONFIG_CORESIGHT_TRBE)
-static struct resource trbe_resources[] = {
- {
- /* irq */
- .flags = IORESOURCE_IRQ,
- }
-};
-
-static struct platform_device trbe_dev = {
- .name = ARMV8_TRBE_PDEV_NAME,
- .id = -1,
- .resource = trbe_resources,
- .num_resources = ARRAY_SIZE(trbe_resources)
-};
-
static u16 arm_trbe_parse_gsi(struct acpi_madt_generic_interrupt *gicc)
{
return gicc->trbe_interrupt;
@@ -184,8 +277,11 @@ static u16 arm_trbe_parse_gsi(struct acpi_madt_generic_interrupt *gicc)
static void arm_trbe_acpi_register_device(void)
{
- int ret = arm_acpi_register_pmu_device(&trbe_dev, ACPI_MADT_GICC_TRBE,
- arm_trbe_parse_gsi);
+ int ret;
+
+ ret = arm_acpi_register_pmu_devices(ARMV8_TRBE_PDEV_NAME,
+ ACPI_MADT_GICC_TRBE,
+ arm_trbe_parse_gsi);
if (ret)
pr_warn("ACPI: TRBE: Unable to register device\n");
}
--
2.55.0