Skip to contentSkip to conversation

perf: Support Arm Bus Monitor Unit

PATCH Superseded Robin Murphy 3 participants 8 messages Jul 16 – Jul 29

Subsystems (2)

v2 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

v2 cover perf: Support Arm Bus Monitor Unit

Hi all,

Here we have the delight of another new PMU driver. While not am Arm product in its own right, the BMU is found in the latest Neoverse Compute Subsystems, including the recently-announced AGI CPU.

For now it is expected that BMU users will be ACPI-based (binding in the latest "ACPI for Arm Components" release[1]), so the token DT binding is more for completeness and maybe very early bringup hacking. I've kept it here for the sake of discussion, but the only requirement on me for now is to get patch #1 landed.

And yes, I did initially look at trying to factor out the PMU register accesses that functionally overlap the existing arm_cspmu code, but it very quickly became clear that that would end up far bigger and more complex. This is not a big driver, even with a few dozen lines of nominally-duplicate (but also simplified since the counter size etc. is fixed and known) code, so this way it went.

v1: https://lore.kernel.org/linux-perf-users/[email protected]/
v2: Fix valid Sashiko nits, separate DT support

Thanks,
Robin.

[1] https://developer.arm.com/documentation/den0093/latest/

Robin Murphy (3):
perf: Add Arm Bus Monitor Unit driver
dt-bindings: perf: Add Arm Bus Monitor Unit
perf/arm-bmu: Add Devicetree support

 .../bindings/perf/arm,bus-monitor-unit.yaml   |  41 ++
 drivers/perf/arm_cspmu/Kconfig                |   6 +
 drivers/perf/arm_cspmu/Makefile               |   2 +
 drivers/perf/arm_cspmu/arm-bmu.c              | 538 ++++++++++++++++++
 4 files changed, 587 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/perf/arm,bus-monitor-unit.yaml
 create mode 100644 drivers/perf/arm_cspmu/arm-bmu.c
Signature · 1 line
2.54.0.dirty
#1 lore raw

PATCH v2 1/3 perf: Add Arm Bus Monitor Unit driver

Arm's Bus Monitor Unit is a low-level performance analysis tool for matching and counting transactions at interconnect interfaces. Each BMU consists of a number of "interface monitoring units", with some global controls to synchronise them and so permit accurate calculation of cross-interface metrics. Each IMU contains, among other things, its own PMU largely based on the CoreSight PMU architecture, but with some functional incompatibilities due to the nature of how much per-event filtering needs to be crammed in to categorise AXI/CHI transactions at the protocol level. As such it gets an honorary home in arm_cspmu/ in order to share the common register definitions, but for simplicity does not try to bend the functional abstraction to fit.

---
v2:
- Fix copy-paste typo in group validation logic
- Fix wonky error returns in probe
- Make the handling of IRQ errors what it was supposed to be
- Make format bitmap printing 100% robust for theoretical nitpickers
- Split out DT support

---
 drivers/perf/arm_cspmu/Kconfig   |   6 +
 drivers/perf/arm_cspmu/Makefile  |   2 +
 drivers/perf/arm_cspmu/arm-bmu.c | 525 +++++++++++++++++++++++++++++++
 3 files changed, 533 insertions(+)
 create mode 100644 drivers/perf/arm_cspmu/arm-bmu.c
drivers/perf/arm_cspmu/Kconfig +6 -0
index 6f4e28fc84a2..0cb369f91023 100644
@@ -27,3 +27,9 @@ config AMPERE_CORESIGHT_PMU_ARCH_SYSTEM_PMU
 
 	  In the first phase, the driver enables support on MCU PMU used in
 	  AmpereOne SoC family.
+
+config ARM_BMU
+	tristate "ARM Bus Monitor Unit"
+	depends on (ARM64 && ACPI) || COMPILE_TEST
+	help
+	  Support perf event counting on Arm Bus Monitor Unit devices.
drivers/perf/arm_cspmu/Makefile +2 -0
index 220a734efd54..622b8fc3d5dc 100644
@@ -8,3 +8,5 @@ arm_cspmu_module-y := arm_cspmu.o
 
 obj-$(CONFIG_NVIDIA_CORESIGHT_PMU_ARCH_SYSTEM_PMU) += nvidia_cspmu.o
 obj-$(CONFIG_AMPERE_CORESIGHT_PMU_ARCH_SYSTEM_PMU) += ampere_cspmu.o
+
+obj-$(CONFIG_ARM_BMU) += arm-bmu.o
drivers/perf/arm_cspmu/arm-bmu.c new file +525 -0

525 lines not on this page · raw message

Signature · 1 line
2.54.0.dirty
#2 lore raw

Re: [PATCH v2 1/3] perf: Add Arm Bus Monitor Unit driver

Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider: - [High] Unconditional use of `ACPI_COMPANION()` and `acpi_device_uid()` causes a NULL pointer dereference…

Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [High] Unconditional use of ACPI_COMPANION() and acpi_device_uid() causes a NULL pointer dereference or build failure.
- [High] Missing continue when Non-Secure access is disabled leads to writes to inaccessible PMU registers.
- [High] ABA vulnerability and NMI torn state in lockless prev_count update causes massive PMU count corruption.
- [High] Missing bounds check on the computed PMU region offset against the actual ioremapped memory resource size.
- [High] Missing cleanup of hardware interrupt state on probe error paths causes an unhandled interrupt storm.
- [Medium] TOCTOU race in PMU CPU assignment during probe can bind the PMU to an offline CPU, leaving it non-functional.
- [Medium] Ignoring -EPROBE_DEFER for the interrupt permanently breaks IRQ functionality.
- [Medium] False-positive WARN_ON triggered by a race between event deletion and pending PMU interrupts.
- [Low] Format string type mismatch passing a 32-bit resource_size_t / phys_addr_t to a 64-bit %llx specifier. --

--- Patch [1]: [PATCH v2 1/3] perf: Add Arm Bus Monitor Unit driver --- commit 46bae7b04d539138ea677d6d9ef5697dcb457c20
Author: Robin Murphy <[email protected]>

perf: Add Arm Bus Monitor Unit driver

Arm's Bus Monitor Unit is a low-level performance analysis tool for matching and counting transactions at interconnect interfaces. Each BMU consists of a number of "interface monitoring units", with some global controls to synchronise them and so permit accurate calculation of cross-interface metrics.

Quoted 5 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +0 -0
new file mode 100644
index 0000000000000..c829a0728e9b2

[ ... ]

Quoted 16 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +16 -0
+static void arm_bmu_event_read(struct perf_event *event)
+{
+	struct hw_perf_event *hw = &event->hw;
+	u64 count, prev;
+
+	do {
+		prev = local64_read(&hw->prev_count);
+		count = arm_bmu_read_evcnt(event);
+	} while (local64_cmpxchg(&hw->prev_count, prev, count) != prev);
+
+	count -= prev;
+	local64_add(count, &event->count);
+}
+
+static void arm_bmu_event_start(struct perf_event *event, int flags)
+{

drivers/perf/arm_cspmu/arm-bmu.c line 236

Quoted 10 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +10 -0
+	struct arm_bmu_pmu *pmu = to_bmu_pmu(event);
+
+	writel_relaxed(1ULL << event->hw.idx, pmu->base + PMCNTENSET);
+}
+
+static void arm_bmu_event_stop(struct perf_event *event, int flags)
+{
+	struct arm_bmu_pmu *pmu = to_bmu_pmu(event);
+
+	writel_relaxed(1ULL << event->hw.idx, pmu->base + PMCNTENCLR);

[Severity: Medium]
Should the hardware overflow state (PMOVSCLR) be cleared here when stopping the event?

If an overflow occurs right before stopping, the pending interrupt bit remains set. When arm_bmu_event_del() completes, it clears the event pointer. When the interrupt is finally delivered, wouldn't the interrupt handler see the overflow bit set and incorrectly trigger the WARN_ON for a NULL event pointer?

Quoted 3 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +3 -0
+	if (flags & PERF_EF_UPDATE)
+		arm_bmu_event_read(event);
+}

[ ... ]

drivers/perf/arm_cspmu/arm-bmu.c line 288

Quoted 13 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +13 -0
+static void arm_bmu_pmu_irq(struct arm_bmu_pmu *pmu)
+{
+	u32 reg = readl_relaxed(pmu->base + PMOVSCLR);
+	u64 __iomem *pmevcnt = pmu->base + PMEVCNTR_LO;
+
+	for (int i = 0; i < PMU_MAX_COUNTERS; i++) {
+		if (!(reg & (1U << i)))
+			continue;
+		if (WARN_ON(!pmu->evcnt[i]))
+			continue;
+		arm_bmu_event_read(pmu->evcnt[i]);
+		local64_set(&pmu->evcnt[i]->hw.prev_count, S64_MIN);
+		lo_hi_writeq_relaxed(S64_MIN, pmevcnt + i);

[Severity: High]
Is the lockless compare-and-exchange logic in arm_bmu_event_read() vulnerable to the ABA problem when resetting prev_count here?

If an NMI interrupts this IRQ handler right after prev_count is set to S64_MIN but before the hardware register is written, arm_bmu_event_read() could read the old wrapped hardware value and falsely succeed in the cmpxchg because prev_count is S64_MIN.

This would save the wrapped value as prev_count. When the IRQ handler resumes and resets the hardware to S64_MIN, wouldn't subsequent delta calculations underflow and cause massive permanent counter corruption?

Quoted 3 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +3 -0
+	}
+	writel_relaxed(reg, pmu->base + PMOVSCLR);
+}

[ ... ]

Quoted 2 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +2 -0
+static int arm_bmu_probe(struct platform_device *pdev)
+{

[ ... ]

drivers/perf/arm_cspmu/arm-bmu.c line 343

Quoted 3 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +3 -0
+	/* The PMU pages *are* exclusively ours */
+	off = SZ_4K * FIELD_GET(MCUCFG_PMU_ELEMENT_START, cfg);
+	if (!devm_request_mem_region(dev, res->start + off, num * SZ_4K, dev_name(dev)))

[Severity: High]
Does this compute the PMU region offset without verifying it fits within the originally mapped memory resource size?

If the hardware provides an offset such that the new region exceeds resource_size(res), applying this unvalidated offset to the virtual base pointer and dereferencing it later will access memory outside the mapped virtual memory area, potentially causing a kernel page fault or panic.

Quoted 9 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +9 -0
+		return dev_err_probe(dev, -EADDRINUSE, "Unable to request PMU region\n");
+
+	bmu = devm_kzalloc(dev, struct_size(bmu, imus, num), GFP_KERNEL);
+	if (!bmu)
+		return -ENOMEM;
+
+	bmu->dev = dev;
+	bmu->base = base;
+	bmu->num_imus = num;

drivers/perf/arm_cspmu/arm-bmu.c line 362

Quoted 10 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +10 -0
+	platform_set_drvdata(pdev, bmu);
+
+	base += off;
+	for (int i = 0; i < bmu->num_imus; i++, base += SZ_4K) {
+		/* At least PMCFGR.SIZE should always be nonzero if visible */
+		reg = readl_relaxed(base + PMCFGR);
+		if (!reg) {
+			dev_warn(dev, "Non-Secure access to PMU %d not enabled?\n", i);
+			num = 0;
+		} else {

[Severity: High]
Should there be a continue statement when Non-Secure access is disabled?

Without a continue, the code proceeds to unconditionally write to BMU_PMCR and other registers for this PMU below. Writing to inaccessible secure hardware from EL1 typically triggers a Synchronous External Abort (SError), resulting in an immediate kernel panic.

Quoted 5 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +5 -0
+			num = 1 + FIELD_GET(PMCFGR_N, reg);
+		}
+		if (num > PMU_MAX_COUNTERS) {
+			dev_notice(dev, "PMU %d has %d counters, only using %d\n",
+				   i, num, PMU_MAX_COUNTERS);

drivers/perf/arm_cspmu/arm-bmu.c line 377

Quoted 10 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +10 -0
+			num = PMU_MAX_COUNTERS;
+		}
+		bmu->imus[i].base = base;
+		bmu->imus[i].num_counters = num;
+
+		writel_relaxed(PMCR_P | PMCR_E, base + BMU_PMCR);
+		writel_relaxed(U32_MAX, base + PMCNTENCLR);
+		writel_relaxed(U32_MAX, base + PMOVSCLR);
+		writel_relaxed(U32_MAX, base + PMINTENSET);
+	}

[Severity: High]
Are hardware interrupts left enabled if an error occurs later in the probe function?

If cpuhp_state_add_instance_nocalls() or perf_pmu_register() fails, the error path exits without clearing PMINTENCLR. If the hardware asserts the IRQ line, the kernel would receive an interrupt without a registered handler, leading to an unhandled interrupt storm.

drivers/perf/arm_cspmu/arm-bmu.c line 379

Quoted 2 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +2 -0
+
+	bmu->cpu = cpumask_local_spread(atomic_fetch_inc(&n), dev_to_node(dev));

[Severity: Medium]
Is there a race in assigning the target CPU without holding the CPU hotplug read lock?

If the selected CPU goes offline before the hotplug registration, the arm_bmu_offline_cpu callback will not be invoked retroactively because cpuhp_state_add_instance_nocalls() is used below. Could this leave the PMU bound to an offline CPU, rendering it non-functional?

drivers/perf/arm_cspmu/arm-bmu.c line 391

Quoted 12 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +12 -0
+	bmu->irq = platform_get_irq(pdev, 0);
+	if (bmu->irq > 0) {
+		err = devm_request_irq(dev, bmu->irq, arm_bmu_handle_irq,
+				       IRQF_NOBALANCING | IRQF_NO_THREAD,
+				       dev_name(dev), bmu);
+		if (err)
+			bmu->irq = err;
+		else
+			irq_set_affinity(bmu->irq, cpumask_of(bmu->cpu));
+	}
+	if (bmu->irq < 0)
+		dev_info(dev, "Continuing without IRQ\n");

[Severity: Medium]
Does this completely ignore -EPROBE_DEFER from platform_get_irq()?

If platform_get_irq() returns -EPROBE_DEFER, the probe simply succeeds and falls back to polling mode. This permanently breaks deferred probing for the interrupt, preventing the driver from retrying when the interrupt controller is ready.

Quoted 8 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +8 -0
+
+	bmu->pmu = (struct pmu) {
+		.module = THIS_MODULE,
+		.parent = dev,
+		.attr_groups = arm_bmu_attr_groups,
+		.capabilities = PERF_PMU_CAP_NO_EXCLUDE,
+		.task_ctx_nr = perf_invalid_context,
+		.pmu_enable = arm_bmu_enable,

drivers/perf/arm_cspmu/arm-bmu.c line 409

Quoted 10 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +10 -0
+		.pmu_disable = arm_bmu_disable,
+		.event_init = arm_bmu_event_init,
+		.add = arm_bmu_event_add,
+		.del = arm_bmu_event_del,
+		.start = arm_bmu_event_start,
+		.stop = arm_bmu_event_stop,
+		.read = arm_bmu_event_read,
+	};
+
+	name = acpi_device_uid(ACPI_COMPANION(dev));

[Severity: High]
Will this cause a NULL pointer dereference if the device has no ACPI companion or if CONFIG_ACPI is disabled?

ACPI_COMPANION() returns NULL in these cases, and acpi_device_uid() directly dereferences it. This would lead to a kernel panic during driver probe on such configurations.

drivers/perf/arm_cspmu/arm-bmu.c line 414

Quoted 5 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +5 -0
+
+	if (name)
+		name = devm_kasprintf(dev, GFP_KERNEL, "arm_bmu_%s", name);
+	else
+		name = devm_kasprintf(dev, GFP_KERNEL, "arm_bmu_%llx", res->start >> 12);

[Severity: Low]
Is there a format string type mismatch here?

When CONFIG_PHYS_ADDR_T_64BIT is not enabled, resource_size_t is 32-bit. Passing a 32-bit integer to a %llx specifier causes undefined behavior, which can read garbage from the stack for the upper 32 bits.

Quoted 13 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +13 -0
+	if (!name)
+		return -ENOMEM;
+
+	err = cpuhp_state_add_instance_nocalls(arm_bmu_cpuhp_state, &bmu->cpuhp_node);
+	if (err)
+		return err;
+
+	err = perf_pmu_register(&bmu->pmu, name, -1);
+	if (err)
+		cpuhp_state_remove_instance_nocalls(arm_bmu_cpuhp_state, &bmu->cpuhp_node);
+
+	return err;
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
#3 lore raw

PATCH v2 2/3 dt-bindings: perf: Add Arm Bus Monitor Unit

In reply to Robin Murphy

Arm's Bus Monitor Unit is a low-level performance analysis tool for matching and counting transactions at interconnect interfaces. Typical platforms will have multiple BMUs each measuring multiple interfaces in various parts of the system, hence for the sake of usability we make it mandatory for the platform to label each instance such that the end user can correlate with platform documentation to know what's what.

---
v2: Invent example

---
 .../bindings/perf/arm,bus-monitor-unit.yaml   | 41 +++++++++++++++++++
 1 file changed, 41 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/perf/arm,bus-monitor-unit.yaml
Documentation/devicetree/bindings/perf/arm,bus-monitor-unit.yaml new file +41 -0
new file mode 100644
index 000000000000..3da59e5447e0
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/perf/arm,bus-monitor-unit.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Arm Bus Monitor Unit
+
+maintainers:
+  - Robin Murphy <[email protected]>
+
+properties:
+  compatible:
+    const: arm,bus-monitor-unit
+
+  reg:
+    items:
+      - description: Full BMU register space
+
+  interrupts:
+    items:
+      - description: PMU overflow interrupt
+
+  label:
+    description: BMU instance identifier (same as ACPI _UID), per the
+      platform optimisation guide
+
+required:
+  - compatible
+  - reg
+  - label
+
+additionalProperties: false
+
+examples:
+  - |
+    pmu@12340000 {
+        compatible = "arm,bus-monitor-unit";
+        reg = <0x12340000 0x40000>;
+        label = "ddr_ch_1_2";
+    };
Signature · 1 line
2.54.0.dirty
#4 lore raw

Re: [PATCH v2 2/3] dt-bindings: perf: Add Arm Bus Monitor Unit

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] The driver calculates an offset from hardware configuration registers and accesses memory within…

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The driver calculates an offset from hardware configuration registers and accesses memory within an ioremapped region without bounds checking, which can lead to out-of-bounds MMIO accesses.
- [High] The CPU hotplug instance is added before PMU registration completes, exposing a race condition where a CPU hotplug event will cause a NULL pointer dereference.
- [Medium] A pending overflow interrupt will trigger a spurious WARN_ON when an event is deleted because the hardware interrupt flag is not cleared.
--

--- Patch [2]: [PATCH v2 2/3] dt-bindings: perf: Add Arm Bus Monitor Unit --- commit 46bae7b04d539138ea677d6d9ef5697dcb457c20
Author: Robin Murphy <[email protected]>

perf: Add Arm Bus Monitor Unit driver

This commit introduces the Arm Bus Monitor Unit (BMU) driver, which provides a low-level performance analysis tool for matching and counting transactions at interconnect interfaces. It supports multiple Interface Monitoring Units (IMUs) by exposing them as perf PMU instances.

Quoted 5 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +0 -0
new file mode 100644
index 0000000000000..c829a0728e9b2

[ ... ]

drivers/perf/arm_cspmu/arm-bmu.c

Quoted 7 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +7 -0
+static void arm_bmu_event_del(struct perf_event *event, int flags)
+{
+	struct arm_bmu_pmu *pmu = to_bmu_pmu(event);
+
+	arm_bmu_event_stop(event, PERF_EF_UPDATE);
+	pmu->evcnt[event->hw.idx] = NULL;
+}

[Severity: Medium]
Will an unhandled pending overflow interrupt trigger a spurious warning here?

If a performance counter overflows right before or during arm_bmu_event_del(), local interrupts are disabled by the perf core, leaving the interrupt pending. Since arm_bmu_event_del() stops the counter and sets the evcnt pointer to NULL but doesn't clear the pending overflow interrupt from the PMOVSCLR register, this pending interrupt will be processed when interrupts are re-enabled.

This leads to arm_bmu_handle_irq() executing and reading the uncleared bit from PMOVSCLR, which evaluates to true in the following WARN_ON():

[ ... ]

Quoted 10 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +10 -0
+static void arm_bmu_pmu_irq(struct arm_bmu_pmu *pmu)
+{
+	u32 reg = readl_relaxed(pmu->base + PMOVSCLR);
+	u64 __iomem *pmevcnt = pmu->base + PMEVCNTR_LO;
+
+	for (int i = 0; i < PMU_MAX_COUNTERS; i++) {
+		if (!(reg & (1U << i)))
+			continue;
+		if (WARN_ON(!pmu->evcnt[i]))
+			continue;

[ ... ]

Quoted 5 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +5 -0
+static int arm_bmu_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const struct resource *res;
+	struct arm_bmu *bmu;

[ ... ]

Quoted 8 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +8 -0
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -EINVAL;
+
+	/* PMUs and MPAM monitors are intermingled so we can't claim the whole resource */
+	base = devm_ioremap(dev, res->start, resource_size(res));
+	if (!base)
+		return -ENOMEM;

[ ... ]

drivers/perf/arm_cspmu/arm-bmu.c

Quoted 11 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +11 -0
+	cfg = lo_hi_readq_relaxed(base + MCU_CONFIG);
+	num = 1 + FIELD_GET(MCUCFG_NUM_IMU_MONITORS, cfg);
+	/* We don't expect to have dual-page complications to worry about */
+	sz = FIELD_GET(MCUCFG_PMU_ELEMENT_SIZE, cfg);
+	if (sz != 1)
+		return dev_err_probe(dev, -EINVAL, "PMU_ELEMENT_SIZE 0x%x not supported\n", sz);
+
+	/* The PMU pages *are* exclusively ours */
+	off = SZ_4K * FIELD_GET(MCUCFG_PMU_ELEMENT_START, cfg);
+	if (!devm_request_mem_region(dev, res->start + off, num * SZ_4K, dev_name(dev)))
+		return dev_err_probe(dev, -EADDRINUSE, "Unable to request PMU region\n");

[Severity: High]
Does this calculation permit an out-of-bounds MMIO access if the offset from the hardware configuration register is unexpectedly large?

The off and num values are read from the MCU_CONFIG hardware register and used to calculate an offset into an ioremapped memory region that was mapped to resource_size(res). There does not appear to be a check verifying that off + num * SZ_4K does not exceed resource_size(res).

If the hardware registers provide an unexpectedly large offset, accessing memory outside the ioremapped bounds will trigger a synchronous external abort when the driver attempts to read the PMCFGR register below:

Quoted 14 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +14 -0
+
+	bmu = devm_kzalloc(dev, struct_size(bmu, imus, num), GFP_KERNEL);
+	if (!bmu)
+		return -ENOMEM;
+
+	bmu->dev = dev;
+	bmu->base = base;
+	bmu->num_imus = num;
+	platform_set_drvdata(pdev, bmu);
+
+	base += off;
+	for (int i = 0; i < bmu->num_imus; i++, base += SZ_4K) {
+		/* At least PMCFGR.SIZE should always be nonzero if visible */
+		reg = readl_relaxed(base + PMCFGR);

[ ... ]

drivers/perf/arm_cspmu/arm-bmu.c

Quoted 5 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +5 -0
+	err = cpuhp_state_add_instance_nocalls(arm_bmu_cpuhp_state, &bmu->cpuhp_node);
+	if (err)
+		return err;
+
+	err = perf_pmu_register(&bmu->pmu, name, -1);

[Severity: High]
Could a CPU hotplug event during this narrow window cause a NULL pointer dereference?

The cpuhp_state_add_instance_nocalls() function is invoked before perf_pmu_register(). If the assigned CPU goes offline during this exact window, the hotplug state machine invokes arm_bmu_offline_cpu() which in turn calls arm_bmu_migrate().

This leads to perf_pmu_migrate_context() being called. Because perf_pmu_register() has not yet executed, the core context pointer is still NULL, resulting in a dereference of an invalid pointer at the start of the percpu offset region.

Quoted 5 lines
drivers/perf/arm_cspmu/arm-bmu.c new file +5 -0
+	if (err)
+		cpuhp_state_remove_instance_nocalls(arm_bmu_cpuhp_state, &bmu->cpuhp_node);
+
+	return err;
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Reviewed-by on v2 2/3
#5 lore raw

Re: [PATCH v2 2/3] dt-bindings: perf: Add Arm Bus Monitor Unit

In reply to Robin Murphy
Quoted 11 lines · Robin Murphy

Arm's Bus Monitor Unit is a low-level performance analysis tool for matching and counting transactions at interconnect interfaces. Typical platforms will have multiple BMUs each measuring multiple interfaces in various parts of the system, hence for the sake of usability we make it mandatory for the platform to label each instance such that the end user can correlate with platform documentation to know what's what.

---
v2: Invent example

Best regards,
Krzysztof

#6 lore raw

PATCH v2 3/3 perf/arm-bmu: Add Devicetree support

In reply to Robin Murphy

Initial production BMU users will be ACPI-based, but DT consumers will almost certainly come along sooner or later.

---
v2: Separate patch as it's not a critical dependency right now

---
 drivers/perf/arm_cspmu/Kconfig   |  2 +-
 drivers/perf/arm_cspmu/arm-bmu.c | 15 ++++++++++++++-
 2 files changed, 15 insertions(+), 2 deletions(-)
drivers/perf/arm_cspmu/Kconfig +1 -1
index 0cb369f91023..8a7e2082b850 100644
@@ -30,6 +30,6 @@ config AMPERE_CORESIGHT_PMU_ARCH_SYSTEM_PMU
 
 config ARM_BMU
 	tristate "ARM Bus Monitor Unit"
-	depends on (ARM64 && ACPI) || COMPILE_TEST
+	depends on ARM64 || COMPILE_TEST
 	help
 	  Support perf event counting on Arm Bus Monitor Unit devices.
drivers/perf/arm_cspmu/arm-bmu.c +14 -1
index c829a0728e9b..0c2442392f7b 100644
@@ -6,6 +6,7 @@
 #include <linux/interrupt.h>
 #include <linux/io-64-nonatomic-lo-hi.h>
 #include <linux/module.h>
+#include <linux/of.h>
 #include <linux/perf_event.h>
 #include <linux/platform_device.h>
 #include <linux/property.h>
@@ -406,7 +407,10 @@ static int arm_bmu_probe(struct platform_device *pdev)
 		.read = arm_bmu_event_read,
 	};
 
-	name = acpi_device_uid(ACPI_COMPANION(dev));
+	if (has_acpi_companion(dev))
+		name = acpi_device_uid(ACPI_COMPANION(dev));
+	else
+		device_property_read_string(dev, "label", &name);
 
 	if (name)
 		name = devm_kasprintf(dev, GFP_KERNEL, "arm_bmu_%s", name);
@@ -437,6 +441,14 @@ static void arm_bmu_remove(struct platform_device *pdev)
 	cpuhp_state_remove_instance_nocalls(arm_bmu_cpuhp_state, &bmu->cpuhp_node);
 }
 
+#ifdef CONFIG_OF
+static const struct of_device_id arm_bmu_of_match[] = {
+	{ .compatible = "arm,bus-monitor-unit" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, arm_bmu_of_match);
+#endif
+
 #ifdef CONFIG_ACPI
 static const struct acpi_device_id arm_bmu_acpi_match[] = {
 	{ "ARMHB001" },
@@ -448,6 +460,7 @@ MODULE_DEVICE_TABLE(acpi, arm_bmu_acpi_match);
 static struct platform_driver arm_bmu_driver = {
 	.driver = {
 		.name = "arm-bmu",
+		.of_match_table = of_match_ptr(arm_bmu_of_match),
 		.acpi_match_table = ACPI_PTR(arm_bmu_acpi_match),
 		.suppress_bind_attrs = true,
 	},
Signature · 1 line
2.54.0.dirty
#7 lore raw

Re: [PATCH v2 3/3] perf/arm-bmu: Add Devicetree support

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] Implicit declaration of function 'acpi_device_uid' causes a build failure when CONFIG_ACPI is…

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Implicit declaration of function 'acpi_device_uid' causes a build failure when CONFIG_ACPI is disabled. --

--- Patch [3]: [PATCH v2 3/3] perf/arm-bmu: Add Devicetree support --- commit b787d1765a1d6aaa8fa04e23d64224ec5f6d4dc3
Author: Robin Murphy <[email protected]>

perf/arm-bmu: Add Devicetree support

This commit introduces Devicetree support for the ARM Bus Monitor Unit. It adds the necessary OF match tables and relaxes the Kconfig dependencies so the driver is no longer strictly tied to ACPI.

Quoted 10 lines
drivers/perf/arm_cspmu/Kconfig +1 -1
@@ -30,6 +30,6 @@ config AMPERE_CORESIGHT_PMU_ARCH_SYSTEM_PMU
 
 config ARM_BMU
 	tristate "ARM Bus Monitor Unit"
-	depends on (ARM64 && ACPI) || COMPILE_TEST
+	depends on ARM64 || COMPILE_TEST
 	help

[ ... ]

Quoted 3 lines
drivers/perf/arm_cspmu/arm-bmu.c +0 -0

[ ... ]

drivers/perf/arm_cspmu/arm-bmu.c static int arm_bmu_probe(struct platform_device *pdev) · line 411

Quoted 7 lines
drivers/perf/arm_cspmu/arm-bmu.c +2 -1
@@ -406,7 +407,10 @@ static int arm_bmu_probe(struct platform_device *pdev)
 		.read = arm_bmu_event_read,
 	};
 
-	name = acpi_device_uid(ACPI_COMPANION(dev));
+	if (has_acpi_companion(dev))
+		name = acpi_device_uid(ACPI_COMPANION(dev));

[Severity: Low]
Does this cause a build failure in arm_bmu_probe() when CONFIG_ACPI is disabled?

Since the strict ACPI dependency was removed in Kconfig, building for standard ARM64 without ACPI is now possible. It appears acpi_device_uid() might not be stubbed out or available when CONFIG_ACPI is turned off, leading to an implicit function declaration error during compilation.

Quoted 4 lines
drivers/perf/arm_cspmu/arm-bmu.c +2 -0
+	else
+		device_property_read_string(dev, "label", &name);
 
 	if (name)
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3