// SPDX-License-Identifier: GPL-2.0-only
/*
* Perf support for the Statistical Profiling Extension, introduced as
* part of ARMv8.2.
*
* Copyright (C) 2016 ARM Limited
*
* Author: Will Deacon <will.deacon@arm.com>
*/
#define PMUNAME "arm_spe"
#define DRVNAME PMUNAME "_pmu"
#define pr_fmt(fmt) DRVNAME ": " fmt
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/bug.h>
#include <linux/capability.h>
#include <linux/cpuhotplug.h>
#include <linux/cpumask.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/perf_event.h>
#include <linux/perf/arm_pmu.h>
#include <linux/platform_device.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/smp.h>
#include <linux/vmalloc.h>
#include <asm/barrier.h>
#include <asm/cpufeature.h>
#include <asm/mmu.h>
#include <asm/sysreg.h>
/*
* Cache if the event is allowed to trace Context information.
* This allows us to perform the check, i.e, perfmon_capable(),
* in the context of the event owner, once, during the event_init().
*/
#define SPE_PMU_HW_FLAGS_CX 0x00001
static_assert((PERF_EVENT_FLAG_ARCH & SPE_PMU_HW_FLAGS_CX) == SPE_PMU_HW_FLAGS_CX);
static void set_spe_event_has_cx(struct perf_event *event)
{
if (IS_ENABLED(CONFIG_PID_IN_CONTEXTIDR) && perfmon_capable())
event->hw.flags |= SPE_PMU_HW_FLAGS_CX;
}
static bool get_spe_event_has_cx(struct perf_event *event)
{
return !!(event->hw.flags & SPE_PMU_HW_FLAGS_CX);
}
#define ARM_SPE_BUF_PAD_BYTE 0
struct arm_spe_pmu_buf {
int nr_pages;
bool snapshot;
void *base;
};
struct arm_spe_pmu {
struct pmu pmu;
struct platform_device *pdev;
cpumask_t supported_cpus;
struct hlist_node hotplug_node;
int irq; /* PPI */
u16 pmsver;
u16 min_period;
u16 counter_sz;
#define SPE_PMU_FEAT_FILT_EVT (1UL << 0)
#define SPE_PMU_FEAT_FILT_TYP (1UL << 1)
#define SPE_PMU_FEAT_FILT_LAT (1UL << 2)
#define SPE_PMU_FEAT_ARCH_INST (1UL << 3)
#define SPE_PMU_FEAT_LDS (1UL << 4)
#define SPE_PMU_FEAT_ERND (1UL << 5)
#define SPE_PMU_FEAT_INV_FILT_EVT (1UL << 6)
#define SPE_PMU_FEAT_DEV_PROBED (1UL << 63)
u64 features;
u16 max_record_sz;
u16 align;
struct perf_output_handle __percpu *handle;
};
#define to_spe_pmu(p) (container_of(p, struct arm_spe_pmu, pmu))
/* Convert a free-running index from perf into an SPE buffer offset */
#define PERF_IDX2OFF(idx, buf) ((idx) % ((buf)->nr_pages << PAGE_SHIFT))
/* Keep track of our dynamic hotplug state */
static enum cpuhp_state arm_spe_pmu_online;
enum arm_spe_pmu_buf_fault_action {
SPE_PMU_BUF_FAULT_ACT_SPURIOUS,
SPE_PMU_BUF_FAULT_ACT_FATAL,
SPE_PMU_BUF_FAULT_ACT_OK,
};
/* This sysfs gunk was really good fun to write. */
enum arm_spe_pmu_capabilities {
SPE_PMU_CAP_ARCH_INST = 0,
SPE_PMU_CAP_ERND,
SPE_PMU_CAP_FEAT_MAX,
SPE_PMU_CAP_CNT_SZ = SPE_PMU_CAP_FEAT_MAX,
SPE_PMU_CAP_MIN_IVAL,
};
static int arm_spe_pmu_feat_caps[SPE_PMU_CAP_FEAT_MAX] = {
[SPE_PMU_CAP_ARCH_INST] = SPE_PMU_FEAT_ARCH_INST,
[SPE_PMU_CAP_ERND] = SPE_PMU_FEAT_ERND,
};
static u32 arm_spe_pmu_cap_get(struct arm_spe_pmu *spe_pmu, int cap)
{
if (cap < SPE_PMU_CAP_FEAT_MAX)
return !!(spe_pmu->features & arm_spe_pmu_feat_caps[