/*
* Perf support for the Statistical Profiling Extension, introduced as
* part of ARMv8.2.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* 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/cpuhotplug.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/perf_event.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <asm/sysreg.h>
#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 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_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[cap]);
switch (cap) {
case SPE_PMU_CAP_CNT_SZ:
return spe_pmu->counter_sz;
case SPE_PMU_CAP_MIN_IVAL:
return spe_pmu->min_period;
default:
WARN(1, "unknown cap %d\n", cap);
}
return 0;
}
static ssize_t arm_spe_pmu_cap_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct platform_device *pdev = to_platform_device(dev);
struct arm_spe_pmu *spe_pmu = platform_get_drvdata(pdev);
struct dev_ext_attribute *ea =
container_of(attr, struct dev_ext_attribute, attr);
int cap