[PATCH v5 6/8] iommu/riscv: Add SpacemiT T100 IOATC HPM support

From: Lv Zheng

Date: Sat Feb 28 2026 - 09:51:01 EST


Add IOATC discovery and HPM support for SpacemiT T100.

SpacemiT T100 supports distributed architecture which allows IOTLBs to be
cached in adjacent to the DMA masters. Such IOTLB controllers are called
as IOATCs. Adds distributed HPM support for IOATCs.

Signed-off-by: Lv Zheng <lv.zheng@xxxxxxxxxxxxxxxxxx>
Signed-off-by: Jingyu Li <joey.li@xxxxxxxxxxxx>
---
drivers/iommu/riscv/iommu-bits.h | 12 +++
drivers/iommu/riscv/iommu-platform.c | 4 +-
drivers/iommu/riscv/iommu.c | 107 ++++++++++++++++++++++++++-
drivers/iommu/riscv/iommu.h | 2 +-
drivers/perf/riscv_iommu_hpm.c | 48 +++++++++++-
include/linux/riscv_iommu.h | 6 +-
6 files changed, 171 insertions(+), 8 deletions(-)

diff --git a/drivers/iommu/riscv/iommu-bits.h b/drivers/iommu/riscv/iommu-bits.h
index 98daf0e1a306..c8536c64ef42 100644
--- a/drivers/iommu/riscv/iommu-bits.h
+++ b/drivers/iommu/riscv/iommu-bits.h
@@ -278,6 +278,14 @@ enum riscv_iommu_hpmevent_id {
#define RISCV_IOMMU_ICVEC_PMIV GENMASK_ULL(11, 8)
#define RISCV_IOMMU_ICVEC_PIV GENMASK_ULL(15, 12)

+/* 5.28 Distributed translation interface status register (dtisr0-3) (4 * 32-bits) */
+#define RISCV_IOMMU_REG_DTISR_BASE 0x02B0
+#define RISCV_IOMMU_REG_DTISR(_n) (RISCV_IOMMU_REG_DTISR_BASE + ((_n) * 0x04))
+#define RISCV_IOMMU_DTI_STS_SHIFT(_n) (((_n) % 16) * 2)
+#define RISCV_IOMMU_DTI_STS_MASK(_n) (0x3 << RISCV_IOMMU_DTI_STS_SHIFT(_n))
+#define RISCV_IOMMU_DTI_STS_NONE 0x0
+#define RISCV_IOMMU_DTI_STS_IOATC 0x1
+
/* 5.28 MSI Configuration table (32 * 64bits) */
#define RISCV_IOMMU_REG_MSI_CFG_TBL 0x0300
#define RISCV_IOMMU_REG_MSI_CFG_TBL_ADDR(_n) \
@@ -292,6 +300,10 @@ enum riscv_iommu_hpmevent_id {

#define RISCV_IOMMU_REG_SIZE 0x1000

+/* SpacemiT IOMMU IOATC registers */
+#define MAX_RISCV_IOMMU_IOATC 64
+#define RISCV_IOMMU_IOATC_BASE(_idx) (((_idx) + 1) * RISCV_IOMMU_REG_SIZE)
+
/*
* Chapter 2: Data structures
*/
diff --git a/drivers/iommu/riscv/iommu-platform.c b/drivers/iommu/riscv/iommu-platform.c
index e8e52bca8856..469b8bfd2151 100644
--- a/drivers/iommu/riscv/iommu-platform.c
+++ b/drivers/iommu/riscv/iommu-platform.c
@@ -75,8 +75,8 @@ static int riscv_iommu_platform_probe(struct platform_device *pdev)
if (iommu->irqs_count <= 0)
return dev_err_probe(dev, -ENODEV,
"no IRQ resources provided\n");
- if (iommu->irqs_count > RISCV_IOMMU_INTR_COUNT)
- iommu->irqs_count = RISCV_IOMMU_INTR_COUNT;
+ if (iommu->irqs_count > RISCV_IOMMU_INTR_COUNT + MAX_RISCV_IOMMU_IOATC)
+ iommu->irqs_count = RISCV_IOMMU_INTR_COUNT + MAX_RISCV_IOMMU_IOATC;

igs = FIELD_GET(RISCV_IOMMU_CAPABILITIES_IGS, iommu->caps);
switch (igs) {
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index 21ff7e50d115..e10011493228 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -55,9 +55,21 @@ struct riscv_iommu_devres {
void *addr;
};

+static unsigned int riscv_iommu_reg_ipsr(struct riscv_iommu_subdev *subdev)
+{
+ struct riscv_iommu_hpm_info *info = subdev->info;
+ unsigned int offset;
+
+ if (info && info->is_ioatc)
+ offset = RISCV_IOMMU_IOATC_BASE(info->index);
+ else
+ offset = 0;
+ return offset + RISCV_IOMMU_REG_IPSR;
+}
+
bool riscv_iommu_pmip_status(struct riscv_iommu_subdev *subdev)
{
- u32 ipsr = riscv_iommu_readl(subdev->iommu, RISCV_IOMMU_REG_IPSR);
+ u32 ipsr = riscv_iommu_readl(subdev->iommu, riscv_iommu_reg_ipsr(subdev));

return !!(ipsr & RISCV_IOMMU_IPSR_PMIP);
}
@@ -65,7 +77,7 @@ EXPORT_SYMBOL_GPL(riscv_iommu_pmip_status);

void riscv_iommu_clear_pmip(struct riscv_iommu_subdev *subdev)
{
- riscv_iommu_writel(subdev->iommu, RISCV_IOMMU_REG_IPSR,
+ riscv_iommu_writel(subdev->iommu, riscv_iommu_reg_ipsr(subdev),
RISCV_IOMMU_IPSR_PMIP);
}
EXPORT_SYMBOL_GPL(riscv_iommu_clear_pmip);
@@ -1710,6 +1722,7 @@ static void riscv_iommu_enumerate_hpm(struct riscv_iommu_device *iommu)
return;

hpm_info->irq = irq;
+ hpm_info->is_ioatc = false;

params = (struct riscv_iommu_subdev_params) {
.name = "riscv_iommu_hpm",
@@ -1731,6 +1744,95 @@ static void riscv_iommu_enumerate_hpm(struct riscv_iommu_device *iommu)
}
}

+struct riscv_iommu_ioatc_desc {
+ int irq;
+ u32 index;
+};
+
+static int riscv_iommu_collect_ioatcs(struct riscv_iommu_device *iommu,
+ struct riscv_iommu_ioatc_desc *descs,
+ int max_desc)
+{
+ struct device *dev = iommu->dev;
+ int count, index, i, j;
+ u32 dtisr, state;
+ int nr_ioats_irqs, nr_ioatc_irqs;
+
+ if (!of_device_is_compatible(dev->of_node, "spacemit,t100"))
+ return 0;
+
+ if (iommu->fctl & RISCV_IOMMU_FCTL_WSI)
+ nr_ioats_irqs = 1;
+ else
+ nr_ioats_irqs = RISCV_IOMMU_INTR_COUNT;
+
+ if (iommu->irqs_count > nr_ioats_irqs)
+ nr_ioatc_irqs = iommu->irqs_count - nr_ioats_irqs;
+ else
+ nr_ioatc_irqs = 0;
+
+ count = 0;
+ for (i = 0; i < 4 && count < max_desc; i++) {
+ dtisr = riscv_iommu_readl(iommu, RISCV_IOMMU_REG_DTISR(i));
+ for (j = 0; j < 16 && count < max_desc; j++) {
+ index = i * 16 + j;
+ state = (dtisr & RISCV_IOMMU_DTI_STS_MASK(index)) >>
+ RISCV_IOMMU_DTI_STS_SHIFT(index);
+ if (state != RISCV_IOMMU_DTI_STS_IOATC)
+ continue;
+ descs[count].index = index;
+ if (count < nr_ioatc_irqs && index < MAX_RISCV_IOMMU_IOATC)
+ descs[count].irq = iommu->irqs[count + nr_ioats_irqs];
+ else
+ descs[count].irq = 0;
+ count++;
+ }
+ }
+ return count;
+}
+
+static void riscv_iommu_enumerate_ioatc(struct riscv_iommu_device *iommu)
+{
+ struct riscv_iommu_ioatc_desc ioatcs[MAX_RISCV_IOMMU_IOATC];
+ struct riscv_iommu_hpm_info *ioatc_info;
+ struct riscv_iommu_subdev_params params;
+ void __iomem *base;
+ int nr_ioatcs, i, ret;
+
+ nr_ioatcs = riscv_iommu_collect_ioatcs(iommu, ioatcs, ARRAY_SIZE(ioatcs));
+ if (nr_ioatcs <= 0)
+ return;
+
+ for (i = 0; i < nr_ioatcs; i++) {
+ if (ioatcs[i].irq <= 0)
+ continue;
+
+ ioatc_info = kzalloc(sizeof(*ioatc_info), GFP_KERNEL);
+ if (!ioatc_info)
+ continue;
+
+ ioatc_info->irq = ioatcs[i].irq;
+ ioatc_info->index = ioatcs[i].index;
+ ioatc_info->global_filter = true;
+ ioatc_info->is_ioatc = true;
+
+ base = iommu->reg + RISCV_IOMMU_IOATC_BASE(ioatcs[i].index);
+
+ params = (struct riscv_iommu_subdev_params) {
+ .name = "spacemit_ioatc_hpm",
+ .info = ioatc_info,
+ .base = base + RISCV_IOMMU_REG_IOCOUNTOVF,
+ };
+
+ ret = riscv_iommu_subdev_add(iommu, &params);
+ if (ret) {
+ kfree(ioatc_info);
+ dev_warn(iommu->dev, "Failed to add IOATC%u: %d\n",
+ ioatcs[i].index, ret);
+ }
+ }
+}
+
/**
* riscv_iommu_subdev_setup - Enumerate auxiliary bus subdevices
*
@@ -1743,6 +1845,7 @@ static void riscv_iommu_enumerate_hpm(struct riscv_iommu_device *iommu)
void riscv_iommu_subdev_setup(struct riscv_iommu_device *iommu)
{
riscv_iommu_enumerate_hpm(iommu);
+ riscv_iommu_enumerate_ioatc(iommu);
}

/**
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index 1296625488ef..f1bb682dd478 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -51,7 +51,7 @@ struct riscv_iommu_device {
u32 fctl;

/* available interrupt numbers, MSI or WSI */
- unsigned int irqs[RISCV_IOMMU_INTR_COUNT];
+ unsigned int irqs[RISCV_IOMMU_INTR_COUNT + MAX_RISCV_IOMMU_IOATC];
unsigned int irqs_count;
unsigned int icvec;

diff --git a/drivers/perf/riscv_iommu_hpm.c b/drivers/perf/riscv_iommu_hpm.c
index efa65caef0dc..b166b3cb6d4f 100644
--- a/drivers/perf/riscv_iommu_hpm.c
+++ b/drivers/perf/riscv_iommu_hpm.c
@@ -681,6 +681,31 @@ static const struct attribute_group *riscv_iommu_hpm_attr_grps[] = {
NULL
};

+#define IOMMU_IOATC_EVENT_ATTR(_name, _id) \
+ PMU_EVENT_ATTR_ID(_name, riscv_iommu_hpm_event_show, _id)
+
+static struct attribute *riscv_iommu_hpm_ioatc_events[] = {
+ IOMMU_IOATC_EVENT_ATTR(cycles, RISCV_IOMMU_HPMEVENT_CYCLES),
+ IOMMU_IOATC_EVENT_ATTR(untrans_rq, RISCV_IOMMU_HPMEVENT_URQ),
+ IOMMU_IOATC_EVENT_ATTR(trans_rq, RISCV_IOMMU_HPMEVENT_TRQ),
+ IOMMU_IOATC_EVENT_ATTR(tlb_mis, RISCV_IOMMU_HPMEVENT_TLB_MISS),
+ NULL
+};
+
+static const struct attribute_group riscv_iommu_hpm_ioatc_events_group = {
+ .name = "events",
+ .attrs = riscv_iommu_hpm_ioatc_events,
+ .is_visible = riscv_iommu_hpm_event_is_visible,
+};
+
+static const struct attribute_group *riscv_iommu_hpm_ioatc_attr_grps[] = {
+ &riscv_iommu_hpm_cpumask_group,
+ &riscv_iommu_hpm_ioatc_events_group,
+ &riscv_iommu_hpm_format_group,
+ &riscv_iommu_hpm_vendor_group,
+ NULL
+};
+
static irqreturn_t riscv_iommu_hpm_handle_irq(int irq_num, void *data)
{
struct riscv_iommu_hpm *hpm = data;
@@ -826,6 +851,15 @@ static void riscv_iommu_hpm_set_standard_events(struct riscv_iommu_hpm *hpm)
set_bit(RISCV_IOMMU_HPMEVENT_G_WALKS, hpm->supported_events);
}

+static void riscv_iommu_hpm_set_ioatc_events(struct riscv_iommu_hpm *hpm)
+{
+ /* SpacemiT T100 IOATC: subset of events (URQ, TRQ, TLB_MISS) */
+ set_bit(RISCV_IOMMU_HPMEVENT_CYCLES, hpm->supported_events);
+ set_bit(RISCV_IOMMU_HPMEVENT_URQ, hpm->supported_events);
+ set_bit(RISCV_IOMMU_HPMEVENT_TRQ, hpm->supported_events);
+ set_bit(RISCV_IOMMU_HPMEVENT_TLB_MISS, hpm->supported_events);
+}
+
static int riscv_iommu_hpm_probe(struct auxiliary_device *auxdev,
const struct auxiliary_device_id *id)
{
@@ -834,6 +868,8 @@ static int riscv_iommu_hpm_probe(struct auxiliary_device *auxdev,
struct device *dev = &auxdev->dev;
struct riscv_iommu_hpm_info *info;
const char *hpm_name;
+ const struct attribute_group **attr_grps;
+ bool is_ioatc;
u32 val;
int err;

@@ -858,6 +894,7 @@ static int riscv_iommu_hpm_probe(struct auxiliary_device *auxdev,
hpm->on_cpu = raw_smp_processor_id();
hpm->irq = info->irq;
hpm->global_filter = info->global_filter;
+ is_ioatc = info->is_ioatc;

bitmap_zero(hpm->used_counters, RISCV_IOMMU_HPMCOUNTER_MAX);
bitmap_zero(hpm->supported_events, RISCV_IOMMU_HPMEVENT_MAX);
@@ -869,7 +906,13 @@ static int riscv_iommu_hpm_probe(struct auxiliary_device *auxdev,
return -ENODEV;

riscv_iommu_hpm_reset(hpm);
- riscv_iommu_hpm_set_standard_events(hpm);
+ if (is_ioatc)
+ riscv_iommu_hpm_set_ioatc_events(hpm);
+ else
+ riscv_iommu_hpm_set_standard_events(hpm);
+
+ attr_grps = is_ioatc ? riscv_iommu_hpm_ioatc_attr_grps :
+ riscv_iommu_hpm_attr_grps;

hpm_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL);
if (!hpm_name)
@@ -893,7 +936,7 @@ static int riscv_iommu_hpm_probe(struct auxiliary_device *auxdev,
.start = riscv_iommu_hpm_event_start,
.stop = riscv_iommu_hpm_event_stop,
.read = riscv_iommu_hpm_event_update,
- .attr_groups = riscv_iommu_hpm_attr_grps,
+ .attr_groups = attr_grps,
.capabilities = PERF_PMU_CAP_NO_EXCLUDE,
};

@@ -931,6 +974,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" },
+ { .name = "iommu.spacemit_ioatc_hpm" },
{}
};
MODULE_DEVICE_TABLE(auxiliary, riscv_iommu_hpm_ids);
diff --git a/include/linux/riscv_iommu.h b/include/linux/riscv_iommu.h
index 6af592dfaa00..71a961731c22 100644
--- a/include/linux/riscv_iommu.h
+++ b/include/linux/riscv_iommu.h
@@ -34,13 +34,17 @@ struct riscv_iommu_subdev {
};

/**
- * struct riscv_iommu_hpm_info - HPM info for IOATS (main IOMMU HPM)
+ * struct riscv_iommu_hpm_info - HPM info for IOATS and IOATC
* @irq: interrupt number
* @global_filter: true if single global filter
+ * @is_ioatc: false for IOATS, true for IOATC
+ * @index: DTISR index for IOATC (0-63), 0 for IOATS
*/
struct riscv_iommu_hpm_info {
unsigned int irq;
bool global_filter;
+ bool is_ioatc;
+ u8 index;
};

/**
--
2.43.0