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>; + }; 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/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 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-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-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..469b8bfd21510c 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. */ @@ -72,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 2d8fb0859f30fc..c14714a3bb8f0a 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,40 @@ 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; }; +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(subdev)); + + 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(subdev), + 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 +1632,279 @@ 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; + subdev->identifier = params->identifier; + + 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; +} + +/* 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; + 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; + hpm_info->is_ioatc = false; + + params = (struct riscv_iommu_subdev_params) { + .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")) { + hpm_info->global_filter = true; + params.name = "spacemit_ioats_hpm"; + } + + 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); + } +} + +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, + .identifier = riscv_iommu_get_hpm_identifier(iommu->dev), + }; + + 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 + * + * @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_enumerate_ioatc(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 +1914,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 +1970,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..7f34c1bbfe2598 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,13 +43,15 @@ 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; 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; @@ -60,11 +63,31 @@ 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 + * @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); 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/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..fdc18affbe02c3 --- /dev/null +++ b/drivers/perf/riscv_iommu_hpm.c @@ -0,0 +1,1012 @@ +// 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; + bool global_filter; + const char *identifier; + 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); +} + +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 + * + * @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 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); + 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); + + 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, + 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; + } + + if (riscv_iommu_hpm_apply_event_filter(hpm, event, idx)) + return -EAGAIN; + 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 (!riscv_iommu_hpm_events_compatible(event->group_leader, event)) + return -EINVAL; + if (++group_num_events > hpm->num_counters) + return -EINVAL; + } + + 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; + } + + 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 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 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 +}; + +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 +}; + +#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; + 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 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) +{ + 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; + const struct attribute_group **attr_grps; + bool is_ioatc; + 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->identifier = subdev->identifier; + 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); + + 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); + 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; + + /* jevents name: auxdev->name + "_" + auxdev->id */ + hpm_name = devm_kasprintf(dev, GFP_KERNEL, "%s_%u", auxdev->name, + auxdev->id); + 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 = 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, %s filter)\n", + hpm_name, hpm->num_counters, hpm->irq, + hpm->global_filter ? "global" : "per-counter"); + + 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" }, + { .name = "iommu.spacemit_ioats_hpm" }, + { .name = "iommu.spacemit_ioatc_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"); diff --git a/include/linux/riscv_iommu.h b/include/linux/riscv_iommu.h new file mode 100644 index 00000000000000..d21d89251ed542 --- /dev/null +++ b/include/linux/riscv_iommu.h @@ -0,0 +1,83 @@ +/* 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 + * @identifier: Vendor identifier for userspace jevent + */ +struct riscv_iommu_subdev { + struct list_head link; + struct auxiliary_device auxdev; + void __iomem *base; + struct riscv_iommu_device *iommu; + void *info; + const char *identifier; +}; + +/** + * 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; +}; + +/** + * 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_ */ 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" + } +]