162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2013 Advanced Micro Devices, Inc. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Author: Steven Kinney <Steven.Kinney@amd.com> 662306a36Sopenharmony_ci * Author: Suravee Suthikulpanit <Suraveee.Suthikulpanit@amd.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Perf: amd_iommu - AMD IOMMU Performance Counter PMU implementation 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#define pr_fmt(fmt) "perf/amd_iommu: " fmt 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/perf_event.h> 1462306a36Sopenharmony_ci#include <linux/init.h> 1562306a36Sopenharmony_ci#include <linux/cpumask.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci#include <linux/amd-iommu.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include "../perf_event.h" 2062306a36Sopenharmony_ci#include "iommu.h" 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci/* iommu pmu conf masks */ 2362306a36Sopenharmony_ci#define GET_CSOURCE(x) ((x)->conf & 0xFFULL) 2462306a36Sopenharmony_ci#define GET_DEVID(x) (((x)->conf >> 8) & 0xFFFFULL) 2562306a36Sopenharmony_ci#define GET_DOMID(x) (((x)->conf >> 24) & 0xFFFFULL) 2662306a36Sopenharmony_ci#define GET_PASID(x) (((x)->conf >> 40) & 0xFFFFFULL) 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci/* iommu pmu conf1 masks */ 2962306a36Sopenharmony_ci#define GET_DEVID_MASK(x) ((x)->conf1 & 0xFFFFULL) 3062306a36Sopenharmony_ci#define GET_DOMID_MASK(x) (((x)->conf1 >> 16) & 0xFFFFULL) 3162306a36Sopenharmony_ci#define GET_PASID_MASK(x) (((x)->conf1 >> 32) & 0xFFFFFULL) 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#define IOMMU_NAME_SIZE 16 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistruct perf_amd_iommu { 3662306a36Sopenharmony_ci struct list_head list; 3762306a36Sopenharmony_ci struct pmu pmu; 3862306a36Sopenharmony_ci struct amd_iommu *iommu; 3962306a36Sopenharmony_ci char name[IOMMU_NAME_SIZE]; 4062306a36Sopenharmony_ci u8 max_banks; 4162306a36Sopenharmony_ci u8 max_counters; 4262306a36Sopenharmony_ci u64 cntr_assign_mask; 4362306a36Sopenharmony_ci raw_spinlock_t lock; 4462306a36Sopenharmony_ci}; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic LIST_HEAD(perf_amd_iommu_list); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci/*--------------------------------------------- 4962306a36Sopenharmony_ci * sysfs format attributes 5062306a36Sopenharmony_ci *---------------------------------------------*/ 5162306a36Sopenharmony_ciPMU_FORMAT_ATTR(csource, "config:0-7"); 5262306a36Sopenharmony_ciPMU_FORMAT_ATTR(devid, "config:8-23"); 5362306a36Sopenharmony_ciPMU_FORMAT_ATTR(domid, "config:24-39"); 5462306a36Sopenharmony_ciPMU_FORMAT_ATTR(pasid, "config:40-59"); 5562306a36Sopenharmony_ciPMU_FORMAT_ATTR(devid_mask, "config1:0-15"); 5662306a36Sopenharmony_ciPMU_FORMAT_ATTR(domid_mask, "config1:16-31"); 5762306a36Sopenharmony_ciPMU_FORMAT_ATTR(pasid_mask, "config1:32-51"); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic struct attribute *iommu_format_attrs[] = { 6062306a36Sopenharmony_ci &format_attr_csource.attr, 6162306a36Sopenharmony_ci &format_attr_devid.attr, 6262306a36Sopenharmony_ci &format_attr_pasid.attr, 6362306a36Sopenharmony_ci &format_attr_domid.attr, 6462306a36Sopenharmony_ci &format_attr_devid_mask.attr, 6562306a36Sopenharmony_ci &format_attr_pasid_mask.attr, 6662306a36Sopenharmony_ci &format_attr_domid_mask.attr, 6762306a36Sopenharmony_ci NULL, 6862306a36Sopenharmony_ci}; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic struct attribute_group amd_iommu_format_group = { 7162306a36Sopenharmony_ci .name = "format", 7262306a36Sopenharmony_ci .attrs = iommu_format_attrs, 7362306a36Sopenharmony_ci}; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci/*--------------------------------------------- 7662306a36Sopenharmony_ci * sysfs events attributes 7762306a36Sopenharmony_ci *---------------------------------------------*/ 7862306a36Sopenharmony_cistatic struct attribute_group amd_iommu_events_group = { 7962306a36Sopenharmony_ci .name = "events", 8062306a36Sopenharmony_ci}; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistruct amd_iommu_event_desc { 8362306a36Sopenharmony_ci struct device_attribute attr; 8462306a36Sopenharmony_ci const char *event; 8562306a36Sopenharmony_ci}; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic ssize_t _iommu_event_show(struct device *dev, 8862306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci struct amd_iommu_event_desc *event = 9162306a36Sopenharmony_ci container_of(attr, struct amd_iommu_event_desc, attr); 9262306a36Sopenharmony_ci return sprintf(buf, "%s\n", event->event); 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci#define AMD_IOMMU_EVENT_DESC(_name, _event) \ 9662306a36Sopenharmony_ci{ \ 9762306a36Sopenharmony_ci .attr = __ATTR(_name, 0444, _iommu_event_show, NULL), \ 9862306a36Sopenharmony_ci .event = _event, \ 9962306a36Sopenharmony_ci} 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_cistatic struct amd_iommu_event_desc amd_iommu_v2_event_descs[] = { 10262306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_pass_untrans, "csource=0x01"), 10362306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_pass_pretrans, "csource=0x02"), 10462306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_pass_excl, "csource=0x03"), 10562306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_target_abort, "csource=0x04"), 10662306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_trans_total, "csource=0x05"), 10762306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_iommu_tlb_pte_hit, "csource=0x06"), 10862306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_iommu_tlb_pte_mis, "csource=0x07"), 10962306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_iommu_tlb_pde_hit, "csource=0x08"), 11062306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_iommu_tlb_pde_mis, "csource=0x09"), 11162306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_dte_hit, "csource=0x0a"), 11262306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_dte_mis, "csource=0x0b"), 11362306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(page_tbl_read_tot, "csource=0x0c"), 11462306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(page_tbl_read_nst, "csource=0x0d"), 11562306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(page_tbl_read_gst, "csource=0x0e"), 11662306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(int_dte_hit, "csource=0x0f"), 11762306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(int_dte_mis, "csource=0x10"), 11862306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(cmd_processed, "csource=0x11"), 11962306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(cmd_processed_inv, "csource=0x12"), 12062306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(tlb_inv, "csource=0x13"), 12162306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(ign_rd_wr_mmio_1ff8h, "csource=0x14"), 12262306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(vapic_int_non_guest, "csource=0x15"), 12362306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(vapic_int_guest, "csource=0x16"), 12462306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(smi_recv, "csource=0x17"), 12562306a36Sopenharmony_ci AMD_IOMMU_EVENT_DESC(smi_blk, "csource=0x18"), 12662306a36Sopenharmony_ci { /* end: all zeroes */ }, 12762306a36Sopenharmony_ci}; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci/*--------------------------------------------- 13062306a36Sopenharmony_ci * sysfs cpumask attributes 13162306a36Sopenharmony_ci *---------------------------------------------*/ 13262306a36Sopenharmony_cistatic cpumask_t iommu_cpumask; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic ssize_t _iommu_cpumask_show(struct device *dev, 13562306a36Sopenharmony_ci struct device_attribute *attr, 13662306a36Sopenharmony_ci char *buf) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci return cpumap_print_to_pagebuf(true, buf, &iommu_cpumask); 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_cistatic DEVICE_ATTR(cpumask, S_IRUGO, _iommu_cpumask_show, NULL); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cistatic struct attribute *iommu_cpumask_attrs[] = { 14362306a36Sopenharmony_ci &dev_attr_cpumask.attr, 14462306a36Sopenharmony_ci NULL, 14562306a36Sopenharmony_ci}; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic struct attribute_group amd_iommu_cpumask_group = { 14862306a36Sopenharmony_ci .attrs = iommu_cpumask_attrs, 14962306a36Sopenharmony_ci}; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci/*---------------------------------------------*/ 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_cistatic int get_next_avail_iommu_bnk_cntr(struct perf_event *event) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci struct perf_amd_iommu *piommu = container_of(event->pmu, struct perf_amd_iommu, pmu); 15662306a36Sopenharmony_ci int max_cntrs = piommu->max_counters; 15762306a36Sopenharmony_ci int max_banks = piommu->max_banks; 15862306a36Sopenharmony_ci u32 shift, bank, cntr; 15962306a36Sopenharmony_ci unsigned long flags; 16062306a36Sopenharmony_ci int retval; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci raw_spin_lock_irqsave(&piommu->lock, flags); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci for (bank = 0; bank < max_banks; bank++) { 16562306a36Sopenharmony_ci for (cntr = 0; cntr < max_cntrs; cntr++) { 16662306a36Sopenharmony_ci shift = bank + (bank*3) + cntr; 16762306a36Sopenharmony_ci if (piommu->cntr_assign_mask & BIT_ULL(shift)) { 16862306a36Sopenharmony_ci continue; 16962306a36Sopenharmony_ci } else { 17062306a36Sopenharmony_ci piommu->cntr_assign_mask |= BIT_ULL(shift); 17162306a36Sopenharmony_ci event->hw.iommu_bank = bank; 17262306a36Sopenharmony_ci event->hw.iommu_cntr = cntr; 17362306a36Sopenharmony_ci retval = 0; 17462306a36Sopenharmony_ci goto out; 17562306a36Sopenharmony_ci } 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci retval = -ENOSPC; 17962306a36Sopenharmony_ciout: 18062306a36Sopenharmony_ci raw_spin_unlock_irqrestore(&piommu->lock, flags); 18162306a36Sopenharmony_ci return retval; 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cistatic int clear_avail_iommu_bnk_cntr(struct perf_amd_iommu *perf_iommu, 18562306a36Sopenharmony_ci u8 bank, u8 cntr) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci unsigned long flags; 18862306a36Sopenharmony_ci int max_banks, max_cntrs; 18962306a36Sopenharmony_ci int shift = 0; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci max_banks = perf_iommu->max_banks; 19262306a36Sopenharmony_ci max_cntrs = perf_iommu->max_counters; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci if ((bank > max_banks) || (cntr > max_cntrs)) 19562306a36Sopenharmony_ci return -EINVAL; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci shift = bank + cntr + (bank*3); 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci raw_spin_lock_irqsave(&perf_iommu->lock, flags); 20062306a36Sopenharmony_ci perf_iommu->cntr_assign_mask &= ~(1ULL<<shift); 20162306a36Sopenharmony_ci raw_spin_unlock_irqrestore(&perf_iommu->lock, flags); 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci return 0; 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cistatic int perf_iommu_event_init(struct perf_event *event) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci /* test the event attr type check for PMU enumeration */ 21162306a36Sopenharmony_ci if (event->attr.type != event->pmu->type) 21262306a36Sopenharmony_ci return -ENOENT; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci /* 21562306a36Sopenharmony_ci * IOMMU counters are shared across all cores. 21662306a36Sopenharmony_ci * Therefore, it does not support per-process mode. 21762306a36Sopenharmony_ci * Also, it does not support event sampling mode. 21862306a36Sopenharmony_ci */ 21962306a36Sopenharmony_ci if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK) 22062306a36Sopenharmony_ci return -EINVAL; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci if (event->cpu < 0) 22362306a36Sopenharmony_ci return -EINVAL; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci /* update the hw_perf_event struct with the iommu config data */ 22662306a36Sopenharmony_ci hwc->conf = event->attr.config; 22762306a36Sopenharmony_ci hwc->conf1 = event->attr.config1; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci return 0; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic inline struct amd_iommu *perf_event_2_iommu(struct perf_event *ev) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci return (container_of(ev->pmu, struct perf_amd_iommu, pmu))->iommu; 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cistatic void perf_iommu_enable_event(struct perf_event *ev) 23862306a36Sopenharmony_ci{ 23962306a36Sopenharmony_ci struct amd_iommu *iommu = perf_event_2_iommu(ev); 24062306a36Sopenharmony_ci struct hw_perf_event *hwc = &ev->hw; 24162306a36Sopenharmony_ci u8 bank = hwc->iommu_bank; 24262306a36Sopenharmony_ci u8 cntr = hwc->iommu_cntr; 24362306a36Sopenharmony_ci u64 reg = 0ULL; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci reg = GET_CSOURCE(hwc); 24662306a36Sopenharmony_ci amd_iommu_pc_set_reg(iommu, bank, cntr, IOMMU_PC_COUNTER_SRC_REG, ®); 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci reg = GET_DEVID_MASK(hwc); 24962306a36Sopenharmony_ci reg = GET_DEVID(hwc) | (reg << 32); 25062306a36Sopenharmony_ci if (reg) 25162306a36Sopenharmony_ci reg |= BIT(31); 25262306a36Sopenharmony_ci amd_iommu_pc_set_reg(iommu, bank, cntr, IOMMU_PC_DEVID_MATCH_REG, ®); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci reg = GET_PASID_MASK(hwc); 25562306a36Sopenharmony_ci reg = GET_PASID(hwc) | (reg << 32); 25662306a36Sopenharmony_ci if (reg) 25762306a36Sopenharmony_ci reg |= BIT(31); 25862306a36Sopenharmony_ci amd_iommu_pc_set_reg(iommu, bank, cntr, IOMMU_PC_PASID_MATCH_REG, ®); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci reg = GET_DOMID_MASK(hwc); 26162306a36Sopenharmony_ci reg = GET_DOMID(hwc) | (reg << 32); 26262306a36Sopenharmony_ci if (reg) 26362306a36Sopenharmony_ci reg |= BIT(31); 26462306a36Sopenharmony_ci amd_iommu_pc_set_reg(iommu, bank, cntr, IOMMU_PC_DOMID_MATCH_REG, ®); 26562306a36Sopenharmony_ci} 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_cistatic void perf_iommu_disable_event(struct perf_event *event) 26862306a36Sopenharmony_ci{ 26962306a36Sopenharmony_ci struct amd_iommu *iommu = perf_event_2_iommu(event); 27062306a36Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 27162306a36Sopenharmony_ci u64 reg = 0ULL; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci amd_iommu_pc_set_reg(iommu, hwc->iommu_bank, hwc->iommu_cntr, 27462306a36Sopenharmony_ci IOMMU_PC_COUNTER_SRC_REG, ®); 27562306a36Sopenharmony_ci} 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_cistatic void perf_iommu_start(struct perf_event *event, int flags) 27862306a36Sopenharmony_ci{ 27962306a36Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci if (WARN_ON_ONCE(!(hwc->state & PERF_HES_STOPPED))) 28262306a36Sopenharmony_ci return; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE)); 28562306a36Sopenharmony_ci hwc->state = 0; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci /* 28862306a36Sopenharmony_ci * To account for power-gating, which prevents write to 28962306a36Sopenharmony_ci * the counter, we need to enable the counter 29062306a36Sopenharmony_ci * before setting up counter register. 29162306a36Sopenharmony_ci */ 29262306a36Sopenharmony_ci perf_iommu_enable_event(event); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci if (flags & PERF_EF_RELOAD) { 29562306a36Sopenharmony_ci u64 count = 0; 29662306a36Sopenharmony_ci struct amd_iommu *iommu = perf_event_2_iommu(event); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci /* 29962306a36Sopenharmony_ci * Since the IOMMU PMU only support counting mode, 30062306a36Sopenharmony_ci * the counter always start with value zero. 30162306a36Sopenharmony_ci */ 30262306a36Sopenharmony_ci amd_iommu_pc_set_reg(iommu, hwc->iommu_bank, hwc->iommu_cntr, 30362306a36Sopenharmony_ci IOMMU_PC_COUNTER_REG, &count); 30462306a36Sopenharmony_ci } 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci perf_event_update_userpage(event); 30762306a36Sopenharmony_ci} 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_cistatic void perf_iommu_read(struct perf_event *event) 31062306a36Sopenharmony_ci{ 31162306a36Sopenharmony_ci u64 count; 31262306a36Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 31362306a36Sopenharmony_ci struct amd_iommu *iommu = perf_event_2_iommu(event); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci if (amd_iommu_pc_get_reg(iommu, hwc->iommu_bank, hwc->iommu_cntr, 31662306a36Sopenharmony_ci IOMMU_PC_COUNTER_REG, &count)) 31762306a36Sopenharmony_ci return; 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci /* IOMMU pc counter register is only 48 bits */ 32062306a36Sopenharmony_ci count &= GENMASK_ULL(47, 0); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci /* 32362306a36Sopenharmony_ci * Since the counter always start with value zero, 32462306a36Sopenharmony_ci * simply just accumulate the count for the event. 32562306a36Sopenharmony_ci */ 32662306a36Sopenharmony_ci local64_add(count, &event->count); 32762306a36Sopenharmony_ci} 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_cistatic void perf_iommu_stop(struct perf_event *event, int flags) 33062306a36Sopenharmony_ci{ 33162306a36Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci if (hwc->state & PERF_HES_UPTODATE) 33462306a36Sopenharmony_ci return; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci /* 33762306a36Sopenharmony_ci * To account for power-gating, in which reading the counter would 33862306a36Sopenharmony_ci * return zero, we need to read the register before disabling. 33962306a36Sopenharmony_ci */ 34062306a36Sopenharmony_ci perf_iommu_read(event); 34162306a36Sopenharmony_ci hwc->state |= PERF_HES_UPTODATE; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci perf_iommu_disable_event(event); 34462306a36Sopenharmony_ci WARN_ON_ONCE(hwc->state & PERF_HES_STOPPED); 34562306a36Sopenharmony_ci hwc->state |= PERF_HES_STOPPED; 34662306a36Sopenharmony_ci} 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_cistatic int perf_iommu_add(struct perf_event *event, int flags) 34962306a36Sopenharmony_ci{ 35062306a36Sopenharmony_ci int retval; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci event->hw.state = PERF_HES_UPTODATE | PERF_HES_STOPPED; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci /* request an iommu bank/counter */ 35562306a36Sopenharmony_ci retval = get_next_avail_iommu_bnk_cntr(event); 35662306a36Sopenharmony_ci if (retval) 35762306a36Sopenharmony_ci return retval; 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci if (flags & PERF_EF_START) 36062306a36Sopenharmony_ci perf_iommu_start(event, PERF_EF_RELOAD); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci return 0; 36362306a36Sopenharmony_ci} 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_cistatic void perf_iommu_del(struct perf_event *event, int flags) 36662306a36Sopenharmony_ci{ 36762306a36Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 36862306a36Sopenharmony_ci struct perf_amd_iommu *perf_iommu = 36962306a36Sopenharmony_ci container_of(event->pmu, struct perf_amd_iommu, pmu); 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci perf_iommu_stop(event, PERF_EF_UPDATE); 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci /* clear the assigned iommu bank/counter */ 37462306a36Sopenharmony_ci clear_avail_iommu_bnk_cntr(perf_iommu, 37562306a36Sopenharmony_ci hwc->iommu_bank, hwc->iommu_cntr); 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci perf_event_update_userpage(event); 37862306a36Sopenharmony_ci} 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_cistatic __init int _init_events_attrs(void) 38162306a36Sopenharmony_ci{ 38262306a36Sopenharmony_ci int i = 0, j; 38362306a36Sopenharmony_ci struct attribute **attrs; 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci while (amd_iommu_v2_event_descs[i].attr.attr.name) 38662306a36Sopenharmony_ci i++; 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci attrs = kcalloc(i + 1, sizeof(*attrs), GFP_KERNEL); 38962306a36Sopenharmony_ci if (!attrs) 39062306a36Sopenharmony_ci return -ENOMEM; 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci for (j = 0; j < i; j++) 39362306a36Sopenharmony_ci attrs[j] = &amd_iommu_v2_event_descs[j].attr.attr; 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci amd_iommu_events_group.attrs = attrs; 39662306a36Sopenharmony_ci return 0; 39762306a36Sopenharmony_ci} 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_cistatic const struct attribute_group *amd_iommu_attr_groups[] = { 40062306a36Sopenharmony_ci &amd_iommu_format_group, 40162306a36Sopenharmony_ci &amd_iommu_cpumask_group, 40262306a36Sopenharmony_ci &amd_iommu_events_group, 40362306a36Sopenharmony_ci NULL, 40462306a36Sopenharmony_ci}; 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_cistatic const struct pmu iommu_pmu __initconst = { 40762306a36Sopenharmony_ci .event_init = perf_iommu_event_init, 40862306a36Sopenharmony_ci .add = perf_iommu_add, 40962306a36Sopenharmony_ci .del = perf_iommu_del, 41062306a36Sopenharmony_ci .start = perf_iommu_start, 41162306a36Sopenharmony_ci .stop = perf_iommu_stop, 41262306a36Sopenharmony_ci .read = perf_iommu_read, 41362306a36Sopenharmony_ci .task_ctx_nr = perf_invalid_context, 41462306a36Sopenharmony_ci .attr_groups = amd_iommu_attr_groups, 41562306a36Sopenharmony_ci .capabilities = PERF_PMU_CAP_NO_EXCLUDE, 41662306a36Sopenharmony_ci}; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_cistatic __init int init_one_iommu(unsigned int idx) 41962306a36Sopenharmony_ci{ 42062306a36Sopenharmony_ci struct perf_amd_iommu *perf_iommu; 42162306a36Sopenharmony_ci int ret; 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci perf_iommu = kzalloc(sizeof(struct perf_amd_iommu), GFP_KERNEL); 42462306a36Sopenharmony_ci if (!perf_iommu) 42562306a36Sopenharmony_ci return -ENOMEM; 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci raw_spin_lock_init(&perf_iommu->lock); 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci perf_iommu->pmu = iommu_pmu; 43062306a36Sopenharmony_ci perf_iommu->iommu = get_amd_iommu(idx); 43162306a36Sopenharmony_ci perf_iommu->max_banks = amd_iommu_pc_get_max_banks(idx); 43262306a36Sopenharmony_ci perf_iommu->max_counters = amd_iommu_pc_get_max_counters(idx); 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci if (!perf_iommu->iommu || 43562306a36Sopenharmony_ci !perf_iommu->max_banks || 43662306a36Sopenharmony_ci !perf_iommu->max_counters) { 43762306a36Sopenharmony_ci kfree(perf_iommu); 43862306a36Sopenharmony_ci return -EINVAL; 43962306a36Sopenharmony_ci } 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci snprintf(perf_iommu->name, IOMMU_NAME_SIZE, "amd_iommu_%u", idx); 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci ret = perf_pmu_register(&perf_iommu->pmu, perf_iommu->name, -1); 44462306a36Sopenharmony_ci if (!ret) { 44562306a36Sopenharmony_ci pr_info("Detected AMD IOMMU #%d (%d banks, %d counters/bank).\n", 44662306a36Sopenharmony_ci idx, perf_iommu->max_banks, perf_iommu->max_counters); 44762306a36Sopenharmony_ci list_add_tail(&perf_iommu->list, &perf_amd_iommu_list); 44862306a36Sopenharmony_ci } else { 44962306a36Sopenharmony_ci pr_warn("Error initializing IOMMU %d.\n", idx); 45062306a36Sopenharmony_ci kfree(perf_iommu); 45162306a36Sopenharmony_ci } 45262306a36Sopenharmony_ci return ret; 45362306a36Sopenharmony_ci} 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_cistatic __init int amd_iommu_pc_init(void) 45662306a36Sopenharmony_ci{ 45762306a36Sopenharmony_ci unsigned int i, cnt = 0; 45862306a36Sopenharmony_ci int ret; 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci /* Make sure the IOMMU PC resource is available */ 46162306a36Sopenharmony_ci if (!amd_iommu_pc_supported()) 46262306a36Sopenharmony_ci return -ENODEV; 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci ret = _init_events_attrs(); 46562306a36Sopenharmony_ci if (ret) 46662306a36Sopenharmony_ci return ret; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci /* 46962306a36Sopenharmony_ci * An IOMMU PMU is specific to an IOMMU, and can function independently. 47062306a36Sopenharmony_ci * So we go through all IOMMUs and ignore the one that fails init 47162306a36Sopenharmony_ci * unless all IOMMU are failing. 47262306a36Sopenharmony_ci */ 47362306a36Sopenharmony_ci for (i = 0; i < amd_iommu_get_num_iommus(); i++) { 47462306a36Sopenharmony_ci ret = init_one_iommu(i); 47562306a36Sopenharmony_ci if (!ret) 47662306a36Sopenharmony_ci cnt++; 47762306a36Sopenharmony_ci } 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_ci if (!cnt) { 48062306a36Sopenharmony_ci kfree(amd_iommu_events_group.attrs); 48162306a36Sopenharmony_ci return -ENODEV; 48262306a36Sopenharmony_ci } 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci /* Init cpumask attributes to only core 0 */ 48562306a36Sopenharmony_ci cpumask_set_cpu(0, &iommu_cpumask); 48662306a36Sopenharmony_ci return 0; 48762306a36Sopenharmony_ci} 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_cidevice_initcall(amd_iommu_pc_init); 490