mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v3 0/2] perf: arm_spe: Support heterogeneous ACPI systems
@ 2026-11-02  8:41 Johnny.Fan
  2026-11-02  8:41 ` [PATCH v3 1/2] perf: arm_pmu: Accept a per-device CPU mask from platform data Johnny.Fan
  2026-11-02  8:41 ` [PATCH v3 2/2] perf: arm_pmu_acpi: Support heterogeneous ACPI systems Johnny.Fan
  0 siblings, 2 replies; 3+ messages in thread
From: Johnny.Fan @ 2026-11-02  8:41 UTC (permalink / raw)
  To: will, mark.rutland
  Cc: suzuki.poulose, leo.yan, jeremy.linton, maz, mike.leach,
	james.clark, alexander.shishkin, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, cix-kernel-upstream, jie.fu,
	bob.zhang, Johnny.Fan

From: "Johnny.Fan" <Johnny.Fan@cixtech.com>

This series makes SPE and TRBE usable on heterogeneous ACPI systems,
where not all CPUs implement the feature and the ones that do may use
different PPIs. The MADT describes both per CPU, and the PPTT hetero id
tells identical implementations apart.

Patch 1 lets the drivers take the CPU mask from platform data; patch 2
creates one platform device per (hetero id, interrupt) group and passes
the mask along.

v2: https://lore.kernel.org/all/20261021020322.3015121-1-johnny.fan@cixtech.com/

Changes since v2:
- Split into two patches: drivers first (no functional change), then
  the ACPI producer. This keeps every patch bisectable; with the
  opposite order an intermediate kernel would create devices whose CPU
  mask falls back to cpu_possible_mask and probe CPUs that do not
  implement the feature.
- Reject GICC entries with inconsistent lengths instead of silently
  skipping them, while still treating tables that never describe the
  field (pre-ACPI 6.3 for SPE, 6.5 for TRBE) as "feature absent".
- Allocate the groups dynamically instead of a possible-CPUs-sized
  array; the counting pass remains as the firmware consistency check.
- Only release a shared GSI once no successfully registered device uses
  it, fixing the mapping leak when every group on that GSI fails.

On Leo's question about exposing the mask through
platform_get_irq_affinity(): these devices are synthetic and have no
fwnode, so the lookup falls back to cpu_possible_mask. The mask is a
property of the device, not of the interrupt: a GSI can be shared by
groups with disjoint CPU masks, so a per-interrupt affinity would be
wrong for each of them. ACPI cannot describe PPI affinity today
anyway.

Johnny.Fan (2):
  perf: arm_pmu: Accept a per-device CPU mask from platform data
  perf: arm_pmu_acpi: Support heterogeneous ACPI systems

 drivers/hwtracing/coresight/coresight-trbe.c |  22 +-
 drivers/perf/arm_pmu_acpi.c                  | 238 +++++++++++++------
 drivers/perf/arm_spe_pmu.c                   |  10 +-
 include/linux/perf/arm_pmu.h                 |  10 +
 4 files changed, 205 insertions(+), 75 deletions(-)

--
2.55.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v3 1/2] perf: arm_pmu: Accept a per-device CPU mask from platform data
  2026-11-02  8:41 [PATCH v3 0/2] perf: arm_spe: Support heterogeneous ACPI systems Johnny.Fan
@ 2026-11-02  8:41 ` Johnny.Fan
  2026-11-02  8:41 ` [PATCH v3 2/2] perf: arm_pmu_acpi: Support heterogeneous ACPI systems Johnny.Fan
  1 sibling, 0 replies; 3+ messages in thread
From: Johnny.Fan @ 2026-11-02  8:41 UTC (permalink / raw)
  To: will, mark.rutland
  Cc: suzuki.poulose, leo.yan, jeremy.linton, maz, mike.leach,
	james.clark, alexander.shishkin, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, cix-kernel-upstream, jie.fu,
	bob.zhang, Johnny.Fan, Fugang Duan

From: "Johnny.Fan" <Johnny.Fan@cixtech.com>

Synthetic platform devices created from ACPI data have no fwnode, so
platform_get_irq_affinity() cannot tell which CPUs implement the
feature and falls back to cpu_possible_mask.

Add an optional CPU mask to the platform data and make both the SPE and
the TRBE drivers prefer it over the firmware-described IRQ affinity.
Device tree devices have no platform data and keep using the affinity.

TRBE must also pass a stable copy of the mask to
request_percpu_irq_affinity(), because supported_cpus is pruned as CPUs
are probed, while the IRQ action keeps a pointer to the mask.

This is a preparatory change: no ACPI device carries platform data yet,
so behaviour is unchanged.

Signed-off-by: Johnny.Fan <Johnny.Fan@cixtech.com>
Reviewed-by: Fugang Duan <fugang.duan@cixtech.com>
---
 drivers/hwtracing/coresight/coresight-trbe.c | 22 +++++++++++++++++---
 drivers/perf/arm_spe_pmu.c                   | 10 ++++++++-
 include/linux/perf/arm_pmu.h                 | 10 +++++++++
 3 files changed, 38 insertions(+), 4 deletions(-)

diff --git a/drivers/hwtracing/coresight/coresight-trbe.c b/drivers/hwtracing/coresight/coresight-trbe.c
index c7cbca45f2de..0c4bc8fbcaf3 100644
--- a/drivers/hwtracing/coresight/coresight-trbe.c
+++ b/drivers/hwtracing/coresight/coresight-trbe.c
@@ -158,6 +158,7 @@ struct trbe_drvdata {
 	struct hlist_node hotplug_node;
 	int irq;
 	cpumask_t supported_cpus;
+	cpumask_t irq_affinity;
 	enum cpuhp_state trbe_online;
 	struct platform_device *pdev;
 };
@@ -1533,6 +1534,8 @@ static void arm_trbe_remove_cpuhp(struct trbe_drvdata *drvdata)
 static int arm_trbe_probe_irq(struct platform_device *pdev,
 			      struct trbe_drvdata *drvdata)
 {
+	const struct arm_pmu_acpi_dev_data *acpi_data =
+		dev_get_platdata(&pdev->dev);
 	const struct cpumask *affinity;
 	int ret;
 
@@ -1547,14 +1550,27 @@ static int arm_trbe_probe_irq(struct platform_device *pdev,
 		return -EINVAL;
 	}
 
-	cpumask_copy(&drvdata->supported_cpus, affinity);
+	/*
+	 * The ACPI path has no fwnode describing which CPUs implement TRBE,
+	 * so it passes the mask explicitly. DT devices still use the
+	 * firmware-described IRQ affinity.
+	 */
+	cpumask_copy(&drvdata->supported_cpus,
+		     acpi_data ? &acpi_data->cpus : affinity);
+
+	/*
+	 * supported_cpus gets pruned as CPUs are probed, so keep a stable
+	 * copy for the IRQ action.
+	 */
+	cpumask_copy(&drvdata->irq_affinity, &drvdata->supported_cpus);
 
 	drvdata->handle = alloc_percpu(struct perf_output_handle *);
 	if (!drvdata->handle)
 		return -ENOMEM;
 
-	ret = request_percpu_irq_affinity(drvdata->irq, arm_trbe_irq_handler, DRVNAME,
-					  affinity, drvdata->handle);
+	ret = request_percpu_irq_affinity(drvdata->irq, arm_trbe_irq_handler,
+					  DRVNAME, &drvdata->irq_affinity,
+					  drvdata->handle);
 	if (ret) {
 		free_percpu(drvdata->handle);
 		return ret;
diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c
index b70f3fedf028..712b7f187301 100644
--- a/drivers/perf/arm_spe_pmu.c
+++ b/drivers/perf/arm_spe_pmu.c
@@ -1330,6 +1330,8 @@ static void arm_spe_pmu_dev_teardown(struct arm_spe_pmu *spe_pmu)
 static int arm_spe_pmu_irq_probe(struct arm_spe_pmu *spe_pmu)
 {
 	struct platform_device *pdev = spe_pmu->pdev;
+	const struct arm_pmu_acpi_dev_data *acpi_data =
+		dev_get_platdata(&pdev->dev);
 	const struct cpumask *affinity;
 	int irq;
 
@@ -1342,7 +1344,13 @@ static int arm_spe_pmu_irq_probe(struct arm_spe_pmu *spe_pmu)
 		return -EINVAL;
 	}
 
-	cpumask_copy(&spe_pmu->supported_cpus, affinity);
+	/*
+	 * The ACPI path has no fwnode describing which CPUs implement SPE,
+	 * so it passes the mask explicitly. DT devices still use the
+	 * firmware-described IRQ affinity.
+	 */
+	cpumask_copy(&spe_pmu->supported_cpus,
+		     acpi_data ? &acpi_data->cpus : affinity);
 
 	spe_pmu->irq = irq;
 	return 0;
diff --git a/include/linux/perf/arm_pmu.h b/include/linux/perf/arm_pmu.h
index 02d2c7f45b52..7f1c03c55025 100644
--- a/include/linux/perf/arm_pmu.h
+++ b/include/linux/perf/arm_pmu.h
@@ -8,6 +8,7 @@
 #ifndef __ARM_PMU_H__
 #define __ARM_PMU_H__
 
+#include <linux/cpumask.h>
 #include <linux/interrupt.h>
 #include <linux/perf_event.h>
 #include <linux/platform_device.h>
@@ -199,6 +200,15 @@ void armpmu_free_irq(struct arm_pmu * __percpu *armpmu, int irq, int cpu);
 #define ARMV8_SPE_PDEV_NAME "arm,spe-v1"
 #define ARMV8_TRBE_PDEV_NAME "arm,trbe"
 
+/*
+ * Per-device CPU mask passed by the ACPI MADT walk. The synthetic
+ * platform devices have no fwnode, so the IRQ affinity cannot describe
+ * which CPUs actually implement the feature.
+ */
+struct arm_pmu_acpi_dev_data {
+	struct cpumask cpus;
+};
+
 /* Why does everything I do descend into this? */
 #define __GEN_PMU_FORMAT_ATTR(cfg, lo, hi)				\
 	(lo) == (hi) ? #cfg ":" #lo "\n" : #cfg ":" #lo "-" #hi
-- 
2.55.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v3 2/2] perf: arm_pmu_acpi: Support heterogeneous ACPI systems
  2026-11-02  8:41 [PATCH v3 0/2] perf: arm_spe: Support heterogeneous ACPI systems Johnny.Fan
  2026-11-02  8:41 ` [PATCH v3 1/2] perf: arm_pmu: Accept a per-device CPU mask from platform data Johnny.Fan
@ 2026-11-02  8:41 ` Johnny.Fan
  1 sibling, 0 replies; 3+ messages in thread
From: Johnny.Fan @ 2026-11-02  8:41 UTC (permalink / raw)
  To: will, mark.rutland
  Cc: suzuki.poulose, leo.yan, jeremy.linton, maz, mike.leach,
	james.clark, alexander.shishkin, coresight, linux-arm-kernel,
	linux-perf-users, linux-kernel, cix-kernel-upstream, jie.fu,
	bob.zhang, Johnny.Fan, Fugang Duan

From: "Johnny.Fan" <Johnny.Fan@cixtech.com>

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@cixtech.com>
Reviewed-by: Fugang Duan <fugang.duan@cixtech.com>
---
 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


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-11-02  8:41 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-02  8:41 [PATCH v3 0/2] perf: arm_spe: Support heterogeneous ACPI systems Johnny.Fan
2026-11-02  8:41 ` [PATCH v3 1/2] perf: arm_pmu: Accept a per-device CPU mask from platform data Johnny.Fan
2026-11-02  8:41 ` [PATCH v3 2/2] perf: arm_pmu_acpi: Support heterogeneous ACPI systems Johnny.Fan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®