blob: db5791a5445512a2f6cd7a884c7b1560f9342be6 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Support Intel IOMMU PerfMon
* Copyright(c) 2023 Intel Corporation.
*/
#define pr_fmt(fmt) "DMAR: " fmt
#define dev_fmt(fmt) pr_fmt(fmt)
#include <linux/dmar.h>
#include "iommu.h"
#include "perfmon.h"
static inline void __iomem *
get_perf_reg_address(struct intel_iommu *iommu, u32 offset)
{
u32 off = dmar_readl(iommu->reg + offset);
return iommu->reg + off;
}
int alloc_iommu_pmu(struct intel_iommu *iommu)
{
struct iommu_pmu *iommu_pmu;
int i, j, ret;
u64 perfcap;
u32 cap;
if (!ecap_pms(iommu->ecap))
return 0;
/* The IOMMU PMU requires the ECMD support as well */
if (!cap_ecmds(iommu->cap))
return -ENODEV;
perfcap = dmar_readq(iommu->reg + DMAR_PERFCAP_REG);
/* The performance monitoring is not supported. */
if (!perfcap)
return -ENODEV;
/* Sanity check for the number of the counters and event groups */
if (!pcap_num_cntr(perfcap) || !pcap_num_event_group(perfcap))
return -ENODEV;
/* The interrupt on overflow is required */
if (!pcap_interrupt(perfcap))
return -ENODEV;
iommu_pmu = kzalloc(sizeof(*iommu_pmu), GFP_KERNEL);
if (!iommu_pmu)
return -ENOMEM;
iommu_pmu->num_cntr = pcap_num_cntr(perfcap);
iommu_pmu->cntr_width = pcap_cntr_width(perfcap);
iommu_pmu->filter = pcap_filters_mask(perfcap);
iommu_pmu->cntr_stride = pcap_cntr_stride(perfcap);
iommu_pmu->num_eg = pcap_num_event_group(perfcap);
iommu_pmu->evcap = kcalloc(iommu_pmu->num_eg, sizeof(u64), GFP_KERNEL);
if (!iommu_pmu->evcap) {
ret = -ENOMEM;
goto free_pmu;
}
/* Parse event group capabilities */
for (i = 0; i < iommu_pmu->num_eg; i++) {
u64 pcap;
pcap = dmar_readq(iommu->reg + DMAR_PERFEVNTCAP_REG +
i * IOMMU_PMU_CAP_REGS_STEP);
iommu_pmu->evcap[i] = pecap_es(pcap);
}
iommu_pmu->cntr_evcap = kcalloc(iommu_pmu->num_cntr, sizeof(u32 *), GFP_KERNEL);
if (!iommu_pmu->cntr_evcap) {
ret = -ENOMEM;
goto free_pmu_evcap;
}
for (i = 0; i < iommu_pmu->num_cntr; i++) {
iommu_pmu->cntr_evcap[i] = kcalloc(iommu_pmu->num_eg, sizeof(u32), GFP_KERNEL);
if (!iommu_pmu->cntr_evcap[i]) {
ret = -ENOMEM;
goto free_pmu_cntr_evcap;
}
/*
* Set to the global capabilities, will adjust according
* to per-counter capabilities later.
*/
for (j = 0; j < iommu_pmu->num_eg; j++)
iommu_pmu->cntr_evcap[i][j] = (u32)iommu_pmu->evcap[j];
}
iommu_pmu->cfg_reg = get_perf_reg_address(iommu, DMAR_PERFCFGOFF_REG);
iommu_pmu->cntr_reg = get_perf_reg_address(iommu, DMAR_PERFCNTROFF_REG);
iommu_pmu->overflow = get_perf_reg_address(iommu, DMAR_PERFOVFOFF_REG);
/*
* Check per-counter capabilities. All counters should have the
* same capabilities on Interrupt on Overflow Support and Counter
* Width.
*/
for (i = 0; i < iommu_pmu->num_cntr; i++) {
cap = dmar_readl(iommu_pmu->cfg_reg +
i * IOMMU_PMU_CFG_OFFSET +
IOMMU_PMU_CFG_CNTRCAP_OFFSET);
if (!iommu_cntrcap_pcc(cap))
continue;
/*
* It's possible that some counters have a different
* capability because of e.g., HW bug. Check the corner
* case here and simply drop those counters.
*/
if ((iommu_cntrcap_cw(cap) != iommu_pmu->cntr_width) ||
!iommu_cntrcap_ios(cap)) {
iommu_pmu->num_cntr = i;
pr_warn("PMU counter capability inconsistent, counter number reduced to %d\n",
iommu_pmu->num_cntr);
}
/* Clear the pre-defined events group */
for (j = 0; j < iommu_pmu->num_eg; j++)
iommu_pmu->cntr_evcap[i][j] = 0;
/* Override with per-counter event capabilities */
for (j = 0; j < iommu_cntrcap_egcnt(cap); j++) {
cap = dmar_readl(iommu_pmu->cfg_reg + i * IOMMU_PMU_CFG_OFFSET +
IOMMU_PMU_CFG_CNTREVCAP_OFFSET +
(j * IOMMU_PMU_OFF_REGS_STEP));
iommu_pmu->cntr_evcap[i][iommu_event_group(cap)] = iommu_event_select(cap);
/*
* Some events may only be supported by a specific counter.
* Track them in the evcap as well.
*/
iommu_pmu->evcap[iommu_event_group(cap)] |= iommu_event_select(cap);
}
}
iommu_pmu->iommu = iommu;
iommu->pmu = iommu_pmu;
return 0;
free_pmu_cntr_evcap:
for (i = 0; i < iommu_pmu->num_cntr; i++)
kfree(iommu_pmu->cntr_evcap[i]);
kfree(iommu_pmu->cntr_evcap);
free_pmu_evcap:
kfree(iommu_pmu->evcap);
free_pmu:
kfree(iommu_pmu);
return ret;
}
void free_iommu_pmu(struct intel_iommu *iommu)
{
struct iommu_pmu *iommu_pmu = iommu->pmu;
if (!iommu_pmu)
return;
if (iommu_pmu->evcap) {
int i;
for (i = 0; i < iommu_pmu->num_cntr; i++)
kfree(iommu_pmu->cntr_evcap[i]);
kfree(iommu_pmu->cntr_evcap);
}
kfree(iommu_pmu->evcap);
kfree(iommu_pmu);
iommu->pmu = NULL;
}