282 lines
8.1 KiB
C
Executable File
282 lines
8.1 KiB
C
Executable File
/**
|
|
* 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.
|
|
*/
|
|
|
|
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];
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
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) {
|
|
kfree(uncore_pmu);
|
|
}
|
|
}
|
|
mutex_unlock(&pmu_mutex);
|
|
}
|