[PATCH v1 1/3] iommu/riscv: Add iommu perf driver

Yaxing Guo
2025-09-15T02:10:13Z

This patch introduces a performance monitor driver for RISC-V IOMMU
iohpm implementions that support hardware performance counters.
The driver expose RISC-V-IOMMU-specific performance events (eg. cycles,
tlb miss ...)through the Linux perf subsystem, enabling developers
to profile and optimize I/O translation performance.

Key features:
- Implements a perf PMU driver for RISC-V IOMMU.

- Exposes IOMMU perf events, such as 'cycles', 'tlb_miss'... via
/sys/devices/riscv-iommu-pmu/events/, allowing use with perf
tools:
perf stat -C 0 -e riscv-iommu-pmu/cycles/ ...
perf stat -C 0 -e riscv-iommu-pmu/tlb_miss/ ...
...
- Supports event filtering through configureable attributes exposed
in /sys/devices/riscv-iommu-pmu/format/, including:
dv_gscv, pv_pscv，did_gscid，pid_pscid，idt (riscv iommu spec 5-23).

- Implements overflow interrupt handling.

- Adds device tree binding support via optional 'pmu-name' property
allowing platform-specific IOMMU implementations to specify a custom
defined event ID list. If not provided, the driver defaults to only
supporting 'cycles'. When set to 'dummy', it enables all 9 standard
event IDs as defined in riscv iommu spec(5-23).

Signed-off-by: Yaxing Guo <guoyaxing@bosc.ac.cn>
---
drivers/iommu/riscv/iommu-perf.c | 535 +++++++++++++++++++++++++++++++
drivers/iommu/riscv/iommu-perf.h | 88 +++++
drivers/iommu/riscv/iommu.h | 8 +
3 files changed, 631 insertions(+)
create mode 100644 drivers/iommu/riscv/iommu-perf.c
create mode 100644 drivers/iommu/riscv/iommu-perf.h

diff --git a/drivers/iommu/riscv/iommu-perf.c b/drivers/iommu/riscv/iommu-perf.c
new file mode 100644
index 000000000000..a9a8788a5776
--- /dev/null
+++ b/drivers/iommu/riscv/iommu-perf.c
@@ -0,0 +1,535 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IOMMU hpm implementations
+ * Copyright(c) 2025 Beijing Institute of Open Source Chip (BOSC)
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/perf_event.h>
+#include <linux/iommu.h>
+#include "iommu.h"
+#include "iommu-perf.h"
+
+#define EVENT_CYCLES 0
+#define EVENT_UNTRANSLATED_REQ 1
+#define EVENT_TRANSLATED_REQUESTS 2
+#define EVENT_ATS_TRANSLATION_REQ 3
+#define EVENT_TLB_MISS 4
+#define EVENT_DDT_WALK 5
+#define EVENT_PDT_WALK 6
+#define EVENT_STAGE1_PT_WALK 7
+#define EVENT_STAGE2_PT_WALK 8
+
+RISCV_IOMMU_PMU_EXT_EVENT_ATTR(translated_req, "config=0x001")
+RISCV_IOMMU_PMU_EXT_EVENT_ATTR(untranslated_req, "config=0x002")
+RISCV_IOMMU_PMU_EXT_EVENT_ATTR(ats_translation_req, "config=0x003")
+RISCV_IOMMU_PMU_EXT_EVENT_ATTR(tlb_miss, "config=0x004")
+RISCV_IOMMU_PMU_EXT_EVENT_ATTR(ddt_walk, "config=0x005")
+RISCV_IOMMU_PMU_EXT_EVENT_ATTR(pdt_walk, "config=0x006")
+RISCV_IOMMU_PMU_EXT_EVENT_ATTR(stage1_pt_walk, "config=0x007")
+RISCV_IOMMU_PMU_EXT_EVENT_ATTR(stage2_pt_walk, "config=0x008")
+
+static const struct attribute_group *riscv_iommu_pmu_attr_update_dummy[] = {
+ &translated_req,
+ &untranslated_req,
+ &ats_translation_req,
+ &tlb_miss,
+ &ddt_walk,
+ &pdt_walk,
+ &stage1_pt_walk,
+ &stage2_pt_walk,
+ NULL
+};
+
+static struct riscv_iommu_pmu_event_map event_map[] = {
+ { "dummy", riscv_iommu_pmu_attr_update_dummy },
+ { NULL, NULL}
+};
+
+PMU_EVENT_ATTR_STRING(pv_pscv, format_pv_pscv_attr, "config1:0");
+PMU_EVENT_ATTR_STRING(dv_gscv, format_dv_gscv_attr, "config1:1");
+PMU_EVENT_ATTR_STRING(idt, format_idt_attr, "config1:2");
+PMU_EVENT_ATTR_STRING(pid_pscid, format_pid_pscid_attr, "config1:20-39");
+PMU_EVENT_ATTR_STRING(did_gscid, format_did_gscid_attr, "config1:40-63");
+
+static struct attribute *formats_attrs[] = {
+ &format_pv_pscv_attr.attr.attr,
+ &format_dv_gscv_attr.attr.attr,
+ &format_idt_attr.attr.attr,
+ &format_pid_pscid_attr.attr.attr,
+ &format_did_gscid_attr.attr.attr,
+ NULL
+};
+
+static struct attribute_group riscv_iommu_pmu_format_attr_group = {
+ .name = "format",
+ .attrs = formats_attrs,
+};
+
+PMU_EVENT_ATTR_STRING(cycles, event_cycles_attr, "config=0x0");
+
+static struct attribute *events_attrs[] = {
+ &event_cycles_attr.attr.attr,
+ NULL
+};
+
+static struct attribute_group riscv_iommu_pmu_events_attr_group = {
+ .name = "events",
+ .attrs = events_attrs,
+};
+
+static const struct attribute_group *riscv_iommu_pmu_attr_groups[] = {
+ &riscv_iommu_pmu_format_attr_group,
+ &riscv_iommu_pmu_events_attr_group,
+ NULL,
+};
+
+static void __riscv_iommu_pmu_unregister(struct riscv_iommu_pmu *iommu_pmu);
+static void riscv_iommu_pmu_read(struct perf_event *event);
+
+static void riscv_iommu_pmu_hpmevt_set(iohpmevt_t *hpmevt, unsigned long event_id,
+ int pv_pscv, int dv_gscv, int idt,
+ int pid_pscid, int did_gscid, int of)
+{
+ hpmevt->val = 0;
+ hpmevt->eventID = event_id;
+ hpmevt->IDT = idt;
+ hpmevt->OF = of;
+ hpmevt->PID_PSCID = pid_pscid;
+ hpmevt->DID_GSCID = did_gscid;
+ hpmevt->PV_PSCV = pv_pscv;
+ hpmevt->DV_GSCV = dv_gscv;
+}
+
+static struct riscv_iommu_perf_event *
+get_riscv_iommu_perf_event(struct riscv_iommu_pmu *iommu_pmu,
+ struct perf_event *event,
+ int pv_pscv, int dv_gscv, int idt,
+ int pid_pscid, int did_gscid,
+ int *idx)
+{
+ int i, nr;
+ struct riscv_iommu_device *iommu = iommu_pmu->iommu;
+ struct riscv_iommu_perf_event *iommu_event;
+
+ for (i = 0; i < RISCV_IOMMU_IOHPMCTR_CNT; i++) {
+ iommu_event = iommu->events[i];
+ if (iommu_event == NULL)
+ continue;
+
+ if (iommu_event->perf_event == event) {
+ nr = i;
+ goto update;
+ }
+ }
+
+ nr = find_first_zero_bit(&iommu->iohpmctr_bitmap,
+ RISCV_IOMMU_IOHPMCTR_CNT);
+ if (nr >= RISCV_IOMMU_IOHPMCTR_CNT)
+ return NULL;
+again:
+ if (test_and_set_bit(nr, &iommu_pmu->iommu->iohpmctr_bitmap))
+ goto again;
+
+ iommu_event = kzalloc(sizeof(struct riscv_iommu_perf_event), GFP_KERNEL);
+ if (!iommu_event)
+ return NULL;
+update:
+ iommu_event->perf_event = event;
+ iommu_event->pv_pscv = pv_pscv;
+ iommu_event->dv_gscv = dv_gscv;
+ iommu_event->idt = idt;
+ iommu_event->pid_pscid = pid_pscid;
+ iommu_event->did_gscid = did_gscid;
+ iommu->events[nr] = iommu_event;
+
+ *idx = nr;
+
+ return iommu_event;
+}
+
+static int riscv_iommu_pmu_event_add(struct riscv_iommu_pmu *iommu_pmu,
+ struct perf_event *event)
+{
+ int nr = -1, of;
+ unsigned long event_id = event->attr.config;
+ riscv_iommu_pmu_cfg1_t config1;
+ struct hw_perf_event *hwc = &event->hw;
+ struct riscv_iommu_device *iommu = iommu_pmu->iommu;
+
+ config1.val = event->attr.config1;
+
+ if (event_id >= RISCV_IOMMU_IOHPMCTR_CNT)
+ return -EINVAL;
+
+ if (iommu->hpm_irq)
+ of = 0;
+ else
+ of = 1;
+
+ if (event_id == EVENT_CYCLES) {
+ unsigned long val;
+
+ val = riscv_iommu_readq(iommu_pmu->iommu, RISCV_IOMMU_REG_IOHPMCYCLES);
+ if (of)
+ val &= ~RISCV_IOMMU_IOHPMCYCLES_OF;
+ else
+ val |= RISCV_IOMMU_IOHPMCYCLES_OF;
+ riscv_iommu_writeq(iommu_pmu->iommu, RISCV_IOMMU_REG_IOHPMCYCLES, val);
+
+ hwc->idx = 0;
+ iommu->events[0]->perf_event = event;
+ } else {
+ struct riscv_iommu_perf_event *iommu_perf_event;
+
+ iommu_perf_event = get_riscv_iommu_perf_event(iommu_pmu, event,
+ config1.pv_pscv, config1.dv_gscv,
+ config1.idt, config1.pid_pscid,
+ config1.did_gscid, &nr);
+ if (!iommu_perf_event)
+ return -ENOSPC;
+
+ riscv_iommu_pmu_hpmevt_set(&iommu_pmu->iommu->iohpmevt[nr], event_id,
+ iommu_perf_event->pv_pscv, iommu_perf_event->dv_gscv,
+ iommu_perf_event->idt, iommu_perf_event->pid_pscid,
+ iommu_perf_event->did_gscid, of);
+ riscv_iommu_writeq(iommu_pmu->iommu, RISCV_IOMMU_REG_IOHPMEVT(nr),
+ iommu_pmu->iommu->iohpmevt[nr].val);
+
+ hwc->idx = nr;
+ }
+
+ return 0;
+}
+
+static int riscv_iommu_pmu_hpmevt_idx_get(struct riscv_iommu_pmu *iommu_pmu, int event_id)
+{
+ int i;
+ iohpmevt_t *iohpmevt;
+
+ for (i = 0; i < RISCV_IOMMU_IOHPMCTR_CNT; i++) {
+ iohpmevt = &iommu_pmu->iommu->iohpmevt[i];
+ if (iohpmevt->eventID == event_id)
+ return iohpmevt - iommu_pmu->iommu->iohpmevt;
+ }
+
+ return -1;
+}
+
+static int riscv_iommu_event_del(struct riscv_iommu_pmu *iommu_pmu,
+ struct perf_event *event)
+{
+ unsigned long config = event->attr.config;
+ struct riscv_iommu_device *iommu = iommu_pmu->iommu;
+
+ if (config >= RISCV_IOMMU_IOHPMCTR_CNT)
+ return -EINVAL;
+
+ if (config == EVENT_CYCLES) {
+ iommu->events[0] = NULL;
+ } else {
+ int nr;
+
+ nr = riscv_iommu_pmu_hpmevt_idx_get(iommu_pmu, config);
+ if (-1 == nr)
+ return -1;
+ riscv_iommu_pmu_hpmevt_set(&iommu_pmu->iommu->iohpmevt[nr], 0,
+ 0, 0, 0, 0, 0, 0);
+ clear_bit(nr, &iommu_pmu->iommu->iohpmctr_bitmap);
+ riscv_iommu_writeq(iommu_pmu->iommu, RISCV_IOMMU_REG_IOHPMEVT(nr),
+ 0);
+ kfree(iommu->events[nr]);
+ iommu->events[nr] = NULL;
+ }
+
+ return 0;
+}
+
+static int riscv_iommu_pmu_event_init(struct perf_event *event)
+{
+ struct hw_perf_event *hwc = &event->hw;
+
+ if (event->attr.sample_period)
+ return -EINVAL;
+
+ if (event->cpu < 0)
+ return -EINVAL;
+
+ hwc->config = event->attr.config;
+
+ return 0;
+}
+
+static void riscv_iommu_pmu_enable(struct pmu *pmu)
+{
+}
+
+static void riscv_iommu_pmu_disable(struct pmu *pmu)
+{
+}
+
+static void riscv_iommu_pmu_start(struct perf_event *event, int flags)
+{
+ struct riscv_iommu_pmu *iommu_pmu = riscv_iommu_event_to_pmu(event);
+ struct hw_perf_event *hwc = &event->hw;
+ unsigned long count;
+
+ hwc->state = 0;
+
+ if (hwc->idx == EVENT_CYCLES)
+ count = riscv_iommu_readq(iommu_pmu->iommu, RISCV_IOMMU_REG_IOHPMCYCLES);
+ else
+ count = riscv_iommu_readq(iommu_pmu->iommu, RISCV_IOMMU_REG_IOHPMCTR(hwc->idx));
+
+ local64_set((&hwc->prev_count), count);
+
+ perf_event_update_userpage(event);
+}
+
+static void riscv_iommu_pmu_stop(struct perf_event *event, int flags)
+{
+ struct hw_perf_event *hwc = &event->hw;
+
+ if (!(hwc->state & PERF_HES_STOPPED)) {
+ riscv_iommu_pmu_read(event);
+ hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
+ }
+}
+
+static int riscv_iommu_pmu_add(struct perf_event *event, int flags)
+{
+ struct riscv_iommu_pmu *iommu_pmu = riscv_iommu_event_to_pmu(event);
+ struct hw_perf_event *hwc = &event->hw;
+
+ riscv_iommu_pmu_event_add(iommu_pmu, event);
+
+ hwc->state = PERF_HES_UPTODATE | PERF_HES_STOPPED;
+
+ if (flags & PERF_EF_START)
+ riscv_iommu_pmu_start(event, 0);
+
+ return 0;
+}
+
+static void riscv_iommu_pmu_del(struct perf_event *event, int flags)
+{
+ struct riscv_iommu_pmu *iommu_pmu = riscv_iommu_event_to_pmu(event);
+
+ riscv_iommu_pmu_stop(event, PERF_EF_UPDATE);
+
+ riscv_iommu_event_del(iommu_pmu, event);
+ event->hw.idx = -1;
+
+ perf_event_update_userpage(event);
+}
+
+static void riscv_iommu_pmu_read(struct perf_event *event)
+{
+ struct riscv_iommu_pmu *iommu_pmu = riscv_iommu_event_to_pmu(event);
+ struct hw_perf_event *hwc = &event->hw;
+ unsigned long prev_count, new_count, delta;
+
+again:
+ prev_count = local64_read(&hwc->prev_count);
+ if (hwc->idx == EVENT_CYCLES)
+ new_count = riscv_iommu_readq(iommu_pmu->iommu, RISCV_IOMMU_REG_IOHPMCYCLES);
+ else
+ new_count = riscv_iommu_readq(iommu_pmu->iommu, RISCV_IOMMU_REG_IOHPMCTR(hwc->idx));
+
+ if (local64_xchg(&hwc->prev_count, new_count) != prev_count)
+ goto again;
+ delta = new_count - prev_count;
+
+ local64_add(delta, &event->count);
+}
+
+int riscv_iommu_pmu_alloc(struct riscv_iommu_device *iommu)
+{
+ struct riscv_iommu_pmu *iommu_pmu;
+ struct riscv_iommu_perf_event *iommu_event;
+ int ret = 0;
+
+ if (iommu->pmu)
+ return -EEXIST;
+
+ iommu_pmu = kzalloc(sizeof(struct riscv_iommu_pmu), GFP_KERNEL);
+ if (!iommu_pmu)
+ return -ENOMEM;
+
+ iommu_pmu->iommu = iommu;
+ iommu->pmu = iommu_pmu;
+
+ set_bit(0, &iommu_pmu->iommu->iohpmctr_bitmap);
+ iommu_event = kzalloc(sizeof(struct riscv_iommu_perf_event), GFP_KERNEL);
+ if (!iommu_event) {
+ ret = -ENOMEM;
+ goto free_pmu;
+ }
+ iommu->events[0] = iommu_event;
+
+ return 0;
+
+free_pmu:
+ kfree(iommu_pmu);
+ return ret;
+}
+
+static void riscv_iommu_pmu_do_overflow(struct riscv_iommu_device *iommu)
+{
+ int idx;
+ struct riscv_iommu_perf_event *iommu_event;
+
+ for_each_set_bit(idx, &iommu->iohpmctr_bitmap,
+ RISCV_IOMMU_IOHPMCTR_CNT) {
+ iohpmevt_t hpmevt;
+ unsigned int val;
+
+ if (idx == 0)
+ continue;
+ iommu_event = iommu->events[idx];
+ if (!iommu_event)
+ continue;
+ hpmevt.val = riscv_iommu_readq(iommu, RISCV_IOMMU_REG_IOHPMEVT(idx));
+ if (hpmevt.OF) {
+ riscv_iommu_pmu_read(iommu_event->perf_event);
+
+ hpmevt.OF = 0;
+ riscv_iommu_writeq(iommu, RISCV_IOMMU_REG_IOHPMEVT(idx), hpmevt.val);
+
+ val = riscv_iommu_readl(iommu, RISCV_IOMMU_REG_IPSR);
+ val &= ~RISCV_IOMMU_IPSR_PMIP;
+ riscv_iommu_writel(iommu, RISCV_IOMMU_REG_IPSR, val);
+ }
+ }
+}
+
+static irqreturn_t riscv_iommu_pmu_irq_handler(int irq, void *data)
+{
+ struct riscv_iommu_device *iommu = (struct riscv_iommu_device *)data;
+
+ riscv_iommu_pmu_do_overflow(iommu);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t riscv_iommu_pmu_ipsr(int irq, void *data)
+{
+ struct riscv_iommu_device *iommu = (struct riscv_iommu_device *)data;
+
+ if (riscv_iommu_readl(iommu, RISCV_IOMMU_REG_IPSR) & RISCV_IOMMU_IPSR_PMIP)
+ return IRQ_WAKE_THREAD;
+
+ return IRQ_NONE;
+}
+
+static int riscv_iommu_pmu_vec(struct riscv_iommu_device *iommu)
+{
+ return FIELD_GET(RISCV_IOMMU_ICVEC_PMIV, iommu->icvec);
+}
+
+static int riscv_iommu_pmu_set_irq(struct riscv_iommu_device *iommu)
+{
+ int irq, ret;
+
+ if (!iommu)
+ return -EINVAL;
+ irq = iommu->irqs[riscv_iommu_pmu_vec(iommu)];
+ if (!irq)
+ return -EEXIST;
+ iommu->hpm_irq = irq;
+
+ ret = request_threaded_irq(irq, riscv_iommu_pmu_ipsr,
+ riscv_iommu_pmu_irq_handler,
+ IRQF_ONESHOT, "rv-iommu-pmu-irq", iommu);
+ if (ret) {
+ iommu->hpm_irq = 0;
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct attribute_group **
+riscv_iommu_get_ext_attr(struct riscv_iommu_pmu *iommu_pmu)
+{
+ struct riscv_iommu_pmu_event_map *map = event_map;
+ const char *str;
+ struct device *dev = iommu_pmu->iommu->dev;
+
+ if (of_property_read_string(dev->of_node, "pmu-name", &str))
+ return NULL;
+
+ while (map->compatible) {
+ if (!strcmp(map->compatible, str))
+ return map->attr_group;
+ }
+
+ return NULL;
+}
+
+static int __riscv_iommu_pmu_register(struct riscv_iommu_pmu *iommu_pmu,
+ const char *name)
+{
+ int ret;
+
+ if (!iommu_pmu)
+ return -EINVAL;
+
+ iommu_pmu->pmu.attr_groups = riscv_iommu_pmu_attr_groups;
+ iommu_pmu->pmu.attr_update = riscv_iommu_get_ext_attr(iommu_pmu);
+ iommu_pmu->pmu.task_ctx_nr = perf_invalid_context;
+ iommu_pmu->pmu.event_init = riscv_iommu_pmu_event_init;
+ iommu_pmu->pmu.pmu_enable = riscv_iommu_pmu_enable;
+ iommu_pmu->pmu.pmu_disable = riscv_iommu_pmu_disable;
+ iommu_pmu->pmu.add = riscv_iommu_pmu_add;
+ iommu_pmu->pmu.del = riscv_iommu_pmu_del;
+ iommu_pmu->pmu.start = riscv_iommu_pmu_start;
+ iommu_pmu->pmu.stop = riscv_iommu_pmu_stop;
+ iommu_pmu->pmu.read = riscv_iommu_pmu_read;
+
+ ret = perf_pmu_register(&iommu_pmu->pmu, name, -1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void __riscv_iommu_pmu_unregister(struct riscv_iommu_pmu *iommu_pmu)
+{
+ if (!iommu_pmu)
+ return;
+
+ perf_pmu_unregister(&iommu_pmu->pmu);
+
+ kfree(iommu_pmu->iommu->events[0]);
+ kfree(iommu_pmu);
+}
+
+int riscv_iommu_pmu_register(struct riscv_iommu_device *iommu)
+{
+ int ret;
+
+ ret = __riscv_iommu_pmu_register(iommu->pmu, "riscv-iommu-pmu");
+ if (ret)
+ goto err;
+
+ ret = riscv_iommu_pmu_set_irq(iommu);
+ if (ret)
+ goto unregister;
+
+ return 0;
+
+unregister:
+ riscv_iommu_pmu_unregister(iommu);
+err:
+ return ret;
+}
+
+void riscv_iommu_pmu_unregister(struct riscv_iommu_device *iommu)
+{
+ __riscv_iommu_pmu_unregister(iommu->pmu);
+
+ iommu->pmu = NULL;
+}
diff --git a/drivers/iommu/riscv/iommu-perf.h b/drivers/iommu/riscv/iommu-perf.h
new file mode 100644
index 000000000000..5c4cc7b9a978
--- /dev/null
+++ b/drivers/iommu/riscv/iommu-perf.h
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IOMMU hpm implementations
+ * Copyright(c) 2025 Beijing Institute of Open Source Chip (BOSC)
+ */
+
+#ifndef __IOMMU_PERF_H__
+#define __IOMMU_PERF_H__
+
+#include <linux/sysfs.h>
+#include <linux/perf_event.h>
+#include <linux/bitops.h>
+
+typedef union {
+ struct {
+ unsigned long eventID:15;
+ unsigned long DMASK:1;
+ unsigned long PID_PSCID:20;
+ unsigned long DID_GSCID:24;
+ unsigned long PV_PSCV:1;
+ unsigned long DV_GSCV:1;
+ unsigned long IDT:1;
+ unsigned long OF:1;
+ };
+ unsigned long val;
+} iohpmevt_t;
+
+typedef union {
+ struct {
+ unsigned long pv_pscv:1;
+ unsigned long dv_gscv:1;
+ unsigned long idt:1;
+ unsigned long reserved:17;
+ unsigned long pid_pscid:20;
+ unsigned long did_gscid:24;
+ };
+ unsigned long val;
+} riscv_iommu_pmu_cfg1_t;
+
+struct riscv_iommu_pmu_event_map {
+ const char *compatible;
+ const struct attribute_group **attr_group;
+};
+
+#define RISCV_IOMMU_IOHPMCTR_CNT 32
+
+struct riscv_iommu_perf_event {
+ int pv_pscv;
+ int dv_gscv;
+ int idt;
+ int pid_pscid;
+ int did_gscid;
+ struct perf_event *perf_event;
+};
+
+struct riscv_iommu_pmu {
+ struct riscv_iommu_device *iommu;
+ struct pmu pmu;
+};
+
+static inline struct riscv_iommu_pmu *dev_to_riscv_iommu_pmu(struct device *dev)
+{
+ return container_of(dev_get_drvdata(dev), struct riscv_iommu_pmu, pmu);
+}
+
+static inline struct riscv_iommu_pmu *riscv_iommu_event_to_pmu(struct perf_event *event)
+{
+ return container_of(event->pmu, struct riscv_iommu_pmu, pmu);
+}
+
+#define RISCV_IOMMU_PMU_EXT_EVENT_ATTR(_name, _string) \
+ PMU_EVENT_ATTR_STRING(_name, event_attr_##_name, _string) \
+ \
+static struct attribute *_name##_attr[] = { \
+ &event_attr_##_name.attr.attr, \
+ NULL \
+}; \
+ \
+static struct attribute_group _name = { \
+ .name = "events", \
+ .attrs = _name##_attr, \
+};
+
+int riscv_iommu_pmu_alloc(struct riscv_iommu_device *iommu);
+int riscv_iommu_pmu_register(struct riscv_iommu_device *iommu);
+void riscv_iommu_pmu_unregister(struct riscv_iommu_device *iommu);
+
+#endif
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index 46df79dd5495..1eabe04dbbc1 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -14,8 +14,10 @@
#include <linux/iommu.h>
#include <linux/types.h>
#include <linux/iopoll.h>
+#include <linux/perf_event.h>

#include "iommu-bits.h"
+#include "iommu-perf.h"

struct riscv_iommu_device;

@@ -60,6 +62,12 @@ struct riscv_iommu_device {
unsigned int ddt_mode;
dma_addr_t ddt_phys;
u64 *ddt_root;
+
+ iohpmevt_t iohpmevt[RISCV_IOMMU_IOHPMCTR_CNT];
+ unsigned long iohpmctr_bitmap;
+ struct riscv_iommu_pmu *pmu;
+ int hpm_irq;
+ struct riscv_iommu_perf_event *events[RISCV_IOMMU_IOHPMCTR_CNT];
};

int riscv_iommu_init(struct riscv_iommu_device *iommu);
--
2.34.1