[PATCH v5 5/8] spacemit/t100: Add global filter awareness for RISC-V IOMMU HPM
From: Lv Zheng
Date: Sat Feb 28 2026 - 09:53:13 EST
Adds global filter support for RISC-V IOMMU HPM. The global filter can be
seen in SpacemiT T100 which only supports single filter to be applied to
all event counters.
Drivers can program filters in each iohpmevt registers as normal in such a
silicon design, however the underlying hardware filters are wired together
as a global filter applying to all iohpmevt(s). Since the mechanism is
compatible with standard iohpmevt in programming interface, only adds
sanity checks to allow it to be configured with "global" awareness to
inform users a filter incompatiblity.
Signed-off-by: Lv Zheng <lv.zheng@xxxxxxxxxxxxxxxxxx>
Signed-off-by: Jingyu Li <joey.li@xxxxxxxxxxxx>
---
drivers/iommu/riscv/iommu.c | 5 ++
drivers/perf/riscv_iommu_hpm.c | 94 ++++++++++++++++++++++++++++++++--
include/linux/riscv_iommu.h | 2 +
3 files changed, 96 insertions(+), 5 deletions(-)
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index 1aa942486e3a..21ff7e50d115 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -1717,6 +1717,11 @@ static void riscv_iommu_enumerate_hpm(struct riscv_iommu_device *iommu)
.base = iommu->reg + RISCV_IOMMU_REG_IOCOUNTOVF,
};
+ if (of_device_is_compatible(iommu->dev->of_node, "spacemit,t100")) {
+ hpm_info->global_filter = true;
+ params.name = "spacemit_ioats_hpm";
+ }
+
ret = riscv_iommu_subdev_add(iommu, ¶ms);
if (ret) {
kfree(hpm_info);
diff --git a/drivers/perf/riscv_iommu_hpm.c b/drivers/perf/riscv_iommu_hpm.c
index 4d5ce0f380e7..efa65caef0dc 100644
--- a/drivers/perf/riscv_iommu_hpm.c
+++ b/drivers/perf/riscv_iommu_hpm.c
@@ -87,6 +87,7 @@ struct riscv_iommu_hpm {
void __iomem *base;
unsigned int irq;
unsigned int on_cpu;
+ bool global_filter;
struct hlist_node node;
/*
* Layout of events:
@@ -210,6 +211,33 @@ static inline void riscv_iommu_hpm_interrupt_clear(struct riscv_iommu_hpm *hpm)
riscv_iommu_clear_pmip(hpm->subdev);
}
+static bool riscv_iommu_hpm_check_global_filter(struct perf_event *curr,
+ struct perf_event *new)
+{
+ return get_filter_pid_pscid(curr) == get_filter_pid_pscid(new) &&
+ get_filter_did_gscid(curr) == get_filter_did_gscid(new) &&
+ get_filter_pv_pscv(curr) == get_filter_pv_pscv(new) &&
+ get_filter_dv_gscv(curr) == get_filter_dv_gscv(new) &&
+ get_filter_idt(curr) == get_filter_idt(new) &&
+ get_filter_dmask(curr) == get_filter_dmask(new);
+}
+
+static bool riscv_iommu_hpm_events_compatible(struct perf_event *curr,
+ struct perf_event *new)
+{
+ struct riscv_iommu_hpm *hpm;
+
+ if (new->pmu != curr->pmu)
+ return false;
+
+ hpm = to_iommu_hpm(new->pmu);
+ if (hpm->global_filter &&
+ !riscv_iommu_hpm_check_global_filter(curr, new))
+ return false;
+
+ return true;
+}
+
/**
* riscv_iommu_hpm_event_update() - Update and return RISC-V IOMMU HPM
* event counters
@@ -344,9 +372,10 @@ static void riscv_iommu_hpm_set_event_filter(struct perf_event *event, int idx,
RISCV_IOMMU_REG_IOHPMEVT(idx), event_cfg);
}
-static void riscv_iommu_hpm_apply_event_filter(struct riscv_iommu_hpm *hpm,
- struct perf_event *event, int idx)
+static int riscv_iommu_hpm_apply_event_filter(struct riscv_iommu_hpm *hpm,
+ struct perf_event *event, int idx)
{
+ unsigned int cur_idx, num_ctrs = hpm->num_counters;
u32 pid_pscid, did_gscid, pv_pscv, dv_gscv, idt, dmask;
pid_pscid = get_filter_pid_pscid(event);
@@ -356,8 +385,32 @@ static void riscv_iommu_hpm_apply_event_filter(struct riscv_iommu_hpm *hpm,
idt = get_filter_idt(event);
dmask = get_filter_dmask(event);
+ if (hpm->global_filter) {
+ cur_idx = find_first_bit(hpm->used_counters, num_ctrs - 1);
+ if (cur_idx == num_ctrs - 1) {
+ /* First event, set the global filter at iohpmevt0 */
+ riscv_iommu_hpm_set_event_filter(event, 0, pid_pscid,
+ did_gscid,
+ pv_pscv, dv_gscv, idt, dmask);
+ } else {
+ /* Check if the new event's filter matches the global filter */
+ if (!riscv_iommu_hpm_check_global_filter(hpm->events[cur_idx + 1],
+ event)) {
+ dev_dbg(hpm->pmu.dev,
+ "HPM: Filter incompatible with global filter\n");
+ return -EAGAIN;
+ }
+ /* Program event at this counter; filter is shared by hardware */
+ riscv_iommu_hpm_set_event_filter(event, idx, pid_pscid,
+ did_gscid,
+ pv_pscv, dv_gscv, idt, dmask);
+ }
+ return 0;
+ }
+
riscv_iommu_hpm_set_event_filter(event, idx, pid_pscid, did_gscid,
pv_pscv, dv_gscv, idt, dmask);
+ return 0;
}
static int riscv_iommu_hpm_get_event_idx(struct riscv_iommu_hpm *hpm,
@@ -385,7 +438,8 @@ static int riscv_iommu_hpm_get_event_idx(struct riscv_iommu_hpm *hpm,
return -EAGAIN;
}
- riscv_iommu_hpm_apply_event_filter(hpm, event, idx);
+ if (riscv_iommu_hpm_apply_event_filter(hpm, event, idx))
+ return -EAGAIN;
set_bit(idx, hpm->used_counters);
return idx;
@@ -484,6 +538,8 @@ static int riscv_iommu_hpm_event_init(struct perf_event *event)
}
if (!is_software_event(event->group_leader)) {
+ if (!riscv_iommu_hpm_events_compatible(event->group_leader, event))
+ return -EINVAL;
if (++group_num_events > hpm->num_counters)
return -EINVAL;
}
@@ -491,6 +547,8 @@ static int riscv_iommu_hpm_event_init(struct perf_event *event)
for_each_sibling_event(sibling, event->group_leader) {
if (is_software_event(sibling))
continue;
+ if (!riscv_iommu_hpm_events_compatible(sibling, event))
+ return -EINVAL;
if (++group_num_events > hpm->num_counters)
return -EINVAL;
}
@@ -593,10 +651,33 @@ static const struct attribute_group riscv_iommu_hpm_format_group = {
.attrs = riscv_iommu_hpm_formats,
};
+static ssize_t riscv_iommu_hpm_global_filter_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct riscv_iommu_hpm *hpm = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%s\n",
+ hpm->global_filter ? "true" : "false");
+}
+
+static struct device_attribute riscv_iommu_hpm_global_filter_attr =
+ __ATTR(global_filter, 0444, riscv_iommu_hpm_global_filter_show, NULL);
+
+static struct attribute *riscv_iommu_hpm_vendor_attrs[] = {
+ &riscv_iommu_hpm_global_filter_attr.attr,
+ NULL
+};
+
+static const struct attribute_group riscv_iommu_hpm_vendor_group = {
+ .attrs = riscv_iommu_hpm_vendor_attrs,
+};
+
static const struct attribute_group *riscv_iommu_hpm_attr_grps[] = {
&riscv_iommu_hpm_cpumask_group,
&riscv_iommu_hpm_events_group,
&riscv_iommu_hpm_format_group,
+ &riscv_iommu_hpm_vendor_group,
NULL
};
@@ -776,6 +857,7 @@ static int riscv_iommu_hpm_probe(struct auxiliary_device *auxdev,
hpm->base = subdev->base;
hpm->on_cpu = raw_smp_processor_id();
hpm->irq = info->irq;
+ hpm->global_filter = info->global_filter;
bitmap_zero(hpm->used_counters, RISCV_IOMMU_HPMCOUNTER_MAX);
bitmap_zero(hpm->supported_events, RISCV_IOMMU_HPMEVENT_MAX);
@@ -831,8 +913,9 @@ static int riscv_iommu_hpm_probe(struct auxiliary_device *auxdev,
auxiliary_set_drvdata(auxdev, hpm);
- dev_info(dev, "HPM: Registered %s (%d counters, IRQ %d)\n",
- hpm_name, hpm->num_counters, hpm->irq);
+ dev_info(dev, "HPM: Registered %s (%d counters, IRQ %d, %s filter)\n",
+ hpm_name, hpm->num_counters, hpm->irq,
+ hpm->global_filter ? "global" : "per-counter");
return 0;
}
@@ -847,6 +930,7 @@ static void riscv_iommu_hpm_remove(struct auxiliary_device *auxdev)
static const struct auxiliary_device_id riscv_iommu_hpm_ids[] = {
{ .name = "iommu.riscv_iommu_hpm" },
+ { .name = "iommu.spacemit_ioats_hpm" },
{}
};
MODULE_DEVICE_TABLE(auxiliary, riscv_iommu_hpm_ids);
diff --git a/include/linux/riscv_iommu.h b/include/linux/riscv_iommu.h
index 0447bc4d1fab..6af592dfaa00 100644
--- a/include/linux/riscv_iommu.h
+++ b/include/linux/riscv_iommu.h
@@ -36,9 +36,11 @@ struct riscv_iommu_subdev {
/**
* struct riscv_iommu_hpm_info - HPM info for IOATS (main IOMMU HPM)
* @irq: interrupt number
+ * @global_filter: true if single global filter
*/
struct riscv_iommu_hpm_info {
unsigned int irq;
+ bool global_filter;
};
/**
--
2.43.0