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, &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, &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, &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, &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, &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