From 8357e3efa8223334210a9d6c50293350d1d5f65b Mon Sep 17 00:00:00 2001 From: Jingyu Li Date: Sat, 28 Feb 2026 22:44:04 +0800 Subject: [PATCH 1/8] iommu/riscv: Enable IOMMU DMA mapping support Enable IOMMU DMA mapping support for RISC-V, so that DMACs can be tested with translation enabled. Known Possible Issue: 1. When CONFIG_IOMMU_DMA is enabled, on the tested Linux, RISC-V IOMMU is lack of PCIe support, causing riscv_iommu_fault:522 in dealing with NVMe PCIe devices. Signed-off-by: Jingyu Li Signed-off-by: Lv Zheng Signed-off-by: Linux RISC-V bot --- drivers/iommu/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index f86262b11416d1..34d8a792339f54 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -151,7 +151,7 @@ config OF_IOMMU # IOMMU-agnostic DMA-mapping layer config IOMMU_DMA - def_bool ARM64 || X86 || S390 + def_bool ARM64 || X86 || S390 || RISCV select DMA_OPS_HELPERS select IOMMU_API select IOMMU_IOVA From b0afc1e6a48c17ffa5ed552bd76e948ab6b70d3a Mon Sep 17 00:00:00 2001 From: Jingyu Li Date: Sat, 28 Feb 2026 22:44:14 +0800 Subject: [PATCH 2/8] iommu/riscv: Add auxiliary bus framework and HPM device support Introduces auxiliary bus support for RISC-V IOMMU to enable modular extension of IOMMU capabilities. The framework allows creating auxiliary devices that can be bound to separate drivers. The IOMMU HPM featured PMU device ("iommu.riscv_iommu_hpm.0") is created and registered as RISC-V IOMMU auxiliary device. Signed-off-by: Jingyu Li Signed-off-by: Lv Zheng Link: https://github.com/riscv-non-isa/riscv-iommu Cc: Zong Li Cc: Yaxing Guo Signed-off-by: Linux RISC-V bot --- drivers/iommu/riscv/Kconfig | 1 + drivers/iommu/riscv/iommu-pci.c | 8 +- drivers/iommu/riscv/iommu-platform.c | 3 + drivers/iommu/riscv/iommu.c | 169 +++++++++++++++++++++++++++ drivers/iommu/riscv/iommu.h | 21 ++++ include/linux/riscv_iommu.h | 75 ++++++++++++ 6 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 include/linux/riscv_iommu.h diff --git a/drivers/iommu/riscv/Kconfig b/drivers/iommu/riscv/Kconfig index c071816f59a67b..26122a3a73d27d 100644 --- a/drivers/iommu/riscv/Kconfig +++ b/drivers/iommu/riscv/Kconfig @@ -6,6 +6,7 @@ config RISCV_IOMMU depends on RISCV && 64BIT default y select IOMMU_API + select AUXILIARY_BUS help Support for implementations of the RISC-V IOMMU architecture that complements the RISC-V MMU capabilities, providing similar address diff --git a/drivers/iommu/riscv/iommu-pci.c b/drivers/iommu/riscv/iommu-pci.c index d82d2b00904c77..478e72e9a285f4 100644 --- a/drivers/iommu/riscv/iommu-pci.c +++ b/drivers/iommu/riscv/iommu-pci.c @@ -34,6 +34,8 @@ static int riscv_iommu_pci_probe(struct pci_dev *pdev, const struct pci_device_i { struct device *dev = &pdev->dev; struct riscv_iommu_device *iommu; + phys_addr_t reg_phys; + resource_size_t reg_size; int rc, vec; rc = pcim_enable_device(pdev); @@ -43,7 +45,9 @@ static int riscv_iommu_pci_probe(struct pci_dev *pdev, const struct pci_device_i if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) return -ENODEV; - if (pci_resource_len(pdev, 0) < RISCV_IOMMU_REG_SIZE) + reg_phys = pci_resource_start(pdev, 0); + reg_size = pci_resource_len(pdev, 0); + if (reg_size < RISCV_IOMMU_REG_SIZE) return -ENODEV; rc = pcim_iomap_regions(pdev, BIT(0), pci_name(pdev)); @@ -56,6 +60,8 @@ static int riscv_iommu_pci_probe(struct pci_dev *pdev, const struct pci_device_i iommu->dev = dev; iommu->reg = pcim_iomap_table(pdev)[0]; + iommu->reg_phys = reg_phys; + iommu->reg_size = reg_size; pci_set_master(pdev); dev_set_drvdata(dev, iommu); diff --git a/drivers/iommu/riscv/iommu-platform.c b/drivers/iommu/riscv/iommu-platform.c index 83a28c83f99145..e8e52bca885660 100644 --- a/drivers/iommu/riscv/iommu-platform.c +++ b/drivers/iommu/riscv/iommu-platform.c @@ -62,6 +62,9 @@ static int riscv_iommu_platform_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(iommu->reg), "could not map register region\n"); + iommu->reg_phys = res->start; + iommu->reg_size = resource_size(res); + dev_set_drvdata(dev, iommu); /* Check device reported capabilities / features. */ diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c index 2d8fb0859f30fc..3fe29d9643ebf3 100644 --- a/drivers/iommu/riscv/iommu.c +++ b/drivers/iommu/riscv/iommu.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -47,11 +48,28 @@ static DEFINE_IDA(riscv_iommu_pscids); #define RISCV_IOMMU_MAX_PSCID (BIT(20) - 1) +static DEFINE_IDA(riscv_iommu_subdev_ida); + /* Device resource-managed allocations */ struct riscv_iommu_devres { void *addr; }; +bool riscv_iommu_pmip_status(struct riscv_iommu_subdev *subdev) +{ + u32 ipsr = riscv_iommu_readl(subdev->iommu, RISCV_IOMMU_REG_IPSR); + + return !!(ipsr & RISCV_IOMMU_IPSR_PMIP); +} +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_IPSR_PMIP); +} +EXPORT_SYMBOL_GPL(riscv_iommu_clear_pmip); + static void riscv_iommu_devres_pages_release(struct device *dev, void *res) { struct riscv_iommu_devres *devres = res; @@ -1602,10 +1620,154 @@ static int riscv_iommu_init_check(struct riscv_iommu_device *iommu) return 0; } +static void riscv_iommu_subdev_release(struct device *dev) +{ + struct riscv_iommu_subdev *subdev = riscv_iommu_get_subdev(dev); + + ida_free(&riscv_iommu_subdev_ida, subdev->auxdev.id); + kfree(subdev->info); + kfree(subdev); +} + +static int riscv_iommu_subdev_add(struct riscv_iommu_device *iommu, + const struct riscv_iommu_subdev_params *params) +{ + struct riscv_iommu_subdev *subdev; + struct auxiliary_device *auxdev; + int id, ret; + + if (!params->info) + return -EINVAL; + + id = ida_alloc(&riscv_iommu_subdev_ida, GFP_KERNEL); + if (id < 0) + return id; + + subdev = kzalloc(sizeof(*subdev), GFP_KERNEL); + if (!subdev) { + ret = -ENOMEM; + goto err_free; + } + + subdev->base = params->base; + subdev->iommu = iommu; + subdev->info = params->info; + + auxdev = &subdev->auxdev; + auxdev->name = params->name; + auxdev->id = id; + auxdev->dev.parent = iommu->dev; + auxdev->dev.release = riscv_iommu_subdev_release; + + ret = auxiliary_device_init(auxdev); + if (ret) { + dev_err(iommu->dev, "Failed to init %s auxiliary device: %d\n", + params->name, ret); + goto err_free; + } + + ret = auxiliary_device_add(auxdev); + if (ret) { + dev_err(iommu->dev, "Failed to add %s auxiliary device: %d\n", + params->name, ret); + goto err_uninit; + } + + spin_lock(&iommu->subdev_lock); + list_add_tail(&subdev->link, &iommu->subdev_list); + spin_unlock(&iommu->subdev_lock); + dev_info(iommu->dev, "%s auxiliary device created\n", params->name); + return 0; + +err_uninit: + auxiliary_device_uninit(auxdev); + return ret; + +err_free: + kfree(subdev); + ida_free(&riscv_iommu_subdev_ida, id); + return ret; +} + +static void riscv_iommu_enumerate_hpm(struct riscv_iommu_device *iommu) +{ + struct riscv_iommu_hpm_info *hpm_info; + struct riscv_iommu_subdev_params params; + int irq; + int ret; + + if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_HPM)) + return; + + irq = iommu->irqs[riscv_iommu_queue_vec(iommu, RISCV_IOMMU_INTR_PM)]; + if (irq <= 0) { + dev_err(iommu->dev, "HPM: No IRQ available\n"); + return; + } + + hpm_info = kzalloc(sizeof(*hpm_info), GFP_KERNEL); + if (!hpm_info) + return; + + hpm_info->irq = irq; + + params = (struct riscv_iommu_subdev_params) { + .name = "riscv_iommu_hpm", + .info = hpm_info, + .base = iommu->reg + RISCV_IOMMU_REG_IOCOUNTOVF, + }; + + ret = riscv_iommu_subdev_add(iommu, ¶ms); + if (ret) { + kfree(hpm_info); + dev_warn(iommu->dev, + "Failed to enumerate HPM auxiliary device: %d\n", + ret); + } +} + +/** + * riscv_iommu_subdev_setup - Enumerate auxiliary bus subdevices + * + * @iommu: RISC-V IOMMU device + * + * Enumerates HPM, or other extended subdevices via the auxiliary bus. To + * add new extended device types, implement an enumerate function and call + * it from here. + */ +void riscv_iommu_subdev_setup(struct riscv_iommu_device *iommu) +{ + riscv_iommu_enumerate_hpm(iommu); +} + +/** + * riscv_iommu_subdev_cleanup - Remove all auxiliary bus subdevices + * + * @iommu: RISC-V IOMMU device + * + * Iterates over the subdev_list in reverse order, deletes each auxiliary + * device from the bus and uninitializes it. + */ +void riscv_iommu_subdev_cleanup(struct riscv_iommu_device *iommu) +{ + struct riscv_iommu_subdev *subdev, *next; + + spin_lock(&iommu->subdev_lock); + list_for_each_entry_safe_reverse(subdev, next, &iommu->subdev_list, link) { + list_del_init(&subdev->link); + spin_unlock(&iommu->subdev_lock); + auxiliary_device_delete(&subdev->auxdev); + auxiliary_device_uninit(&subdev->auxdev); + spin_lock(&iommu->subdev_lock); + } + spin_unlock(&iommu->subdev_lock); +} + void riscv_iommu_remove(struct riscv_iommu_device *iommu) { iommu_device_unregister(&iommu->iommu); iommu_device_sysfs_remove(&iommu->iommu); + riscv_iommu_subdev_cleanup(iommu); riscv_iommu_iodir_set_mode(iommu, RISCV_IOMMU_DDTP_IOMMU_MODE_OFF); riscv_iommu_queue_disable(&iommu->cmdq); riscv_iommu_queue_disable(&iommu->fltq); @@ -1615,6 +1777,8 @@ int riscv_iommu_init(struct riscv_iommu_device *iommu) { int rc; + spin_lock_init(&iommu->subdev_lock); + INIT_LIST_HEAD(&iommu->subdev_list); RISCV_IOMMU_QUEUE_INIT(&iommu->cmdq, CQ); RISCV_IOMMU_QUEUE_INIT(&iommu->fltq, FQ); @@ -1669,6 +1833,11 @@ int riscv_iommu_init(struct riscv_iommu_device *iommu) goto err_remove_sysfs; } + /* Initialize auxiliary devices for extended features. These are not + * critical to IOMMU operation, so failures are non-fatal. + */ + riscv_iommu_subdev_setup(iommu); + return 0; err_remove_sysfs: diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h index 46df79dd549570..1296625488ef33 100644 --- a/drivers/iommu/riscv/iommu.h +++ b/drivers/iommu/riscv/iommu.h @@ -14,6 +14,7 @@ #include #include #include +#include #include "iommu-bits.h" @@ -42,6 +43,8 @@ struct riscv_iommu_device { /* hardware control register space */ void __iomem *reg; + phys_addr_t reg_phys; + resource_size_t reg_size; /* supported and enabled hardware capabilities */ u64 caps; @@ -60,11 +63,29 @@ struct riscv_iommu_device { unsigned int ddt_mode; dma_addr_t ddt_phys; u64 *ddt_root; + + /* auxiliary subdevices */ + spinlock_t subdev_lock; + struct list_head subdev_list; +}; + +/** + * struct riscv_iommu_subdev_params - params for adding auxiliary subdevice + * @name: auxiliary device name + * @info: device-specific info, freed in release + * @base: PMU register base + */ +struct riscv_iommu_subdev_params { + const char *name; + void *info; + void __iomem *base; }; int riscv_iommu_init(struct riscv_iommu_device *iommu); void riscv_iommu_remove(struct riscv_iommu_device *iommu); void riscv_iommu_disable(struct riscv_iommu_device *iommu); +void riscv_iommu_subdev_setup(struct riscv_iommu_device *iommu); +void riscv_iommu_subdev_cleanup(struct riscv_iommu_device *iommu); #define riscv_iommu_readl(iommu, addr) \ readl_relaxed((iommu)->reg + (addr)) diff --git a/include/linux/riscv_iommu.h b/include/linux/riscv_iommu.h new file mode 100644 index 00000000000000..0447bc4d1fab77 --- /dev/null +++ b/include/linux/riscv_iommu.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * RISC-V IOMMU Common Interface + * + * This header provides a common interface for sharing resources between + * the RISC-V IOMMU driver and its auxiliary bus child drivers. + * + * Copyright (C) 2026 SpacemiT Technologies Inc. + * Author: 2026 Jingyu Li + * Lv Zheng + */ + +#ifndef _LINUX_RISCV_IOMMU_H_ +#define _LINUX_RISCV_IOMMU_H_ + +#include + +struct riscv_iommu_device; + +/** + * struct riscv_iommu_subdev - RISC-V IOMMU auxiliary bus subdevice + * @link: list node for iommu->subdev_list + * @auxdev: auxiliary bus device ((use auxdev.id for unique id) + * @base: PMU register base + * @iommu: parent IOMMU (opaque) + * @info: subdevice-specific info, freed in release + */ +struct riscv_iommu_subdev { + struct list_head link; + struct auxiliary_device auxdev; + void __iomem *base; + struct riscv_iommu_device *iommu; + void *info; +}; + +/** + * struct riscv_iommu_hpm_info - HPM info for IOATS (main IOMMU HPM) + * @irq: interrupt number + */ +struct riscv_iommu_hpm_info { + unsigned int irq; +}; + +/** + * riscv_iommu_get_subdev - get riscv_iommu_subdev from device + * + * @dev: &device of the auxiliary device (auxdev->dev) + * + * Returns the riscv_iommu_subdev pointer, or NULL if @dev is NULL. + */ +static inline struct riscv_iommu_subdev *riscv_iommu_get_subdev(struct device *dev) +{ + if (!dev) + return NULL; + return container_of(container_of(dev, struct auxiliary_device, dev), + struct riscv_iommu_subdev, auxdev); +} + +/** + * riscv_iommu_pmip_status - test if PM interrupt is pending + * + * @subdev: subdevice with iommu + * + * Returns true if PM interrupt pending, false otherwise. + */ +bool riscv_iommu_pmip_status(struct riscv_iommu_subdev *subdev); + +/** + * riscv_iommu_clear_pmip - clear PMIP bit in IPSR to ack PMU interrupt + * + * @subdev: subdevice with iommu + */ +void riscv_iommu_clear_pmip(struct riscv_iommu_subdev *subdev); + +#endif /* _LINUX_RISCV_IOMMU_H_ */ From 41553b343e7b99a9b60375588ddf32e306e384d3 Mon Sep 17 00:00:00 2001 From: Jingyu Li Date: Sat, 28 Feb 2026 22:44:36 +0800 Subject: [PATCH 3/8] iommu/riscv: Add HPM support for performance monitoring Introduces perf-based HPM driver for RISC-V IOMMU, enabling performance monitoring capabilities. Note that the RISC-V IOMMU HPM module uses COUNTER_MAX-1 as a static counter index of HPMCYCLES, and 0~COUNTER_MAX-2 as the dynamic counter indexes of other HPMEVENTS in order to correctly index into IOHPMEVT and IOHPMCTR registers that have already been defined in the iommu-bits.h. However the users treat 0 as the index of HPMCYCLES and 1~COUNTER_MAX-1 as the indexes of other HPMEVENTS, thus care should be taken in dealing with counter indexes between userspace and kernel space. Signed-off-by: Jingyu Li Signed-off-by: Lv Zheng Link: https://github.com/riscv-non-isa/riscv-iommu Cc: Zong Li Cc: Yaxing Guo Signed-off-by: Linux RISC-V bot --- drivers/perf/Kconfig | 9 + drivers/perf/Makefile | 1 + drivers/perf/riscv_iommu_hpm.c | 864 +++++++++++++++++++++++++++++++++ 3 files changed, 874 insertions(+) create mode 100644 drivers/perf/riscv_iommu_hpm.c diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig index 638321fc9800ca..1edf66d8ae4171 100644 --- a/drivers/perf/Kconfig +++ b/drivers/perf/Kconfig @@ -311,4 +311,13 @@ config MARVELL_PEM_PMU Enable support for PCIe Interface performance monitoring on Marvell platform. +config RISCV_IOMMU_HPM + tristate "RISC-V IOMMU HPM support" + depends on RISCV_IOMMU && PERF_EVENTS + help + Support for the Hardware Performance Monitor (HPM) in RISC-V IOMMU, + which provides monitoring of transactions passing through the + IOMMU and allows the resulting information to be filtered based + on the device/process ID of the corresponding master. + endmenu diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile index ea52711a87e326..89a347232c29c7 100644 --- a/drivers/perf/Makefile +++ b/drivers/perf/Makefile @@ -35,3 +35,4 @@ obj-$(CONFIG_DWC_PCIE_PMU) += dwc_pcie_pmu.o obj-$(CONFIG_ARM_CORESIGHT_PMU_ARCH_SYSTEM_PMU) += arm_cspmu/ obj-$(CONFIG_MESON_DDR_PMU) += amlogic/ obj-$(CONFIG_CXL_PMU) += cxl_pmu.o +obj-$(CONFIG_RISCV_IOMMU_HPM) += riscv_iommu_hpm.o diff --git a/drivers/perf/riscv_iommu_hpm.c b/drivers/perf/riscv_iommu_hpm.c new file mode 100644 index 00000000000000..4d5ce0f380e76a --- /dev/null +++ b/drivers/perf/riscv_iommu_hpm.c @@ -0,0 +1,864 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * RISC-V IOMMU Hardware Performance Monitor driver + * + * This driver provides perf-based performance monitoring for RISC-V IOMMU + * devices via the auxiliary bus framework. + * + * Copyright (C) 2026 SpacemiT Technologies Inc. + * Author: 2026 Jingyu Li + * Lv Zheng + */ + +#include +#include +#include + +/* Performance monitoring registers */ +#define RISCV_IOMMU_REG_IOCOUNTOVF 0x0000 +#define RISCV_IOMMU_IOCOUNTOVF_CY BIT(0) +#define RISCV_IOMMU_IOCOUNTOVF_HPM GENMASK_ULL(31, 1) + +#define RISCV_IOMMU_REG_IOCOUNTINH 0x0004 +#define RISCV_IOMMU_IOCOUNTINH_CY BIT(0) +#define RISCV_IOMMU_IOCOUNTINH_HPM GENMASK(31, 1) + +#define RISCV_IOMMU_REG_IOHPMCYCLES 0x0008 +#define RISCV_IOMMU_IOHPMCYCLES_COUNTER GENMASK_ULL(62, 0) +#define RISCV_IOMMU_IOHPMCYCLES_OF BIT_ULL(63) + +#define RISCV_IOMMU_REG_IOHPMCTR_BASE 0x0010 +#define RISCV_IOMMU_REG_IOHPMCTR(_n) (RISCV_IOMMU_REG_IOHPMCTR_BASE + ((_n) * 0x8)) +#define RISCV_IOMMU_IOHPMEVENT_COUNTER GENMASK_ULL(63, 0) + +#define RISCV_IOMMU_REG_IOHPMEVT_BASE 0x0108 +#define RISCV_IOMMU_REG_IOHPMEVT(_n) (RISCV_IOMMU_REG_IOHPMEVT_BASE + ((_n) * 0x8)) +#define RISCV_IOMMU_IOHPMEVT_EVENTID GENMASK_ULL(14, 0) +#define RISCV_IOMMU_IOHPMEVT_DMASK BIT_ULL(15) +#define RISCV_IOMMU_IOHPMEVT_PID_PSCID GENMASK_ULL(35, 16) +#define RISCV_IOMMU_IOHPMEVT_DID_GSCID GENMASK_ULL(59, 36) +#define RISCV_IOMMU_IOHPMEVT_PV_PSCV BIT_ULL(60) +#define RISCV_IOMMU_IOHPMEVT_DV_GSCV BIT_ULL(61) +#define RISCV_IOMMU_IOHPMEVT_IDT BIT_ULL(62) +#define RISCV_IOMMU_IOHPMEVT_OF BIT_ULL(63) + +/* HPM counter definitions */ +#define RISCV_IOMMU_IOHPMEVT_CNT 31 +#define RISCV_IOMMU_HPMCOUNTER_CYCLES RISCV_IOMMU_IOHPMEVT_CNT +#define RISCV_IOMMU_HPMCOUNTER_MAX (RISCV_IOMMU_IOHPMEVT_CNT + 1) + +/* HPM event IDs */ +enum riscv_iommu_hpmevent_id { + RISCV_IOMMU_HPMEVENT_INVALID = 0, + RISCV_IOMMU_HPMEVENT_URQ = 1, + RISCV_IOMMU_HPMEVENT_TRQ = 2, + RISCV_IOMMU_HPMEVENT_ATS_RQ = 3, + RISCV_IOMMU_HPMEVENT_TLB_MISS = 4, + RISCV_IOMMU_HPMEVENT_DD_WALK = 5, + RISCV_IOMMU_HPMEVENT_PD_WALK = 6, + RISCV_IOMMU_HPMEVENT_S_VS_WALKS = 7, + RISCV_IOMMU_HPMEVENT_G_WALKS = 8, + RISCV_IOMMU_HPMEVENT_MAX = 128 +}; + +#define RISCV_IOMMU_HPMEVENT_CYCLES RISCV_IOMMU_HPMEVENT_INVALID +#define RISCV_IOMMU_HPMEVENT_MAX 128 + +#define to_iommu_hpm(p) (container_of(p, struct riscv_iommu_hpm, pmu)) + +#define RISCV_IOMMU_HPM_EVENT_EXTRACTOR(_n, _c, _s, _e) \ + static inline u32 get_##_n(struct perf_event *event) \ + { \ + return FIELD_GET(GENMASK_ULL(_e, _s), \ + event->attr._c); \ + } + +RISCV_IOMMU_HPM_EVENT_EXTRACTOR(event, config, 0, 14); +RISCV_IOMMU_HPM_EVENT_EXTRACTOR(filter_dmask, config1, 15, 15); +RISCV_IOMMU_HPM_EVENT_EXTRACTOR(filter_pid_pscid, config1, 16, 35); +RISCV_IOMMU_HPM_EVENT_EXTRACTOR(filter_did_gscid, config1, 36, 59); +RISCV_IOMMU_HPM_EVENT_EXTRACTOR(filter_pv_pscv, config1, 60, 60); +RISCV_IOMMU_HPM_EVENT_EXTRACTOR(filter_dv_gscv, config1, 61, 61); +RISCV_IOMMU_HPM_EVENT_EXTRACTOR(filter_idt, config1, 62, 62); + +struct riscv_iommu_hpm { + struct pmu pmu; + struct riscv_iommu_subdev *subdev; + void __iomem *base; + unsigned int irq; + unsigned int on_cpu; + struct hlist_node node; + /* + * Layout of events: + * 0 -> HPMCYCLES + * 1...n-1 -> HPMEVENTS + */ + struct perf_event *events[RISCV_IOMMU_HPMCOUNTER_MAX]; + DECLARE_BITMAP(supported_events, RISCV_IOMMU_HPMEVENT_MAX); + DECLARE_BITMAP(used_counters, RISCV_IOMMU_HPMCOUNTER_MAX); + unsigned int num_counters; +}; + +static DEFINE_MUTEX(riscv_iommu_hpm_lock); +static int cpuhp_state_num = -1; + +static inline void riscv_iommu_hpm_writel(struct riscv_iommu_hpm *hpm, u32 reg, + u32 val) +{ + writel_relaxed(val, hpm->base + reg); +} + +static inline u32 riscv_iommu_hpm_readl(struct riscv_iommu_hpm *hpm, u32 reg) +{ + return readl_relaxed(hpm->base + reg); +} + +static inline void riscv_iommu_hpm_writeq(struct riscv_iommu_hpm *hpm, u32 reg, + u64 val) +{ + writeq_relaxed(val, hpm->base + reg); +} + +static inline u64 riscv_iommu_hpm_readq(struct riscv_iommu_hpm *hpm, u32 reg) +{ + return readq_relaxed(hpm->base + reg); +} + +/* All iohpmcycles and iohpmctr registers are 64-bit wide and WARL. If a + * 32-bit counter width should be supported according to a newer revision + * of the standard or a vendor specific implementation, the following + * functions should be extended. + */ +static inline void riscv_iommu_hpm_cycles_set_value(struct riscv_iommu_hpm *hpm, + u64 value) +{ + riscv_iommu_hpm_writeq(hpm, RISCV_IOMMU_REG_IOHPMCYCLES, + value & RISCV_IOMMU_IOHPMCYCLES_COUNTER); +} + +static inline u64 riscv_iommu_hpm_cycles_get_value(struct riscv_iommu_hpm *hpm) +{ + return riscv_iommu_hpm_readq(hpm, RISCV_IOMMU_REG_IOHPMCYCLES) & + RISCV_IOMMU_IOHPMCYCLES_COUNTER; +} + +static inline void riscv_iommu_hpm_counter_set_value(struct riscv_iommu_hpm *hpm, + u32 idx, u64 value) +{ + riscv_iommu_hpm_writeq(hpm, RISCV_IOMMU_REG_IOHPMCTR(idx), value); +} + +static inline u64 riscv_iommu_hpm_counter_get_value(struct riscv_iommu_hpm *hpm, + u32 idx) +{ + return riscv_iommu_hpm_readq(hpm, RISCV_IOMMU_REG_IOHPMCTR(idx)); +} + +static inline void riscv_iommu_hpm_cycles_enable(struct riscv_iommu_hpm *hpm) +{ + u32 val = riscv_iommu_hpm_readl(hpm, RISCV_IOMMU_REG_IOCOUNTINH); + + val &= ~RISCV_IOMMU_IOCOUNTINH_CY; + riscv_iommu_hpm_writel(hpm, RISCV_IOMMU_REG_IOCOUNTINH, val); +} + +static inline void riscv_iommu_hpm_cycles_disable(struct riscv_iommu_hpm *hpm) +{ + u32 val = riscv_iommu_hpm_readl(hpm, RISCV_IOMMU_REG_IOCOUNTINH); + + val |= RISCV_IOMMU_IOCOUNTINH_CY; + riscv_iommu_hpm_writel(hpm, RISCV_IOMMU_REG_IOCOUNTINH, val); +} + +static inline void riscv_iommu_hpm_counter_enable(struct riscv_iommu_hpm *hpm, + u32 idx) +{ + u32 val = riscv_iommu_hpm_readl(hpm, RISCV_IOMMU_REG_IOCOUNTINH); + + val &= ~BIT(idx + 1); + riscv_iommu_hpm_writel(hpm, RISCV_IOMMU_REG_IOCOUNTINH, val); +} + +static inline void riscv_iommu_hpm_counter_disable(struct riscv_iommu_hpm *hpm, + u32 idx) +{ + u32 val = riscv_iommu_hpm_readl(hpm, RISCV_IOMMU_REG_IOCOUNTINH); + + val |= BIT(idx + 1); + riscv_iommu_hpm_writel(hpm, RISCV_IOMMU_REG_IOCOUNTINH, val); +} + +static inline void riscv_iommu_hpm_cycles_clear_ovf(struct riscv_iommu_hpm *hpm) +{ + u64 val = riscv_iommu_hpm_readq(hpm, RISCV_IOMMU_REG_IOHPMCYCLES); + + val &= ~RISCV_IOMMU_IOHPMCYCLES_OF; + riscv_iommu_hpm_writeq(hpm, RISCV_IOMMU_REG_IOHPMCYCLES, val); +} + +static inline void riscv_iommu_hpm_counter_clear_ovf(struct riscv_iommu_hpm *hpm, + u32 idx) +{ + u64 val = riscv_iommu_hpm_readq(hpm, RISCV_IOMMU_REG_IOHPMEVT(idx)); + + val &= ~RISCV_IOMMU_IOHPMEVT_OF; + riscv_iommu_hpm_writeq(hpm, RISCV_IOMMU_REG_IOHPMEVT(idx), val); +} + +static inline void riscv_iommu_hpm_interrupt_clear(struct riscv_iommu_hpm *hpm) +{ + riscv_iommu_clear_pmip(hpm->subdev); +} + +/** + * riscv_iommu_hpm_event_update() - Update and return RISC-V IOMMU HPM + * event counters + * + * @event: IOMMU performance event + * + * This function can be used to implement the .read() interface of pmu. + */ +static void riscv_iommu_hpm_event_update(struct perf_event *event) +{ + struct hw_perf_event *hwc = &event->hw; + struct riscv_iommu_hpm *hpm = to_iommu_hpm(event->pmu); + u64 delta, prev, now; + u32 idx = hwc->idx; + + do { + prev = local64_read(&hwc->prev_count); + if (idx == RISCV_IOMMU_HPMCOUNTER_CYCLES) + now = riscv_iommu_hpm_cycles_get_value(hpm); + else + now = riscv_iommu_hpm_counter_get_value(hpm, idx); + } while (local64_cmpxchg(&hwc->prev_count, prev, now) != prev); + + delta = now - prev; + if (idx == RISCV_IOMMU_HPMCOUNTER_CYCLES) + delta &= RISCV_IOMMU_IOHPMCYCLES_COUNTER; + else + delta &= RISCV_IOMMU_IOHPMEVENT_COUNTER; + + local64_add(delta, &event->count); +} + +static void riscv_iommu_hpm_set_period(struct riscv_iommu_hpm *hpm, + struct hw_perf_event *hwc) +{ + u32 idx = hwc->idx; + u64 new, max_period; + + if (idx == RISCV_IOMMU_HPMCOUNTER_CYCLES) + max_period = RISCV_IOMMU_IOHPMCYCLES_COUNTER; + else + max_period = RISCV_IOMMU_IOHPMEVENT_COUNTER; + + /* Start at half the counter range */ + new = max_period >> 1; + + if (idx == RISCV_IOMMU_HPMCOUNTER_CYCLES) + riscv_iommu_hpm_cycles_set_value(hpm, new); + else + riscv_iommu_hpm_counter_set_value(hpm, idx, new); + + local64_set(&hwc->prev_count, new); +} + +/** + * riscv_iommu_hpm_event_start() - Start RISC-V IOMMU HPM event + * + * @event: IOMMU performance event + * @flags: Performance event flags + * + * This function can be used to implement the .start() interface of pmu. + */ +static void riscv_iommu_hpm_event_start(struct perf_event *event, int flags) +{ + struct riscv_iommu_hpm *hpm = to_iommu_hpm(event->pmu); + struct hw_perf_event *hwc = &event->hw; + u32 idx = hwc->idx; + + hwc->state = 0; + riscv_iommu_hpm_set_period(hpm, hwc); + + if (idx == RISCV_IOMMU_HPMCOUNTER_CYCLES) + riscv_iommu_hpm_cycles_enable(hpm); + else + riscv_iommu_hpm_counter_enable(hpm, idx); +} + +/** + * riscv_iommu_hpm_event_stop() - Stop RISC-V IOMMU HPM event + * + * @event: IOMMU performance event + * @flags: Performance event flags + * + * This function can be used to implement the .stop() interface of pmu. + */ +static void riscv_iommu_hpm_event_stop(struct perf_event *event, int flags) +{ + struct riscv_iommu_hpm *hpm = to_iommu_hpm(event->pmu); + struct hw_perf_event *hwc = &event->hw; + u32 idx = hwc->idx; + + if (hwc->state & PERF_HES_STOPPED) + return; + + if (idx == RISCV_IOMMU_HPMCOUNTER_CYCLES) + riscv_iommu_hpm_cycles_disable(hpm); + else + riscv_iommu_hpm_counter_disable(hpm, idx); + + if (flags & PERF_EF_UPDATE) + riscv_iommu_hpm_event_update(event); + hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE; +} + +static void riscv_iommu_hpm_set_event_filter(struct perf_event *event, int idx, + u32 pid_pscid, u32 did_gscid, + u32 pv_pscv, + u32 dv_gscv, u32 idt, u32 dmask) +{ + struct riscv_iommu_hpm *hpm = to_iommu_hpm(event->pmu); + u64 event_cfg; + + /* Start with event ID */ + event_cfg = get_event(event); + /* Set ID fields - values of 0 are valid */ + event_cfg |= FIELD_PREP(RISCV_IOMMU_IOHPMEVT_PID_PSCID, + pid_pscid & 0xFFFFF); + event_cfg |= FIELD_PREP(RISCV_IOMMU_IOHPMEVT_DID_GSCID, + did_gscid & 0xFFFFFF); + /* Set control flags - 0 means disabled, 1 means enabled */ + if (pv_pscv) + event_cfg |= RISCV_IOMMU_IOHPMEVT_PV_PSCV; + if (dv_gscv) + event_cfg |= RISCV_IOMMU_IOHPMEVT_DV_GSCV; + if (idt) + event_cfg |= RISCV_IOMMU_IOHPMEVT_IDT; + if (dmask) + event_cfg |= RISCV_IOMMU_IOHPMEVT_DMASK; + + /* Write to the specific event register for this counter */ + riscv_iommu_hpm_writeq(hpm, + 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) +{ + u32 pid_pscid, did_gscid, pv_pscv, dv_gscv, idt, dmask; + + pid_pscid = get_filter_pid_pscid(event); + did_gscid = get_filter_did_gscid(event); + pv_pscv = get_filter_pv_pscv(event); + dv_gscv = get_filter_dv_gscv(event); + idt = get_filter_idt(event); + dmask = get_filter_dmask(event); + + riscv_iommu_hpm_set_event_filter(event, idx, pid_pscid, did_gscid, + pv_pscv, dv_gscv, idt, dmask); +} + +static int riscv_iommu_hpm_get_event_idx(struct riscv_iommu_hpm *hpm, + struct perf_event *event) +{ + int idx; + unsigned int num_ctrs = hpm->num_counters; + u16 event_id = get_event(event); + + /* Handle cycles event specially */ + if (event_id == RISCV_IOMMU_HPMEVENT_CYCLES) { + /* Check if cycles counter is already in use */ + if (test_and_set_bit(RISCV_IOMMU_HPMCOUNTER_CYCLES, + hpm->used_counters)) { + dev_dbg(hpm->pmu.dev, + "Cycles counter already in use\n"); + return -EAGAIN; + } + return RISCV_IOMMU_HPMCOUNTER_CYCLES; + } + + idx = find_first_zero_bit(hpm->used_counters, num_ctrs - 1); + if (idx == num_ctrs - 1) { + dev_dbg(hpm->pmu.dev, "All counters already in use\n"); + return -EAGAIN; + } + + riscv_iommu_hpm_apply_event_filter(hpm, event, idx); + set_bit(idx, hpm->used_counters); + + return idx; +} + +/** + * riscv_iommu_hpm_event_add() - Add a RISC-V IOMMU HPM event + * + * @event - IOMMU performance event + * @flags - Performance event flags + * + * This function can be used to implement the .add() interface of pmu. + */ +static int riscv_iommu_hpm_event_add(struct perf_event *event, int flags) +{ + struct riscv_iommu_hpm *hpm = to_iommu_hpm(event->pmu); + struct hw_perf_event *hwc = &event->hw; + int idx; + + idx = riscv_iommu_hpm_get_event_idx(hpm, event); + if (idx < 0) + return idx; + + hwc->idx = idx; + if (idx == RISCV_IOMMU_HPMCOUNTER_CYCLES) + hpm->events[0] = event; + else + hpm->events[idx + 1] = event; + + hwc->state = PERF_HES_UPTODATE | PERF_HES_STOPPED; + if (flags & PERF_EF_START) + riscv_iommu_hpm_event_start(event, flags); + perf_event_update_userpage(event); + + return 0; +} + +/** + * riscv_iommu_hpm_event_del() - Delete a RISC-V IOMMU HPM event + * + * @event: IOMMU performance event + * @flags: Performance event flags + * + * This function can be used to implement the .del() interface of pmu. + */ +static void riscv_iommu_hpm_event_del(struct perf_event *event, int flags) +{ + struct riscv_iommu_hpm *hpm = to_iommu_hpm(event->pmu); + struct hw_perf_event *hwc = &event->hw; + u32 idx = hwc->idx; + + riscv_iommu_hpm_event_stop(event, flags | PERF_EF_UPDATE); + + /* Clear the used counter bit and event array entry */ + if (idx == RISCV_IOMMU_HPMCOUNTER_CYCLES) { + clear_bit(RISCV_IOMMU_HPMCOUNTER_CYCLES, + hpm->used_counters); + hpm->events[0] = NULL; + } else { + clear_bit(idx, hpm->used_counters); + hpm->events[idx + 1] = NULL; + } + + perf_event_update_userpage(event); +} + +/** + * riscv_iommu_hpm_event_init() - Initialize HPM event attributes + * + * @event: IOMMU performance event + * + * This function can be used to implement the .event_init() interface of + * pmu. + */ +static int riscv_iommu_hpm_event_init(struct perf_event *event) +{ + struct riscv_iommu_hpm *hpm = to_iommu_hpm(event->pmu); + struct hw_perf_event *hwc = &event->hw; + struct perf_event *sibling; + int group_num_events = 1; + u16 event_id; + + if (event->attr.type != event->pmu->type) + return -ENOENT; + if (hwc->sample_period) + return -EINVAL; + if (event->cpu < 0) + return -EINVAL; + + event_id = get_event(event); + if (event_id >= RISCV_IOMMU_HPMEVENT_MAX || + !test_bit(event_id, hpm->supported_events)) { + dev_dbg(hpm->pmu.dev, "Invalid event %d for this HPM\n", + event_id); + return -EINVAL; + } + + if (!is_software_event(event->group_leader)) { + if (++group_num_events > hpm->num_counters) + return -EINVAL; + } + + for_each_sibling_event(sibling, event->group_leader) { + if (is_software_event(sibling)) + continue; + if (++group_num_events > hpm->num_counters) + return -EINVAL; + } + + event->cpu = hpm->on_cpu; + hwc->idx = -1; + + return 0; +} + +static ssize_t riscv_iommu_hpm_cpumask_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct riscv_iommu_hpm *hpm = dev_get_drvdata(dev); + + return cpumap_print_to_pagebuf(true, buf, cpumask_of(hpm->on_cpu)); +} + +static struct device_attribute riscv_iommu_hpm_cpumask_attr = + __ATTR(cpumask, 0444, riscv_iommu_hpm_cpumask_show, NULL); + +static struct attribute *riscv_iommu_hpm_cpumask_attrs[] = { + &riscv_iommu_hpm_cpumask_attr.attr, + NULL +}; + +static const struct attribute_group riscv_iommu_hpm_cpumask_group = { + .attrs = riscv_iommu_hpm_cpumask_attrs, +}; + +#define IOMMU_HPM_EVENT_ATTR(name, config) \ + PMU_EVENT_ATTR_ID(name, riscv_iommu_hpm_event_show, config) + +static ssize_t riscv_iommu_hpm_event_show(struct device *dev, + struct device_attribute *attr, + char *page) +{ + struct perf_pmu_events_attr *hpm_attr; + + hpm_attr = container_of(attr, struct perf_pmu_events_attr, attr); + return sysfs_emit(page, "event=0x%02llx\n", hpm_attr->id); +} + +static struct attribute *riscv_iommu_hpm_events[] = { + IOMMU_HPM_EVENT_ATTR(cycles, RISCV_IOMMU_HPMEVENT_CYCLES), + IOMMU_HPM_EVENT_ATTR(untrans_rq, RISCV_IOMMU_HPMEVENT_URQ), + IOMMU_HPM_EVENT_ATTR(trans_rq, RISCV_IOMMU_HPMEVENT_TRQ), + IOMMU_HPM_EVENT_ATTR(ats_rq, RISCV_IOMMU_HPMEVENT_ATS_RQ), + IOMMU_HPM_EVENT_ATTR(tlb_mis, RISCV_IOMMU_HPMEVENT_TLB_MISS), + IOMMU_HPM_EVENT_ATTR(dd_walk, RISCV_IOMMU_HPMEVENT_DD_WALK), + IOMMU_HPM_EVENT_ATTR(pd_walk, RISCV_IOMMU_HPMEVENT_PD_WALK), + IOMMU_HPM_EVENT_ATTR(s_walk, RISCV_IOMMU_HPMEVENT_S_VS_WALKS), + IOMMU_HPM_EVENT_ATTR(g_walk, RISCV_IOMMU_HPMEVENT_G_WALKS), + NULL +}; + +static umode_t riscv_iommu_hpm_event_is_visible(struct kobject *kobj, + struct attribute *attr, + int unused) +{ + struct device *dev = kobj_to_dev(kobj); + struct riscv_iommu_hpm *hpm = dev_get_drvdata(dev); + struct perf_pmu_events_attr *hpm_attr; + + hpm_attr = container_of(attr, struct perf_pmu_events_attr, attr.attr); + if (test_bit(hpm_attr->id, hpm->supported_events)) + return attr->mode; + + return 0; +} + +static const struct attribute_group riscv_iommu_hpm_events_group = { + .name = "events", + .attrs = riscv_iommu_hpm_events, + .is_visible = riscv_iommu_hpm_event_is_visible, +}; + +PMU_FORMAT_ATTR(event, "config:0-14"); +PMU_FORMAT_ATTR(filter_pid_pscid, "config1:16-35"); +PMU_FORMAT_ATTR(filter_did_gscid, "config1:36-59"); +PMU_FORMAT_ATTR(filter_pv_pscv, "config1:60"); +PMU_FORMAT_ATTR(filter_dv_gscv, "config1:61"); +PMU_FORMAT_ATTR(filter_idt, "config1:62"); +PMU_FORMAT_ATTR(filter_dmask, "config1:15"); + +static struct attribute *riscv_iommu_hpm_formats[] = { + &format_attr_event.attr, + &format_attr_filter_pid_pscid.attr, + &format_attr_filter_did_gscid.attr, + &format_attr_filter_pv_pscv.attr, + &format_attr_filter_dv_gscv.attr, + &format_attr_filter_idt.attr, + &format_attr_filter_dmask.attr, + NULL +}; + +static const struct attribute_group riscv_iommu_hpm_format_group = { + .name = "format", + .attrs = riscv_iommu_hpm_formats, +}; + +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, + NULL +}; + +static irqreturn_t riscv_iommu_hpm_handle_irq(int irq_num, void *data) +{ + struct riscv_iommu_hpm *hpm = data; + struct perf_event *event; + int idx; + u32 ovf; + DECLARE_BITMAP(ovs, 32); + + if (!riscv_iommu_pmip_status(hpm->subdev)) + return IRQ_NONE; + + ovf = riscv_iommu_hpm_readl(hpm, RISCV_IOMMU_REG_IOCOUNTOVF); + if (!ovf) + return IRQ_HANDLED; + + /* Handle cycles counter overflow (always stored at index 0) */ + if (ovf & RISCV_IOMMU_IOCOUNTOVF_CY) { + event = hpm->events[0]; + if (event && event->hw.idx == RISCV_IOMMU_HPMCOUNTER_CYCLES) { + riscv_iommu_hpm_cycles_clear_ovf(hpm); + riscv_iommu_hpm_event_update(event); + riscv_iommu_hpm_set_period(hpm, &event->hw); + } + } + + /* + * Handle regular HPM counter overflows. + * IOCOUNTOVF bit mapping: + * bit 0: cycles (already handled above) + * bit 1: counter 0 -> events[1] + * bit 2: counter 1 -> events[2] + * ... + * bit N: counter N-1 -> events[N] + * We need to check bits [1..num_counters] and skip bit 0. + */ + bitmap_from_u64(ovs, ovf); + for_each_set_bit(idx, ovs, hpm->num_counters) { + /* Skip bit 0 (cycles counter, already handled) */ + if (idx == 0) + continue; + + /* IOCOUNTOVF bit N corresponds to counter N-1, stored in + * events[N] + */ + event = hpm->events[idx]; + if (WARN_ON_ONCE(!event)) + continue; + + dev_dbg(&hpm->subdev->auxdev.dev, "counter overflow: hw_idx=%d, counter=%d\n", + idx, idx - 1); + riscv_iommu_hpm_counter_clear_ovf(hpm, idx - 1); + riscv_iommu_hpm_event_update(event); + riscv_iommu_hpm_set_period(hpm, &event->hw); + } + + riscv_iommu_hpm_interrupt_clear(hpm); + + return IRQ_HANDLED; +} + +static int riscv_iommu_hpm_offline_cpu(unsigned int cpu, + struct hlist_node *node) +{ + struct riscv_iommu_hpm *hpm; + unsigned int target; + + hpm = hlist_entry_safe(node, struct riscv_iommu_hpm, node); + if (cpu != hpm->on_cpu) + return 0; + + if (!hpm->irq) + return 0; + + target = cpumask_any_but(cpu_online_mask, cpu); + if (target >= nr_cpu_ids) + return 0; + + perf_pmu_migrate_context(&hpm->pmu, cpu, target); + hpm->on_cpu = target; + if (hpm->irq > 0) + WARN_ON(irq_set_affinity(hpm->irq, cpumask_of(target))); + + return 0; +} + +/* Protected by riscv_iommu_hpm_lock. This block is self-contained; any + * modifications to cpuhp handling must preserve mutex serialization around + * cpuhp_state_num and the cpuhp_state_*() calls. + */ +static int riscv_iommu_hpm_cpuhp_add(struct hlist_node *node) +{ + int err = 0; + + mutex_lock(&riscv_iommu_hpm_lock); + if (cpuhp_state_num < 0) { + cpuhp_state_num = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, + "perf/riscv/iommu:online", + NULL, + riscv_iommu_hpm_offline_cpu); + if (cpuhp_state_num < 0) { + err = -EINVAL; + goto unlock; + } + } + err = cpuhp_state_add_instance_nocalls(cpuhp_state_num, node); +unlock: + mutex_unlock(&riscv_iommu_hpm_lock); + return err; +} + +static void riscv_iommu_hpm_cpuhp_remove(struct hlist_node *node) +{ + mutex_lock(&riscv_iommu_hpm_lock); + if (cpuhp_state_num >= 0) + cpuhp_state_remove_instance_nocalls(cpuhp_state_num, node); + mutex_unlock(&riscv_iommu_hpm_lock); +} + +static void riscv_iommu_hpm_reset(struct riscv_iommu_hpm *hpm) +{ + u64 counter_present_mask = (1ULL << hpm->num_counters) - 1; + + /* Disable all counters */ + riscv_iommu_hpm_writel(hpm, RISCV_IOMMU_REG_IOCOUNTINH, + counter_present_mask); + /* Clear interrupt pending status */ + riscv_iommu_hpm_interrupt_clear(hpm); +} + +static void riscv_iommu_hpm_set_standard_events(struct riscv_iommu_hpm *hpm) +{ + /* Cycles counter is always supported */ + set_bit(RISCV_IOMMU_HPMEVENT_CYCLES, hpm->supported_events); + + /* Standard RISC-V IOMMU HPM events */ + set_bit(RISCV_IOMMU_HPMEVENT_URQ, hpm->supported_events); + set_bit(RISCV_IOMMU_HPMEVENT_TRQ, hpm->supported_events); + set_bit(RISCV_IOMMU_HPMEVENT_ATS_RQ, hpm->supported_events); + set_bit(RISCV_IOMMU_HPMEVENT_TLB_MISS, hpm->supported_events); + set_bit(RISCV_IOMMU_HPMEVENT_DD_WALK, hpm->supported_events); + set_bit(RISCV_IOMMU_HPMEVENT_PD_WALK, hpm->supported_events); + set_bit(RISCV_IOMMU_HPMEVENT_S_VS_WALKS, hpm->supported_events); + set_bit(RISCV_IOMMU_HPMEVENT_G_WALKS, hpm->supported_events); +} + +static int riscv_iommu_hpm_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct riscv_iommu_subdev *subdev; + struct riscv_iommu_hpm *hpm; + struct device *dev = &auxdev->dev; + struct riscv_iommu_hpm_info *info; + const char *hpm_name; + u32 val; + int err; + + subdev = riscv_iommu_get_subdev(dev); + if (!subdev) { + dev_err(dev, "No auxiliary device data\n"); + return -ENODEV; + } + + info = subdev->info; + if (!info || info->irq <= 0) { + dev_err(dev, "HPM: No IRQ available\n"); + return -EINVAL; + } + + hpm = devm_kzalloc(dev, sizeof(*hpm), GFP_KERNEL); + if (!hpm) + return -ENOMEM; + + hpm->subdev = subdev; + hpm->base = subdev->base; + hpm->on_cpu = raw_smp_processor_id(); + hpm->irq = info->irq; + + bitmap_zero(hpm->used_counters, RISCV_IOMMU_HPMCOUNTER_MAX); + bitmap_zero(hpm->supported_events, RISCV_IOMMU_HPMEVENT_MAX); + + riscv_iommu_hpm_writel(hpm, RISCV_IOMMU_REG_IOCOUNTINH, 0xFFFFFFFF); + val = riscv_iommu_hpm_readl(hpm, RISCV_IOMMU_REG_IOCOUNTINH); + hpm->num_counters = hweight32(val & RISCV_IOMMU_IOCOUNTINH_HPM); + if (!hpm->num_counters) + return -ENODEV; + + riscv_iommu_hpm_reset(hpm); + riscv_iommu_hpm_set_standard_events(hpm); + + hpm_name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL); + if (!hpm_name) + return -ENOMEM; + + err = devm_request_threaded_irq(dev, hpm->irq, NULL, + riscv_iommu_hpm_handle_irq, + IRQF_SHARED | IRQF_ONESHOT, + hpm_name, hpm); + if (err) + return err; + WARN_ON(irq_set_affinity(hpm->irq, cpumask_of(hpm->on_cpu))); + + hpm->pmu = (struct pmu) { + .name = hpm_name, + .module = THIS_MODULE, + .task_ctx_nr = perf_invalid_context, + .event_init = riscv_iommu_hpm_event_init, + .add = riscv_iommu_hpm_event_add, + .del = riscv_iommu_hpm_event_del, + .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, + .capabilities = PERF_PMU_CAP_NO_EXCLUDE, + }; + + err = perf_pmu_register(&hpm->pmu, hpm_name, -1); + if (err) { + dev_err(dev, "HPM: Failed to register PMU\n"); + return err; + } + + dev_set_drvdata(dev, hpm); + + err = riscv_iommu_hpm_cpuhp_add(&hpm->node); + if (err) { + perf_pmu_unregister(&hpm->pmu); + return err; + } + + auxiliary_set_drvdata(auxdev, hpm); + + dev_info(dev, "HPM: Registered %s (%d counters, IRQ %d)\n", + hpm_name, hpm->num_counters, hpm->irq); + + return 0; +} + +static void riscv_iommu_hpm_remove(struct auxiliary_device *auxdev) +{ + struct riscv_iommu_hpm *hpm = auxiliary_get_drvdata(auxdev); + + riscv_iommu_hpm_cpuhp_remove(&hpm->node); + perf_pmu_unregister(&hpm->pmu); +} + +static const struct auxiliary_device_id riscv_iommu_hpm_ids[] = { + { .name = "iommu.riscv_iommu_hpm" }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, riscv_iommu_hpm_ids); + +static struct auxiliary_driver riscv_iommu_hpm_driver = { + .name = "riscv_iommu_hpm", + .id_table = riscv_iommu_hpm_ids, + .probe = riscv_iommu_hpm_probe, + .remove = riscv_iommu_hpm_remove, +}; + +module_auxiliary_driver(riscv_iommu_hpm_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("RISC-V IOMMU Hardware Performance Monitor"); From 12638c1e3d799b6476c40db820a5aab6e5aecd77 Mon Sep 17 00:00:00 2001 From: Lv Zheng Date: Sat, 28 Feb 2026 22:44:49 +0800 Subject: [PATCH 4/8] dt-bindings: iommu: Add spacemit/t100 features Adds device tree bindings for SpacemiT T100 specific features by introducing spacemit,t100 compatible. T100 contains distributed IOATCs, each of which exposes pmiv interrupt. Signed-off-by: Lv Zheng Signed-off-by: Jingyu Li Signed-off-by: Linux RISC-V bot --- .../bindings/iommu/riscv,iommu.yaml | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/iommu/riscv,iommu.yaml b/Documentation/devicetree/bindings/iommu/riscv,iommu.yaml index d4838c3b3741f0..6b775ebf7f42b4 100644 --- a/Documentation/devicetree/bindings/iommu/riscv,iommu.yaml +++ b/Documentation/devicetree/bindings/iommu/riscv,iommu.yaml @@ -30,8 +30,19 @@ properties: # Since PCI provides built-in identification methods, compatible is not # actually required. For non-PCIe hardware implementations 'riscv,iommu' # should be specified along with 'reg' property providing MMIO location. + # SpacemiT T100 (distributed IOMMU with split IOATCs placed in adjacent + # to the DMA masters) can be seen in SpacemiT SoCs. It is integerated in + # V100 (server SoC) with coherent page table walk supported and in K3 + # (client SoC) without coherent page table walk supported. compatible: oneOf: + - description: SpacemiT distributed IOMMUs + items: + - enum: + - spacemit,k3-t100 + - spacemit,v100-t100 + - const: spacemit,t100 + - const: riscv,iommu - items: - enum: - qemu,riscv-iommu @@ -57,11 +68,21 @@ properties: interrupts: minItems: 1 - maxItems: 4 + maxItems: 68 description: Wired interrupt vectors available for RISC-V IOMMU to notify the RISC-V HARTS. The cause to interrupt vector is software defined - using IVEC IOMMU register. + using ICVEC IOMMU register. For WSI only mode, the number of the + interrupt vectors should be 1 while for MSI possible mode, the + maximum of the interrupt vectors should be 4 with the cause indexed + as "CIV=0, FIV=1, PIV=2, PMIV=3". + SpacemiT distributed IOMMU includes additional interrupts for + IOATCs. Each IOATC exposes PMIV wired vector as standalone + interrupt and the maximum number of IOATCs can be up to 64. Thus for + WSI only mode, the maximum number of the interrupt vectors should be + 65 while for MSI possible mode, the maximum number of the interrupt + vectors should be 68 with the cause indexed as "IOATS CIV=0, + IOATS FIV=1, IOATS PIV=2, IOATS PMIV=3, IOATC0..n PMIV=4..4+n". msi-parent: true @@ -75,6 +96,18 @@ required: additionalProperties: false +allOf: + - if: + properties: + compatible: + not: + contains: + const: spacemit,t100 + then: + properties: + interrupts: + maxItems: 4 + examples: - |+ /* Example 1 (IOMMU device with wired interrupts) */ @@ -145,3 +178,24 @@ examples: }; }; }; + + - |+ + /* Example 5 (SpacemiT distributed IOMMU) */ + #include + + iommu4: iommu@1bccd000 { + compatible = "spacemit,k3-t100", "spacemit,t100", "riscv,iommu"; + reg = <0x1bccd000 0x1000>; + interrupts = <58 IRQ_TYPE_LEVEL_HIGH>, + <62 IRQ_TYPE_LEVEL_HIGH>, <63 IRQ_TYPE_LEVEL_HIGH>, + <62 IRQ_TYPE_LEVEL_HIGH>, <63 IRQ_TYPE_LEVEL_HIGH>; + interrupt-parent = <&saplic>; + #iommu-cells = <0x01>; + }; + + /* Device with four IOMMU device IDs */ + master2 { + #iommu-cells = <1>; + iommus = <&iommu4 0xc0010>, <&iommu4 0xc0011>, + <&iommu4 0xc0012>, <&iommu4 0xc0013>; + }; From 88abebc1ee9dc26fb7b3586c156b8f8ec8ef530a Mon Sep 17 00:00:00 2001 From: Lv Zheng Date: Sat, 28 Feb 2026 22:44:59 +0800 Subject: [PATCH 5/8] spacemit/t100: Add global filter awareness for RISC-V IOMMU HPM 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 Signed-off-by: Jingyu Li Signed-off-by: Linux RISC-V bot --- 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 3fe29d9643ebf3..91fcd1e29e284f 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 4d5ce0f380e76a..efa65caef0dcc4 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 0447bc4d1fab77..6af592dfaa0081 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; }; /** From cf703bcb48dfd826884d1ba43fb7632848e94f9b Mon Sep 17 00:00:00 2001 From: Lv Zheng Date: Sat, 28 Feb 2026 22:45:08 +0800 Subject: [PATCH 6/8] iommu/riscv: Add SpacemiT T100 IOATC HPM support 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 Signed-off-by: Jingyu Li Signed-off-by: Linux RISC-V bot --- 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 98daf0e1a30690..c8536c64ef42b2 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 e8e52bca885660..469b8bfd21510c 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 91fcd1e29e284f..41ce3e473c9614 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, ¶ms); + 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 1296625488ef33..f1bb682dd4785a 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 efa65caef0dcc4..b166b3cb6d4fc9 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 6af592dfaa0081..71a961731c22aa 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; }; /** From 8c77de4b9c05ad6ba6d993822fa1f9297fee1ce9 Mon Sep 17 00:00:00 2001 From: Lv Zheng Date: Sat, 28 Feb 2026 22:45:22 +0800 Subject: [PATCH 7/8] iommu/riscv: Add vendor event support for RISC-V IOMMU HPM Add a mechanism to allow vendor events to be registered via userspace jevents. The PMU exposes an "identifier" sysfs attribute derived from the device tree compatible string (e.g. "spacemit,t100" or "riscv,iommu"), which perf's jevents uses to match JSON event definitions to the PMU. Signed-off-by: Lv Zheng Signed-off-by: Jingyu Li Signed-off-by: Linux RISC-V bot --- drivers/iommu/riscv/iommu.c | 29 +++++++++++++++++++++++++++++ drivers/iommu/riscv/iommu.h | 2 ++ drivers/perf/riscv_iommu_hpm.c | 22 +++++++++++++++++++++- include/linux/riscv_iommu.h | 2 ++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c index 41ce3e473c9614..c14714a3bb8f0a 100644 --- a/drivers/iommu/riscv/iommu.c +++ b/drivers/iommu/riscv/iommu.c @@ -1664,6 +1664,7 @@ static int riscv_iommu_subdev_add(struct riscv_iommu_device *iommu, subdev->base = params->base; subdev->iommu = iommu; subdev->info = params->info; + subdev->identifier = params->identifier; auxdev = &subdev->auxdev; auxdev->name = params->name; @@ -1701,6 +1702,32 @@ static int riscv_iommu_subdev_add(struct riscv_iommu_device *iommu, return ret; } +/* Compatible strings that serve as PMU identifier for userspace jevents */ +static const char *const riscv_iommu_hpm_identifiers[] = { + "spacemit,t100", + "riscv,iommu", +}; + +static const char *riscv_iommu_get_hpm_identifier(struct device *dev) +{ + struct device_node *np = dev->of_node; + int i, ret; + + if (!np) + return NULL; + + for (i = 0; i < ARRAY_SIZE(riscv_iommu_hpm_identifiers); i++) { + ret = of_property_match_string(np, "compatible", + riscv_iommu_hpm_identifiers[i]); + if (ret >= 0) + return devm_kstrdup(dev, + riscv_iommu_hpm_identifiers[i], + GFP_KERNEL); + } + + return NULL; +} + static void riscv_iommu_enumerate_hpm(struct riscv_iommu_device *iommu) { struct riscv_iommu_hpm_info *hpm_info; @@ -1728,6 +1755,7 @@ static void riscv_iommu_enumerate_hpm(struct riscv_iommu_device *iommu) .name = "riscv_iommu_hpm", .info = hpm_info, .base = iommu->reg + RISCV_IOMMU_REG_IOCOUNTOVF, + .identifier = riscv_iommu_get_hpm_identifier(iommu->dev), }; if (of_device_is_compatible(iommu->dev->of_node, "spacemit,t100")) { @@ -1822,6 +1850,7 @@ static void riscv_iommu_enumerate_ioatc(struct riscv_iommu_device *iommu) .name = "spacemit_ioatc_hpm", .info = ioatc_info, .base = base + RISCV_IOMMU_REG_IOCOUNTOVF, + .identifier = riscv_iommu_get_hpm_identifier(iommu->dev), }; ret = riscv_iommu_subdev_add(iommu, ¶ms); diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h index f1bb682dd4785a..7f34c1bbfe2598 100644 --- a/drivers/iommu/riscv/iommu.h +++ b/drivers/iommu/riscv/iommu.h @@ -74,11 +74,13 @@ struct riscv_iommu_device { * @name: auxiliary device name * @info: device-specific info, freed in release * @base: PMU register base + * @identifier: perf PMU identifier for JSON Compat matching */ struct riscv_iommu_subdev_params { const char *name; void *info; void __iomem *base; + const char *identifier; }; int riscv_iommu_init(struct riscv_iommu_device *iommu); diff --git a/drivers/perf/riscv_iommu_hpm.c b/drivers/perf/riscv_iommu_hpm.c index b166b3cb6d4fc9..fdc18affbe02c3 100644 --- a/drivers/perf/riscv_iommu_hpm.c +++ b/drivers/perf/riscv_iommu_hpm.c @@ -88,6 +88,7 @@ struct riscv_iommu_hpm { unsigned int irq; unsigned int on_cpu; bool global_filter; + const char *identifier; struct hlist_node node; /* * Layout of events: @@ -664,8 +665,24 @@ static ssize_t riscv_iommu_hpm_global_filter_show(struct device *dev, static struct device_attribute riscv_iommu_hpm_global_filter_attr = __ATTR(global_filter, 0444, riscv_iommu_hpm_global_filter_show, NULL); +static ssize_t riscv_iommu_hpm_identifier_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct riscv_iommu_hpm *hpm = dev_get_drvdata(dev); + + if (!hpm->identifier) + return 0; + + return sysfs_emit(buf, "%s\n", hpm->identifier); +} + +static struct device_attribute riscv_iommu_hpm_identifier_attr = + __ATTR(identifier, 0444, riscv_iommu_hpm_identifier_show, NULL); + static struct attribute *riscv_iommu_hpm_vendor_attrs[] = { &riscv_iommu_hpm_global_filter_attr.attr, + &riscv_iommu_hpm_identifier_attr.attr, NULL }; @@ -891,6 +908,7 @@ static int riscv_iommu_hpm_probe(struct auxiliary_device *auxdev, hpm->subdev = subdev; hpm->base = subdev->base; + hpm->identifier = subdev->identifier; hpm->on_cpu = raw_smp_processor_id(); hpm->irq = info->irq; hpm->global_filter = info->global_filter; @@ -914,7 +932,9 @@ static int riscv_iommu_hpm_probe(struct auxiliary_device *auxdev, 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); + /* jevents name: auxdev->name + "_" + auxdev->id */ + hpm_name = devm_kasprintf(dev, GFP_KERNEL, "%s_%u", auxdev->name, + auxdev->id); if (!hpm_name) return -ENOMEM; diff --git a/include/linux/riscv_iommu.h b/include/linux/riscv_iommu.h index 71a961731c22aa..d21d89251ed542 100644 --- a/include/linux/riscv_iommu.h +++ b/include/linux/riscv_iommu.h @@ -24,6 +24,7 @@ struct riscv_iommu_device; * @base: PMU register base * @iommu: parent IOMMU (opaque) * @info: subdevice-specific info, freed in release + * @identifier: Vendor identifier for userspace jevent */ struct riscv_iommu_subdev { struct list_head link; @@ -31,6 +32,7 @@ struct riscv_iommu_subdev { void __iomem *base; struct riscv_iommu_device *iommu; void *info; + const char *identifier; }; /** From 1145eef68e13751a741ba57d8624ec8f05d30b0d Mon Sep 17 00:00:00 2001 From: Lv Zheng Date: Sat, 28 Feb 2026 22:45:32 +0800 Subject: [PATCH 8/8] perf vendor events riscv: Add SpacemiT T100 HPM event aliases Add JSON HPM event aliases for SpacemiT distributed IOMMU (T100) which is general and compatible for all SpacemiT RISC-V SoCs. Signed-off-by: Lv Zheng Signed-off-by: Jingyu Li Signed-off-by: Linux RISC-V bot --- MAINTAINERS | 5 + .../arch/riscv/spacemit/iommu/sys/ioatc.json | 30 ++++ .../arch/riscv/spacemit/iommu/sys/ioats.json | 163 ++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 tools/perf/pmu-events/arch/riscv/spacemit/iommu/sys/ioatc.json create mode 100644 tools/perf/pmu-events/arch/riscv/spacemit/iommu/sys/ioats.json diff --git a/MAINTAINERS b/MAINTAINERS index dc82a6bd1a6129..a71f07af7efc84 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22709,12 +22709,17 @@ K: riscv RISC-V IOMMU M: Tomasz Jeznach +M: Lv Zheng +M: Jingyu Li L: iommu@lists.linux.dev L: linux-riscv@lists.infradead.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux.git F: Documentation/devicetree/bindings/iommu/riscv,iommu.yaml F: drivers/iommu/riscv/ +F: drivers/perf/riscv_iommu_hpm.c +F: include/linux/riscv_iommu.h +F: tools/perf/pmu-events/arch/riscv/spacemit/iommu/ RISC-V MICROCHIP SUPPORT M: Conor Dooley diff --git a/tools/perf/pmu-events/arch/riscv/spacemit/iommu/sys/ioatc.json b/tools/perf/pmu-events/arch/riscv/spacemit/iommu/sys/ioatc.json new file mode 100644 index 00000000000000..eb2e6fe24c62ae --- /dev/null +++ b/tools/perf/pmu-events/arch/riscv/spacemit/iommu/sys/ioatc.json @@ -0,0 +1,30 @@ +[ + { + "EventName": "mtlb_lkp", + "EventCode": "0x38", + "BriefDescription": "IOATC main TLB (MTLB) lookups", + "Unit": "spacemit_ioatc_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "mtlb_mis", + "EventCode": "0x39", + "BriefDescription": "IOATC main TLB (MTLB) misses", + "Unit": "spacemit_ioatc_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "utlb_lkp", + "EventCode": "0x3A", + "BriefDescription": "IOATC micro TLB (uTLB) lookups", + "Unit": "spacemit_ioatc_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "utlb_mis", + "EventCode": "0x3B", + "BriefDescription": "IOATC micro TLB (uTLB) misses", + "Unit": "spacemit_ioatc_hpm", + "Compat": "spacemit,t100" + } +] diff --git a/tools/perf/pmu-events/arch/riscv/spacemit/iommu/sys/ioats.json b/tools/perf/pmu-events/arch/riscv/spacemit/iommu/sys/ioats.json new file mode 100644 index 00000000000000..709c0a9d2a8e59 --- /dev/null +++ b/tools/perf/pmu-events/arch/riscv/spacemit/iommu/sys/ioats.json @@ -0,0 +1,163 @@ +[ + { + "EventName": "pri_rq", + "EventCode": "0x10", + "BriefDescription": "IOATS PCIe page request interface (PRI) requests", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "ptwc_rq", + "EventCode": "0x11", + "BriefDescription": "IOATS page table walk (PTW) cache requests", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "dtwc_rq", + "EventCode": "0x12", + "BriefDescription": "IOATS directory table walk (DTW) cache requests", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "dtwc_mis", + "EventCode": "0x13", + "BriefDescription": "IOATS directory table walk (DTW) cache misses", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "ptwc_mis", + "EventCode": "0x14", + "BriefDescription": "IOATS page table walk (PTW) cache misses", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "all_trans_rq", + "EventCode": "0x15", + "BriefDescription": "IOATS all translation requests", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "dtwc_lkp", + "EventCode": "0x20", + "BriefDescription": "IOATS directory table walk (DTW) cache lookups", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "s0_ptwc_lkp", + "EventCode": "0x28", + "BriefDescription": "IOATS s-stage level-0 PTW cache lookups", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "s0_ptwc_mis", + "EventCode": "0x29", + "BriefDescription": "IOATS s-stage level-0 PTW cache misses", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "s1_ptwc_lkp", + "EventCode": "0x2A", + "BriefDescription": "IOATS s-stage level-1 PTW cache lookups", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "s1_ptwc_mis", + "EventCode": "0x2B", + "BriefDescription": "IOATS s-stage level-1 PTW cache misses", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "s2_ptwc_lkp", + "EventCode": "0x2C", + "BriefDescription": "IOATS s-stage level-2 PTW cache lookups", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "s2_ptwc_mis", + "EventCode": "0x2D", + "BriefDescription": "IOATS s-stage level-2 PTW cache misses", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "s3_ptwc_lkp", + "EventCode": "0x2E", + "BriefDescription": "IOATS s-stage level-3 PTW cache lookups", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "s3_ptwc_mis", + "EventCode": "0x2F", + "BriefDescription": "IOATS s-stage level-3 PTW cache misses", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "g0_ptwc_lkp", + "EventCode": "0x30", + "BriefDescription": "IOATS g-stage level-0 PTW cache lookups", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "g0_ptwc_mis", + "EventCode": "0x31", + "BriefDescription": "IOATS g-stage level-0 PTW cache misses", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "g1_ptwc_lkp", + "EventCode": "0x32", + "BriefDescription": "IOATS g-stage level-1 PTW cache lookups", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "g1_ptwc_mis", + "EventCode": "0x33", + "BriefDescription": "IOATS g-stage level-1 PTW cache misses", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "g2_ptwc_lkp", + "EventCode": "0x34", + "BriefDescription": "IOATS g-stage level-2 PTW cache lookups", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "g2_ptwc_mis", + "EventCode": "0x35", + "BriefDescription": "IOATS g-stage level-2 PTW cache misses", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "g3_ptwc_lkp", + "EventCode": "0x36", + "BriefDescription": "IOATS g-stage level-3 PTW cache lookups", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + }, + { + "EventName": "g3_ptwc_mis", + "EventCode": "0x37", + "BriefDescription": "IOATS g-stage level-3 PTW cache misses", + "Unit": "spacemit_ioats_hpm", + "Compat": "spacemit,t100" + } +]