blob: e4499265ca8b9b356f4b7398537115cb55bce7f1 [file] [log] [blame]
/**
* Copyright (C) Arm Limited 2015-2016. All rights reserved.
*
* 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.
*/
#include <linux/cpumask.h>
#ifdef CONFIG_64BIT
# define GATOR_ATOMIC_T atomic64_t
# define GATOR_ATOMIC_CMPXCHG atomic64_cmpxchg
# define GATOR_ATOMIC_READ atomic64_read
#else
# define GATOR_ATOMIC_T atomic_t
# define GATOR_ATOMIC_CMPXCHG atomic_cmpxchg
# define GATOR_ATOMIC_READ atomic_read
#endif
#define GATOR_ATOMIC_PTR_TO_COUNTER(ptr) ((unsigned long) (ptr))
struct uncore_pmu {
struct list_head list;
unsigned long pmnc_counters;
unsigned long has_cycles_counter;
/* Perf PMU name */
char pmnc_name[MAXSIZE_CORE_NAME];
/* gatorfs event name */
char core_name[MAXSIZE_CORE_NAME];
/* cpumask - actually an atomic so we can CAS the pointer once without lock*/
GATOR_ATOMIC_T cpumask_atomic;
};
static LIST_HEAD(uncore_pmus);
static LIST_HEAD(gator_cpus);
static DEFINE_MUTEX(pmu_mutex);
static struct super_block *gator_sb;
static struct dentry *gator_events_dir;
static const struct gator_cpu gator_pmu_other = {
.pmnc_name = "Other",
.cpuid = 0xfffff,
.core_name = "Other",
.pmnc_counters = 6,
};
const struct gator_cpu *gator_clusters[GATOR_CLUSTER_COUNT];
int gator_cluster_count;
static ulong gator_cluster_ids[GATOR_CLUSTER_COUNT];
static const struct gator_cpu *gator_find_cpu_by_cpuid(const u32 cpuid)
{
const struct gator_cpu *gator_cpu;
list_for_each_entry(gator_cpu, &gator_cpus, list) {
if (gator_cpu->cpuid == cpuid)
return gator_cpu;
}
return NULL;
}
static const char OLD_PMU_PREFIX[] = "ARMv7 Cortex-";
static const char NEW_PMU_PREFIX[] = "ARMv7_Cortex_";
__maybe_unused
static const struct gator_cpu *gator_find_cpu_by_pmu_name(const char *const name)
{
const struct gator_cpu *gator_cpu;
list_for_each_entry(gator_cpu, &gator_cpus, list) {
if (gator_cpu->pmnc_name != NULL &&
/* Do the names match exactly? */
(strcasecmp(gator_cpu->pmnc_name, name) == 0 ||
/* Do these names match but have the old vs new prefix? */
((strncasecmp(name, OLD_PMU_PREFIX, sizeof(OLD_PMU_PREFIX) - 1) == 0 &&
strncasecmp(gator_cpu->pmnc_name, NEW_PMU_PREFIX, sizeof(NEW_PMU_PREFIX) - 1) == 0 &&
strcasecmp(name + sizeof(OLD_PMU_PREFIX) - 1, gator_cpu->pmnc_name + sizeof(NEW_PMU_PREFIX) - 1) == 0))))
return gator_cpu;
}
return NULL;
}
__maybe_unused
static const struct uncore_pmu *gator_find_uncore_pmu(const char *const name)
{
const struct uncore_pmu *uncore_pmu;
list_for_each_entry(uncore_pmu, &uncore_pmus, list) {
if (uncore_pmu->pmnc_name != NULL && strcasecmp(uncore_pmu->pmnc_name, name) == 0)
return uncore_pmu;
}
return NULL;
}
static ssize_t gator_pmu_init_write(struct file *file, char const __user *buf, size_t count, loff_t *offset)
{
struct gator_interface *gi;
int i;
if (gator_events_perf_pmu_reread() != 0 ||
gator_events_perf_pmu_create_files(gator_sb, gator_events_dir) != 0)
return -EINVAL;
if (gator_cluster_count == 0)
gator_clusters[gator_cluster_count++] = &gator_pmu_other;
/* cluster information */
{
struct dentry *dir;
dir = gatorfs_mkdir(gator_sb, file->f_path.dentry->d_parent, "clusters");
for (i = 0; i < gator_cluster_count; i++) {
gator_cluster_ids[i] = i;
gatorfs_create_ro_ulong(gator_sb, dir, gator_clusters[i]->pmnc_name, &gator_cluster_ids[i]);
}
}
/* needs PMU info, so initialize afterwards */
gator_trace_power_init();
if (gator_trace_power_create_files(gator_sb, gator_events_dir) != 0)
return -EINVAL;
gator_trace_sched_init();
if (sched_trace_create_files(gator_sb, gator_events_dir) != 0)
return -EINVAL;
/* events sources */
for (i = 0; i < ARRAY_SIZE(gator_events_list); i++)
if (gator_events_list[i])
gator_events_list[i]();
list_for_each_entry(gi, &gator_events, list)
if (gi->create_files)
if (gi->create_files(gator_sb, gator_events_dir) != 0)
pr_err("gator: create_files failed for %s\n", gi->name);
return count;
}
static const struct file_operations gator_pmu_init_fops = {
.write = gator_pmu_init_write,
};
static ssize_t gator_pmu_str_read_file(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
char *const val = file->private_data;
return simple_read_from_buffer(buf, count, offset, val, strlen(val));
}
static ssize_t gator_pmu_str_write_file(struct file *file, char const __user *buf, size_t count, loff_t *offset)
{
char *value = file->private_data;
if (*offset)
return -EINVAL;
if (count >= MAXSIZE_CORE_NAME)
return -EINVAL;
if (copy_from_user(value, buf, count))
return -EFAULT;
value[count] = 0;
value = strstrip(value);
return count;
}
static const struct file_operations gator_pmu_str_fops = {
.read = gator_pmu_str_read_file,
.write = gator_pmu_str_write_file,
.open = default_open,
};
static int gator_pmu_create_str(struct super_block *sb, struct dentry *root, char const *name, char *const val)
{
struct dentry *d = __gatorfs_create_file(sb, root, name, &gator_pmu_str_fops, 0644);
if (!d)
return -EFAULT;
d->d_inode->i_private = val;
return 0;
}
#define GATOR_NONE_STRING "(none)"
static ssize_t gator_pmu_cpumask_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
struct uncore_pmu * uncore_pmu = NULL;
struct cpumask * cpumask = NULL;
int cpu = 0;
int bpos = 0;
char buffer[128];
uncore_pmu = (struct uncore_pmu *) file->private_data;
if (!uncore_pmu)
return -EFAULT;
cpumask = (struct cpumask *) GATOR_ATOMIC_READ(&(uncore_pmu->cpumask_atomic));
if (!cpumask)
return simple_read_from_buffer(buf, count, offset, GATOR_NONE_STRING, strlen(GATOR_NONE_STRING));
for_each_cpu(cpu, cpumask) {
bpos = snprintf(buffer + bpos, sizeof(buffer) - bpos, (bpos > 0 ? ",%d" : "%d"), cpu);
if (bpos >= (sizeof(buffer) - 1))
break;
}
return simple_read_from_buffer(buf, count, offset, buffer, strlen(buffer));
}
static ssize_t gator_pmu_cpumask_write(struct file *file, char const __user *ubuf, size_t count, loff_t *offset)
{
struct uncore_pmu * uncore_pmu = NULL;
struct cpumask * cpumask = NULL;
unsigned long value = 0;
int retval = 0;
uncore_pmu = (struct uncore_pmu *) file->private_data;
if (!uncore_pmu)
return -EFAULT;
/* validate args */
if (*offset)
return -EINVAL;
/* parse ulong */
retval = gatorfs_ulong_from_user(&value, ubuf, count);
if (retval)
return retval;
if (value >= nr_cpu_ids)
return -EINVAL;
/* allocate the mask if it does not already exist */
cpumask = (struct cpumask *) GATOR_ATOMIC_READ(&(uncore_pmu->cpumask_atomic));
if (!cpumask) {
struct cpumask * ret = NULL;
cpumask = kzalloc(cpumask_size(), GFP_KERNEL);
if (!cpumask)
return -ENOMEM;
ret = (struct cpumask * ) GATOR_ATOMIC_CMPXCHG(&(uncore_pmu->cpumask_atomic), GATOR_ATOMIC_PTR_TO_COUNTER(NULL), GATOR_ATOMIC_PTR_TO_COUNTER(cpumask));
if (ret != NULL) {
/* someone else got there first, free the one we allocated and use the existing instead */
kfree(cpumask);
cpumask = ret;
}
}
cpumask_set_cpu(value, cpumask);
return count;
}
static const struct file_operations cpumask_fops = {
.read = gator_pmu_cpumask_read,
.write = gator_pmu_cpumask_write,
.open = default_open,
};
static ssize_t gator_pmu_export_write(struct file *file, char const __user *ubuf, size_t count, loff_t *offset)
{
struct dentry *dir;
struct dentry *parent;
char buf[MAXSIZE_CORE_NAME];
const char *str;
if (*offset)
return -EINVAL;
if (count >= sizeof(buf))
return -EINVAL;
if (copy_from_user(&buf, ubuf, count))
return -EFAULT;
buf[count] = 0;
str = strstrip(buf);
parent = file->f_path.dentry->d_parent;
dir = gatorfs_mkdir(gator_sb, parent, buf);
if (!dir)
return -EINVAL;
if (strcmp("pmu", parent->d_name.name) == 0) {
struct gator_cpu *gator_cpu;
gator_cpu = kmalloc(sizeof(*gator_cpu), GFP_KERNEL);
if (gator_cpu == NULL)
return -ENOMEM;
memset(gator_cpu, 0, sizeof(*gator_cpu));
gatorfs_create_ulong(gator_sb, dir, "cpuid", &gator_cpu->cpuid);
gator_pmu_create_str(gator_sb, dir, "core_name", gator_cpu->core_name);
strcpy(gator_cpu->pmnc_name, str);
gator_pmu_create_str(gator_sb, dir, "dt_name", gator_cpu->dt_name);
gatorfs_create_ulong(gator_sb, dir, "pmnc_counters", &gator_cpu->pmnc_counters);
mutex_lock(&pmu_mutex);
list_add_tail(&gator_cpu->list, &gator_cpus); /* mutex */
mutex_unlock(&pmu_mutex);
} else {
struct uncore_pmu *uncore_pmu;
uncore_pmu = kmalloc(sizeof(*uncore_pmu), GFP_KERNEL);
if (uncore_pmu == NULL)
return -ENOMEM;
memset(uncore_pmu, 0, sizeof(*uncore_pmu));
strcpy(uncore_pmu->pmnc_name, str);
gator_pmu_create_str(gator_sb, dir, "core_name", uncore_pmu->core_name);
gatorfs_create_ulong(gator_sb, dir, "pmnc_counters", &uncore_pmu->pmnc_counters);
gatorfs_create_ulong(gator_sb, dir, "has_cycles_counter", &uncore_pmu->has_cycles_counter);
gatorfs_create_file_data(gator_sb, dir, "cpumask", &cpumask_fops, uncore_pmu);
mutex_lock(&pmu_mutex);
list_add_tail(&uncore_pmu->list, &uncore_pmus); /* mutex */
mutex_unlock(&pmu_mutex);
}
return count;
}
static const struct file_operations export_fops = {
.write = gator_pmu_export_write,
};
static int gator_pmu_create_files(struct super_block *sb, struct dentry *root, struct dentry *events)
{
struct dentry *dir;
gator_sb = sb;
gator_events_dir = events;
gatorfs_create_file(sb, root, "pmu_init", &gator_pmu_init_fops);
dir = gatorfs_mkdir(sb, root, "pmu");
if (!dir)
return -1;
gatorfs_create_file(sb, dir, "export", &export_fops);
dir = gatorfs_mkdir(sb, root, "uncore_pmu");
if (!dir)
return -1;
gatorfs_create_file(sb, dir, "export", &export_fops);
return 0;
}
static void gator_pmu_exit(void)
{
mutex_lock(&pmu_mutex);
{
struct gator_cpu *gator_cpu;
struct gator_cpu *next;
list_for_each_entry_safe(gator_cpu, next, &gator_cpus, list) {
kfree(gator_cpu);
}
}
{
struct uncore_pmu *uncore_pmu;
struct uncore_pmu *next;
list_for_each_entry_safe(uncore_pmu, next, &uncore_pmus, list) {
struct cpumask * cpumask = (struct cpumask *) GATOR_ATOMIC_READ(&(uncore_pmu->cpumask_atomic));
if (cpumask) {
kfree(cpumask);
}
kfree(uncore_pmu);
}
}
mutex_unlock(&pmu_mutex);
}