The Linux Kernel Mailing List
 help / color / mirror / Atom feed
From: Shivani Nittor <shivani@linux.ibm.com>
To: maddy@linux.ibm.com, linuxppc-dev@lists.ozlabs.org
Cc: linux-kernel@vger.kernel.org, atrajeev@linux.ibm.com,
	shivani@linux.ibm.com, tshah@linux.ibm.com
Subject: [RFC 1/3] powerpc/perf: Register PMU from device tree and expose events
Date: Mon, 29 Jun 2026 11:40:20 +0530	[thread overview]
Message-ID: <20260629061101.43119-2-shivani@linux.ibm.com> (raw)
In-Reply-To: <20260629061101.43119-1-shivani@linux.ibm.com>

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


  reply	other threads:[~2026-06-29  6:11 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260629061101.43119-2-shivani@linux.ibm.com \
    --to=shivani@linux.ibm.com \
    --cc=atrajeev@linux.ibm.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linuxppc-dev@lists.ozlabs.org \
    --cc=maddy@linux.ibm.com \
    --cc=tshah@linux.ibm.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox