lkml.org 
[lkml]   [2023]   [Jan]   [13]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Date
SubjectRe: [PATCH 2/7] iommu/vt-d: Retrieve IOMMU perfmon capability information
From
On 2023/1/12 4:24, kan.liang@linux.intel.com wrote:
> From: Kan Liang <kan.liang@linux.intel.com>
>
> The performance monitoring infrastructure, perfmon, is to support
> collection of information about key events occurring during operation of
> the remapping hardware, to aid performance tuning and debug. Each
> remapping hardware unit has capability registers that indicate support
> for performance monitoring features and enumerate the capabilities.
>
> Add alloc_iommu_pmu() to retrieve IOMMU perfmon capability information
> for each iommu unit. The information is stored in the iommu->pmu data
> structure. Capability registers are read-only, so it's safe to prefetch
> and store them in the pmu structure. This could avoid unnecessary VMEXIT
> when this code is running in the virtualization environment.
>
> Add free_iommu_pmu() to free the saved capability information when
> freeing the iommu unit.
>
> Add a kernel config option for the IOMMU perfmon feature. Unless a user
> explicitly uses the perf tool to monitor the IOMMU perfmon event, there
> isn't any impact for the existing IOMMU. Enable it by default.
>
> Signed-off-by: Kan Liang <kan.liang@linux.intel.com>
> ---
> drivers/iommu/intel/Kconfig | 9 ++
> drivers/iommu/intel/Makefile | 1 +
> drivers/iommu/intel/dmar.c | 7 ++
> drivers/iommu/intel/iommu.h | 41 +++++++++
> drivers/iommu/intel/perfmon.c | 159 ++++++++++++++++++++++++++++++++++
> drivers/iommu/intel/perfmon.h | 41 +++++++++
> 6 files changed, 258 insertions(+)
> create mode 100644 drivers/iommu/intel/perfmon.c
> create mode 100644 drivers/iommu/intel/perfmon.h
>
> diff --git a/drivers/iommu/intel/Kconfig b/drivers/iommu/intel/Kconfig
> index b7dff5092fd2..1a4aebddc9a6 100644
> --- a/drivers/iommu/intel/Kconfig
> +++ b/drivers/iommu/intel/Kconfig
> @@ -96,4 +96,13 @@ config INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON
> passing intel_iommu=sm_on to the kernel. If not sure, please use
> the default value.
>
> +config INTEL_IOMMU_PERF_EVENTS
> + def_bool y
> + bool "Intel IOMMU performance events"
> + depends on INTEL_IOMMU && PERF_EVENTS
> + help
> + Include support for Intel IOMMU performance events. These are
> + available on modern processors which support Intel VT-d 4.0 and
> + later.
> +
> endif # INTEL_IOMMU
> diff --git a/drivers/iommu/intel/Makefile b/drivers/iommu/intel/Makefile
> index fa0dae16441c..7af3b8a4f2a0 100644
> --- a/drivers/iommu/intel/Makefile
> +++ b/drivers/iommu/intel/Makefile
> @@ -6,3 +6,4 @@ obj-$(CONFIG_DMAR_PERF) += perf.o
> obj-$(CONFIG_INTEL_IOMMU_DEBUGFS) += debugfs.o
> obj-$(CONFIG_INTEL_IOMMU_SVM) += svm.o
> obj-$(CONFIG_IRQ_REMAP) += irq_remapping.o
> +obj-$(CONFIG_INTEL_IOMMU_PERF_EVENTS) += perfmon.o
> diff --git a/drivers/iommu/intel/dmar.c b/drivers/iommu/intel/dmar.c
> index 6a411d964474..91bb48267df2 100644
> --- a/drivers/iommu/intel/dmar.c
> +++ b/drivers/iommu/intel/dmar.c
> @@ -34,6 +34,7 @@
> #include "../irq_remapping.h"
> #include "perf.h"
> #include "trace.h"
> +#include "perfmon.h"
>
> typedef int (*dmar_res_handler_t)(struct acpi_dmar_header *, void *);
> struct dmar_res_callback {
> @@ -1114,6 +1115,9 @@ static int alloc_iommu(struct dmar_drhd_unit *drhd)
> if (sts & DMA_GSTS_QIES)
> iommu->gcmd |= DMA_GCMD_QIE;
>
> + if (alloc_iommu_pmu(iommu))
> + pr_debug("Cannot alloc PMU for iommu (seq_id = %d)\n", iommu->seq_id);
> +
> raw_spin_lock_init(&iommu->register_lock);
>
> /*
> @@ -1148,6 +1152,7 @@ static int alloc_iommu(struct dmar_drhd_unit *drhd)
> err_sysfs:
> iommu_device_sysfs_remove(&iommu->iommu);
> err_unmap:
> + free_iommu_pmu(iommu);
> unmap_iommu(iommu);
> error_free_seq_id:
> ida_free(&dmar_seq_ids, iommu->seq_id);
> @@ -1163,6 +1168,8 @@ static void free_iommu(struct intel_iommu *iommu)
> iommu_device_sysfs_remove(&iommu->iommu);
> }
>
> + free_iommu_pmu(iommu);
> +
> if (iommu->irq) {
> if (iommu->pr_irq) {
> free_irq(iommu->pr_irq, iommu);
> diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h
> index 06e61e474856..5bcefbea55c9 100644
> --- a/drivers/iommu/intel/iommu.h
> +++ b/drivers/iommu/intel/iommu.h
> @@ -125,6 +125,11 @@
> #define DMAR_MTRR_PHYSMASK8_REG 0x208
> #define DMAR_MTRR_PHYSBASE9_REG 0x210
> #define DMAR_MTRR_PHYSMASK9_REG 0x218
> +#define DMAR_PERFCAP_REG 0x300
> +#define DMAR_PERFCFGOFF_REG 0x310
> +#define DMAR_PERFOVFOFF_REG 0x318
> +#define DMAR_PERFCNTROFF_REG 0x31c
> +#define DMAR_PERFEVNTCAP_REG 0x380
> #define DMAR_VCCAP_REG 0xe30 /* Virtual command capability register */
> #define DMAR_VCMD_REG 0xe00 /* Virtual command register */
> #define DMAR_VCRSP_REG 0xe10 /* Virtual command response register */
> @@ -148,6 +153,7 @@
> */
> #define cap_esrtps(c) (((c) >> 63) & 1)
> #define cap_esirtps(c) (((c) >> 62) & 1)
> +#define cap_ecmds(c) (((c) >> 61) & 1)
> #define cap_fl5lp_support(c) (((c) >> 60) & 1)
> #define cap_pi_support(c) (((c) >> 59) & 1)
> #define cap_fl1gp_support(c) (((c) >> 56) & 1)
> @@ -179,6 +185,7 @@
> * Extended Capability Register
> */
>
> +#define ecap_pms(e) (((e) >> 51) & 0x1)
> #define ecap_rps(e) (((e) >> 49) & 0x1)
> #define ecap_smpwc(e) (((e) >> 48) & 0x1)
> #define ecap_flts(e) (((e) >> 47) & 0x1)
> @@ -210,6 +217,22 @@
> #define ecap_max_handle_mask(e) (((e) >> 20) & 0xf)
> #define ecap_sc_support(e) (((e) >> 7) & 0x1) /* Snooping Control */
>
> +/*
> + * Decoding Perf Capability Register
> + */
> +#define pcap_num_cntr(p) ((p) & 0xffff)
> +#define pcap_cntr_width(p) (((p) >> 16) & 0x7f)
> +#define pcap_num_event_group(p) (((p) >> 24) & 0x1f)
> +#define pcap_filters_mask(p) (((p) >> 32) & 0x1f)
> +#define pcap_interrupt(p) (((p) >> 50) & 0x1)
> +/* The counter stride is calculated as 2 ^ (x+10) bytes */
> +#define pcap_cntr_stride(p) (1ULL << ((((p) >> 52) & 0x7) + 10))
> +
> +/*
> + * Decoding Perf Event Capability Register
> + */
> +#define pecap_es(p) ((p) & 0xfffffff)
> +
> /* Virtual command interface capability */
> #define vccap_pasid(v) (((v) & DMA_VCS_PAS)) /* PASID allocation */
>
> @@ -554,6 +577,22 @@ struct dmar_domain {
> iommu core */
> };
>
> +struct iommu_pmu {
> + struct intel_iommu *iommu;
> + u32 num_cntr; /* Number of counters */
> + u32 num_eg; /* Number of event group */
> + u32 cntr_width; /* Counter width */
> + u32 cntr_stride; /* Counter Stride */
> + u32 filter; /* Bitmask of filter support */
> + void __iomem *base; /* the PerfMon base address */
> + void __iomem *cfg_reg; /* counter configuration base address */
> + void __iomem *cntr_reg; /* counter 0 address*/
> + void __iomem *overflow; /* overflow status register */
> +
> + u64 *evcap; /* Indicates all supported events */
> + u32 **cntr_evcap; /* Supported events of each counter. */
> +};
> +
> struct intel_iommu {
> void __iomem *reg; /* Pointer to hardware regs, virtual addr */
> u64 reg_phys; /* physical address of hw register set */
> @@ -600,6 +639,8 @@ struct intel_iommu {
>
> struct dmar_drhd_unit *drhd;
> void *perf_statistic;
> +
> + struct iommu_pmu *pmu;
> };
>
> /* PCI domain-device relationship */
> diff --git a/drivers/iommu/intel/perfmon.c b/drivers/iommu/intel/perfmon.c
> new file mode 100644
> index 000000000000..bc090f329c32
> --- /dev/null
> +++ b/drivers/iommu/intel/perfmon.c
> @@ -0,0 +1,159 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Support Intel IOMMU PerfMon
> + * Copyright(c) 2022 Intel Corporation.

Copyright 2023

> + */
> +
> +#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;
> +
> + /* The IOMMU PMU requires the ECMD support as well */
> + if (!ecap_pms(iommu->ecap) || !cap_ecmds(iommu->cap))
> + return -ENODEV;

It's normal that PMS is not supported on an IOMMU, how about,

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;
> + iommu_pmu->num_cntr = i;

Do we really need above line? kfree() is friendly to a NULL pointer,
right?

> + 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
> + */

Please re-org this comment and make it neat.

> + 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;
> + if ((iommu_cntrcap_cw(cap) != iommu_pmu->cntr_width) ||
> + !iommu_cntrcap_ios(cap))
> + iommu_pmu->num_cntr = i;

Can you please add some words describing how do you handle the
capability inconsistent case? It seems that you just truncate the number
of counters? Any rationality behind that?

> +
> + /* 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;
> +}
> diff --git a/drivers/iommu/intel/perfmon.h b/drivers/iommu/intel/perfmon.h
> new file mode 100644
> index 000000000000..8587c80501cd
> --- /dev/null
> +++ b/drivers/iommu/intel/perfmon.h
> @@ -0,0 +1,41 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +/*
> + * PERFCFGOFF_REG, PERFFRZOFF_REG
> + * PERFOVFOFF_REG, PERFCNTROFF_REG
> + */
> +#define IOMMU_PMU_NUM_OFF_REGS 4
> +#define IOMMU_PMU_OFF_REGS_STEP 4
> +
> +#define IOMMU_PMU_CFG_OFFSET 0x100
> +#define IOMMU_PMU_CFG_CNTRCAP_OFFSET 0x80
> +#define IOMMU_PMU_CFG_CNTREVCAP_OFFSET 0x84
> +#define IOMMU_PMU_CFG_SIZE 0x8
> +#define IOMMU_PMU_CFG_FILTERS_OFFSET 0x4
> +
> +
> +#define IOMMU_PMU_CAP_REGS_STEP 8
> +
> +#define iommu_cntrcap_pcc(p) ((p) & 0x1)
> +#define iommu_cntrcap_cw(p) ((p >> 8) & 0xff)
> +#define iommu_cntrcap_ios(p) ((p >> 16) & 0x1)
> +#define iommu_cntrcap_egcnt(p) ((p >> 28) & 0xf)
> +
> +#define iommu_event_select(p) ((p) & 0xfffffff)
> +#define iommu_event_group(p) ((p >> 28) & 0xf)
> +
> +#ifdef CONFIG_INTEL_IOMMU_PERF_EVENTS
> +int alloc_iommu_pmu(struct intel_iommu *iommu);
> +void free_iommu_pmu(struct intel_iommu *iommu);
> +#else
> +static inline int
> +alloc_iommu_pmu(struct intel_iommu *iommu)
> +{
> + return 0;
> +}
> +
> +static inline void
> +free_iommu_pmu(struct intel_iommu *iommu)
> +{
> +}
> +#endif /* CONFIG_INTEL_IOMMU_PERF_EVENTS */
--
Best regards,
baolu

\
 
 \ /
  Last update: 2023-03-26 23:39    [W:0.087 / U:0.100 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site