18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2013 Advanced Micro Devices, Inc. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Author: Steven Kinney <Steven.Kinney@amd.com> 68c2ecf20Sopenharmony_ci * Author: Suravee Suthikulpanit <Suraveee.Suthikulpanit@amd.com> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Perf: amd_iommu - AMD IOMMU Performance Counter PMU implementation 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "perf/amd_iommu: " fmt 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/perf_event.h> 148c2ecf20Sopenharmony_ci#include <linux/init.h> 158c2ecf20Sopenharmony_ci#include <linux/cpumask.h> 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include "../perf_event.h" 198c2ecf20Sopenharmony_ci#include "iommu.h" 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci/* iommu pmu conf masks */ 228c2ecf20Sopenharmony_ci#define GET_CSOURCE(x) ((x)->conf & 0xFFULL) 238c2ecf20Sopenharmony_ci#define GET_DEVID(x) (((x)->conf >> 8) & 0xFFFFULL) 248c2ecf20Sopenharmony_ci#define GET_DOMID(x) (((x)->conf >> 24) & 0xFFFFULL) 258c2ecf20Sopenharmony_ci#define GET_PASID(x) (((x)->conf >> 40) & 0xFFFFFULL) 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci/* iommu pmu conf1 masks */ 288c2ecf20Sopenharmony_ci#define GET_DEVID_MASK(x) ((x)->conf1 & 0xFFFFULL) 298c2ecf20Sopenharmony_ci#define GET_DOMID_MASK(x) (((x)->conf1 >> 16) & 0xFFFFULL) 308c2ecf20Sopenharmony_ci#define GET_PASID_MASK(x) (((x)->conf1 >> 32) & 0xFFFFFULL) 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define IOMMU_NAME_SIZE 16 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistruct perf_amd_iommu { 358c2ecf20Sopenharmony_ci struct list_head list; 368c2ecf20Sopenharmony_ci struct pmu pmu; 378c2ecf20Sopenharmony_ci struct amd_iommu *iommu; 388c2ecf20Sopenharmony_ci char name[IOMMU_NAME_SIZE]; 398c2ecf20Sopenharmony_ci u8 max_banks; 408c2ecf20Sopenharmony_ci u8 max_counters; 418c2ecf20Sopenharmony_ci u64 cntr_assign_mask; 428c2ecf20Sopenharmony_ci raw_spinlock_t lock; 438c2ecf20Sopenharmony_ci}; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic LIST_HEAD(perf_amd_iommu_list); 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci/*--------------------------------------------- 488c2ecf20Sopenharmony_ci * sysfs format attributes 498c2ecf20Sopenharmony_ci *---------------------------------------------*/ 508c2ecf20Sopenharmony_ciPMU_FORMAT_ATTR(csource, "config:0-7"); 518c2ecf20Sopenharmony_ciPMU_FORMAT_ATTR(devid, "config:8-23"); 528c2ecf20Sopenharmony_ciPMU_FORMAT_ATTR(domid, "config:24-39"); 538c2ecf20Sopenharmony_ciPMU_FORMAT_ATTR(pasid, "config:40-59"); 548c2ecf20Sopenharmony_ciPMU_FORMAT_ATTR(devid_mask, "config1:0-15"); 558c2ecf20Sopenharmony_ciPMU_FORMAT_ATTR(domid_mask, "config1:16-31"); 568c2ecf20Sopenharmony_ciPMU_FORMAT_ATTR(pasid_mask, "config1:32-51"); 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic struct attribute *iommu_format_attrs[] = { 598c2ecf20Sopenharmony_ci &format_attr_csource.attr, 608c2ecf20Sopenharmony_ci &format_attr_devid.attr, 618c2ecf20Sopenharmony_ci &format_attr_pasid.attr, 628c2ecf20Sopenharmony_ci &format_attr_domid.attr, 638c2ecf20Sopenharmony_ci &format_attr_devid_mask.attr, 648c2ecf20Sopenharmony_ci &format_attr_pasid_mask.attr, 658c2ecf20Sopenharmony_ci &format_attr_domid_mask.attr, 668c2ecf20Sopenharmony_ci NULL, 678c2ecf20Sopenharmony_ci}; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic struct attribute_group amd_iommu_format_group = { 708c2ecf20Sopenharmony_ci .name = "format", 718c2ecf20Sopenharmony_ci .attrs = iommu_format_attrs, 728c2ecf20Sopenharmony_ci}; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci/*--------------------------------------------- 758c2ecf20Sopenharmony_ci * sysfs events attributes 768c2ecf20Sopenharmony_ci *---------------------------------------------*/ 778c2ecf20Sopenharmony_cistatic struct attribute_group amd_iommu_events_group = { 788c2ecf20Sopenharmony_ci .name = "events", 798c2ecf20Sopenharmony_ci}; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistruct amd_iommu_event_desc { 828c2ecf20Sopenharmony_ci struct device_attribute attr; 838c2ecf20Sopenharmony_ci const char *event; 848c2ecf20Sopenharmony_ci}; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic ssize_t _iommu_event_show(struct device *dev, 878c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci struct amd_iommu_event_desc *event = 908c2ecf20Sopenharmony_ci container_of(attr, struct amd_iommu_event_desc, attr); 918c2ecf20Sopenharmony_ci return sprintf(buf, "%s\n", event->event); 928c2ecf20Sopenharmony_ci} 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci#define AMD_IOMMU_EVENT_DESC(_name, _event) \ 958c2ecf20Sopenharmony_ci{ \ 968c2ecf20Sopenharmony_ci .attr = __ATTR(_name, 0444, _iommu_event_show, NULL), \ 978c2ecf20Sopenharmony_ci .event = _event, \ 988c2ecf20Sopenharmony_ci} 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cistatic struct amd_iommu_event_desc amd_iommu_v2_event_descs[] = { 1018c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_pass_untrans, "csource=0x01"), 1028c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_pass_pretrans, "csource=0x02"), 1038c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_pass_excl, "csource=0x03"), 1048c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_target_abort, "csource=0x04"), 1058c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_trans_total, "csource=0x05"), 1068c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_iommu_tlb_pte_hit, "csource=0x06"), 1078c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_iommu_tlb_pte_mis, "csource=0x07"), 1088c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_iommu_tlb_pde_hit, "csource=0x08"), 1098c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_iommu_tlb_pde_mis, "csource=0x09"), 1108c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_dte_hit, "csource=0x0a"), 1118c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(mem_dte_mis, "csource=0x0b"), 1128c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(page_tbl_read_tot, "csource=0x0c"), 1138c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(page_tbl_read_nst, "csource=0x0d"), 1148c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(page_tbl_read_gst, "csource=0x0e"), 1158c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(int_dte_hit, "csource=0x0f"), 1168c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(int_dte_mis, "csource=0x10"), 1178c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(cmd_processed, "csource=0x11"), 1188c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(cmd_processed_inv, "csource=0x12"), 1198c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(tlb_inv, "csource=0x13"), 1208c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(ign_rd_wr_mmio_1ff8h, "csource=0x14"), 1218c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(vapic_int_non_guest, "csource=0x15"), 1228c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(vapic_int_guest, "csource=0x16"), 1238c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(smi_recv, "csource=0x17"), 1248c2ecf20Sopenharmony_ci AMD_IOMMU_EVENT_DESC(smi_blk, "csource=0x18"), 1258c2ecf20Sopenharmony_ci { /* end: all zeroes */ }, 1268c2ecf20Sopenharmony_ci}; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci/*--------------------------------------------- 1298c2ecf20Sopenharmony_ci * sysfs cpumask attributes 1308c2ecf20Sopenharmony_ci *---------------------------------------------*/ 1318c2ecf20Sopenharmony_cistatic cpumask_t iommu_cpumask; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_cistatic ssize_t _iommu_cpumask_show(struct device *dev, 1348c2ecf20Sopenharmony_ci struct device_attribute *attr, 1358c2ecf20Sopenharmony_ci char *buf) 1368c2ecf20Sopenharmony_ci{ 1378c2ecf20Sopenharmony_ci return cpumap_print_to_pagebuf(true, buf, &iommu_cpumask); 1388c2ecf20Sopenharmony_ci} 1398c2ecf20Sopenharmony_cistatic DEVICE_ATTR(cpumask, S_IRUGO, _iommu_cpumask_show, NULL); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_cistatic struct attribute *iommu_cpumask_attrs[] = { 1428c2ecf20Sopenharmony_ci &dev_attr_cpumask.attr, 1438c2ecf20Sopenharmony_ci NULL, 1448c2ecf20Sopenharmony_ci}; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cistatic struct attribute_group amd_iommu_cpumask_group = { 1478c2ecf20Sopenharmony_ci .attrs = iommu_cpumask_attrs, 1488c2ecf20Sopenharmony_ci}; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci/*---------------------------------------------*/ 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic int get_next_avail_iommu_bnk_cntr(struct perf_event *event) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci struct perf_amd_iommu *piommu = container_of(event->pmu, struct perf_amd_iommu, pmu); 1558c2ecf20Sopenharmony_ci int max_cntrs = piommu->max_counters; 1568c2ecf20Sopenharmony_ci int max_banks = piommu->max_banks; 1578c2ecf20Sopenharmony_ci u32 shift, bank, cntr; 1588c2ecf20Sopenharmony_ci unsigned long flags; 1598c2ecf20Sopenharmony_ci int retval; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci raw_spin_lock_irqsave(&piommu->lock, flags); 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci for (bank = 0, shift = 0; bank < max_banks; bank++) { 1648c2ecf20Sopenharmony_ci for (cntr = 0; cntr < max_cntrs; cntr++) { 1658c2ecf20Sopenharmony_ci shift = bank + (bank*3) + cntr; 1668c2ecf20Sopenharmony_ci if (piommu->cntr_assign_mask & BIT_ULL(shift)) { 1678c2ecf20Sopenharmony_ci continue; 1688c2ecf20Sopenharmony_ci } else { 1698c2ecf20Sopenharmony_ci piommu->cntr_assign_mask |= BIT_ULL(shift); 1708c2ecf20Sopenharmony_ci event->hw.iommu_bank = bank; 1718c2ecf20Sopenharmony_ci event->hw.iommu_cntr = cntr; 1728c2ecf20Sopenharmony_ci retval = 0; 1738c2ecf20Sopenharmony_ci goto out; 1748c2ecf20Sopenharmony_ci } 1758c2ecf20Sopenharmony_ci } 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci retval = -ENOSPC; 1788c2ecf20Sopenharmony_ciout: 1798c2ecf20Sopenharmony_ci raw_spin_unlock_irqrestore(&piommu->lock, flags); 1808c2ecf20Sopenharmony_ci return retval; 1818c2ecf20Sopenharmony_ci} 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_cistatic int clear_avail_iommu_bnk_cntr(struct perf_amd_iommu *perf_iommu, 1848c2ecf20Sopenharmony_ci u8 bank, u8 cntr) 1858c2ecf20Sopenharmony_ci{ 1868c2ecf20Sopenharmony_ci unsigned long flags; 1878c2ecf20Sopenharmony_ci int max_banks, max_cntrs; 1888c2ecf20Sopenharmony_ci int shift = 0; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci max_banks = perf_iommu->max_banks; 1918c2ecf20Sopenharmony_ci max_cntrs = perf_iommu->max_counters; 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci if ((bank > max_banks) || (cntr > max_cntrs)) 1948c2ecf20Sopenharmony_ci return -EINVAL; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci shift = bank + cntr + (bank*3); 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci raw_spin_lock_irqsave(&perf_iommu->lock, flags); 1998c2ecf20Sopenharmony_ci perf_iommu->cntr_assign_mask &= ~(1ULL<<shift); 2008c2ecf20Sopenharmony_ci raw_spin_unlock_irqrestore(&perf_iommu->lock, flags); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci return 0; 2038c2ecf20Sopenharmony_ci} 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cistatic int perf_iommu_event_init(struct perf_event *event) 2068c2ecf20Sopenharmony_ci{ 2078c2ecf20Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci /* test the event attr type check for PMU enumeration */ 2108c2ecf20Sopenharmony_ci if (event->attr.type != event->pmu->type) 2118c2ecf20Sopenharmony_ci return -ENOENT; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci /* 2148c2ecf20Sopenharmony_ci * IOMMU counters are shared across all cores. 2158c2ecf20Sopenharmony_ci * Therefore, it does not support per-process mode. 2168c2ecf20Sopenharmony_ci * Also, it does not support event sampling mode. 2178c2ecf20Sopenharmony_ci */ 2188c2ecf20Sopenharmony_ci if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK) 2198c2ecf20Sopenharmony_ci return -EINVAL; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci if (event->cpu < 0) 2228c2ecf20Sopenharmony_ci return -EINVAL; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci /* update the hw_perf_event struct with the iommu config data */ 2258c2ecf20Sopenharmony_ci hwc->conf = event->attr.config; 2268c2ecf20Sopenharmony_ci hwc->conf1 = event->attr.config1; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci return 0; 2298c2ecf20Sopenharmony_ci} 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_cistatic inline struct amd_iommu *perf_event_2_iommu(struct perf_event *ev) 2328c2ecf20Sopenharmony_ci{ 2338c2ecf20Sopenharmony_ci return (container_of(ev->pmu, struct perf_amd_iommu, pmu))->iommu; 2348c2ecf20Sopenharmony_ci} 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_cistatic void perf_iommu_enable_event(struct perf_event *ev) 2378c2ecf20Sopenharmony_ci{ 2388c2ecf20Sopenharmony_ci struct amd_iommu *iommu = perf_event_2_iommu(ev); 2398c2ecf20Sopenharmony_ci struct hw_perf_event *hwc = &ev->hw; 2408c2ecf20Sopenharmony_ci u8 bank = hwc->iommu_bank; 2418c2ecf20Sopenharmony_ci u8 cntr = hwc->iommu_cntr; 2428c2ecf20Sopenharmony_ci u64 reg = 0ULL; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci reg = GET_CSOURCE(hwc); 2458c2ecf20Sopenharmony_ci amd_iommu_pc_set_reg(iommu, bank, cntr, IOMMU_PC_COUNTER_SRC_REG, ®); 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci reg = GET_DEVID_MASK(hwc); 2488c2ecf20Sopenharmony_ci reg = GET_DEVID(hwc) | (reg << 32); 2498c2ecf20Sopenharmony_ci if (reg) 2508c2ecf20Sopenharmony_ci reg |= BIT(31); 2518c2ecf20Sopenharmony_ci amd_iommu_pc_set_reg(iommu, bank, cntr, IOMMU_PC_DEVID_MATCH_REG, ®); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci reg = GET_PASID_MASK(hwc); 2548c2ecf20Sopenharmony_ci reg = GET_PASID(hwc) | (reg << 32); 2558c2ecf20Sopenharmony_ci if (reg) 2568c2ecf20Sopenharmony_ci reg |= BIT(31); 2578c2ecf20Sopenharmony_ci amd_iommu_pc_set_reg(iommu, bank, cntr, IOMMU_PC_PASID_MATCH_REG, ®); 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci reg = GET_DOMID_MASK(hwc); 2608c2ecf20Sopenharmony_ci reg = GET_DOMID(hwc) | (reg << 32); 2618c2ecf20Sopenharmony_ci if (reg) 2628c2ecf20Sopenharmony_ci reg |= BIT(31); 2638c2ecf20Sopenharmony_ci amd_iommu_pc_set_reg(iommu, bank, cntr, IOMMU_PC_DOMID_MATCH_REG, ®); 2648c2ecf20Sopenharmony_ci} 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_cistatic void perf_iommu_disable_event(struct perf_event *event) 2678c2ecf20Sopenharmony_ci{ 2688c2ecf20Sopenharmony_ci struct amd_iommu *iommu = perf_event_2_iommu(event); 2698c2ecf20Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 2708c2ecf20Sopenharmony_ci u64 reg = 0ULL; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci amd_iommu_pc_set_reg(iommu, hwc->iommu_bank, hwc->iommu_cntr, 2738c2ecf20Sopenharmony_ci IOMMU_PC_COUNTER_SRC_REG, ®); 2748c2ecf20Sopenharmony_ci} 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_cistatic void perf_iommu_start(struct perf_event *event, int flags) 2778c2ecf20Sopenharmony_ci{ 2788c2ecf20Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci if (WARN_ON_ONCE(!(hwc->state & PERF_HES_STOPPED))) 2818c2ecf20Sopenharmony_ci return; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE)); 2848c2ecf20Sopenharmony_ci hwc->state = 0; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci /* 2878c2ecf20Sopenharmony_ci * To account for power-gating, which prevents write to 2888c2ecf20Sopenharmony_ci * the counter, we need to enable the counter 2898c2ecf20Sopenharmony_ci * before setting up counter register. 2908c2ecf20Sopenharmony_ci */ 2918c2ecf20Sopenharmony_ci perf_iommu_enable_event(event); 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci if (flags & PERF_EF_RELOAD) { 2948c2ecf20Sopenharmony_ci u64 count = 0; 2958c2ecf20Sopenharmony_ci struct amd_iommu *iommu = perf_event_2_iommu(event); 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci /* 2988c2ecf20Sopenharmony_ci * Since the IOMMU PMU only support counting mode, 2998c2ecf20Sopenharmony_ci * the counter always start with value zero. 3008c2ecf20Sopenharmony_ci */ 3018c2ecf20Sopenharmony_ci amd_iommu_pc_set_reg(iommu, hwc->iommu_bank, hwc->iommu_cntr, 3028c2ecf20Sopenharmony_ci IOMMU_PC_COUNTER_REG, &count); 3038c2ecf20Sopenharmony_ci } 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ci perf_event_update_userpage(event); 3068c2ecf20Sopenharmony_ci} 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_cistatic void perf_iommu_read(struct perf_event *event) 3098c2ecf20Sopenharmony_ci{ 3108c2ecf20Sopenharmony_ci u64 count; 3118c2ecf20Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 3128c2ecf20Sopenharmony_ci struct amd_iommu *iommu = perf_event_2_iommu(event); 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci if (amd_iommu_pc_get_reg(iommu, hwc->iommu_bank, hwc->iommu_cntr, 3158c2ecf20Sopenharmony_ci IOMMU_PC_COUNTER_REG, &count)) 3168c2ecf20Sopenharmony_ci return; 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci /* IOMMU pc counter register is only 48 bits */ 3198c2ecf20Sopenharmony_ci count &= GENMASK_ULL(47, 0); 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci /* 3228c2ecf20Sopenharmony_ci * Since the counter always start with value zero, 3238c2ecf20Sopenharmony_ci * simply just accumulate the count for the event. 3248c2ecf20Sopenharmony_ci */ 3258c2ecf20Sopenharmony_ci local64_add(count, &event->count); 3268c2ecf20Sopenharmony_ci} 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_cistatic void perf_iommu_stop(struct perf_event *event, int flags) 3298c2ecf20Sopenharmony_ci{ 3308c2ecf20Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci if (hwc->state & PERF_HES_UPTODATE) 3338c2ecf20Sopenharmony_ci return; 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci /* 3368c2ecf20Sopenharmony_ci * To account for power-gating, in which reading the counter would 3378c2ecf20Sopenharmony_ci * return zero, we need to read the register before disabling. 3388c2ecf20Sopenharmony_ci */ 3398c2ecf20Sopenharmony_ci perf_iommu_read(event); 3408c2ecf20Sopenharmony_ci hwc->state |= PERF_HES_UPTODATE; 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_ci perf_iommu_disable_event(event); 3438c2ecf20Sopenharmony_ci WARN_ON_ONCE(hwc->state & PERF_HES_STOPPED); 3448c2ecf20Sopenharmony_ci hwc->state |= PERF_HES_STOPPED; 3458c2ecf20Sopenharmony_ci} 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_cistatic int perf_iommu_add(struct perf_event *event, int flags) 3488c2ecf20Sopenharmony_ci{ 3498c2ecf20Sopenharmony_ci int retval; 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci event->hw.state = PERF_HES_UPTODATE | PERF_HES_STOPPED; 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci /* request an iommu bank/counter */ 3548c2ecf20Sopenharmony_ci retval = get_next_avail_iommu_bnk_cntr(event); 3558c2ecf20Sopenharmony_ci if (retval) 3568c2ecf20Sopenharmony_ci return retval; 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ci if (flags & PERF_EF_START) 3598c2ecf20Sopenharmony_ci perf_iommu_start(event, PERF_EF_RELOAD); 3608c2ecf20Sopenharmony_ci 3618c2ecf20Sopenharmony_ci return 0; 3628c2ecf20Sopenharmony_ci} 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_cistatic void perf_iommu_del(struct perf_event *event, int flags) 3658c2ecf20Sopenharmony_ci{ 3668c2ecf20Sopenharmony_ci struct hw_perf_event *hwc = &event->hw; 3678c2ecf20Sopenharmony_ci struct perf_amd_iommu *perf_iommu = 3688c2ecf20Sopenharmony_ci container_of(event->pmu, struct perf_amd_iommu, pmu); 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci perf_iommu_stop(event, PERF_EF_UPDATE); 3718c2ecf20Sopenharmony_ci 3728c2ecf20Sopenharmony_ci /* clear the assigned iommu bank/counter */ 3738c2ecf20Sopenharmony_ci clear_avail_iommu_bnk_cntr(perf_iommu, 3748c2ecf20Sopenharmony_ci hwc->iommu_bank, hwc->iommu_cntr); 3758c2ecf20Sopenharmony_ci 3768c2ecf20Sopenharmony_ci perf_event_update_userpage(event); 3778c2ecf20Sopenharmony_ci} 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_cistatic __init int _init_events_attrs(void) 3808c2ecf20Sopenharmony_ci{ 3818c2ecf20Sopenharmony_ci int i = 0, j; 3828c2ecf20Sopenharmony_ci struct attribute **attrs; 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ci while (amd_iommu_v2_event_descs[i].attr.attr.name) 3858c2ecf20Sopenharmony_ci i++; 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_ci attrs = kcalloc(i + 1, sizeof(*attrs), GFP_KERNEL); 3888c2ecf20Sopenharmony_ci if (!attrs) 3898c2ecf20Sopenharmony_ci return -ENOMEM; 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci for (j = 0; j < i; j++) 3928c2ecf20Sopenharmony_ci attrs[j] = &amd_iommu_v2_event_descs[j].attr.attr; 3938c2ecf20Sopenharmony_ci 3948c2ecf20Sopenharmony_ci amd_iommu_events_group.attrs = attrs; 3958c2ecf20Sopenharmony_ci return 0; 3968c2ecf20Sopenharmony_ci} 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_cistatic const struct attribute_group *amd_iommu_attr_groups[] = { 3998c2ecf20Sopenharmony_ci &amd_iommu_format_group, 4008c2ecf20Sopenharmony_ci &amd_iommu_cpumask_group, 4018c2ecf20Sopenharmony_ci &amd_iommu_events_group, 4028c2ecf20Sopenharmony_ci NULL, 4038c2ecf20Sopenharmony_ci}; 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_cistatic const struct pmu iommu_pmu __initconst = { 4068c2ecf20Sopenharmony_ci .event_init = perf_iommu_event_init, 4078c2ecf20Sopenharmony_ci .add = perf_iommu_add, 4088c2ecf20Sopenharmony_ci .del = perf_iommu_del, 4098c2ecf20Sopenharmony_ci .start = perf_iommu_start, 4108c2ecf20Sopenharmony_ci .stop = perf_iommu_stop, 4118c2ecf20Sopenharmony_ci .read = perf_iommu_read, 4128c2ecf20Sopenharmony_ci .task_ctx_nr = perf_invalid_context, 4138c2ecf20Sopenharmony_ci .attr_groups = amd_iommu_attr_groups, 4148c2ecf20Sopenharmony_ci .capabilities = PERF_PMU_CAP_NO_EXCLUDE, 4158c2ecf20Sopenharmony_ci}; 4168c2ecf20Sopenharmony_ci 4178c2ecf20Sopenharmony_cistatic __init int init_one_iommu(unsigned int idx) 4188c2ecf20Sopenharmony_ci{ 4198c2ecf20Sopenharmony_ci struct perf_amd_iommu *perf_iommu; 4208c2ecf20Sopenharmony_ci int ret; 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_ci perf_iommu = kzalloc(sizeof(struct perf_amd_iommu), GFP_KERNEL); 4238c2ecf20Sopenharmony_ci if (!perf_iommu) 4248c2ecf20Sopenharmony_ci return -ENOMEM; 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_ci raw_spin_lock_init(&perf_iommu->lock); 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_ci perf_iommu->pmu = iommu_pmu; 4298c2ecf20Sopenharmony_ci perf_iommu->iommu = get_amd_iommu(idx); 4308c2ecf20Sopenharmony_ci perf_iommu->max_banks = amd_iommu_pc_get_max_banks(idx); 4318c2ecf20Sopenharmony_ci perf_iommu->max_counters = amd_iommu_pc_get_max_counters(idx); 4328c2ecf20Sopenharmony_ci 4338c2ecf20Sopenharmony_ci if (!perf_iommu->iommu || 4348c2ecf20Sopenharmony_ci !perf_iommu->max_banks || 4358c2ecf20Sopenharmony_ci !perf_iommu->max_counters) { 4368c2ecf20Sopenharmony_ci kfree(perf_iommu); 4378c2ecf20Sopenharmony_ci return -EINVAL; 4388c2ecf20Sopenharmony_ci } 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci snprintf(perf_iommu->name, IOMMU_NAME_SIZE, "amd_iommu_%u", idx); 4418c2ecf20Sopenharmony_ci 4428c2ecf20Sopenharmony_ci ret = perf_pmu_register(&perf_iommu->pmu, perf_iommu->name, -1); 4438c2ecf20Sopenharmony_ci if (!ret) { 4448c2ecf20Sopenharmony_ci pr_info("Detected AMD IOMMU #%d (%d banks, %d counters/bank).\n", 4458c2ecf20Sopenharmony_ci idx, perf_iommu->max_banks, perf_iommu->max_counters); 4468c2ecf20Sopenharmony_ci list_add_tail(&perf_iommu->list, &perf_amd_iommu_list); 4478c2ecf20Sopenharmony_ci } else { 4488c2ecf20Sopenharmony_ci pr_warn("Error initializing IOMMU %d.\n", idx); 4498c2ecf20Sopenharmony_ci kfree(perf_iommu); 4508c2ecf20Sopenharmony_ci } 4518c2ecf20Sopenharmony_ci return ret; 4528c2ecf20Sopenharmony_ci} 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_cistatic __init int amd_iommu_pc_init(void) 4558c2ecf20Sopenharmony_ci{ 4568c2ecf20Sopenharmony_ci unsigned int i, cnt = 0; 4578c2ecf20Sopenharmony_ci int ret; 4588c2ecf20Sopenharmony_ci 4598c2ecf20Sopenharmony_ci /* Make sure the IOMMU PC resource is available */ 4608c2ecf20Sopenharmony_ci if (!amd_iommu_pc_supported()) 4618c2ecf20Sopenharmony_ci return -ENODEV; 4628c2ecf20Sopenharmony_ci 4638c2ecf20Sopenharmony_ci ret = _init_events_attrs(); 4648c2ecf20Sopenharmony_ci if (ret) 4658c2ecf20Sopenharmony_ci return ret; 4668c2ecf20Sopenharmony_ci 4678c2ecf20Sopenharmony_ci /* 4688c2ecf20Sopenharmony_ci * An IOMMU PMU is specific to an IOMMU, and can function independently. 4698c2ecf20Sopenharmony_ci * So we go through all IOMMUs and ignore the one that fails init 4708c2ecf20Sopenharmony_ci * unless all IOMMU are failing. 4718c2ecf20Sopenharmony_ci */ 4728c2ecf20Sopenharmony_ci for (i = 0; i < amd_iommu_get_num_iommus(); i++) { 4738c2ecf20Sopenharmony_ci ret = init_one_iommu(i); 4748c2ecf20Sopenharmony_ci if (!ret) 4758c2ecf20Sopenharmony_ci cnt++; 4768c2ecf20Sopenharmony_ci } 4778c2ecf20Sopenharmony_ci 4788c2ecf20Sopenharmony_ci if (!cnt) { 4798c2ecf20Sopenharmony_ci kfree(amd_iommu_events_group.attrs); 4808c2ecf20Sopenharmony_ci return -ENODEV; 4818c2ecf20Sopenharmony_ci } 4828c2ecf20Sopenharmony_ci 4838c2ecf20Sopenharmony_ci /* Init cpumask attributes to only core 0 */ 4848c2ecf20Sopenharmony_ci cpumask_set_cpu(0, &iommu_cpumask); 4858c2ecf20Sopenharmony_ci return 0; 4868c2ecf20Sopenharmony_ci} 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_cidevice_initcall(amd_iommu_pc_init); 489