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