diff options
Diffstat (limited to 'arch/arm/oprofile/common.c')
-rw-r--r-- | arch/arm/oprofile/common.c | 132 |
1 files changed, 132 insertions, 0 deletions
diff --git a/arch/arm/oprofile/common.c b/arch/arm/oprofile/common.c new file mode 100644 index 000000000..7cb3e0453 --- /dev/null +++ b/arch/arm/oprofile/common.c @@ -0,0 +1,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/perf_event.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <asm/stacktrace.h> +#include <linux/uaccess.h> + +#include <asm/perf_event.h> +#include <asm/ptrace.h> + +#ifdef CONFIG_HW_PERF_EVENTS + +/* + * 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[] = { + { "armv5_xscale1", "arm/xscale1" }, + { "armv5_xscale2", "arm/xscale2" }, + { "armv6_1136", "arm/armv6" }, + { "armv6_1156", "arm/armv6" }, + { "armv6_1176", "arm/armv6" }, + { "armv6_11mpcore", "arm/mpcore" }, + { "armv7_cortex_a8", "arm/armv7" }, + { "armv7_cortex_a9", "arm/armv7-ca9" }, +}; + +char *op_name_from_perf_id(void) +{ + 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; + } + + return NULL; +} +#endif + +static int report_trace(struct stackframe *frame, void *d) +{ + unsigned int *depth = d; + + if (*depth) { + oprofile_add_trace(frame->pc); + (*depth)--; + } + + return *depth == 0; +} + +/* + * 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 struct frame_tail* user_backtrace(struct frame_tail *tail) +{ + struct frame_tail buftail[2]; + + /* Also check accessibility of one struct frame_tail beyond */ + if (!access_ok(tail, sizeof(buftail))) + return NULL; + if (__copy_from_user_inatomic(buftail, tail, sizeof(buftail))) + return NULL; + + 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; + + return buftail[0].fp-1; +} + +static void arm_backtrace(struct pt_regs * const regs, unsigned int depth) +{ + struct frame_tail *tail = ((struct frame_tail *) regs->ARM_fp) - 1; + + if (!user_mode(regs)) { + struct stackframe frame; + arm_get_current_stackframe(regs, &frame); + walk_stackframe(&frame, report_trace, &depth); + return; + } + + while (depth-- && tail && !((unsigned long) tail & 3)) + tail = user_backtrace(tail); +} + +int __init oprofile_arch_init(struct oprofile_operations *ops) +{ + /* provide backtrace support also in timer mode: */ + ops->backtrace = arm_backtrace; + + return oprofile_perf_init(ops); +} + +void oprofile_arch_exit(void) +{ + oprofile_perf_exit(); +} |