All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC 0/3] powerpc/perf: Add Device Tree based PMU description framework
@ 2026-06-29  6:10 Shivani Nittor
  2026-06-29  6:10 ` [RFC 1/3] powerpc/perf: Register PMU from device tree and expose events Shivani Nittor
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Shivani Nittor @ 2026-06-29  6:10 UTC (permalink / raw)
  To: maddy, linuxppc-dev; +Cc: linux-kernel, atrajeev, shivani, tshah

PowerPC has a Performance Monitoring Unit (PMU) infrastructure which provides
comprehensive performance monitoring capabilities at the processor level. The
PMU includes programmable counters (PMCs) and control registers (MMCRs) that
enable detailed performance analysis and profiling.

The PowerPC PMU consists of 6 Performance Monitor Counters (PMCs), where PMC1-4
are fully programmable and can monitor any supported event, while PMC5 and PMC6
are dedicated to counting instructions and cycles respectively. The PMU is
controlled through 5 Monitor Mode Control Registers (MMCRs) that configure
event selection, sampling modes, thresholds, and other monitoring parameters.

This patchset enables the PowerPC PMU by providing a device tree specification
that describes the PMU hardware capabilities, event encoding format, register
mappings, and event constraints. The device tree approach allows firmware
(OPAL/skiboot) to communicate PMU capabilities to the kernel dynamically,
enabling better hardware abstraction and forward compatibility.

PMU Hardware Components:
The PowerPC PMU provides the following hardware components:
- 6 Performance Monitor Counters (PMC1-PMC6)
 * PMC1-4: Programmable counters (32-bit, any event)
 * PMC5: Fixed counter for instructions (32-bit)
 * PMC6: Fixed counter for cycles (32-bit)
- 5 Monitor Mode Control Registers
 * MMCR0 (SPR 795): Primary control register
 * MMCR1 (SPR 798): Event selection and configuration
 * MMCR2 (SPR 785): Extended event configuration
 * MMCR3 (SPR 754): Additional event source selection
 * MMCRA (SPR 0x312): Sampling and marking control

Event Encoding Format:
The device tree describes how 64-bit event codes are decomposed into fields
that map to specific bits in the MMCR registers. Key encoding fields include:
- PMCxSEL: 8-bit event selector (256 events per PMC)
- PMCxUNIT: 4-bit unit selector for event source
- PMCxCOMB: 2-bit combine mode for multi-counter operations
- THRESH_SEL/START/STOP: Threshold-based event filtering
- SDAR_MODE: Data address sampling configuration
- MARK: Instruction marking for sampling
- L2L3_SELECT: Cache event selection
- IFM: Instruction fetch marking mode

Event Constraints:
The PMU enforces several hardware constraints that are described in the DTS:
- Counter restrictions: PMC5 limited to instructions (0x500fa), PMC6 limited
 to cycles (0x600f4)
- Sampling constraints: Events with sampling require specific bit patterns
- Threshold constraints: Threshold events require coordinated MMCR field values
- Cache constraints: Cache events restricted to specific units (6,7,8,9) and
 require PMC4 for certain operations
- EBB (Event-Based Branching) constraints: EBB events require PMC assignment
- BHRB (Branch History Rolling Buffer) constraints: BHRB requires EBB enabled
- L1 qualifier constraints: L1 cache qualifiers for load/store filtering
- Radix scope constraints: Radix page table scope qualification

PMU Events Information:
The device tree includes definitions for common performance events such as:
- Core events: cycles, instructions, dispatch/execution stalls
- Branch events: branches, branch-misses, branch predictions
- Cache events: L1/L2/L3 hits and misses for data and instructions
- TLB events: DTLB and ITLB misses
- Memory events: load/store operations and cache reloads

The kernel discovers the PMU configuration in the device tree at the "pmus"
device node which contains a "pmu_dts@0" child node with compatible field
"ibm,power-pmu".

Parsing of the PMU Information:
To parse the PMU configuration, the kernel discovers the "pmus" node and
walks through the PMU definition, extracting:
- Hardware capabilities (number of PMCs, MMCRs)
- SPR (Special Purpose Register) definitions
- Event code format and field mappings
- MMCR bit field configurations
- Event constraints and restrictions
- Pre-defined event list with codes and descriptions

Here is an excerpt of the DTS showing the PMU node structure:
pmus {
	#address-cells = <1>;
	#size-cells = <0>;

	pmu_dts@0 {
		compatible = "ibm,power-pmu";
		reg = <0>;
		pmu-name = "POWER10 PMU";
		pmu-version = "PowerISA 3.1";
		platform = "power10";
		status = "okay";
		nr_pmc = <6>;
		nr_mmcr = <5>;

		sprs {
			pmcs {
				pmc1 {
					sprn = <787>;
					register-width = <32>;
					privilege = "hv";
					programmable = <1>;
					event = "any";
					status = "okay";
				};
				[...]
			};
			mmcr {
				mmcr0 {
					sprn = <795>;
					register-width = <64>;
					privilege = "hv";
					status = "okay";
				};
				[...]
			};
		};

		evt_code_format {
			compatible = "ibm,power-pmu";

			PMCxSEL {
				description = "PMC event selector (256 possible events per PMC)";
				bits = <0 7>;
				length = <8>;
				mmcr = <1>;
				target_field_base = <32>;
				target_field_shift = <8>;
			};
			[...]
		};

		constraints {
			pmc-constraints {
				max-counter = <6>;

				restricted-counters-5 {
					pmc = <5>;
					valid-events = <0x00000000 0x000500fa>;
				};

				restricted-counters-6 {
					pmc = <6>;
					valid-events = <0x00000000 0x000600f4>;
				};
			};
			[...]
		};

		events {
			cycles {
				event_code = <0x600f4>;
				event-category = "core";
				event-class = "primary";
				description = "Number of processor cycles";
				status = "okay";
			};
			instructions {
				event_code = <0x500fa>;
				event-category = "core";
				event-class = "primary";
				description = "Number of instructions completed";
				status = "okay";
			};
			[...]
		};
	};
};

From the device tree, the kernel parses the PMU hardware description, event
encoding format, constraints, and pre-defined events.

After parsing the PMU configuration, the PMU and its events are registered
with the kernel's perf subsystem, making them available for performance
monitoring and profiling.

This series depends on the corresponding skiboot patches:
https://github.com/maddy-kerneldev/skiboot/commit/783e33f17f13412b89b774124cef0988c0578829

Comments/feedback/suggestions are welcome.

Next Steps:
1) Add DTS support for peripheral and unit-specific PMU events.
2) Extend the DTS schema for additional MMCR registers and controls.
3) Support advanced PMU features such as BHRB.
4) Feedback from Community

Shivani Nittor (3):
  powerpc/perf: Register PMU from device tree and expose events
  powerpc/perf: Add DTS-based MMCR computation
  powerpc/perf: Add DTS-based event constraints

 arch/powerpc/include/asm/dts_pmu.h    |  82 ++++
 arch/powerpc/perf/Makefile            |   4 +-
 arch/powerpc/perf/core-book3s.c       |  12 +-
 arch/powerpc/perf/dts_pmu.c           | 536 ++++++++++++++++++++++++++
 arch/powerpc/perf/internal.h          |   1 +
 arch/powerpc/perf/isa207-common.c     | 262 +++++++++++++
 arch/powerpc/perf/isa207-common.h     |   4 +
 arch/powerpc/platforms/powernv/opal.c |  15 +
 8 files changed, 914 insertions(+), 2 deletions(-)
 create mode 100644 arch/powerpc/include/asm/dts_pmu.h
 create mode 100644 arch/powerpc/perf/dts_pmu.c

-- 
2.54.0


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

* [RFC 1/3] powerpc/perf: Register PMU from device tree and expose events
  2026-06-29  6:10 [RFC 0/3] powerpc/perf: Add Device Tree based PMU description framework Shivani Nittor
@ 2026-06-29  6:10 ` Shivani Nittor
  2026-06-29 18:47   ` Krzysztof Kozlowski
  2026-06-29  6:10 ` [RFC 2/3] powerpc/perf: Add DTS-based MMCR computation Shivani Nittor
  2026-06-29  6:10 ` [RFC 3/3] powerpc/perf: Add DTS-based event constraints Shivani Nittor
  2 siblings, 1 reply; 5+ messages in thread
From: Shivani Nittor @ 2026-06-29  6:10 UTC (permalink / raw)
  To: maddy, linuxppc-dev; +Cc: linux-kernel, atrajeev, shivani, tshah

Introduce a DTS-based PMU driver that discovers PMU information
from the device tree and registers a PowerPC PMU instance at
runtime.

The driver parses basic PMU properties such as the number of
counters and MMCR register definitions, creates sysfs event
entries from the device tree event descriptions, and registers
the PMU with the perf subsystem.

To support dynamic PMU registration, add PMU unregistration
support and create a platform device for PMU nodes described
in the device tree.

This forms the foundation for moving PMU descriptions out of
architecture-specific kernel code and into device tree data.

The driver implements the following functionality:

Device Tree Parsing:
- Reads PMU properties including counter count (nr_pmc), PMU name,
  version, and platform information
- Parses MMCR (Monitor Mode Control Register) definitions from the
  sprs/mmcr node hierarchy, storing the MMCR register SPRNs
- Extracts event definitions from the events node, supporting both
  32-bit and 64-bit event codes

Dynamic Event Registration:
- Creates sysfs event entries dynamically from device tree event
  descriptions
- Generates event attributes with format "event=0x<code>" for each
  discovered event
- Exposes events under /sys/devices/<pmu-name>/events/

PMU Registration:
- Registers a power_pmu instance with the perf subsystem using the
  device tree-provided PMU name
- Adds PMU unregistration support to enable proper cleanup
- Creates platform device for "ibm,power-pmu" compatible nodes

Sysfs Interface:
- Provides format attributes (event, pmcsel) under
  /sys/devices/<pmu-name>/format/
- Exposes device tree debug information (nr_pmc) under
  /sys/devices/<pmu-name>/dt/

Platform Integration:
- Adds opal_pmus_init_dev() to create platform devices for PMU nodes
  during OPAL initialization on PowerNV systems
- Uses platform driver model with probe/remove callbacks

With the device tree based PMU registered:
 # ls /sys/devices/cpu_dts/events/

 # Snippet of "perf list" :

  cpu_dts/branch-finished/                               [Kernel PMU event]
  cpu_dts/branch-mispredict-finished/                    [Kernel PMU event]
  cpu_dts/branch-misses/                                 [Kernel PMU event]
  cpu_dts/branches/                                      [Kernel PMU event]
  cpu_dts/cycles/                                        [Kernel PMU event]

Signed-off-by: Shivani Nittor <shivani@linux.ibm.com>
---
 arch/powerpc/include/asm/dts_pmu.h    |  12 ++
 arch/powerpc/perf/Makefile            |   3 +-
 arch/powerpc/perf/core-book3s.c       |  12 +-
 arch/powerpc/perf/dts_pmu.c           | 257 ++++++++++++++++++++++++++
 arch/powerpc/perf/internal.h          |   1 +
 arch/powerpc/perf/isa207-common.c     |   1 +
 arch/powerpc/platforms/powernv/opal.c |  15 ++
 7 files changed, 299 insertions(+), 2 deletions(-)
 create mode 100644 arch/powerpc/include/asm/dts_pmu.h
 create mode 100644 arch/powerpc/perf/dts_pmu.c

diff --git a/arch/powerpc/include/asm/dts_pmu.h b/arch/powerpc/include/asm/dts_pmu.h
new file mode 100644
index 000000000000..791dda370de8
--- /dev/null
+++ b/arch/powerpc/include/asm/dts_pmu.h
@@ -0,0 +1,12 @@
+#ifndef _ASM_DTS_PMU_H
+#define _ASM_DTS_PMU_H
+
+#include <linux/types.h>
+
+#define MAX_MMCR   5
+#define MAX_DTS_EVENTS 32
+#define MAX_PMU_COUNTERS 6
+
+extern u32 mmcr_regs_sprs[MAX_MMCR];
+extern int mmcr_count;
+#endif
diff --git a/arch/powerpc/perf/Makefile b/arch/powerpc/perf/Makefile
index 78dd7e25219e..537f4b87cffe 100644
--- a/arch/powerpc/perf/Makefile
+++ b/arch/powerpc/perf/Makefile
@@ -7,7 +7,8 @@ obj-$(CONFIG_PPC_PERF_CTRS)	+= core-book3s.o
 obj64-$(CONFIG_PPC_PERF_CTRS)	+= ppc970-pmu.o power5-pmu.o \
 				   power5+-pmu.o power6-pmu.o power7-pmu.o \
 				   isa207-common.o power8-pmu.o power9-pmu.o \
-				   generic-compat-pmu.o power10-pmu.o bhrb.o
+				   generic-compat-pmu.o power10-pmu.o bhrb.o \
+				   dts_pmu.o
 obj32-$(CONFIG_PPC_PERF_CTRS)	+= mpc7450-pmu.o
 
 obj-$(CONFIG_PPC_POWERNV)	+= imc-pmu.o
diff --git a/arch/powerpc/perf/core-book3s.c b/arch/powerpc/perf/core-book3s.c
index 8b0081441f85..f33ed1423046 100644
--- a/arch/powerpc/perf/core-book3s.c
+++ b/arch/powerpc/perf/core-book3s.c
@@ -2569,12 +2569,19 @@ int __init register_power_pmu(struct power_pmu *pmu)
 		freeze_events_kernel = MMCR0_FCHV;
 #endif /* CONFIG_PPC64 */
 
-	perf_pmu_register(&power_pmu, "cpu", PERF_TYPE_RAW);
+	perf_pmu_register(&power_pmu, pmu->name, PERF_TYPE_RAW);
 	cpuhp_setup_state(CPUHP_PERF_POWER, "perf/powerpc:prepare",
 			  power_pmu_prepare_cpu, NULL);
 	return 0;
 }
 
+void unregister_power_pmu(struct power_pmu *pmu)
+{
+	if (!ppmu)
+		return;
+
+	perf_pmu_unregister(&power_pmu);
+}
 #ifdef CONFIG_PPC64
 static bool pmu_override = false;
 static unsigned long pmu_override_val;
@@ -2588,6 +2595,9 @@ static void do_pmu_override(void *data)
 
 static int __init init_ppc64_pmu(void)
 {
+	/* Disable registering PMU from here and enable picking from device tree */
+	return 0;
+
 	if (cpu_has_feature(CPU_FTR_HVMODE) && pmu_override) {
 		pr_warn("disabling perf due to pmu_override= command line option.\n");
 		on_each_cpu(do_pmu_override, NULL, 1);
diff --git a/arch/powerpc/perf/dts_pmu.c b/arch/powerpc/perf/dts_pmu.c
new file mode 100644
index 000000000000..67eabd5ec6e5
--- /dev/null
+++ b/arch/powerpc/perf/dts_pmu.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/module.h>
+#include <linux/perf_event.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <asm/reg.h>
+#include <asm/dts_pmu.h>
+
+
+extern void unregister_power_pmu(struct power_pmu *pmu);
+static u32 pmu_dts_nr_pmc;
+
+u32 mmcr_regs_sprs[MAX_MMCR];
+int mmcr_count;
+
+struct pmu_dts_event {
+	struct device_attribute attr;
+	char name[32];
+	char config[32];
+};
+
+static struct pmu_dts_event *dts_events[MAX_DTS_EVENTS];
+static struct attribute *pmu_dts_events_attrs[MAX_DTS_EVENTS + 1];
+static int dts_event_count;
+
+static ssize_t pmu_dts_event_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct pmu_dts_event *evt =
+			container_of(attr, struct pmu_dts_event, attr);
+
+	return sprintf(buf, "%s\n", evt->config);
+}
+
+/* Format attributes */
+PMU_FORMAT_ATTR(event, "config:0-59");
+PMU_FORMAT_ATTR(pmcsel, "config1:18-25");
+
+static struct attribute *pmu_dts_format_attrs[] = {
+	&format_attr_event.attr,
+	&format_attr_pmcsel.attr,
+	NULL,
+};
+
+static struct attribute_group pmu_dts_format_group = {
+	.name = "format",
+	.attrs = pmu_dts_format_attrs,
+};
+
+static ssize_t nr_pmc_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	return sprintf(buf, "%u\n", pmu_dts_nr_pmc);
+}
+
+static DEVICE_ATTR_RO(nr_pmc);
+
+static struct attribute *pmu_dts_dt_attrs[] = {
+	&dev_attr_nr_pmc.attr,
+	NULL,
+};
+
+static struct attribute_group pmu_dts_dt_group = {
+	.name = "dt",
+	.attrs = pmu_dts_dt_attrs,
+};
+
+static struct attribute_group pmu_dts_events_group = {
+	.name = "events",
+	.attrs = pmu_dts_events_attrs,
+};
+
+static const struct attribute_group *pmu_dts_attr_groups[] = {
+	&pmu_dts_events_group,
+	&pmu_dts_format_group,
+	&pmu_dts_dt_group,
+	NULL,
+};
+
+static struct power_pmu dts_pmu = {
+	.name           = "cpu_dts",
+	.n_counter      = MAX_PMU_COUNTERS,
+	.attr_groups    = pmu_dts_attr_groups,
+};
+
+/* Device Tree match */
+static const struct of_device_id pmu_dts_of_match[] = {
+	{ .compatible = "ibm,power-pmu" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, pmu_dts_of_match);
+
+/* Probe function */
+static int pmu_dts_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node, *child, *events_np;
+	struct device_node *sprs_np, *mmcr_np, *mmcr_child;
+	struct pmu_dts_event *evt;
+	u64 code;
+	u32 code32, sprn;
+	u32 code64[2];
+	u32 code128[4];
+	int cells;
+	const char *str;
+
+	pr_info("PMU DTS probe node = %s\n", np->full_name);
+
+	if (!of_property_present(np, "nr_pmc")) {
+
+		for_each_child_of_node(np, child) {
+			pr_info("child node = %s\n", child->full_name);
+
+			if (of_property_present(child, "nr_pmc")) {
+				np = child;
+				break;
+			}
+		}
+	}
+
+	if (of_property_read_u32(np, "nr_pmc", &pmu_dts_nr_pmc)) {
+		pr_err("pmu_dts: nr_pmc not found in %s\n", np->full_name);
+		return -EINVAL;
+	}
+
+	if (!of_property_read_string(np, "pmu-name", &str))
+		pr_info("PMU Name: %s\n", str);
+
+	if (!of_property_read_string(np, "pmu-version", &str))
+		pr_info("PMU Version: %s\n", str);
+
+	if (!of_property_read_string(np, "platform", &str))
+		pr_info("Platform: %s\n", str);
+
+	sprs_np = of_get_child_by_name(np, "sprs");
+	if (!sprs_np) {
+		pr_err("pmu_dts: no sprs node\n");
+		return -EINVAL;
+	}
+
+	mmcr_np = of_get_child_by_name(sprs_np, "mmcr");
+	if (!mmcr_np) {
+		pr_err("pmu_dts: no mmcr node\n");
+		return -EINVAL;
+	}
+
+	mmcr_count = 0;
+	for_each_child_of_node(mmcr_np, mmcr_child) {
+
+		if (of_property_read_u32(mmcr_child, "sprn", &sprn))
+			continue;
+
+		mmcr_regs_sprs[mmcr_count++] = sprn;
+		pr_info("pmu_dts: MMCR[%d] = %u (%s)\n", mmcr_count - 1,
+				sprn, mmcr_child->name);
+
+		if (mmcr_count >= MAX_MMCR)
+			break;
+	}
+
+	if (!mmcr_count) {
+		pr_err("pmu_dts: no MMCR SPRs found\n");
+		return -EINVAL;
+	}
+
+	/* Parse events */
+	events_np = of_get_child_by_name(np, "events");
+	if (!events_np) {
+		pr_err("pmu_dts: no events node found\n");
+		return -EINVAL;
+	}
+
+	dts_event_count = 0;
+	for_each_child_of_node(events_np, child) {
+		if (!of_device_is_available(child))
+			continue;
+
+		cells = of_property_count_u32_elems(child, "event_code");
+		if (cells == 1) {
+			if (of_property_read_u32(child, "event_code", &code32))
+				continue;
+
+			code = code32;
+
+		} else if (cells == 2) {
+			if (of_property_read_u32_array(child, "event_code", code64, 2))
+				continue;
+			code = ((u64)code64[0] << 32) | code64[1];
+
+		} else if (cells == 4) {
+			if (of_property_read_u32_array(child, "event_code", code128, 4))
+				continue;
+			code = ((u64)code128[1] << 32) | code128[3];
+
+		} else {
+			pr_warn("pmu_dts: invalid event_code for %s\n", child->name);
+			continue;
+		}
+
+		evt = kzalloc(sizeof(*evt), GFP_KERNEL);
+		if (!evt)
+			continue;
+
+		snprintf(evt->name, sizeof(evt->name), "%s", child->name);
+		snprintf(evt->config, sizeof(evt->config), "event=0x%llx", code);
+
+		sysfs_attr_init(&evt->attr.attr);
+		evt->attr.attr.name = evt->name;
+		evt->attr.attr.mode = 0444;
+		evt->attr.show = pmu_dts_event_show;
+		dts_events[dts_event_count] = evt;
+		pmu_dts_events_attrs[dts_event_count] = &evt->attr.attr;
+		dts_event_count++;
+
+		if (dts_event_count >= MAX_DTS_EVENTS)
+			break;
+	}
+	pmu_dts_events_attrs[dts_event_count] = NULL;
+
+	/* Register PMU */
+	pr_info("pmu_dts: registering PMU\n");
+	return register_power_pmu(&dts_pmu);
+}
+
+/* Platform driver */
+static struct platform_driver pmu_dts_driver = {
+	.probe = pmu_dts_probe,
+	.driver = {
+		.name = "pmu_dts",
+		.of_match_table = pmu_dts_of_match,
+	},
+};
+
+static int __init pmu_dts_init(void)
+{
+	pr_info("pmu_dts: init\n");
+	return platform_driver_register(&pmu_dts_driver);
+}
+
+static void __exit pmu_dts_exit(void)
+{
+	pr_info("pmu_dts: exit\n");
+	platform_driver_unregister(&pmu_dts_driver);
+	unregister_power_pmu(&dts_pmu);
+}
+module_init(pmu_dts_init);
+module_exit(pmu_dts_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Shivani Nittor");
+MODULE_DESCRIPTION("PMU DTS driver");
diff --git a/arch/powerpc/perf/internal.h b/arch/powerpc/perf/internal.h
index a70ac471a5a5..11154ee31f8e 100644
--- a/arch/powerpc/perf/internal.h
+++ b/arch/powerpc/perf/internal.h
@@ -2,6 +2,7 @@
 //
 // Copyright 2019 Madhavan Srinivasan, IBM Corporation.
 
+void unregister_power_pmu(struct power_pmu *pmu);
 int __init init_ppc970_pmu(void);
 int __init init_power5_pmu(void);
 int __init init_power5p_pmu(void);
diff --git a/arch/powerpc/perf/isa207-common.c b/arch/powerpc/perf/isa207-common.c
index 2b3547fdba4a..e11d1bbbc27b 100644
--- a/arch/powerpc/perf/isa207-common.c
+++ b/arch/powerpc/perf/isa207-common.c
@@ -7,6 +7,7 @@
  * Copyright 2016 Madhavan Srinivasan, IBM Corporation.
  */
 #include "isa207-common.h"
+#include <asm/dts_pmu.h>
 
 PMU_FORMAT_ATTR(event,		"config:0-49");
 PMU_FORMAT_ATTR(pmcxsel,	"config:0-7");
diff --git a/arch/powerpc/platforms/powernv/opal.c b/arch/powerpc/platforms/powernv/opal.c
index 1946dbdc9fa1..35be67eef2c6 100644
--- a/arch/powerpc/platforms/powernv/opal.c
+++ b/arch/powerpc/platforms/powernv/opal.c
@@ -947,6 +947,18 @@ static void __init opal_imc_init_dev(void)
 
 	of_node_put(np);
 }
+#define PMU_DTB "ibm,power-pmu"
+
+static void __init opal_pmus_init_dev(void)
+{
+	struct device_node *np;
+
+	np = of_find_compatible_node(NULL, NULL, PMU_DTB);
+	if (np)
+		of_platform_device_create(np, NULL, NULL);
+
+	of_node_put(np);
+}
 
 static int kopald(void *unused)
 {
@@ -1032,6 +1044,9 @@ static int __init opal_init(void)
 	/* Detect In-Memory Collection counters and create devices*/
 	opal_imc_init_dev();
 
+    /*Detect PMU node and create device*/
+	opal_pmus_init_dev();
+
 	/* Create leds platform devices */
 	leds = of_find_node_by_path("/ibm,opal/leds");
 	if (leds) {
-- 
2.54.0


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

* [RFC 2/3] powerpc/perf: Add DTS-based MMCR computation
  2026-06-29  6:10 [RFC 0/3] powerpc/perf: Add Device Tree based PMU description framework Shivani Nittor
  2026-06-29  6:10 ` [RFC 1/3] powerpc/perf: Register PMU from device tree and expose events Shivani Nittor
@ 2026-06-29  6:10 ` Shivani Nittor
  2026-06-29  6:10 ` [RFC 3/3] powerpc/perf: Add DTS-based event constraints Shivani Nittor
  2 siblings, 0 replies; 5+ messages in thread
From: Shivani Nittor @ 2026-06-29  6:10 UTC (permalink / raw)
  To: maddy, linuxppc-dev; +Cc: linux-kernel, atrajeev, shivani, tshah

Add support for computing MMCR register values from event
descriptions provided in the device tree.

Parse the evt_code_format node to build mappings between event
code fields and MMCR programming fields. Use these mappings to
generate MMCR register values dynamically from the event code.

This allows event encodings and MMCR programming information to
be described in the device tree rather than being hardcoded in
the PMU implementation.

Add compute_mmcr_dts() to isa207-common.c to compute MMCR register
values dynamically from event codes described in the device tree.

The function extracts PMC and field values from the event code using
field_maps[], which are populated by parsing the evt_code_format node
in the DTS during pmu_dts_probe(). Each field entry captures the bit
range in the event code, the target MMCR register, and the shift
position to use when programming the register.

For non-PMC, non-MMCRA fields, the shift is computed either using a
per-PMC target_field_shift formula (target_field_base - pmc *
target_field_shift) or a fixed pgm_start offset. MMCRA and PMC fields
always use the fixed pgm_start path.

MMCR2 privilege filtering (FCP, FCS, FCH) is applied per-event based
on perf_event attr exclude_* flags.

Wire up the DTS PMU to use dts_compute_mmcr() as its compute_mmcr
callback, and carry the ISA207 constraint fields, alternatives,
memory data source and weight helpers, and PPMU flags to match the
Power10 PMU profile.

Also temporarily comment out the duplicate compute_mmcr call site and
the surrounding mtspr block in power_pmu_enable() while the DTS path
is brought up; these will be restored once the transition is complete.

Signed-off-by: Shivani Nittor <shivani@linux.ibm.com>
---
 arch/powerpc/include/asm/dts_pmu.h |  17 ++++
 arch/powerpc/perf/dts_pmu.c        | 118 +++++++++++++++++++++++++++-
 arch/powerpc/perf/isa207-common.c  | 120 +++++++++++++++++++++++++++++
 arch/powerpc/perf/isa207-common.h  |   2 +
 4 files changed, 253 insertions(+), 4 deletions(-)

diff --git a/arch/powerpc/include/asm/dts_pmu.h b/arch/powerpc/include/asm/dts_pmu.h
index 791dda370de8..1309a45ce604 100644
--- a/arch/powerpc/include/asm/dts_pmu.h
+++ b/arch/powerpc/include/asm/dts_pmu.h
@@ -3,10 +3,27 @@
 
 #include <linux/types.h>
 
+#define MAX_FIELDS 17
 #define MAX_MMCR   5
 #define MAX_DTS_EVENTS 32
 #define MAX_PMU_COUNTERS 6
 
+struct dts_field_map {
+	u32 bits_start, bits_end;
+	u32 target_field_base, target_field_shift;
+	u32 pgm_start, mmcr;
+	bool use_target_field_shift, is_pmc;
+	char name[32];
+};
+
+extern struct dts_field_map field_maps[MAX_FIELDS];
+extern int field_count;
+
 extern u32 mmcr_regs_sprs[MAX_MMCR];
 extern int mmcr_count;
+
+int compute_mmcr_dts(u64 event[], int n_ev,
+			unsigned int hwc[], struct mmcr_regs *mmcr,
+			struct perf_event *pevents[], u32 flags);
+
 #endif
diff --git a/arch/powerpc/perf/dts_pmu.c b/arch/powerpc/perf/dts_pmu.c
index 67eabd5ec6e5..39107af6d467 100644
--- a/arch/powerpc/perf/dts_pmu.c
+++ b/arch/powerpc/perf/dts_pmu.c
@@ -10,10 +10,14 @@
 #include <linux/string.h>
 #include <asm/reg.h>
 #include <asm/dts_pmu.h>
-
+#include "isa207-common.h"
 
 extern void unregister_power_pmu(struct power_pmu *pmu);
 static u32 pmu_dts_nr_pmc;
+struct dts_field_map pmcsel_map;
+struct dts_field_map pmc_map;
+struct dts_field_map field_maps[MAX_FIELDS];
+int field_count;
 
 u32 mmcr_regs_sprs[MAX_MMCR];
 int mmcr_count;
@@ -84,10 +88,55 @@ static const struct attribute_group *pmu_dts_attr_groups[] = {
 	NULL,
 };
 
+static int dts_compute_mmcr(u64 event[], int n_ev,
+					unsigned int hwc[], struct mmcr_regs *mmcr,
+					struct perf_event *pevents[], u32 flags)
+{
+	int ret;
+
+	ret = compute_mmcr_dts(event, n_ev, hwc, mmcr, pevents, flags);
+	if (!ret)
+		mmcr->mmcr0 |= MMCR0_C56RUN;
+	return ret;
+}
+
+static const unsigned int dts_event_alternatives[][MAX_ALT] = {
+	{ 0x600f4, 0x1001e },
+};
+
+static int dts_get_alternatives(u64 event, unsigned int flags, u64 alt[])
+{
+	int num_alt = 0;
+
+	num_alt = isa207_get_alternatives(event, alt,
+					  ARRAY_SIZE(dts_event_alternatives), flags,
+					  dts_event_alternatives);
+
+	return num_alt;
+}
+
 static struct power_pmu dts_pmu = {
-	.name           = "cpu_dts",
-	.n_counter      = MAX_PMU_COUNTERS,
-	.attr_groups    = pmu_dts_attr_groups,
+	.name                   = "cpu_dts",
+	.n_counter              = MAX_PMU_COUNTERS,
+	.attr_groups            = pmu_dts_attr_groups,
+	.add_fields             = ISA207_ADD_FIELDS,
+	.test_adder             = ISA207_TEST_ADDER,
+	.group_constraint_mask  = CNST_CACHE_PMC4_MASK,
+	.group_constraint_val   = CNST_CACHE_PMC4_VAL,
+	.compute_mmcr           = dts_compute_mmcr,
+	// .config_bhrb         = power10_config_bhrb,
+	// .bhrb_filter_map     = power10_bhrb_filte-r_map,
+	.get_alternatives       = dts_get_alternatives,
+	.get_mem_data_src       = isa207_get_mem_data_src,
+	.get_mem_weight         = isa207_get_mem_weight,
+	.disable_pmc            = isa207_disable_pmc,
+	.flags                  = PPMU_HAS_SIER | PPMU_ARCH_207S |
+					PPMU_ARCH_31 | PPMU_HAS_ATTR_CONFIG1 |
+					PPMU_P10,
+	.attr_groups            = pmu_dts_attr_groups,
+	//.bhrb_nr              = 32,
+	.capabilities           = PERF_PMU_CAP_EXTENDED_REGS,
+	//.check_attr_config    = power10_check_attr_config,
 };
 
 /* Device Tree match */
@@ -108,6 +157,8 @@ static int pmu_dts_probe(struct platform_device *pdev)
 	u32 code64[2];
 	u32 code128[4];
 	int cells;
+	struct device_node *fmt_np, *field_np;
+	u32 bits[2], pgm[2];
 	const char *str;
 
 	pr_info("PMU DTS probe node = %s\n", np->full_name);
@@ -170,6 +221,65 @@ static int pmu_dts_probe(struct platform_device *pdev)
 	}
 
 	/* Parse events */
+	fmt_np = of_get_child_by_name(np, "evt_code_format");
+	if (!fmt_np) {
+		pr_err("pmu_dts: no evt_code_format node\n");
+		return -EINVAL;
+	}
+
+	field_count = 0;
+	for_each_child_of_node(fmt_np, field_np) {
+
+		struct dts_field_map *f = &field_maps[field_count];
+
+		printk("field_np->name is %s\n", field_np->name);
+		snprintf(f->name, sizeof(f->name), "%s", field_np->name);
+		f->is_pmc = false;
+		f->use_target_field_shift = false;
+
+		/* Identify PMC field */
+		if (!strcmp(field_np->name, "PMCx"))
+			f->is_pmc = true;
+
+		if (of_property_read_u32_array(field_np, "bits", bits, 2))
+			continue;
+
+		f->bits_start = bits[0];
+		f->bits_end   = bits[1];
+
+		if (of_property_read_u32(field_np, "mmcr", &f->mmcr))
+			continue;
+
+	/* Check target_field_shift-based mapping */
+		if (!f->is_pmc && f->mmcr != 4) {
+			if (!of_property_read_u32(field_np, "target_field_base",
+					&f->target_field_base)) {
+				of_property_read_u32(field_np, "target_field_shift",
+					&f->target_field_shift);
+				f->use_target_field_shift = true;
+
+			} else {
+				if (!of_property_read_u32_array(field_np, "target_fields", pgm, 2))
+					f->pgm_start = pgm[0];
+				else
+					f->pgm_start = 0;
+			}
+
+		} else {
+			/* MMCRA or PMC field → no target_field_shift */
+			if (!of_property_read_u32_array(field_np, "target_fields", pgm, 2))
+				f->pgm_start = pgm[0];
+			else
+				f->pgm_start = 0;
+
+			f->use_target_field_shift = false;
+		}
+
+		field_count++;
+		if (field_count >= MAX_FIELDS)
+			break;
+	}
+
 	events_np = of_get_child_by_name(np, "events");
 	if (!events_np) {
 		pr_err("pmu_dts: no events node found\n");
diff --git a/arch/powerpc/perf/isa207-common.c b/arch/powerpc/perf/isa207-common.c
index e11d1bbbc27b..f912432fd6db 100644
--- a/arch/powerpc/perf/isa207-common.c
+++ b/arch/powerpc/perf/isa207-common.c
@@ -80,6 +80,11 @@ static unsigned long sdar_mod_val(u64 event)
 	return p9_SDAR_MODE(event);
 }
 
+static u64 extract_bits(u64 val, int start, int end)
+{
+	return (val >> start) & ((1ULL << (end - start + 1)) - 1);
+}
+
 static void mmcra_sdar_mode(u64 event, unsigned long *mmcra)
 {
 	/*
@@ -743,6 +748,121 @@ int isa207_compute_mmcr(u64 event[], int n_ev,
 	return 0;
 }
 
+int compute_mmcr_dts(u64 event[], int n_ev,
+			unsigned int hwc[], struct mmcr_regs *mmcr,
+			struct perf_event *pevents[], u32 flags)
+{
+	u64 mmcr_val[MAX_MMCR] = {0};
+	u32 pmc = 0, pmc_inuse = 0;
+	int i, ev;
+	u32 pmc_arr[MAX_HWEVENTS] = {0};
+
+	for (ev = 0; ev < n_ev; ev++) {
+		pmc = (event[ev] >> EVENT_PMC_SHIFT) & EVENT_PMC_MASK;
+		pmc_arr[ev] = pmc;
+		if (pmc)
+			pmc_inuse |= 1 << pmc;
+
+		hwc[ev] = pmc - 1;
+	}
+
+	/*
+	 * Disable bhrb unless explicitly requested
+	 * by setting MMCRA (BHRBRD) bit.
+	 */
+	if (cpu_has_feature(CPU_FTR_ARCH_31))
+		mmcr->mmcra |= MMCRA_BHRB_DISABLE;
+
+	if (!pmc) {
+		for (pmc = 1; pmc <= 4; ++pmc) {
+			if (!(pmc_inuse & (1 << pmc)))
+				break;
+		}
+
+		pmc_inuse |= 1 << pmc;
+	}
+
+	/* Extract PMC from DTS field */
+	for (i = 0; i < field_count; i++) {
+		struct dts_field_map *f = &field_maps[i];
+
+		if (f->is_pmc) {
+			pmc = extract_bits(event[0], f->bits_start, f->bits_end);
+			break;
+		}
+	}
+	for (ev = 0; ev < n_ev; ev++) {
+		pmc = pmc_arr[ev];
+
+		for (i = 0; i < field_count; i++) {
+			struct dts_field_map *f = &field_maps[i];
+			u64 val, shift;
+
+			val = extract_bits(event[ev], f->bits_start, f->bits_end);
+			if (f->is_pmc)
+				continue;
+
+			if (pmc == 5 || pmc == 6)
+				continue;
+
+			if (f->use_target_field_shift)
+				shift = f->target_field_base - (pmc * f->target_field_shift);
+			else
+				shift = f->pgm_start;
+
+			mmcr_val[f->mmcr] |= (val << shift);
+		}
+
+		/* MMCR2 privilege filtering */
+		if (pmc <= 6 && pevents && pevents[ev]) {
+
+			if (pevents[ev]->attr.exclude_user)
+				mmcr_val[2] |= MMCR2_FCP(pmc);
+
+			if (pevents[ev]->attr.exclude_hv)
+				mmcr_val[2] |= MMCR2_FCH(pmc);
+
+			if (pevents[ev]->attr.exclude_kernel) {
+				if (cpu_has_feature(CPU_FTR_HVMODE))
+					mmcr_val[2] |= MMCR2_FCH(pmc);
+				else
+					mmcr_val[2] |= MMCR2_FCS(pmc);
+			}
+		}
+	}
+
+	mmcr->mmcr0 = 0;
+
+	/* pmc_inuse is 1-based */
+	if (pmc_inuse & 2)
+		mmcr->mmcr0 = MMCR0_PMC1CE;
+
+	if (pmc_inuse & 0x7c)
+		mmcr->mmcr0 |= MMCR0_PMCjCE;
+
+	/* If we're not using PMC 5 or 6, freeze them */
+	if (!(pmc_inuse & 0x60))
+		mmcr->mmcr0 |= MMCR0_FC56;
+
+	/*
+	 * Set mmcr0 (PMCCEXT) for p10 which
+	 * will restrict access to group B registers
+	 * when MMCR0 PMCC=0b00.
+	 */
+	if (cpu_has_feature(CPU_FTR_ARCH_31))
+		mmcr->mmcr0 |= MMCR0_PMCCEXT;
+
+	/*
+	 * Many places in core-book3s uses cpuhw->mmcr for enabling events
+	 * Till we move away completely to DTS, maintain values in cpu->mmcr
+	 */
+	mmcr->mmcr1 = mmcr_val[1];
+	mmcr->mmcra = mmcr_val[4];
+	mmcr->mmcr2 = mmcr_val[2];
+	mmcr->mmcr3 = mmcr_val[3];
+	return 0;
+}
+
 void isa207_disable_pmc(unsigned int pmc, struct mmcr_regs *mmcr)
 {
 	if (pmc <= 3)
diff --git a/arch/powerpc/perf/isa207-common.h b/arch/powerpc/perf/isa207-common.h
index f594fa6580d1..e9a7c5a39ec2 100644
--- a/arch/powerpc/perf/isa207-common.h
+++ b/arch/powerpc/perf/isa207-common.h
@@ -281,6 +281,8 @@ int isa207_get_constraint(u64 event, unsigned long *maskp, unsigned long *valp,
 int isa207_compute_mmcr(u64 event[], int n_ev,
 				unsigned int hwc[], struct mmcr_regs *mmcr,
 				struct perf_event *pevents[], u32 flags);
+int compute_mmcr_dts(u64 event[], int n_ev, unsigned int hwc[], struct mmcr_regs *mmcr,
+				struct perf_event *pevents[], u32 flags);
 void isa207_disable_pmc(unsigned int pmc, struct mmcr_regs *mmcr);
 int isa207_get_alternatives(u64 event, u64 alt[], int size, unsigned int flags,
 					const unsigned int ev_alt[][MAX_ALT]);
-- 
2.54.0


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

* [RFC 3/3] powerpc/perf: Add DTS-based event constraints
  2026-06-29  6:10 [RFC 0/3] powerpc/perf: Add Device Tree based PMU description framework Shivani Nittor
  2026-06-29  6:10 ` [RFC 1/3] powerpc/perf: Register PMU from device tree and expose events Shivani Nittor
  2026-06-29  6:10 ` [RFC 2/3] powerpc/perf: Add DTS-based MMCR computation Shivani Nittor
@ 2026-06-29  6:10 ` Shivani Nittor
  2 siblings, 0 replies; 5+ messages in thread
From: Shivani Nittor @ 2026-06-29  6:10 UTC (permalink / raw)
  To: maddy, linuxppc-dev; +Cc: linux-kernel, atrajeev, shivani, tshah

Add support for describing PMU event constraints in the device
tree.

Parse constraint definitions from the constraints node and use
them to build PMU scheduling constraints at runtime. Implement
isa207_get_constraint_dts() to validate event combinations and
derive constraint masks and values from the device tree
description.

The constraint system handles:
- PMC restrictions (e.g., PMC5 for instructions, PMC6 for cycles)
- Maximum counter limits
- Cache selector requirements
- EBB (Event-Based Branch) and BHRB (Branch History Rolling Buffer)
  dependencies
- Threshold, sampling, and L1 qualifier constraints
- Radix scope and fabric match constraints
- NC (Next Counter) increment logic

Constraints are conditionally applied based on event properties
(marked events, threshold events, L1 events, BHRB requests) and
CPU features (ARCH_31 for radix scope).

The parse_constraints() function reads constraint definitions from
DTS nodes including pmc-constraints, sample, threshold, cache, ebb,
bhrb, l1-qualifier, fab-match, radix-scope, and nc. Each constraint
field specifies event masks, shifts, constraint masks, and shifts
to build the final constraint value.

This allows PMU constraint information to be described in DTS
rather than being hardcoded in the PMU implementation.

Signed-off-by: Shivani Nittor <shivani@linux.ibm.com>
---
 arch/powerpc/include/asm/dts_pmu.h |  53 +++++++++
 arch/powerpc/perf/Makefile         |   1 +
 arch/powerpc/perf/dts_pmu.c        | 177 ++++++++++++++++++++++++++++-
 arch/powerpc/perf/isa207-common.c  | 141 +++++++++++++++++++++++
 arch/powerpc/perf/isa207-common.h  |   2 +
 5 files changed, 370 insertions(+), 4 deletions(-)

diff --git a/arch/powerpc/include/asm/dts_pmu.h b/arch/powerpc/include/asm/dts_pmu.h
index 1309a45ce604..5632c4f8f748 100644
--- a/arch/powerpc/include/asm/dts_pmu.h
+++ b/arch/powerpc/include/asm/dts_pmu.h
@@ -7,6 +7,13 @@
 #define MAX_MMCR   5
 #define MAX_DTS_EVENTS 32
 #define MAX_PMU_COUNTERS 6
+#define DTS_COND_ALWAYS      0
+#define DTS_COND_MARKED      1
+#define DTS_COND_THRESHOLD   2
+#define DTS_COND_L1          3
+#define DTS_COND_BHRB        4
+#define DTS_COND_CACHE       5
+#define DTS_COND_RADIX       6
 
 struct dts_field_map {
 	u32 bits_start, bits_end;
@@ -21,9 +28,55 @@ extern int field_count;
 
 extern u32 mmcr_regs_sprs[MAX_MMCR];
 extern int mmcr_count;
+extern struct dts_constraint_map *constraint_maps;
+extern int constraint_map_count;
 
 int compute_mmcr_dts(u64 event[], int n_ev,
 			unsigned int hwc[], struct mmcr_regs *mmcr,
 			struct perf_event *pevents[], u32 flags);
 
+struct dts_constraint_field {
+	u64 event_mask, constraint_mask;
+	u32 event_shift, constraint_shift, condition;
+};
+
+struct dts_constraint_map {
+	struct dts_constraint_field field;
+};
+
+struct restricted_counter {
+	u32 pmc;
+	u64 event;
+};
+
+struct dts_threshold_constraints {
+	bool supported;
+	struct dts_constraint_field thresh_sel, thresh_cmp, thresh_ctl;
+};
+
+struct dts_nc_constraints {
+	u64 mask;
+	u32 shift, increment;
+};
+
+struct dts_pmu_constraints {
+	u32 max_counter;
+
+	struct restricted_counter restricted[8];
+	int num_restricted;
+
+	bool require_cache_selector_zero;
+	u32 cache_selector_mask;
+
+	bool require_pmc_for_ebb, bhrb_requires_ebb;
+
+	struct dts_constraint_field sample, ebb, bhrb, l1_qualifier,
+				    fab_match, radix_scope, cache_group,
+				    cache_pmc4, l2l3_group;
+
+	struct dts_threshold_constraints threshold;
+	struct dts_nc_constraints nc;
+};
+
+extern struct dts_pmu_constraints dts_constraints;
 #endif
diff --git a/arch/powerpc/perf/Makefile b/arch/powerpc/perf/Makefile
index 537f4b87cffe..7b474bd0d090 100644
--- a/arch/powerpc/perf/Makefile
+++ b/arch/powerpc/perf/Makefile
@@ -9,6 +9,7 @@ obj64-$(CONFIG_PPC_PERF_CTRS)	+= ppc970-pmu.o power5-pmu.o \
 				   isa207-common.o power8-pmu.o power9-pmu.o \
 				   generic-compat-pmu.o power10-pmu.o bhrb.o \
 				   dts_pmu.o
+
 obj32-$(CONFIG_PPC_PERF_CTRS)	+= mpc7450-pmu.o
 
 obj-$(CONFIG_PPC_POWERNV)	+= imc-pmu.o
diff --git a/arch/powerpc/perf/dts_pmu.c b/arch/powerpc/perf/dts_pmu.c
index 39107af6d467..0faea573f144 100644
--- a/arch/powerpc/perf/dts_pmu.c
+++ b/arch/powerpc/perf/dts_pmu.c
@@ -14,10 +14,13 @@
 
 extern void unregister_power_pmu(struct power_pmu *pmu);
 static u32 pmu_dts_nr_pmc;
+struct dts_pmu_constraints dts_constraints;
 struct dts_field_map pmcsel_map;
 struct dts_field_map pmc_map;
 struct dts_field_map field_maps[MAX_FIELDS];
 int field_count;
+struct dts_constraint_map *constraint_maps;
+int constraint_map_count;
 
 u32 mmcr_regs_sprs[MAX_MMCR];
 int mmcr_count;
@@ -32,6 +35,172 @@ static struct pmu_dts_event *dts_events[MAX_DTS_EVENTS];
 static struct attribute *pmu_dts_events_attrs[MAX_DTS_EVENTS + 1];
 static int dts_event_count;
 
+/* Constraints Structure */
+static void parse_constraint_field(struct device_node *np,
+				struct dts_constraint_field *field)
+{
+	u32 val32[2] = {0};
+
+	if (!np)
+		return;
+
+	/* initialize */
+	field->event_mask = 0;
+	field->event_shift = 0;
+	field->constraint_mask = 0;
+	field->constraint_shift = 0;
+
+	/* event-mask */
+	if (!of_property_read_u32(np, "event-mask", &val32[0]))
+		field->event_mask = val32[0];
+
+	/* event-shift */
+	of_property_read_u32(np, "event-shift", &field->event_shift);
+
+	/* constraint-mask */
+	if (!of_property_read_u32_array(np, "constraint-mask", val32, 2))
+		field->constraint_mask = ((u64)val32[0] << 32) | val32[1];
+	else if (!of_property_read_u32(np, "constraint-mask", &val32[0]))
+		field->constraint_mask = val32[0];
+
+	/* constraint-shift */
+	of_property_read_u32(np, "constraint-shift", &field->constraint_shift);
+
+	of_property_read_u32(np, "condition", &field->condition);
+
+	if (constraint_maps && constraint_map_count < 16) {
+		memcpy(&constraint_maps[constraint_map_count].field,
+					field, sizeof(*field));
+		constraint_map_count++;
+	}
+}
+
+static void parse_constraints(struct device_node *pmu_np)
+{
+	struct device_node *np;
+	struct device_node *child;
+	int i = 0;
+
+	constraint_maps = kcalloc(16, sizeof(*constraint_maps), GFP_KERNEL);
+
+	constraint_map_count = 0;
+
+	np = of_get_child_by_name(pmu_np, "constraints");
+	if (!np)
+		return;
+
+	/* PMC constraints */
+	child = of_get_child_by_name(np, "pmc-constraints");
+
+	if (child) {
+		of_property_read_u32(child,
+					"max-counter", &dts_constraints.max_counter);
+
+		for_each_child_of_node(child, child) {
+			of_property_read_u32(child, "pmc",
+					&dts_constraints.restricted[i].pmc);
+
+			of_property_read_u64(child, "valid-events",
+					&dts_constraints.restricted[i].event);
+			i++;
+		}
+
+		dts_constraints.restricted[0].event = 0x500fa;
+		dts_constraints.restricted[1].event = 0x600f4;
+		dts_constraints.num_restricted = i;
+	}
+
+	/* sample */
+	child = of_get_child_by_name(np, "sample");
+
+	if (child)
+		parse_constraint_field(child, &dts_constraints.sample);
+
+	/* threshold */
+	child = of_get_child_by_name(np, "threshold");
+
+	if (child) {
+		dts_constraints.threshold.supported =
+				of_property_read_bool(child, "supported");
+
+		parse_constraint_field(of_get_child_by_name(child, "thresh-sel"),
+					&dts_constraints.threshold.thresh_sel);
+
+		parse_constraint_field(of_get_child_by_name(child, "thresh-cmp"),
+					&dts_constraints.threshold.thresh_cmp);
+
+		parse_constraint_field(of_get_child_by_name(child, "thresh-ctl"),
+					&dts_constraints.threshold.thresh_ctl);
+	}
+
+	/* cache */
+	child = of_get_child_by_name(np, "cache");
+
+	if (child) {
+		of_property_read_u32(child, "cache-selector-mask",
+					&dts_constraints.cache_selector_mask);
+
+		dts_constraints.require_cache_selector_zero =
+					of_property_read_bool(child, "require-cache-selector-zero");
+
+		/* cache-group */
+		parse_constraint_field(of_get_child_by_name(child, "cache-group"),
+					&dts_constraints.cache_group);
+
+		/* cache-pmc4 */
+		parse_constraint_field(of_get_child_by_name(child, "cache-pmc4"),
+					&dts_constraints.cache_pmc4);
+
+		/* l2l3-group */
+		parse_constraint_field(of_get_child_by_name(child, "l2l3-group"),
+					&dts_constraints.l2l3_group);
+	}
+
+	/* ebb */
+	child = of_get_child_by_name(np, "ebb");
+
+	if (child) {
+		parse_constraint_field(child, &dts_constraints.ebb);
+
+		dts_constraints.require_pmc_for_ebb =
+					of_property_read_bool(child, "require-pmc");
+	}
+
+	/* bhrb */
+	child = of_get_child_by_name(np, "bhrb");
+
+	if (child) {
+		parse_constraint_field(child, &dts_constraints.bhrb);
+
+		dts_constraints.bhrb_requires_ebb =
+					of_property_read_bool(child, "requires-ebb");
+	}
+
+	/* others */
+	parse_constraint_field(of_get_child_by_name(np, "l1-qualifier"),
+					&dts_constraints.l1_qualifier);
+
+	parse_constraint_field(of_get_child_by_name(np, "fab-match"),
+					&dts_constraints.fab_match);
+
+	parse_constraint_field(of_get_child_by_name(np, "radix-scope"),
+					&dts_constraints.radix_scope);
+
+	/* NC */
+	child = of_get_child_by_name(np, "nc");
+
+	if (child) {
+		of_property_read_u64(child, "mask",
+					&dts_constraints.nc.mask);
+
+		of_property_read_u32(child, "shift",
+					&dts_constraints.nc.shift);
+
+		of_property_read_u32(child, "increment",
+					&dts_constraints.nc.increment);
+	}
+}
+
 static ssize_t pmu_dts_event_show(struct device *dev,
 				struct device_attribute *attr,
 				char *buf)
@@ -124,8 +293,7 @@ static struct power_pmu dts_pmu = {
 	.group_constraint_mask  = CNST_CACHE_PMC4_MASK,
 	.group_constraint_val   = CNST_CACHE_PMC4_VAL,
 	.compute_mmcr           = dts_compute_mmcr,
-	// .config_bhrb         = power10_config_bhrb,
-	// .bhrb_filter_map     = power10_bhrb_filte-r_map,
+	.get_constraint         = isa207_get_constraint_dts,
 	.get_alternatives       = dts_get_alternatives,
 	.get_mem_data_src       = isa207_get_mem_data_src,
 	.get_mem_weight         = isa207_get_mem_weight,
@@ -134,9 +302,7 @@ static struct power_pmu dts_pmu = {
 					PPMU_ARCH_31 | PPMU_HAS_ATTR_CONFIG1 |
 					PPMU_P10,
 	.attr_groups            = pmu_dts_attr_groups,
-	//.bhrb_nr              = 32,
 	.capabilities           = PERF_PMU_CAP_EXTENDED_REGS,
-	//.check_attr_config    = power10_check_attr_config,
 };
 
 /* Device Tree match */
@@ -175,6 +341,9 @@ static int pmu_dts_probe(struct platform_device *pdev)
 		}
 	}
 
+	/* For format parsing */
+	parse_constraints(np);
+
 	if (of_property_read_u32(np, "nr_pmc", &pmu_dts_nr_pmc)) {
 		pr_err("pmu_dts: nr_pmc not found in %s\n", np->full_name);
 		return -EINVAL;
diff --git a/arch/powerpc/perf/isa207-common.c b/arch/powerpc/perf/isa207-common.c
index f912432fd6db..1d93f9e5c5f2 100644
--- a/arch/powerpc/perf/isa207-common.c
+++ b/arch/powerpc/perf/isa207-common.c
@@ -748,6 +748,147 @@ int isa207_compute_mmcr(u64 event[], int n_ev,
 	return 0;
 }
 
+static void apply_constraint(u64 event,
+			struct dts_constraint_field *field,
+			unsigned long *mask,
+			unsigned long *value)
+{
+	u64 extracted;
+
+	extracted = (event >> field->event_shift) & field->event_mask;
+	*mask |= field->constraint_mask;
+	*value |= (extracted << field->constraint_shift);
+}
+
+static bool constraint_enabled(struct dts_constraint_map *m, u64 event)
+{
+	switch (m->field.condition) {
+
+	case DTS_COND_ALWAYS:
+		return true;
+
+	case DTS_COND_MARKED:
+		return is_event_marked(event);
+
+	case DTS_COND_THRESHOLD:
+		return event_is_threshold(event);
+
+	case DTS_COND_L1:
+		return (event & EVENT_IS_L1);
+
+	case DTS_COND_BHRB:
+		return (event & EVENT_WANTS_BHRB);
+
+	case DTS_COND_RADIX:
+		return cpu_has_feature(CPU_FTR_ARCH_31);
+
+	default:
+		return false;
+	}
+	return false;
+}
+
+int isa207_get_constraint_dts(u64 event,
+			      unsigned long *maskp,
+			      unsigned long *valp,
+			      u64 event_config1)
+{
+	unsigned int pmc, unit, cache, ebb;
+	unsigned long mask = 0;
+	unsigned long value = 0;
+	u64 base_event;
+	int i, valid = 0;
+
+	pmc = (event >> EVENT_PMC_SHIFT) & EVENT_PMC_MASK;
+	unit = (event >> EVENT_UNIT_SHIFT) & EVENT_UNIT_MASK;
+	cache = (event >> EVENT_CACHE_SEL_SHIFT) & EVENT_CACHE_SEL_MASK;
+	ebb = (event >> EVENT_EBB_SHIFT) & EVENT_EBB_MASK;
+	base_event = event & ~EVENT_LINUX_MASK;
+
+	/* max counter */
+	if (pmc > dts_constraints.max_counter)
+		return -1;
+
+	/* restricted counters */
+	if (pmc >= 5) {
+		valid = 0;
+
+		for (i = 0;
+			i < dts_constraints.num_restricted;
+			i++) {
+
+			if (dts_constraints.restricted[i].pmc == pmc &&
+					dts_constraints.restricted[i].event == base_event) {
+				valid = 1;
+				break;
+			}
+		}
+
+		if (!valid)
+			return -1;
+	}
+
+	/* cache selector */
+	if (dts_constraints.require_cache_selector_zero &&
+			(cache & dts_constraints.cache_selector_mask))
+		return -1;
+
+	/* EBB */
+	if (dts_constraints.require_pmc_for_ebb && !pmc && ebb)
+		return -1;
+
+	/* BHRB */
+	if ((event & EVENT_WANTS_BHRB) &&
+	    dts_constraints.bhrb_requires_ebb && !ebb)
+		return -1;
+
+	/* PMC */
+	if (pmc) {
+		mask |= CNST_PMC_MASK(pmc);
+		value |= CNST_PMC_VAL(pmc);
+
+		if (pmc >= 5)
+			goto post_general_constraints;
+	}
+
+	/* NC */
+	if (pmc <= 4) {
+		mask |= dts_constraints.nc.mask;
+
+		value |=
+			(dts_constraints.nc.increment << dts_constraints.nc.shift);
+	}
+
+	/* cache / l2l3 */
+	if (unit >= 6 && unit <= 9) {
+		if (cpu_has_feature(CPU_FTR_ARCH_31)) {
+			apply_constraint (event, &dts_constraints.l2l3_group,
+				&mask, &value);
+		} else {
+			apply_constraint (event, &dts_constraints.cache_group,
+				&mask, &value);
+
+			if (pmc == 4)
+				mask |= dts_constraints.cache_pmc4.constraint_mask;
+		}
+	}
+
+post_general_constraints:
+
+	for (i = 0; i < constraint_map_count; i++) {
+		if (!constraint_enabled(&constraint_maps[i], event))
+			continue;
+
+		apply_constraint(event, &constraint_maps[i].field,
+				&mask, &value);
+	}
+
+	*maskp = mask;
+	*valp = value;
+
+	return 0;
+}
+
 int compute_mmcr_dts(u64 event[], int n_ev,
 			unsigned int hwc[], struct mmcr_regs *mmcr,
 			struct perf_event *pevents[], u32 flags)
diff --git a/arch/powerpc/perf/isa207-common.h b/arch/powerpc/perf/isa207-common.h
index e9a7c5a39ec2..99cf1a7bac20 100644
--- a/arch/powerpc/perf/isa207-common.h
+++ b/arch/powerpc/perf/isa207-common.h
@@ -278,6 +278,8 @@
 #define REM				P(REMOTE, REMOTE)
 
 int isa207_get_constraint(u64 event, unsigned long *maskp, unsigned long *valp, u64 event_config1);
+int isa207_get_constraint_dts(u64 event, unsigned long *maskp,
+				unsigned long *valp, u64 event_config1);
 int isa207_compute_mmcr(u64 event[], int n_ev,
 				unsigned int hwc[], struct mmcr_regs *mmcr,
 				struct perf_event *pevents[], u32 flags);
-- 
2.54.0


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

* Re: [RFC 1/3] powerpc/perf: Register PMU from device tree and expose events
  2026-06-29  6:10 ` [RFC 1/3] powerpc/perf: Register PMU from device tree and expose events Shivani Nittor
@ 2026-06-29 18:47   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 5+ messages in thread
From: Krzysztof Kozlowski @ 2026-06-29 18:47 UTC (permalink / raw)
  To: Shivani Nittor, maddy, linuxppc-dev; +Cc: linux-kernel, atrajeev, tshah

On 29/06/2026 08:10, Shivani Nittor wrote:
> Introduce a DTS-based PMU driver that discovers PMU information
> from the device tree and registers a PowerPC PMU instance at
> runtime.
> 
> The driver parses basic PMU properties such as the number of
> counters and MMCR register definitions, creates sysfs event
> entries from the device tree event descriptions, and registers
> the PMU with the perf subsystem.
> 
> To support dynamic PMU registration, add PMU unregistration
> support and create a platform device for PMU nodes described
> in the device tree.
> 
> This forms the foundation for moving PMU descriptions out of
> architecture-specific kernel code and into device tree data.
> 
> The driver implements the following functionality:
> 
> Device Tree Parsing:
> - Reads PMU properties including counter count (nr_pmc), PMU name,
>   version, and platform information
> - Parses MMCR (Monitor Mode Control Register) definitions from the
>   sprs/mmcr node hierarchy, storing the MMCR register SPRNs
> - Extracts event definitions from the events node, supporting both
>   32-bit and 64-bit event codes
> 
> Dynamic Event Registration:
> - Creates sysfs event entries dynamically from device tree event
>   descriptions
> - Generates event attributes with format "event=0x<code>" for each
>   discovered event
> - Exposes events under /sys/devices/<pmu-name>/events/
> 
> PMU Registration:
> - Registers a power_pmu instance with the perf subsystem using the
>   device tree-provided PMU name
> - Adds PMU unregistration support to enable proper cleanup
> - Creates platform device for "ibm,power-pmu" compatible nodes
> 
> Sysfs Interface:
> - Provides format attributes (event, pmcsel) under
>   /sys/devices/<pmu-name>/format/
> - Exposes device tree debug information (nr_pmc) under
>   /sys/devices/<pmu-name>/dt/

Where did you document the ABI?

...


> +static struct power_pmu dts_pmu = {
> +	.name           = "cpu_dts",
> +	.n_counter      = MAX_PMU_COUNTERS,
> +	.attr_groups    = pmu_dts_attr_groups,
> +};
> +
> +/* Device Tree match */

That's obvious. Can of_device_id be something else than DT match?

> +static const struct of_device_id pmu_dts_of_match[] = {
> +	{ .compatible = "ibm,power-pmu" },

Where is ABI documented?

> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, pmu_dts_of_match);
> +
> +/* Probe function */
> +static int pmu_dts_probe(struct platform_device *pdev)
> +{
> +	struct device_node *np = pdev->dev.of_node, *child, *events_np;
> +	struct device_node *sprs_np, *mmcr_np, *mmcr_child;
> +	struct pmu_dts_event *evt;
> +	u64 code;
> +	u32 code32, sprn;
> +	u32 code64[2];
> +	u32 code128[4];
> +	int cells;
> +	const char *str;
> +
> +	pr_info("PMU DTS probe node = %s\n", np->full_name);

This does not look like useful printk message. Drivers should be silent
on success:
https://elixir.bootlin.com/linux/v6.15-rc7/source/Documentation/process/coding-style.rst#L913
https://elixir.bootlin.com/linux/v6.15-rc7/source/Documentation/process/debugging/driver_development_debugging_guide.rst#L79

> +
> +	if (!of_property_present(np, "nr_pmc")) {
> +
> +		for_each_child_of_node(np, child) {
> +			pr_info("child node = %s\n", child->full_name);

Why are you sending debugging code?

> +
> +			if (of_property_present(child, "nr_pmc")) {
> +				np = child;
> +				break;
> +			}
> +		}
> +	}
> +
> +	if (of_property_read_u32(np, "nr_pmc", &pmu_dts_nr_pmc)) {
> +		pr_err("pmu_dts: nr_pmc not found in %s\n", np->full_name);

Why isn't this using dev interface?

> +		return -EINVAL;
> +	}
> +
> +	if (!of_property_read_string(np, "pmu-name", &str))
> +		pr_info("PMU Name: %s\n", str);
> +
> +	if (!of_property_read_string(np, "pmu-version", &str))
> +		pr_info("PMU Version: %s\n", str);
> +
> +	if (!of_property_read_string(np, "platform", &str))

No, you cannot just add bunch of undocumented ABI and call it a day.


> +		pr_info("Platform: %s\n", str);
> +
> +	sprs_np = of_get_child_by_name(np, "sprs");
> +	if (!sprs_np) {
> +		pr_err("pmu_dts: no sprs node\n");
> +		return -EINVAL;
> +	}
> +
> +	mmcr_np = of_get_child_by_name(sprs_np, "mmcr");
> +	if (!mmcr_np) {
> +		pr_err("pmu_dts: no mmcr node\n");
> +		return -EINVAL;
> +	}
> +
> +	mmcr_count = 0;
> +	for_each_child_of_node(mmcr_np, mmcr_child) {
> +
> +		if (of_property_read_u32(mmcr_child, "sprn", &sprn))
> +			continue;
> +
> +		mmcr_regs_sprs[mmcr_count++] = sprn;
> +		pr_info("pmu_dts: MMCR[%d] = %u (%s)\n", mmcr_count - 1,
> +				sprn, mmcr_child->name);
> +
> +		if (mmcr_count >= MAX_MMCR)
> +			break;

Where is cleanup?

> +	}
> +
> +	if (!mmcr_count) {
> +		pr_err("pmu_dts: no MMCR SPRs found\n");
> +		return -EINVAL;
> +	}
> +
> +	/* Parse events */
> +	events_np = of_get_child_by_name(np, "events");
> +	if (!events_np) {
> +		pr_err("pmu_dts: no events node found\n");
> +		return -EINVAL;
> +	}
> +
> +	dts_event_count = 0;
> +	for_each_child_of_node(events_np, child) {
> +		if (!of_device_is_available(child))

Why are you open-coding available variant of loop?

> +			continue;
> +
> +		cells = of_property_count_u32_elems(child, "event_code");
> +		if (cells == 1) {
> +			if (of_property_read_u32(child, "event_code", &code32))
> +				continue;
> +
> +			code = code32;
> +
> +		} else if (cells == 2) {
> +			if (of_property_read_u32_array(child, "event_code", code64, 2))
> +				continue;
> +			code = ((u64)code64[0] << 32) | code64[1];
> +
> +		} else if (cells == 4) {
> +			if (of_property_read_u32_array(child, "event_code", code128, 4))
> +				continue;
> +			code = ((u64)code128[1] << 32) | code128[3];
> +
> +		} else {
> +			pr_warn("pmu_dts: invalid event_code for %s\n", child->name);
> +			continue;
> +		}
> +
> +		evt = kzalloc(sizeof(*evt), GFP_KERNEL);
> +		if (!evt)
> +			continue;
> +
> +		snprintf(evt->name, sizeof(evt->name), "%s", child->name);
> +		snprintf(evt->config, sizeof(evt->config), "event=0x%llx", code);
> +
> +		sysfs_attr_init(&evt->attr.attr);
> +		evt->attr.attr.name = evt->name;
> +		evt->attr.attr.mode = 0444;
> +		evt->attr.show = pmu_dts_event_show;
> +		dts_events[dts_event_count] = evt;
> +		pmu_dts_events_attrs[dts_event_count] = &evt->attr.attr;
> +		dts_event_count++;
> +
> +		if (dts_event_count >= MAX_DTS_EVENTS)
> +			break;

And node cleanup?

> +	}
> +	pmu_dts_events_attrs[dts_event_count] = NULL;
> +
> +	/* Register PMU */

No.

> +	pr_info("pmu_dts: registering PMU\n");

No.

> +	return register_power_pmu(&dts_pmu);
> +}
> +
> +/* Platform driver */

This is ridicilous comment.

> +static struct platform_driver pmu_dts_driver = {
> +	.probe = pmu_dts_probe,
> +	.driver = {
> +		.name = "pmu_dts",
> +		.of_match_table = pmu_dts_of_match,
> +	},
> +};
> +
> +static int __init pmu_dts_init(void)
> +{
> +	pr_info("pmu_dts: init\n");

NAK, init functions do not print anything. RFC does not mean you can
send debugging code with poor quality and...


> +	return platform_driver_register(&pmu_dts_driver);
> +}
> +
> +static void __exit pmu_dts_exit(void)
> +{
> +	pr_info("pmu_dts: exit\n");
> +	platform_driver_unregister(&pmu_dts_driver);
> +	unregister_power_pmu(&dts_pmu);
> +}
> +module_init(pmu_dts_init);
> +module_exit(pmu_dts_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Shivani Nittor");
> +MODULE_DESCRIPTION("PMU DTS driver");
> diff --git a/arch/powerpc/perf/internal.h b/arch/powerpc/perf/internal.h
> index a70ac471a5a5..11154ee31f8e 100644
> --- a/arch/powerpc/perf/internal.h
> +++ b/arch/powerpc/perf/internal.h
> @@ -2,6 +2,7 @@
>  //
>  // Copyright 2019 Madhavan Srinivasan, IBM Corporation.
>  
> +void unregister_power_pmu(struct power_pmu *pmu);
>  int __init init_ppc970_pmu(void);
>  int __init init_power5_pmu(void);
>  int __init init_power5p_pmu(void);
> diff --git a/arch/powerpc/perf/isa207-common.c b/arch/powerpc/perf/isa207-common.c
> index 2b3547fdba4a..e11d1bbbc27b 100644
> --- a/arch/powerpc/perf/isa207-common.c
> +++ b/arch/powerpc/perf/isa207-common.c
> @@ -7,6 +7,7 @@
>   * Copyright 2016 Madhavan Srinivasan, IBM Corporation.
>   */
>  #include "isa207-common.h"
> +#include <asm/dts_pmu.h>
>  
>  PMU_FORMAT_ATTR(event,		"config:0-49");
>  PMU_FORMAT_ATTR(pmcxsel,	"config:0-7");
> diff --git a/arch/powerpc/platforms/powernv/opal.c b/arch/powerpc/platforms/powernv/opal.c
> index 1946dbdc9fa1..35be67eef2c6 100644
> --- a/arch/powerpc/platforms/powernv/opal.c
> +++ b/arch/powerpc/platforms/powernv/opal.c
> @@ -947,6 +947,18 @@ static void __init opal_imc_init_dev(void)
>  
>  	of_node_put(np);
>  }
> +#define PMU_DTB "ibm,power-pmu"
> +
> +static void __init opal_pmus_init_dev(void)
> +{
> +	struct device_node *np;
> +
> +	np = of_find_compatible_node(NULL, NULL, PMU_DTB);
> +	if (np)
> +		of_platform_device_create(np, NULL, NULL);
> +
> +	of_node_put(np);
> +}
>  
>  static int kopald(void *unused)
>  {
> @@ -1032,6 +1044,9 @@ static int __init opal_init(void)
>  	/* Detect In-Memory Collection counters and create devices*/
>  	opal_imc_init_dev();
>  
> +    /*Detect PMU node and create device*/

.... with completely broken indentation style.

> +	opal_pmus_init_dev();
> +
>  	/* Create leds platform devices */
>  	leds = of_find_node_by_path("/ibm,opal/leds");
>  	if (leds) {


Best regards,
Krzysztof


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

end of thread, other threads:[~2026-06-29 18:47 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-29  6:10 [RFC 0/3] powerpc/perf: Add Device Tree based PMU description framework Shivani Nittor
2026-06-29  6:10 ` [RFC 1/3] powerpc/perf: Register PMU from device tree and expose events Shivani Nittor
2026-06-29 18:47   ` Krzysztof Kozlowski
2026-06-29  6:10 ` [RFC 2/3] powerpc/perf: Add DTS-based MMCR computation Shivani Nittor
2026-06-29  6:10 ` [RFC 3/3] powerpc/perf: Add DTS-based event constraints Shivani Nittor

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.