Skip to contentSkip to conversation

perf: Add Arm Network-on-Chip PMU driver

PATCH Superseded Robin Murphy 2 participants 9 messages Aug 30, 2024 – Sep 6, 2024

Subsystems (4)

v3 of 4 Superseded Latest is v4 →

lore
Share & tools
lore

Robin Murphy

author maintainer
#0 lore raw

v3 cover perf: Add Arm Network-on-Chip PMU driver

v2: https://lore.kernel.org/linux-arm-kernel/[email protected]/

Hi all,

Hopefully this is good for 6.12 now. I've not managed to double-check the interaction with userspace PMU aggregation behaviour yet, but I think that's something to look into separately as it's not specific to this driver.

Thanks,
Robin.

Robin Murphy (3):
dt-bindings/perf: Add Arm NI-700 PMU
perf: Add driver for Arm NI-700 interconnect PMU
MAINTAINERS: List Arm interconnect PMUs as supported

 Documentation/admin-guide/perf/arm-ni.rst     |  17 +
 Documentation/admin-guide/perf/index.rst      |   1 +
 .../devicetree/bindings/perf/arm,ni.yaml      |  30 +
 MAINTAINERS                                   |  11 +
 drivers/perf/Kconfig                          |   7 +
 drivers/perf/Makefile                         |   1 +
 drivers/perf/arm-ni.c                         | 778 ++++++++++++++++++
 7 files changed, 845 insertions(+)
 create mode 100644 Documentation/admin-guide/perf/arm-ni.rst
 create mode 100644 Documentation/devicetree/bindings/perf/arm,ni.yaml
 create mode 100644 drivers/perf/arm-ni.c
Signature · 1 line
2.39.2.101.g768bb238c484.dirty
author maintainer
#1 lore raw

PATCH v3 1/3 dt-bindings/perf: Add Arm NI-700 PMU

Add an initial binding for the Arm NI-700 interconnect PMU. As with the Arm CMN family, there are already future NI products on the roadmap, so the overall binding is named generically just in case any non-discoverable incompatibility between generations crops up.

---

v3: No change

 .../devicetree/bindings/perf/arm,ni.yaml      | 30 +++++++++++++++++++
 1 file changed, 30 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/perf/arm,ni.yaml
Documentation/devicetree/bindings/perf/arm,ni.yaml new file +30 -0
new file mode 100644
index 000000000000..d66fffa256d5
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/perf/arm,ni.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Arm NI (Network-on-Chip Interconnect) Performance Monitors
+
+maintainers:
+  - Robin Murphy <[email protected]>
+
+properties:
+  compatible:
+    const: arm,ni-700
+
+  reg:
+    items:
+      - description: Complete configuration register space
+
+  interrupts:
+    minItems: 1
+    maxItems: 32
+    description: Overflow interrupts, one per clock domain, in order of domain ID
+
+required:
+  - compatible
+  - reg
+  - interrupts
+
+additionalProperties: false
Signature · 1 line
2.39.2.101.g768bb238c484.dirty
author maintainer
#2 lore raw

PATCH v3 2/3 perf: Add driver for Arm NI-700 interconnect PMU

In reply to Robin Murphy

The Arm NI-700 Network-on-Chip Interconnect has a relatively straightforward design with a hierarchy of voltage, power, and clock domains, where each clock domain then contains a number of interface units and a PMU which can monitor events thereon. As such, it begets a relatively straightforward driver to interface those PMUs with perf.

Even more so than with arm-cmn, users will require detailed knowledge of the wider system topology in order to meaningfully analyse anything, since the interconnect itself cannot know what lies beyond the boundary of each inscrutably-numbered interface. Given that, for now they are also expected to refer to the NI-700 documentation for the relevant event IDs to provide as well. An identifier is implemented so we can come back and add jevents if anyone really wants to.

---
v2:
- Add basic usage documentation
- Use __counted_by attribute
- Make group validation logic clearer (and drop PMU type check which perf_event_open() already takes care of)
- Add retry limit to arm_ni_read_ccnt()
v3:
- Update .remove to return void
- Fix group leader validation and make the naming clearer
- Drop NUMA_NO_NODE check for CPU online (the only way that could actually pass both other migration conditions is if the NUMA info is so messed up that it's not worth worrying about anyway)

---
 Documentation/admin-guide/perf/arm-ni.rst |  17 +
 Documentation/admin-guide/perf/index.rst  |   1 +
 drivers/perf/Kconfig                      |   7 +
 drivers/perf/Makefile                     |   1 +
 drivers/perf/arm-ni.c                     | 778 ++++++++++++++++++++++
 5 files changed, 804 insertions(+)
 create mode 100644 Documentation/admin-guide/perf/arm-ni.rst
 create mode 100644 drivers/perf/arm-ni.c
Documentation/admin-guide/perf/arm-ni.rst new file +17 -0
new file mode 100644
index 000000000000..3cd7d0f75f0f
@@ -0,0 +1,17 @@
+====================================
+Arm Network-on Chip Interconnect PMU
+====================================
+
+NI-700 and friends implement a distinct PMU for each clock domain within the
+interconnect. Correspondingly, the driver exposes multiple PMU devices named
+arm_ni_<x>_cd_<y>, where <x> is an (abritrary) instance identifier and <y> is
+the clock domain ID within that particular instance. If multiple NI instances
+exist within a system, the PMU devices can be correlated with the underlying
+hardware instance via sysfs parentage.
+
+Each PMU exposes base event aliases for the interface types present in its clock
+domain. These require qualifying with the "eventid" and "nodeid" parameters
+to specify the event code to count and the interface at which to count it
+(per the configured hardware ID as reflected in the xxNI_NODE_INFO register).
+The exception is the "cycles" alias for the PMU cycle counter, which is encoded
+with the PMU node type and needs no further qualification.
Documentation/admin-guide/perf/index.rst +1 -0
index 7eb3dcd6f4da..8502bc174640 100644
@@ -16,6 +16,7 @@ Performance monitor support
    starfive_starlink_pmu
    arm-ccn
    arm-cmn
+   arm-ni
    xgene-pmu
    arm_dsu_pmu
    thunderx2-pmu
drivers/perf/Kconfig +7 -0
index aa9530b4064f..bab8ba64162f 100644
@@ -48,6 +48,13 @@ config ARM_CMN
 	  Support for PMU events monitoring on the Arm CMN-600 Coherent Mesh
 	  Network interconnect.
 
+config ARM_NI
+	tristate "Arm NI-700 PMU support"
+	depends on ARM64 || COMPILE_TEST
+	help
+	  Support for PMU events monitoring on the Arm NI-700 Network-on-Chip
+	  interconnect and family.
+
 config ARM_PMU
 	depends on ARM || ARM64
 	bool "ARM PMU framework"
drivers/perf/Makefile +1 -0
index d43df81d52f7..8268f38e42c5 100644
@@ -3,6 +3,7 @@ obj-$(CONFIG_ARM_CCI_PMU) += arm-cci.o
 obj-$(CONFIG_ARM_CCN) += arm-ccn.o
 obj-$(CONFIG_ARM_CMN) += arm-cmn.o
 obj-$(CONFIG_ARM_DSU_PMU) += arm_dsu_pmu.o
+obj-$(CONFIG_ARM_NI) += arm-ni.o
 obj-$(CONFIG_ARM_PMU) += arm_pmu.o arm_pmu_platform.o
 obj-$(CONFIG_ARM_PMU_ACPI) += arm_pmu_acpi.o
 obj-$(CONFIG_ARM_PMUV3) += arm_pmuv3.o
drivers/perf/arm-ni.c new file +778 -0

778 lines not on this page · raw message

Signature · 1 line
2.39.2.101.g768bb238c484.dirty
maintainer
#3 lore raw

Re: [PATCH v3 2/3] perf: Add driver for Arm NI-700 interconnect PMU

Hi Robin,

Quoted 16 lines · Robin Murphy

The Arm NI-700 Network-on-Chip Interconnect has a relatively straightforward design with a hierarchy of voltage, power, and clock domains, where each clock domain then contains a number of interface units and a PMU which can monitor events thereon. As such, it begets a relatively straightforward driver to interface those PMUs with perf.

Even more so than with arm-cmn, users will require detailed knowledge of the wider system topology in order to meaningfully analyse anything, since the interconnect itself cannot know what lies beyond the boundary of each inscrutably-numbered interface. Given that, for now they are also expected to refer to the NI-700 documentation for the relevant event IDs to provide as well. An identifier is implemented so we can come back and add jevents if anyone really wants to.

Quoted 13 lines

---
v2:
- Add basic usage documentation
- Use __counted_by attribute
- Make group validation logic clearer (and drop PMU type check which perf_event_open() already takes care of)
- Add retry limit to arm_ni_read_ccnt()
v3:
- Update .remove to return void
- Fix group leader validation and make the naming clearer
- Drop NUMA_NO_NODE check for CPU online (the only way that could actually pass both other migration conditions is if the NUMA info is so messed up that it's not worth worrying about anyway)

Thanks, this is looking pretty good now. I just have a few random comments based on another read-through of the code.

Documentation/admin-guide/perf/arm-ni.rst line 7

Quoted 13 lines
Documentation/admin-guide/perf/arm-ni.rst new file +7 -0
new file mode 100644
index 000000000000..3cd7d0f75f0f
@@ -0,0 +1,17 @@
+====================================
+Arm Network-on Chip Interconnect PMU
+====================================
+
+NI-700 and friends implement a distinct PMU for each clock domain within the
+interconnect. Correspondingly, the driver exposes multiple PMU devices named
+arm_ni_<x>_cd_<y>, where <x> is an (abritrary) instance identifier and <y> is

typo: abritrary

Quoted 10 lines
Documentation/admin-guide/perf/arm-ni.rst new file +10 -0
+the clock domain ID within that particular instance. If multiple NI instances
+exist within a system, the PMU devices can be correlated with the underlying
+hardware instance via sysfs parentage.
+
+Each PMU exposes base event aliases for the interface types present in its clock
+domain. These require qualifying with the "eventid" and "nodeid" parameters
+to specify the event code to count and the interface at which to count it
+(per the configured hardware ID as reflected in the xxNI_NODE_INFO register).
+The exception is the "cycles" alias for the PMU cycle counter, which is encoded
+with the PMU node type and needs no further qualification.

[...]

Documentation/admin-guide/perf/arm-ni.rst

Quoted 8 lines
Documentation/admin-guide/perf/arm-ni.rst new file +8 -0
+static ssize_t arm_ni_format_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct arm_ni_format_attr *fmt = container_of(attr, typeof(*fmt), attr);
+	int lo = __ffs(fmt->field), hi = __fls(fmt->field);
+
+	return sysfs_emit(buf, "config:%d-%d\n", lo, hi);
+}

Nit: if you end up adding single-bit config fields in the future, this will quietly do the wrong thing. Maybe safe-guard the 'lo==hi' case (even if you just warn once and return without doing anything).

[...]

Quoted 90 lines
Documentation/admin-guide/perf/arm-ni.rst new file +90 -0
+static int arm_ni_init_cd(struct arm_ni *ni, struct arm_ni_node *node, u64 res_start)
+{
+	struct arm_ni_cd *cd = ni->cds + node->id;
+	const char *name;
+	int err;
+
+	cd->id = node->id;
+	cd->num_units = node->num_components;
+	cd->units = devm_kcalloc(ni->dev, cd->num_units, sizeof(*(cd->units)), GFP_KERNEL);
+	if (!cd->units)
+		return -ENOMEM;
+
+	for (int i = 0; i < cd->num_units; i++) {
+		u32 reg = readl_relaxed(node->base + NI_CHILD_PTR(i));
+		void __iomem *unit_base = ni->base + reg;
+		struct arm_ni_unit *unit = cd->units + i;
+
+		reg = readl_relaxed(unit_base + NI_NODE_TYPE);
+		unit->type = FIELD_GET(NI_NODE_TYPE_NODE_TYPE, reg);
+		unit->id = FIELD_GET(NI_NODE_TYPE_NODE_ID, reg);
+
+		switch (unit->type) {
+		case NI_PMU:
+			reg = readl_relaxed(unit_base + NI_PMCFGR);
+			if (!reg) {
+				dev_info(ni->dev, "No access to PMU %d\n", cd->id);
+				devm_kfree(ni->dev, cd->units);
+				return 0;
+			}
+			unit->ns = true;
+			cd->pmu_base = unit_base;
+			break;
+		case NI_ASNI:
+		case NI_AMNI:
+		case NI_HSNI:
+		case NI_HMNI:
+		case NI_PMNI:
+			unit->pmusela = unit_base + NI700_PMUSELA;
+			writel_relaxed(1, unit->pmusela);
+			if (readl_relaxed(unit->pmusela) != 1)
+				dev_info(ni->dev, "No access to node 0x%04x%04x\n", unit->id, unit->type);
+			else
+				unit->ns = true;
+			break;
+		default:
+			/*
+			 * e.g. FMU - thankfully bits 3:2 of FMU_ERR_FR0 are RES0 so
+			 * can't alias any of the leaf node types we're looking for.
+			 */
+			dev_dbg(ni->dev, "Mystery node 0x%04x%04x\n", unit->id, unit->type);
+			break;
+		}
+	}
+
+	res_start += cd->pmu_base - ni->base;
+	if (!devm_request_mem_region(ni->dev, res_start, SZ_4K, dev_name(ni->dev))) {
+		dev_err(ni->dev, "Failed to request PMU region 0x%llx\n", res_start);
+		return -EBUSY;
+	}
+
+	writel_relaxed(NI_PMCR_RESET_CCNT | NI_PMCR_RESET_EVCNT,
+		       cd->pmu_base + NI_PMCR);
+	writel_relaxed(U32_MAX, cd->pmu_base + NI_PMCNTENCLR);
+	writel_relaxed(U32_MAX, cd->pmu_base + NI_PMOVSCLR);
+	writel_relaxed(U32_MAX, cd->pmu_base + NI_PMINTENSET);
+
+	cd->irq = platform_get_irq(to_platform_device(ni->dev), cd->id);
+	if (cd->irq < 0)
+		return cd->irq;
+
+	err = devm_request_irq(ni->dev, cd->irq, arm_ni_handle_irq,
+			       IRQF_NOBALANCING | IRQF_NO_THREAD,
+			       dev_name(ni->dev), cd);
+	if (err)
+		return err;
+
+	cd->cpu = cpumask_local_spread(0, dev_to_node(ni->dev));
+	cd->pmu = (struct pmu) {
+		.module = THIS_MODULE,
+		.parent = ni->dev,
+		.attr_groups = arm_ni_attr_groups,
+		.capabilities = PERF_PMU_CAP_NO_EXCLUDE,
+		.task_ctx_nr = perf_invalid_context,
+		.pmu_enable = arm_ni_pmu_enable,
+		.pmu_disable = arm_ni_pmu_disable,
+		.event_init = arm_ni_event_init,
+		.add = arm_ni_event_add,
+		.del = arm_ni_event_del,
+		.start = arm_ni_event_start,
+		.stop = arm_ni_event_stop,

Documentation/admin-guide/perf/arm-ni.rst

Quoted 10 lines
Documentation/admin-guide/perf/arm-ni.rst new file +10 -0
+		.read = arm_ni_event_read,
+	};
+
+	name = devm_kasprintf(ni->dev, GFP_KERNEL, "arm_ni_%d_cd_%d", ni->id, cd->id);
+	if (!name)
+		return -ENOMEM;
+
+	err = cpuhp_state_add_instance(arm_ni_hp_state, &cd->cpuhp_node);
+	if (err)
+		return err;

What happens if there's a CPU hotplug operation here? Can we end up calling perf_pmu_migrate_context() concurrently with perf_pmu_register()?

Documentation/admin-guide/perf/arm-ni.rst

Quoted 1 line
Documentation/admin-guide/perf/arm-ni.rst new file +1 -0
+	return perf_pmu_register(&cd->pmu, name, -1);

Clean up the cpuhp instance if this fails?

Documentation/admin-guide/perf/arm-ni.rst

Quoted 13 lines
Documentation/admin-guide/perf/arm-ni.rst new file +13 -0
+static void arm_ni_remove(struct platform_device *pdev)
+{
+	struct arm_ni *ni = platform_get_drvdata(pdev);
+
+	for (int i = 0; i < ni->num_cds; i++) {
+		struct arm_ni_cd *cd = ni->cds + i;
+
+		if (!cd->pmu_base)
+			continue;
+
+		writel_relaxed(0, cd->pmu_base + NI_PMCR);
+		writel_relaxed(U32_MAX, cd->pmu_base + NI_PMINTENCLR);
+		perf_pmu_unregister(&cd->pmu);

Similarly here, it feels like a CPU hotplug operation could cause problems here.

Documentation/admin-guide/perf/arm-ni.rst

Quoted 3 lines
Documentation/admin-guide/perf/arm-ni.rst new file +3 -0
+		cpuhp_state_remove_instance_nocalls(arm_ni_hp_state, &cd->cpuhp_node);
+	}
+}

Will

author maintainer
#4 lore raw

Re: [PATCH v3 2/3] perf: Add driver for Arm NI-700 interconnect PMU

Quoted 36 lines · Will Deacon

Hi Robin,

Quoted 16 lines · Robin Murphy

The Arm NI-700 Network-on-Chip Interconnect has a relatively straightforward design with a hierarchy of voltage, power, and clock domains, where each clock domain then contains a number of interface units and a PMU which can monitor events thereon. As such, it begets a relatively straightforward driver to interface those PMUs with perf.

Even more so than with arm-cmn, users will require detailed knowledge of the wider system topology in order to meaningfully analyse anything, since the interconnect itself cannot know what lies beyond the boundary of each inscrutably-numbered interface. Given that, for now they are also expected to refer to the NI-700 documentation for the relevant event IDs to provide as well. An identifier is implemented so we can come back and add jevents if anyone really wants to.

Quoted 13 lines

---
v2:
- Add basic usage documentation
- Use __counted_by attribute
- Make group validation logic clearer (and drop PMU type check which perf_event_open() already takes care of)
- Add retry limit to arm_ni_read_ccnt()
v3:
- Update .remove to return void
- Fix group leader validation and make the naming clearer
- Drop NUMA_NO_NODE check for CPU online (the only way that could actually pass both other migration conditions is if the NUMA info is so messed up that it's not worth worrying about anyway)

Thanks, this is looking pretty good now. I just have a few random comments based on another read-through of the code.

Quoted 15 lines

Documentation/admin-guide/perf/arm-ni.rst line 7

Quoted 13 lines
Documentation/admin-guide/perf/arm-ni.rst new file +7 -0
new file mode 100644
index 000000000000..3cd7d0f75f0f
@@ -0,0 +1,17 @@
+====================================
+Arm Network-on Chip Interconnect PMU
+====================================
+
+NI-700 and friends implement a distinct PMU for each clock domain within the
+interconnect. Correspondingly, the driver exposes multiple PMU devices named
+arm_ni_<x>_cd_<y>, where <x> is an (abritrary) instance identifier and <y> is

typo: abritrary

Oops, fixed.

Quoted 13 lines
Quoted 10 lines
Documentation/admin-guide/perf/arm-ni.rst new file +10 -0
+the clock domain ID within that particular instance. If multiple NI instances
+exist within a system, the PMU devices can be correlated with the underlying
+hardware instance via sysfs parentage.
+
+Each PMU exposes base event aliases for the interface types present in its clock
+domain. These require qualifying with the "eventid" and "nodeid" parameters
+to specify the event code to count and the interface at which to count it
+(per the configured hardware ID as reflected in the xxNI_NODE_INFO register).
+The exception is the "cycles" alias for the PMU cycle counter, which is encoded
+with the PMU node type and needs no further qualification.

[...]

Quoted 12 lines

Documentation/admin-guide/perf/arm-ni.rst

Quoted 8 lines
Documentation/admin-guide/perf/arm-ni.rst new file +8 -0
+static ssize_t arm_ni_format_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct arm_ni_format_attr *fmt = container_of(attr, typeof(*fmt), attr);
+	int lo = __ffs(fmt->field), hi = __fls(fmt->field);
+
+	return sysfs_emit(buf, "config:%d-%d\n", lo, hi);
+}

Nit: if you end up adding single-bit config fields in the future, this will quietly do the wrong thing. Maybe safe-guard the 'lo==hi' case (even if you just warn once and return without doing anything).

The counter-argument is that I don't foresee having any reason to add single-bit config fields here in future, nor indeed config1 or config2 fields, so I intentionally pruned the would-be dead code while copy-pasting this implementation from arm-cmn. Yes, if someone were to make an incomplete change without paying attention or testing they could introduce a bug, but when is that ever not true?

TBH I was originally hoping to have much of this boilerplate stuff factored out into a common library beforehand (I also anticipate needing the IRQ-sharing shenanigans here), but as ever, priorities got in the way...

Quoted 99 lines

[...]

Quoted 95 lines
Documentation/admin-guide/perf/arm-ni.rst new file +95 -0
+static int arm_ni_init_cd(struct arm_ni *ni, struct arm_ni_node *node, u64 res_start)
+{
+	struct arm_ni_cd *cd = ni->cds + node->id;
+	const char *name;
+	int err;
+
+	cd->id = node->id;
+	cd->num_units = node->num_components;
+	cd->units = devm_kcalloc(ni->dev, cd->num_units, sizeof(*(cd->units)), GFP_KERNEL);
+	if (!cd->units)
+		return -ENOMEM;
+
+	for (int i = 0; i < cd->num_units; i++) {
+		u32 reg = readl_relaxed(node->base + NI_CHILD_PTR(i));
+		void __iomem *unit_base = ni->base + reg;
+		struct arm_ni_unit *unit = cd->units + i;
+
+		reg = readl_relaxed(unit_base + NI_NODE_TYPE);
+		unit->type = FIELD_GET(NI_NODE_TYPE_NODE_TYPE, reg);
+		unit->id = FIELD_GET(NI_NODE_TYPE_NODE_ID, reg);
+
+		switch (unit->type) {
+		case NI_PMU:
+			reg = readl_relaxed(unit_base + NI_PMCFGR);
+			if (!reg) {
+				dev_info(ni->dev, "No access to PMU %d\n", cd->id);
+				devm_kfree(ni->dev, cd->units);
+				return 0;
+			}
+			unit->ns = true;
+			cd->pmu_base = unit_base;
+			break;
+		case NI_ASNI:
+		case NI_AMNI:
+		case NI_HSNI:
+		case NI_HMNI:
+		case NI_PMNI:
+			unit->pmusela = unit_base + NI700_PMUSELA;
+			writel_relaxed(1, unit->pmusela);
+			if (readl_relaxed(unit->pmusela) != 1)
+				dev_info(ni->dev, "No access to node 0x%04x%04x\n", unit->id, unit->type);
+			else
+				unit->ns = true;
+			break;
+		default:
+			/*
+			 * e.g. FMU - thankfully bits 3:2 of FMU_ERR_FR0 are RES0 so
+			 * can't alias any of the leaf node types we're looking for.
+			 */
+			dev_dbg(ni->dev, "Mystery node 0x%04x%04x\n", unit->id, unit->type);
+			break;
+		}
+	}
+
+	res_start += cd->pmu_base - ni->base;
+	if (!devm_request_mem_region(ni->dev, res_start, SZ_4K, dev_name(ni->dev))) {
+		dev_err(ni->dev, "Failed to request PMU region 0x%llx\n", res_start);
+		return -EBUSY;
+	}
+
+	writel_relaxed(NI_PMCR_RESET_CCNT | NI_PMCR_RESET_EVCNT,
+		       cd->pmu_base + NI_PMCR);
+	writel_relaxed(U32_MAX, cd->pmu_base + NI_PMCNTENCLR);
+	writel_relaxed(U32_MAX, cd->pmu_base + NI_PMOVSCLR);
+	writel_relaxed(U32_MAX, cd->pmu_base + NI_PMINTENSET);
+
+	cd->irq = platform_get_irq(to_platform_device(ni->dev), cd->id);
+	if (cd->irq < 0)
+		return cd->irq;
+
+	err = devm_request_irq(ni->dev, cd->irq, arm_ni_handle_irq,
+			       IRQF_NOBALANCING | IRQF_NO_THREAD,
+			       dev_name(ni->dev), cd);
+	if (err)
+		return err;
+
+	cd->cpu = cpumask_local_spread(0, dev_to_node(ni->dev));
+	cd->pmu = (struct pmu) {
+		.module = THIS_MODULE,
+		.parent = ni->dev,
+		.attr_groups = arm_ni_attr_groups,
+		.capabilities = PERF_PMU_CAP_NO_EXCLUDE,
+		.task_ctx_nr = perf_invalid_context,
+		.pmu_enable = arm_ni_pmu_enable,
+		.pmu_disable = arm_ni_pmu_disable,
+		.event_init = arm_ni_event_init,
+		.add = arm_ni_event_add,
+		.del = arm_ni_event_del,
+		.start = arm_ni_event_start,
+		.stop = arm_ni_event_stop,
+		.read = arm_ni_event_read,
+	};
+
+	name = devm_kasprintf(ni->dev, GFP_KERNEL, "arm_ni_%d_cd_%d", ni->id, cd->id);
+	if (!name)
Quoted 8 lines

Documentation/admin-guide/perf/arm-ni.rst

Quoted 5 lines
Documentation/admin-guide/perf/arm-ni.rst new file +5 -0
+		return -ENOMEM;
+
+	err = cpuhp_state_add_instance(arm_ni_hp_state, &cd->cpuhp_node);
+	if (err)
+		return err;

What happens if there's a CPU hotplug operation here? Can we end up calling perf_pmu_migrate_context() concurrently with perf_pmu_register()?

Yes. Alternatively we could register the PMU before the hotplug handler, then potentially miss a hotplug event and leave a user-visible PMU associated with an invalid CPU. This is a known issue for all system PMU drivers, and the conclusion 5 years ago was that it's impractical to close this race from outside perf core itself[1][2].

Quoted 3 lines

Documentation/admin-guide/perf/arm-ni.rst

Quoted 1 line
Documentation/admin-guide/perf/arm-ni.rst new file +1 -0
+	return perf_pmu_register(&cd->pmu, name, -1);

Clean up the cpuhp instance if this fails?

Indeed, not sure how that managed to escape...

Thanks,
Robin.

[1] https://lore.kernel.org/linux-arm-kernel/[email protected]/
[2] https://lore.kernel.org/linux-arm-kernel/[email protected]/

Quoted 22 lines

Documentation/admin-guide/perf/arm-ni.rst

Quoted 13 lines
Documentation/admin-guide/perf/arm-ni.rst new file +13 -0
+static void arm_ni_remove(struct platform_device *pdev)
+{
+	struct arm_ni *ni = platform_get_drvdata(pdev);
+
+	for (int i = 0; i < ni->num_cds; i++) {
+		struct arm_ni_cd *cd = ni->cds + i;
+
+		if (!cd->pmu_base)
+			continue;
+
+		writel_relaxed(0, cd->pmu_base + NI_PMCR);
+		writel_relaxed(U32_MAX, cd->pmu_base + NI_PMINTENCLR);
+		perf_pmu_unregister(&cd->pmu);

Similarly here, it feels like a CPU hotplug operation could cause problems here.

Documentation/admin-guide/perf/arm-ni.rst

Quoted 3 lines
Documentation/admin-guide/perf/arm-ni.rst new file +3 -0
+		cpuhp_state_remove_instance_nocalls(arm_ni_hp_state, &cd->cpuhp_node);
+	}
+}

Will

maintainer
#5 lore raw

Re: [PATCH v3 2/3] perf: Add driver for Arm NI-700 interconnect PMU

Quoted 14 lines · Robin Murphy
Quoted 4 lines · Will Deacon
Quoted 4 lines
drivers/perf/arm-ni.c new file +4 -0
+static ssize_t arm_ni_format_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct arm_ni_format_attr *fmt = container_of(attr, typeof(*fmt), attr);
Quoted 8 lines

drivers/perf/arm-ni.c line 205

Quoted 4 lines
drivers/perf/arm-ni.c new file +4 -0
+	int lo = __ffs(fmt->field), hi = __fls(fmt->field);
+
+	return sysfs_emit(buf, "config:%d-%d\n", lo, hi);
+}

Nit: if you end up adding single-bit config fields in the future, this will quietly do the wrong thing. Maybe safe-guard the 'lo==hi' case (even if you just warn once and return without doing anything).

Quoted 6 lines

The counter-argument is that I don't foresee having any reason to add single-bit config fields here in future, nor indeed config1 or config2 fields, so I intentionally pruned the would-be dead code while copy-pasting this implementation from arm-cmn. Yes, if someone were to make an incomplete change without paying attention or testing they could introduce a bug, but when is that ever not true?

I guess I'm just a little more wary when it comes to UAPI. Somebody starts relying on the broken message and then you're toast. It's also incredibly easy to avoid by construction and the dead code isn't hurting anybody.

Quoted 11 lines
Quoted 10 lines

drivers/perf/arm-ni.c line 571

Quoted 7 lines
drivers/perf/arm-ni.c new file +7 -0
+	name = devm_kasprintf(ni->dev, GFP_KERNEL, "arm_ni_%d_cd_%d", ni->id, cd->id);
+	if (!name)
+		return -ENOMEM;
+
+	err = cpuhp_state_add_instance(arm_ni_hp_state, &cd->cpuhp_node);
+	if (err)
+		return err;

What happens if there's a CPU hotplug operation here? Can we end up calling perf_pmu_migrate_context() concurrently with perf_pmu_register()?

Quoted 5 lines

Yes. Alternatively we could register the PMU before the hotplug handler, then potentially miss a hotplug event and leave a user-visible PMU associated with an invalid CPU. This is a known issue for all system PMU drivers, and the conclusion 5 years ago was that it's impractical to close this race from outside perf core itself[1][2].

Ok, I'm going to walk right into the trap you've set me...

Why can't we prevent hotplug (e.g. with cpus_read_lock()) while we're setting this up?

... and climbing back out of that trap, is the conversation you had with Thomas written down anywhere?

I don't want to block this patch, but if five years has passed with nobody looking at this then we probably need to address that at some point before adding more and more broken drivers.

Will

author maintainer
#6 lore raw

Re: [PATCH v3 2/3] perf: Add driver for Arm NI-700 interconnect PMU

Quoted 22 lines · Will Deacon
Quoted 14 lines · Robin Murphy
Quoted 4 lines · Will Deacon
Quoted 4 lines
drivers/perf/arm-ni.c new file +4 -0
+static ssize_t arm_ni_format_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct arm_ni_format_attr *fmt = container_of(attr, typeof(*fmt), attr);
Quoted 8 lines

drivers/perf/arm-ni.c line 205

Quoted 4 lines
drivers/perf/arm-ni.c new file +4 -0
+	int lo = __ffs(fmt->field), hi = __fls(fmt->field);
+
+	return sysfs_emit(buf, "config:%d-%d\n", lo, hi);
+}

Nit: if you end up adding single-bit config fields in the future, this will quietly do the wrong thing. Maybe safe-guard the 'lo==hi' case (even if you just warn once and return without doing anything).

Quoted 6 lines

The counter-argument is that I don't foresee having any reason to add single-bit config fields here in future, nor indeed config1 or config2 fields, so I intentionally pruned the would-be dead code while copy-pasting this implementation from arm-cmn. Yes, if someone were to make an incomplete change without paying attention or testing they could introduce a bug, but when is that ever not true?

Quoted 3 lines

I guess I'm just a little more wary when it comes to UAPI. Somebody starts relying on the broken message and then you're toast. It's also incredibly easy to avoid by construction and the dead code isn't hurting anybody.

Even presuming some theoretical unreviewed broken patch did get merged and into real-world kernels without ever being fixed, I still struggle to imagine how userspace could somehow grow to *rely* on one PMU driver displaying a format attribute in an unexpected manner inconsistent with every other PMU driver, as opposed to the far more likely scenario of going wrong trying to parse it.

Anyway, after playing with some fun compile-time checks yesterday I've just realised there is actually an even simpler solution for doing the right thing in general, so I guess thanks for leaning on this :)

Quoted 17 lines
Quoted 11 lines
Quoted 10 lines

drivers/perf/arm-ni.c line 571

Quoted 7 lines
drivers/perf/arm-ni.c new file +7 -0
+	name = devm_kasprintf(ni->dev, GFP_KERNEL, "arm_ni_%d_cd_%d", ni->id, cd->id);
+	if (!name)
+		return -ENOMEM;
+
+	err = cpuhp_state_add_instance(arm_ni_hp_state, &cd->cpuhp_node);
+	if (err)
+		return err;

What happens if there's a CPU hotplug operation here? Can we end up calling perf_pmu_migrate_context() concurrently with perf_pmu_register()?

Quoted 5 lines

Yes. Alternatively we could register the PMU before the hotplug handler, then potentially miss a hotplug event and leave a user-visible PMU associated with an invalid CPU. This is a known issue for all system PMU drivers, and the conclusion 5 years ago was that it's impractical to close this race from outside perf core itself[1][2].

Quoted 7 lines

Ok, I'm going to walk right into the trap you've set me...

Why can't we prevent hotplug (e.g. with cpus_read_lock()) while we're setting this up?

... and climbing back out of that trap, is the conversation you had with Thomas written down anywhere?

I think that must have been the time we talked in person in Cambridge. I can't now remember if there is (or was) anything in perf_pmu_register() that's actually incompatible with being called under cpus_read_lock(), but I do recall that the overall concept of exporting more bits of the hotplug machinery in order to copy-paste the same boilerplate bodge in every PMU driver was... unpopular.

Quoted 3 lines

I don't want to block this patch, but if five years has passed with nobody looking at this then we probably need to address that at some point before adding more and more broken drivers.

Looking at it again, is there actually a problem with the current state of things? Following through the call path:

perf_pmu_migrate_context()

    __perf_pmu_remove()
      perf_event_groups_first()
        __group_cmp()
          perf_event_groups_cmp()

the "pmu" pointer only seems to be used as a key to match events in the current CPU context, which obviously won't find anything at this point. AFAICS it's never even dereferenced, unless any events *are* found for __perf_pmu_install() to do something with, which necessarily implies an initialised and registered PMU for them to have been opened in the first place.

Thanks,
Robin.

maintainer
#7 lore raw

Re: [PATCH v3 2/3] perf: Add driver for Arm NI-700 interconnect PMU

Quoted 27 lines · Robin Murphy
Quoted 22 lines · Will Deacon
Quoted 14 lines · Robin Murphy
Quoted 4 lines · Will Deacon
Quoted 4 lines
drivers/perf/arm-ni.c new file +4 -0
+static ssize_t arm_ni_format_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct arm_ni_format_attr *fmt = container_of(attr, typeof(*fmt), attr);
Quoted 8 lines

drivers/perf/arm-ni.c line 205

Quoted 4 lines
drivers/perf/arm-ni.c new file +4 -0
+	int lo = __ffs(fmt->field), hi = __fls(fmt->field);
+
+	return sysfs_emit(buf, "config:%d-%d\n", lo, hi);
+}

Nit: if you end up adding single-bit config fields in the future, this will quietly do the wrong thing. Maybe safe-guard the 'lo==hi' case (even if you just warn once and return without doing anything).

Quoted 6 lines

The counter-argument is that I don't foresee having any reason to add single-bit config fields here in future, nor indeed config1 or config2 fields, so I intentionally pruned the would-be dead code while copy-pasting this implementation from arm-cmn. Yes, if someone were to make an incomplete change without paying attention or testing they could introduce a bug, but when is that ever not true?

Quoted 3 lines

I guess I'm just a little more wary when it comes to UAPI. Somebody starts relying on the broken message and then you're toast. It's also incredibly easy to avoid by construction and the dead code isn't hurting anybody.

Quoted 6 lines

Even presuming some theoretical unreviewed broken patch did get merged and into real-world kernels without ever being fixed, I still struggle to imagine how userspace could somehow grow to *rely* on one PMU driver displaying a format attribute in an unexpected manner inconsistent with every other PMU driver, as opposed to the far more likely scenario of going wrong trying to parse it.

I think you lose the game when you try to imagine what userspace could do :)

But c'mon, this is simple to address and then we don't have to imagine anything.

Quoted 3 lines

Anyway, after playing with some fun compile-time checks yesterday I've just realised there is actually an even simpler solution for doing the right thing in general, so I guess thanks for leaning on this :)

Hurrah! Thanks.

Quoted 25 lines
Quoted 17 lines
Quoted 11 lines
Quoted 10 lines

drivers/perf/arm-ni.c line 571

Quoted 7 lines
drivers/perf/arm-ni.c new file +7 -0
+	name = devm_kasprintf(ni->dev, GFP_KERNEL, "arm_ni_%d_cd_%d", ni->id, cd->id);
+	if (!name)
+		return -ENOMEM;
+
+	err = cpuhp_state_add_instance(arm_ni_hp_state, &cd->cpuhp_node);
+	if (err)
+		return err;

What happens if there's a CPU hotplug operation here? Can we end up calling perf_pmu_migrate_context() concurrently with perf_pmu_register()?

Quoted 5 lines

Yes. Alternatively we could register the PMU before the hotplug handler, then potentially miss a hotplug event and leave a user-visible PMU associated with an invalid CPU. This is a known issue for all system PMU drivers, and the conclusion 5 years ago was that it's impractical to close this race from outside perf core itself[1][2].

Quoted 7 lines

Ok, I'm going to walk right into the trap you've set me...

Why can't we prevent hotplug (e.g. with cpus_read_lock()) while we're setting this up?

... and climbing back out of that trap, is the conversation you had with Thomas written down anywhere?

Quoted 6 lines

I think that must have been the time we talked in person in Cambridge. I can't now remember if there is (or was) anything in perf_pmu_register() that's actually incompatible with being called under cpus_read_lock(), but I do recall that the overall concept of exporting more bits of the hotplug machinery in order to copy-paste the same boilerplate bodge in every PMU driver was... unpopular.

Unpopular, maybe, but you only have to look at a few PMU drivers to see we're already in that situation for other aspects of the code. Consolidation would be welcome, but I'd sooner have boilerplate code than bugs.

Quoted 13 lines
Quoted 3 lines

I don't want to block this patch, but if five years has passed with nobody looking at this then we probably need to address that at some point before adding more and more broken drivers.

Looking at it again, is there actually a problem with the current state of things? Following through the call path:

perf_pmu_migrate_context()

    __perf_pmu_remove()
      perf_event_groups_first()
        __group_cmp()
          perf_event_groups_cmp()
Quoted 6 lines

the "pmu" pointer only seems to be used as a key to match events in the current CPU context, which obviously won't find anything at this point. AFAICS it's never even dereferenced, unless any events *are* found for __perf_pmu_install() to do something with, which necessarily implies an initialised and registered PMU for them to have been opened in the first place.

You might well be right, but this could will change in future and I don't think it's been written to work safely under this race. I also pointed out that a similar race exists on the unregister path.

Will

author maintainer
#8 lore raw

PATCH v3 3/3 MAINTAINERS: List Arm interconnect PMUs as supported

In reply to Robin Murphy

Whatever I may or may not have hoped for, looking after these drivers seems to have firmly stuck as one of the responsibilities of the job Arm pays me for, and I would still like to be aware of any other patches, so make it official.

CC Ilkka Koskinen <[email protected]>
CC Jing Zhang <[email protected]>

---

v3: No change

 MAINTAINERS | 11 +++++++++++
 1 file changed, 11 insertions(+)
MAINTAINERS +11 -0
index f328373463b0..1e1cd7414f06 100644
@@ -1737,6 +1737,17 @@ F:	drivers/mtd/maps/physmap-versatile.*
 F:	drivers/power/reset/arm-versatile-reboot.c
 F:	drivers/soc/versatile/
 
+ARM INTERCONNECT PMU DRIVERS
+M:	Robin Murphy <[email protected]>
+S:	Supported
+F:	Documentation/admin-guide/perf/arm-cmn.rst
+F:	Documentation/admin-guide/perf/arm-ni.rst
+F:	Documentation/devicetree/bindings/perf/arm,cmn.yaml
+F:	Documentation/devicetree/bindings/perf/arm,ni.yaml
+F:	drivers/perf/arm-cmn.c
+F:	drivers/perf/arm-ni.c
+F:	tools/perf/pmu-events/arch/arm64/arm/cmn/
+
 ARM KOMEDA DRM-KMS DRIVER
 M:	Liviu Dudau <[email protected]>
 S:	Supported
Signature · 1 line
2.39.2.101.g768bb238c484.dirty