[PATCH 6/6] spacemit/t100: Add SpacemiT T100 IOATC HPM support

Lv Zheng
2026-01-28T02:25:29Z

This patch adds 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 IOTLBs controllers are called
as IOATC, this patch adds distributed HPM support for IOATCs.

Signed-off-by: Lv Zheng <lv.zheng@spacemit.com>
Signed-off-by: Jingyu Li <joey.li@spacemit.com>
---
drivers/iommu/riscv/iommu-bits.h | 12 ++
drivers/iommu/riscv/iommu-hpm.c | 173 ++++++++++++++++++++++++++-
drivers/iommu/riscv/iommu-platform.c | 4 +-
drivers/iommu/riscv/iommu.h | 2 +-
4 files changed, 184 insertions(+), 7 deletions(-)

diff --git a/drivers/iommu/riscv/iommu-bits.h b/drivers/iommu/riscv/iommu-bits.h
index d85f2ae898dd..b62869c906be 100644
--- a/drivers/iommu/riscv/iommu-bits.h
+++ b/drivers/iommu/riscv/iommu-bits.h
@@ -283,6 +283,18 @@ 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_TBU_IOATC 0x1
+
+#define MAX_RISCV_IOMMU_IOATC 64
+#define RISCV_IOMMU_IOATC_BASE(_base, _idx) \
+ ((void __iomem *)((u8 __iomem *)(_base) + ((_idx) + 1) * RISCV_IOMMU_REG_SIZE))
+
/* 5.28 MSI Configuration table (32 * 64bits) */
#define RISCV_IOMMU_REG_MSI_CFG_TBL 0x0300
#define RISCV_IOMMU_REG_MSI_CFG_TBL_ADDR(_n) \
diff --git a/drivers/iommu/riscv/iommu-hpm.c b/drivers/iommu/riscv/iommu-hpm.c
index 1273e5e12020..bb3d545515ec 100644
--- a/drivers/iommu/riscv/iommu-hpm.c
+++ b/drivers/iommu/riscv/iommu-hpm.c
@@ -7,6 +7,7 @@
* Lv Zheng <lv.zheng@spacemit.com>
*/

+#include <linux/platform_device.h>
#include "iommu.h"

#define to_iommu_hpm(p) (container_of(p, struct riscv_iommu_hpm, pmu))
@@ -31,6 +32,94 @@ static atomic_t riscv_iommu_hpm_ids = ATOMIC_INIT(0);
static int cpuhp_state_num = -1;
static int cpuhp_refcnt;

+struct riscv_iommu_ioatc_desc {
+ u32 offset;
+ int irq;
+ u32 index;
+};
+
+static int riscv_iommu_hpm_collect_ioatcs(struct riscv_iommu_device *iommu,
+ struct riscv_iommu_ioatc_desc *descs,
+ int max_desc)
+{
+ struct device *dev = iommu->dev;
+ struct device_node *np = dev->of_node;
+ struct platform_device *pdev = to_platform_device(dev);
+ int count = 0;
+ int i;
+ int *ioatc_irqs = NULL;
+
+ if (!descs || max_desc <= 0)
+ return 0;
+
+ if (np) {
+ int names_count = of_property_count_strings(np, "interrupt-names");
+ const char *name;
+ int irq_idx;
+ u32 ioatc_idx;
+
+ ioatc_irqs = kcalloc(MAX_RISCV_IOMMU_IOATC, sizeof(int), GFP_KERNEL);
+ if (!ioatc_irqs)
+ goto discover;
+
+ for (i = 0; i < names_count; i++) {
+ if (of_property_read_string_index(np,
+ "interrupt-names", i, &name))
+ continue;
+
+ if (!strstr(name, "ioatc") || !strstr(name, "hpm"))
+ continue;
+
+ if (sscanf(name, "ioatc%u-hpm", &ioatc_idx) != 1)
+ continue;
+
+ if (ioatc_idx >= MAX_RISCV_IOMMU_IOATC)
+ continue;
+
+ if (i < iommu->irqs_count) {
+ irq_idx = iommu->irqs[i];
+ ioatc_irqs[ioatc_idx] = irq_idx;
+ } else {
+ irq_idx = platform_get_irq(pdev, i);
+ if (irq_idx < 0)
+ ioatc_irqs[ioatc_idx] = 0;
+ else
+ ioatc_irqs[ioatc_idx] = irq_idx;
+ }
+ }
+ }
+
+discover:
+ /* Automatically discover IOATCs by scanning DTISR registers */
+ for (i = 0; i < 4 && count < max_desc; i++) {
+ u32 dtisr = riscv_iommu_readl(iommu, RISCV_IOMMU_REG_DTISR(i));
+ int j;
+
+ for (j = 0; j < 16 && count < max_desc; j++) {
+ u32 idx = i * 16 + j;
+ u32 state;
+
+ state = (dtisr & RISCV_IOMMU_DTI_STS_MASK(idx)) >>
+ RISCV_IOMMU_DTI_STS_SHIFT(idx);
+ if (state == RISCV_IOMMU_DTI_STS_TBU_IOATC) {
+ descs[count].offset = (idx + 1) * RISCV_IOMMU_REG_SIZE;
+ descs[count].index = idx;
+
+ if (ioatc_irqs && idx < MAX_RISCV_IOMMU_IOATC
+ && ioatc_irqs[idx] > 0)
+ descs[count].irq = ioatc_irqs[idx];
+ else
+ descs[count].irq = 0;
+ count++;
+ }
+ }
+ }
+
+ kfree(ioatc_irqs);
+
+ return count;
+}
+
static inline void riscv_iommu_hpm_writel(struct riscv_iommu_hpm *hpm, u32 reg,
u32 val)
{
@@ -565,6 +654,43 @@ static const struct attribute_group *riscv_iommu_hpm_attr_grps[] = {
NULL
};

+#define IOMMU_IOATC_EVENT_ATTR(_name, _id) \
+ PMU_EVENT_ATTR_ID(ioatc_##_name, riscv_iommu_hpm_event_show, _id)
+
+static struct attribute *riscv_iommu_ioatc_events[] = {
+ IOMMU_IOATC_EVENT_ATTR(untranslated_requests, 1),
+ IOMMU_IOATC_EVENT_ATTR(translated_requests, 2),
+ IOMMU_IOATC_EVENT_ATTR(tlb_miss, 4),
+ IOMMU_IOATC_EVENT_ATTR(translation_requests, 21),
+ IOMMU_IOATC_EVENT_ATTR(translations_issued, 24),
+ IOMMU_IOATC_EVENT_ATTR(transactions_unissued_slot_drain, 25),
+ IOMMU_IOATC_EVENT_ATTR(transactions_unissued_token_drain, 26),
+ IOMMU_IOATC_EVENT_ATTR(write_transactions_unissued_wb_full, 27),
+ IOMMU_IOATC_EVENT_ATTR(write_transactions_using_wb, 28),
+ IOMMU_IOATC_EVENT_ATTR(write_transactions_not_using_wb, 29),
+ IOMMU_IOATC_EVENT_ATTR(makeinvalid_downgrades, 30),
+ IOMMU_IOATC_EVENT_ATTR(stash_fails, 31),
+ IOMMU_IOATC_EVENT_ATTR(mtlb_lookups, 56),
+ IOMMU_IOATC_EVENT_ATTR(mtlb_misses, 57),
+ IOMMU_IOATC_EVENT_ATTR(utlb_lookups, 58),
+ IOMMU_IOATC_EVENT_ATTR(utlb_misses, 59),
+ IOMMU_IOATC_EVENT_ATTR(mtlb_reads, 65),
+ IOMMU_IOATC_EVENT_ATTR(mtlb_errors, 108),
+ NULL
+};
+
+static const struct attribute_group riscv_iommu_ioatc_events_group = {
+ .name = "events",
+ .attrs = riscv_iommu_ioatc_events,
+};
+
+static const struct attribute_group *riscv_iommu_ioatc_attr_grps[] = {
+ &riscv_iommu_hpm_cpumask_group,
+ &riscv_iommu_ioatc_events_group,
+ &riscv_iommu_hpm_format_group,
+ NULL
+};
+
static irqreturn_t riscv_iommu_hpm_handle_irq(int irq_num, void *data)
{
struct riscv_iommu_hpm *iommu_hpm = data;
@@ -753,6 +879,18 @@ static void riscv_iommu_hpm_set_standard_events(struct riscv_iommu_hpm *iommu_hp
set_bit(RISCV_IOMMU_HPMEVENT_G_WALKS, iommu_hpm->supported_events);
}

+static void riscv_iommu_hpm_set_ioatc_events(struct riscv_iommu_hpm *iommu_hpm)
+{
+ static const unsigned int ioatc_event_ids[] = {
+ 1, 2, 4, 21, 24, 25, 26, 27, 28, 29, 30, 31,
+ 56, 57, 58, 59, 65, 108,
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ioatc_event_ids); i++)
+ set_bit(ioatc_event_ids[i], iommu_hpm->supported_events);
+}
+
static void riscv_iommu_hpm_remove(void *data)
{
struct riscv_iommu_hpm *iommu_hpm = data;
@@ -763,7 +901,7 @@ static void riscv_iommu_hpm_remove(void *data)
static int riscv_iommu_hpm_register_unit(struct riscv_iommu_device *iommu,
struct riscv_iommu_hpm *iommu_hpm,
u32 offset, int irq,
- bool global_filter,
+ bool global_filter, bool ioatc,
const struct attribute_group **attr_groups,
const char *prefix, int index)
{
@@ -802,7 +940,10 @@ static int riscv_iommu_hpm_register_unit(struct riscv_iommu_device *iommu,
iommu_hpm->on_cpu = raw_smp_processor_id();
iommu_hpm->irq = irq;

- riscv_iommu_hpm_set_standard_events(iommu_hpm);
+ if (ioatc)
+ riscv_iommu_hpm_set_ioatc_events(iommu_hpm);
+ else
+ riscv_iommu_hpm_set_standard_events(iommu_hpm);
riscv_iommu_hpm_reset(iommu_hpm);

if (index >= 0)
@@ -908,8 +1049,9 @@ static void riscv_iommu_hpm_exit(void)
int riscv_iommu_add_hpm(struct riscv_iommu_device *iommu)
{
struct device *dev = iommu->dev;
+ struct riscv_iommu_ioatc_desc ioatcs[MAX_RISCV_IOMMU_IOATC];
struct attribute **vendor_attrs = NULL;
- int num_vendor_events;
+ int ioatc_count, num_vendor_events;
int irq;
int rc, i;

@@ -930,7 +1072,7 @@ int riscv_iommu_add_hpm(struct riscv_iommu_device *iommu)
return rc;

rc = riscv_iommu_hpm_register_unit(iommu, &iommu->hpm,
- 0, irq, true,
+ 0, irq, true, false,
riscv_iommu_hpm_attr_grps,
"riscv_iommu_hpm", -1);
if (rc < 0)
@@ -950,6 +1092,29 @@ int riscv_iommu_add_hpm(struct riscv_iommu_device *iommu)
}
}

+ ioatc_count = riscv_iommu_hpm_collect_ioatcs(iommu, ioatcs,
+ ARRAY_SIZE(ioatcs));
+ for (i = 0; i < ioatc_count; i++) {
+ struct riscv_iommu_hpm *extra;
+
+ extra = devm_kzalloc(dev, sizeof(*extra), GFP_KERNEL);
+ if (!extra)
+ continue;
+
+ rc = riscv_iommu_hpm_register_unit(iommu, extra,
+ ioatcs[i].offset,
+ ioatcs[i].irq,
+ true, true,
+ riscv_iommu_ioatc_attr_grps,
+ "riscv_iommu_ioatc",
+ ioatcs[i].index);
+ if (rc) {
+ dev_warn(dev,
+ "HPM: Failed to register IOATC%u PMU: %d\n",
+ ioatcs[i].index, rc);
+ }
+ }
+
return 0;

err_module:
diff --git a/drivers/iommu/riscv/iommu-platform.c b/drivers/iommu/riscv/iommu-platform.c
index 684bc267ac30..ca8de9ec5266 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.h b/drivers/iommu/riscv/iommu.h
index bafcdc7ff356..ba80de1bd19c 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -75,7 +75,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;

--
2.43.0

This message and any attachment are confidential and may be privileged or otherwise protected from disclosure. If you are not an intended recipient of this message, please delete it and any attachment from your system and notify the sender immediately by reply e-mail. Unintended recipients should not use, copy, disclose or take any action based on this message or any information contained in this message. Emails cannot be guaranteed to be secure or error free as they can be intercepted, amended, lost or destroyed, and you should take full responsibility for security checking.

本邮件及其任何附件具有保密性质，并可能受其他保护或不允许被披露给第三方。如阁下误收到本邮件，敬请立即以回复电子邮件的方式通知发件人，并将本邮件及其任何附件从阁下系统中予以删除。如阁下并非本邮件写明之收件人，敬请切勿使用、复制、披露本邮件或其任何内容，亦请切勿依本邮件或其任何内容而采取任何行动。电子邮件无法保证是一种安全和不会出现任何差错的通信方式，可能会被拦截、修改、丢失或损坏，收件人需自行负责做好安全检查。