diff options
Diffstat (limited to 'arch/arm/oprofile/common.c')
| -rw-r--r-- | arch/arm/oprofile/common.c | 207 |
1 files changed, 92 insertions, 115 deletions
diff --git a/arch/arm/oprofile/common.c b/arch/arm/oprofile/common.c index e57dde88289..99c63d4b6af 100644 --- a/arch/arm/oprofile/common.c +++ b/arch/arm/oprofile/common.c @@ -2,155 +2,132 @@ * @file common.c * * @remark Copyright 2004 Oprofile Authors + * @remark Copyright 2010 ARM Ltd. * @remark Read the file COPYING * * @author Zwane Mwaikambo + * @author Will Deacon [move to perf] */ +#include <linux/cpumask.h> #include <linux/init.h> +#include <linux/mutex.h> #include <linux/oprofile.h> -#include <linux/errno.h> -#include <asm/semaphore.h> -#include <linux/sysdev.h> +#include <linux/perf_event.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <asm/stacktrace.h> +#include <linux/uaccess.h> -#include "op_counter.h" -#include "op_arm_model.h" +#include <asm/perf_event.h> +#include <asm/ptrace.h> -static struct op_arm_model_spec *pmu_model; -static int pmu_enabled; -static struct semaphore pmu_sem; +#ifdef CONFIG_HW_PERF_EVENTS -static int pmu_start(void); -static int pmu_setup(void); -static void pmu_stop(void); -static int pmu_create_files(struct super_block *, struct dentry *); +/* + * OProfile has a curious naming scheme for the ARM PMUs, but they are + * part of the user ABI so we need to map from the perf PMU name for + * supported PMUs. + */ +static struct op_perf_name { + char *perf_name; + char *op_name; +} op_perf_name_map[] = { + { "xscale1", "arm/xscale1" }, + { "xscale1", "arm/xscale2" }, + { "v6", "arm/armv6" }, + { "v6mpcore", "arm/mpcore" }, + { "ARMv7 Cortex-A8", "arm/armv7" }, + { "ARMv7 Cortex-A9", "arm/armv7-ca9" }, +}; -#ifdef CONFIG_PM -static int pmu_suspend(struct sys_device *dev, pm_message_t state) +char *op_name_from_perf_id(void) { - if (pmu_enabled) - pmu_stop(); - return 0; -} + int i; + struct op_perf_name names; + const char *perf_name = perf_pmu_name(); + + for (i = 0; i < ARRAY_SIZE(op_perf_name_map); ++i) { + names = op_perf_name_map[i]; + if (!strcmp(names.perf_name, perf_name)) + return names.op_name; + } -static int pmu_resume(struct sys_device *dev) -{ - if (pmu_enabled) - pmu_start(); - return 0; + return NULL; } +#endif -static struct sysdev_class oprofile_sysclass = { - set_kset_name("oprofile"), - .resume = pmu_resume, - .suspend = pmu_suspend, -}; - -static struct sys_device device_oprofile = { - .id = 0, - .cls = &oprofile_sysclass, -}; - -static int __init init_driverfs(void) +static int report_trace(struct stackframe *frame, void *d) { - int ret; + unsigned int *depth = d; - if (!(ret = sysdev_class_register(&oprofile_sysclass))) - ret = sysdev_register(&device_oprofile); - - return ret; -} + if (*depth) { + oprofile_add_trace(frame->pc); + (*depth)--; + } -static void exit_driverfs(void) -{ - sysdev_unregister(&device_oprofile); - sysdev_class_unregister(&oprofile_sysclass); + return *depth == 0; } -#else -#define init_driverfs() do { } while (0) -#define exit_driverfs() do { } while (0) -#endif /* CONFIG_PM */ -struct op_counter_config counter_config[OP_MAX_COUNTER]; +/* + * The registers we're interested in are at the end of the variable + * length saved register structure. The fp points at the end of this + * structure so the address of this struct is: + * (struct frame_tail *)(xxx->fp)-1 + */ +struct frame_tail { + struct frame_tail *fp; + unsigned long sp; + unsigned long lr; +} __attribute__((packed)); -static int pmu_create_files(struct super_block *sb, struct dentry *root) +static struct frame_tail* user_backtrace(struct frame_tail *tail) { - unsigned int i; - - for (i = 0; i < pmu_model->num_counters; i++) { - struct dentry *dir; - char buf[2]; - - snprintf(buf, sizeof buf, "%d", i); - dir = oprofilefs_mkdir(sb, root, buf); - oprofilefs_create_ulong(sb, dir, "enabled", &counter_config[i].enabled); - oprofilefs_create_ulong(sb, dir, "event", &counter_config[i].event); - oprofilefs_create_ulong(sb, dir, "count", &counter_config[i].count); - oprofilefs_create_ulong(sb, dir, "unit_mask", &counter_config[i].unit_mask); - oprofilefs_create_ulong(sb, dir, "kernel", &counter_config[i].kernel); - oprofilefs_create_ulong(sb, dir, "user", &counter_config[i].user); - } + struct frame_tail buftail[2]; - return 0; -} + /* Also check accessibility of one struct frame_tail beyond */ + if (!access_ok(VERIFY_READ, tail, sizeof(buftail))) + return NULL; + if (__copy_from_user_inatomic(buftail, tail, sizeof(buftail))) + return NULL; -static int pmu_setup(void) -{ - int ret; + oprofile_add_trace(buftail[0].lr); + + /* frame pointers should strictly progress back up the stack + * (towards higher addresses) */ + if (tail + 1 >= buftail[0].fp) + return NULL; - spin_lock(&oprofilefs_lock); - ret = pmu_model->setup_ctrs(); - spin_unlock(&oprofilefs_lock); - return ret; + return buftail[0].fp-1; } -static int pmu_start(void) +static void arm_backtrace(struct pt_regs * const regs, unsigned int depth) { - int ret = -EBUSY; - - down(&pmu_sem); - if (!pmu_enabled) { - ret = pmu_model->start(); - pmu_enabled = !ret; + struct frame_tail *tail = ((struct frame_tail *) regs->ARM_fp) - 1; + + if (!user_mode(regs)) { + struct stackframe frame; + frame.fp = regs->ARM_fp; + frame.sp = regs->ARM_sp; + frame.lr = regs->ARM_lr; + frame.pc = regs->ARM_pc; + walk_stackframe(&frame, report_trace, &depth); + return; } - up(&pmu_sem); - return ret; -} -static void pmu_stop(void) -{ - down(&pmu_sem); - if (pmu_enabled) - pmu_model->stop(); - pmu_enabled = 0; - up(&pmu_sem); + while (depth-- && tail && !((unsigned long) tail & 3)) + tail = user_backtrace(tail); } -int __init pmu_init(struct oprofile_operations *ops, struct op_arm_model_spec *spec) +int __init oprofile_arch_init(struct oprofile_operations *ops) { - init_MUTEX(&pmu_sem); - - if (spec->init() < 0) - return -ENODEV; - - pmu_model = spec; - init_driverfs(); - ops->create_files = pmu_create_files; - ops->setup = pmu_setup; - ops->shutdown = pmu_stop; - ops->start = pmu_start; - ops->stop = pmu_stop; - ops->cpu_type = pmu_model->name; - printk(KERN_INFO "oprofile: using %s PMU\n", spec->name); - - return 0; + /* provide backtrace support also in timer mode: */ + ops->backtrace = arm_backtrace; + + return oprofile_perf_init(ops); } -void pmu_exit(void) +void oprofile_arch_exit(void) { - if (pmu_model) { - exit_driverfs(); - pmu_model = NULL; - } + oprofile_perf_exit(); } - |
