diff options
Diffstat (limited to 'arch/sh/oprofile')
| -rw-r--r-- | arch/sh/oprofile/Makefile | 10 | ||||
| -rw-r--r-- | arch/sh/oprofile/backtrace.c | 73 | ||||
| -rw-r--r-- | arch/sh/oprofile/common.c | 138 | ||||
| -rw-r--r-- | arch/sh/oprofile/op_impl.h | 33 | ||||
| -rw-r--r-- | arch/sh/oprofile/op_model_sh7750.c | 255 |
5 files changed, 57 insertions, 452 deletions
diff --git a/arch/sh/oprofile/Makefile b/arch/sh/oprofile/Makefile index 8e6eec91c14..ce3b119021e 100644 --- a/arch/sh/oprofile/Makefile +++ b/arch/sh/oprofile/Makefile @@ -1,13 +1,15 @@ obj-$(CONFIG_OPROFILE) += oprofile.o +CFLAGS_common.o += -DUTS_MACHINE='"$(UTS_MACHINE)"' + DRIVER_OBJS = $(addprefix ../../../drivers/oprofile/, \ oprof.o cpu_buffer.o buffer_sync.o \ event_buffer.o oprofile_files.o \ oprofilefs.o oprofile_stats.o \ timer_int.o ) -oprofile-y := $(DRIVER_OBJS) common.o backtrace.o +ifeq ($(CONFIG_HW_PERF_EVENTS),y) +DRIVER_OBJS += $(addprefix ../../../drivers/oprofile/, oprofile_perf.o) +endif -oprofile-$(CONFIG_CPU_SUBTYPE_SH7750S) += op_model_sh7750.o -oprofile-$(CONFIG_CPU_SUBTYPE_SH7750) += op_model_sh7750.o -oprofile-$(CONFIG_CPU_SUBTYPE_SH7091) += op_model_sh7750.o +oprofile-y := $(DRIVER_OBJS) common.o backtrace.o diff --git a/arch/sh/oprofile/backtrace.c b/arch/sh/oprofile/backtrace.c index 9499a2914f8..9c88dcd56e8 100644 --- a/arch/sh/oprofile/backtrace.c +++ b/arch/sh/oprofile/backtrace.c @@ -17,9 +17,30 @@ #include <linux/sched.h> #include <linux/kallsyms.h> #include <linux/mm.h> +#include <asm/unwinder.h> #include <asm/ptrace.h> #include <asm/uaccess.h> #include <asm/sections.h> +#include <asm/stacktrace.h> + +static int backtrace_stack(void *data, char *name) +{ + /* Yes, we want all stacks */ + return 0; +} + +static void backtrace_address(void *data, unsigned long addr, int reliable) +{ + unsigned int *depth = data; + + if ((*depth)--) + oprofile_add_trace(addr); +} + +static struct stacktrace_ops backtrace_ops = { + .stack = backtrace_stack, + .address = backtrace_address, +}; /* Limit to stop backtracing too far. */ static int backtrace_limit = 20; @@ -47,50 +68,6 @@ user_backtrace(unsigned long *stackaddr, struct pt_regs *regs) return stackaddr; } -/* - * | | /\ Higher addresses - * | | - * --------------- stack base (address of current_thread_info) - * | thread info | - * . . - * | stack | - * --------------- saved regs->regs[15] value if valid - * . . - * --------------- struct pt_regs stored on stack (struct pt_regs *) - * | | - * . . - * | | - * --------------- ??? - * | | - * | | \/ Lower addresses - * - * Thus, &pt_regs <-> stack base restricts the valid(ish) fp values - */ -static int valid_kernel_stack(unsigned long *stackaddr, struct pt_regs *regs) -{ - unsigned long stack = (unsigned long)regs; - unsigned long stack_base = (stack & ~(THREAD_SIZE - 1)) + THREAD_SIZE; - - return ((unsigned long)stackaddr > stack) && ((unsigned long)stackaddr < stack_base); -} - -static unsigned long * -kernel_backtrace(unsigned long *stackaddr, struct pt_regs *regs) -{ - unsigned long addr; - - /* - * If not a valid kernel address, keep going till we find one - * or the SP stops being a valid address. - */ - do { - addr = *stackaddr++; - oprofile_add_trace(addr); - } while (valid_kernel_stack(stackaddr, regs)); - - return stackaddr; -} - void sh_backtrace(struct pt_regs * const regs, unsigned int depth) { unsigned long *stackaddr; @@ -101,11 +78,11 @@ void sh_backtrace(struct pt_regs * const regs, unsigned int depth) if (depth > backtrace_limit) depth = backtrace_limit; - stackaddr = (unsigned long *)regs->regs[15]; + stackaddr = (unsigned long *)kernel_stack_pointer(regs); if (!user_mode(regs)) { - while (depth-- && valid_kernel_stack(stackaddr, regs)) - stackaddr = kernel_backtrace(stackaddr, regs); - + if (depth) + unwind_stack(NULL, regs, stackaddr, + &backtrace_ops, &depth); return; } diff --git a/arch/sh/oprofile/common.c b/arch/sh/oprofile/common.c index 1d97d64cb95..e4dd5d5a111 100644 --- a/arch/sh/oprofile/common.c +++ b/arch/sh/oprofile/common.c @@ -1,7 +1,7 @@ /* * arch/sh/oprofile/init.c * - * Copyright (C) 2003 - 2008 Paul Mundt + * Copyright (C) 2003 - 2010 Paul Mundt * * Based on arch/mips/oprofile/common.c: * @@ -17,134 +17,48 @@ #include <linux/init.h> #include <linux/errno.h> #include <linux/smp.h> +#include <linux/perf_event.h> +#include <linux/slab.h> #include <asm/processor.h> -#include "op_impl.h" - -extern struct op_sh_model op_model_sh7750_ops __weak; -extern struct op_sh_model op_model_sh4a_ops __weak; - -static struct op_sh_model *model; - -static struct op_counter_config ctr[20]; extern void sh_backtrace(struct pt_regs * const regs, unsigned int depth); -static int op_sh_setup(void) -{ - /* Pre-compute the values to stuff in the hardware registers. */ - model->reg_setup(ctr); - - /* Configure the registers on all cpus. */ - on_each_cpu(model->cpu_setup, NULL, 1); - - return 0; -} - -static int op_sh_create_files(struct super_block *sb, struct dentry *root) -{ - int i, ret = 0; - - for (i = 0; i < model->num_counters; i++) { - struct dentry *dir; - char buf[4]; - - snprintf(buf, sizeof(buf), "%d", i); - dir = oprofilefs_mkdir(sb, root, buf); - - ret |= oprofilefs_create_ulong(sb, dir, "enabled", &ctr[i].enabled); - ret |= oprofilefs_create_ulong(sb, dir, "event", &ctr[i].event); - ret |= oprofilefs_create_ulong(sb, dir, "kernel", &ctr[i].kernel); - ret |= oprofilefs_create_ulong(sb, dir, "user", &ctr[i].user); - - if (model->create_files) - ret |= model->create_files(sb, dir); - else - ret |= oprofilefs_create_ulong(sb, dir, "count", &ctr[i].count); - - /* Dummy entries */ - ret |= oprofilefs_create_ulong(sb, dir, "unit_mask", &ctr[i].unit_mask); - } - - return ret; -} - -static int op_sh_start(void) -{ - /* Enable performance monitoring for all counters. */ - on_each_cpu(model->cpu_start, NULL, 1); - - return 0; -} +#ifdef CONFIG_HW_PERF_EVENTS +/* + * This will need to be reworked when multiple PMUs are supported. + */ +static char *sh_pmu_op_name; -static void op_sh_stop(void) +char *op_name_from_perf_id(void) { - /* Disable performance monitoring for all counters. */ - on_each_cpu(model->cpu_stop, NULL, 1); + return sh_pmu_op_name; } int __init oprofile_arch_init(struct oprofile_operations *ops) { - struct op_sh_model *lmodel = NULL; - int ret; - - /* - * Always assign the backtrace op. If the counter initialization - * fails, we fall back to the timer which will still make use of - * this. - */ ops->backtrace = sh_backtrace; - switch (current_cpu_data.type) { - /* SH-4 types */ - case CPU_SH7750: - case CPU_SH7750S: - lmodel = &op_model_sh7750_ops; - break; - - /* SH-4A types */ - case CPU_SH7763: - case CPU_SH7770: - case CPU_SH7780: - case CPU_SH7781: - case CPU_SH7785: - case CPU_SH7723: - case CPU_SHX3: - lmodel = &op_model_sh4a_ops; - break; - - /* SH4AL-DSP types */ - case CPU_SH7343: - case CPU_SH7722: - case CPU_SH7366: - lmodel = &op_model_sh4a_ops; - break; - } - - if (!lmodel) + if (perf_num_counters() == 0) return -ENODEV; - if (!(current_cpu_data.flags & CPU_HAS_PERF_COUNTER)) - return -ENODEV; - - ret = lmodel->init(); - if (unlikely(ret != 0)) - return ret; - - model = lmodel; - - ops->setup = op_sh_setup; - ops->create_files = op_sh_create_files; - ops->start = op_sh_start; - ops->stop = op_sh_stop; - ops->cpu_type = lmodel->cpu_type; - printk(KERN_INFO "oprofile: using %s performance monitoring.\n", - lmodel->cpu_type); + sh_pmu_op_name = kasprintf(GFP_KERNEL, "%s/%s", + UTS_MACHINE, perf_pmu_name()); + if (unlikely(!sh_pmu_op_name)) + return -ENOMEM; - return 0; + return oprofile_perf_init(ops); } void oprofile_arch_exit(void) { - if (model && model->exit) - model->exit(); + oprofile_perf_exit(); + kfree(sh_pmu_op_name); +} +#else +int __init oprofile_arch_init(struct oprofile_operations *ops) +{ + ops->backtrace = sh_backtrace; + return -ENODEV; } +void oprofile_arch_exit(void) {} +#endif /* CONFIG_HW_PERF_EVENTS */ diff --git a/arch/sh/oprofile/op_impl.h b/arch/sh/oprofile/op_impl.h deleted file mode 100644 index 4d509975eba..00000000000 --- a/arch/sh/oprofile/op_impl.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef __OP_IMPL_H -#define __OP_IMPL_H - -/* Per-counter configuration as set via oprofilefs. */ -struct op_counter_config { - unsigned long enabled; - unsigned long event; - - unsigned long long count; - - /* Dummy values for userspace tool compliance */ - unsigned long kernel; - unsigned long user; - unsigned long unit_mask; -}; - -/* Per-architecture configury and hooks. */ -struct op_sh_model { - void (*reg_setup)(struct op_counter_config *); - int (*create_files)(struct super_block *sb, struct dentry *dir); - void (*cpu_setup)(void *dummy); - int (*init)(void); - void (*exit)(void); - void (*cpu_start)(void *args); - void (*cpu_stop)(void *args); - char *cpu_type; - unsigned char num_counters; -}; - -/* arch/sh/oprofile/common.c */ -extern void sh_backtrace(struct pt_regs * const regs, unsigned int depth); - -#endif /* __OP_IMPL_H */ diff --git a/arch/sh/oprofile/op_model_sh7750.c b/arch/sh/oprofile/op_model_sh7750.c deleted file mode 100644 index c892c7c30c2..00000000000 --- a/arch/sh/oprofile/op_model_sh7750.c +++ /dev/null @@ -1,255 +0,0 @@ -/* - * arch/sh/oprofile/op_model_sh7750.c - * - * OProfile support for SH7750/SH7750S Performance Counters - * - * Copyright (C) 2003 - 2008 Paul Mundt - * - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file "COPYING" in the main directory of this archive - * for more details. - */ -#include <linux/kernel.h> -#include <linux/oprofile.h> -#include <linux/profile.h> -#include <linux/init.h> -#include <linux/errno.h> -#include <linux/interrupt.h> -#include <linux/io.h> -#include <linux/fs.h> -#include "op_impl.h" - -#define PM_CR_BASE 0xff000084 /* 16-bit */ -#define PM_CTR_BASE 0xff100004 /* 32-bit */ - -#define PMCR(n) (PM_CR_BASE + ((n) * 0x04)) -#define PMCTRH(n) (PM_CTR_BASE + 0x00 + ((n) * 0x08)) -#define PMCTRL(n) (PM_CTR_BASE + 0x04 + ((n) * 0x08)) - -#define PMCR_PMM_MASK 0x0000003f - -#define PMCR_CLKF 0x00000100 -#define PMCR_PMCLR 0x00002000 -#define PMCR_PMST 0x00004000 -#define PMCR_PMEN 0x00008000 - -struct op_sh_model op_model_sh7750_ops; - -#define NR_CNTRS 2 - -static struct sh7750_ppc_register_config { - unsigned int ctrl; - unsigned long cnt_hi; - unsigned long cnt_lo; -} regcache[NR_CNTRS]; - -/* - * There are a number of events supported by each counter (33 in total). - * Since we have 2 counters, each counter will take the event code as it - * corresponds to the PMCR PMM setting. Each counter can be configured - * independently. - * - * Event Code Description - * ---------- ----------- - * - * 0x01 Operand read access - * 0x02 Operand write access - * 0x03 UTLB miss - * 0x04 Operand cache read miss - * 0x05 Operand cache write miss - * 0x06 Instruction fetch (w/ cache) - * 0x07 Instruction TLB miss - * 0x08 Instruction cache miss - * 0x09 All operand accesses - * 0x0a All instruction accesses - * 0x0b OC RAM operand access - * 0x0d On-chip I/O space access - * 0x0e Operand access (r/w) - * 0x0f Operand cache miss (r/w) - * 0x10 Branch instruction - * 0x11 Branch taken - * 0x12 BSR/BSRF/JSR - * 0x13 Instruction execution - * 0x14 Instruction execution in parallel - * 0x15 FPU Instruction execution - * 0x16 Interrupt - * 0x17 NMI - * 0x18 trapa instruction execution - * 0x19 UBCA match - * 0x1a UBCB match - * 0x21 Instruction cache fill - * 0x22 Operand cache fill - * 0x23 Elapsed time - * 0x24 Pipeline freeze by I-cache miss - * 0x25 Pipeline freeze by D-cache miss - * 0x27 Pipeline freeze by branch instruction - * 0x28 Pipeline freeze by CPU register - * 0x29 Pipeline freeze by FPU - * - * Unfortunately we don't have a native exception or interrupt for counter - * overflow (although since these counters can run for 16.3 days without - * overflowing, it's not really necessary). - * - * OProfile on the other hand likes to have samples taken periodically, so - * for now we just piggyback the timer interrupt to get the expected - * behavior. - */ - -static int sh7750_timer_notify(struct pt_regs *regs) -{ - oprofile_add_sample(regs, 0); - return 0; -} - -static u64 sh7750_read_counter(int counter) -{ - return (u64)((u64)(__raw_readl(PMCTRH(counter)) & 0xffff) << 32) | - __raw_readl(PMCTRL(counter)); -} - -/* - * Files will be in a path like: - * - * /<oprofilefs mount point>/<counter number>/<file> - * - * So when dealing with <file>, we look to the parent dentry for the counter - * number. - */ -static inline int to_counter(struct file *file) -{ - const unsigned char *name = file->f_path.dentry->d_parent->d_name.name; - - return (int)simple_strtol(name, NULL, 10); -} - -/* - * XXX: We have 48-bit counters, so we're probably going to want something - * more along the lines of oprofilefs_ullong_to_user().. Truncating to - * unsigned long works fine for now though, as long as we don't attempt to - * profile for too horribly long. - */ -static ssize_t sh7750_read_count(struct file *file, char __user *buf, - size_t count, loff_t *ppos) -{ - int counter = to_counter(file); - u64 val = sh7750_read_counter(counter); - - return oprofilefs_ulong_to_user((unsigned long)val, buf, count, ppos); -} - -static ssize_t sh7750_write_count(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) -{ - int counter = to_counter(file); - unsigned long val; - - if (oprofilefs_ulong_from_user(&val, buf, count)) - return -EFAULT; - - /* - * Any write will clear the counter, although only 0 should be - * written for this purpose, as we do not support setting the - * counter to an arbitrary value. - */ - WARN_ON(val != 0); - - __raw_writew(__raw_readw(PMCR(counter)) | PMCR_PMCLR, PMCR(counter)); - - return count; -} - -static const struct file_operations count_fops = { - .read = sh7750_read_count, - .write = sh7750_write_count, -}; - -static int sh7750_ppc_create_files(struct super_block *sb, struct dentry *dir) -{ - return oprofilefs_create_file(sb, dir, "count", &count_fops); -} - -static void sh7750_ppc_reg_setup(struct op_counter_config *ctr) -{ - unsigned int counters = op_model_sh7750_ops.num_counters; - int i; - - for (i = 0; i < counters; i++) { - regcache[i].ctrl = 0; - regcache[i].cnt_hi = 0; - regcache[i].cnt_lo = 0; - - if (!ctr[i].enabled) - continue; - - regcache[i].ctrl |= ctr[i].event | PMCR_PMEN | PMCR_PMST; - regcache[i].cnt_hi = (unsigned long)((ctr->count >> 32) & 0xffff); - regcache[i].cnt_lo = (unsigned long)(ctr->count & 0xffffffff); - } -} - -static void sh7750_ppc_cpu_setup(void *args) -{ - unsigned int counters = op_model_sh7750_ops.num_counters; - int i; - - for (i = 0; i < counters; i++) { - __raw_writew(0, PMCR(i)); - __raw_writel(regcache[i].cnt_hi, PMCTRH(i)); - __raw_writel(regcache[i].cnt_lo, PMCTRL(i)); - } -} - -static void sh7750_ppc_cpu_start(void *args) -{ - unsigned int counters = op_model_sh7750_ops.num_counters; - int i; - - for (i = 0; i < counters; i++) - __raw_writew(regcache[i].ctrl, PMCR(i)); -} - -static void sh7750_ppc_cpu_stop(void *args) -{ - unsigned int counters = op_model_sh7750_ops.num_counters; - int i; - - /* Disable the counters */ - for (i = 0; i < counters; i++) - __raw_writew(__raw_readw(PMCR(i)) & ~PMCR_PMEN, PMCR(i)); -} - -static inline void sh7750_ppc_reset(void) -{ - unsigned int counters = op_model_sh7750_ops.num_counters; - int i; - - /* Clear the counters */ - for (i = 0; i < counters; i++) - __raw_writew(__raw_readw(PMCR(i)) | PMCR_PMCLR, PMCR(i)); -} - -static int sh7750_ppc_init(void) -{ - sh7750_ppc_reset(); - - return register_timer_hook(sh7750_timer_notify); -} - -static void sh7750_ppc_exit(void) -{ - unregister_timer_hook(sh7750_timer_notify); - - sh7750_ppc_reset(); -} - -struct op_sh_model op_model_sh7750_ops = { - .cpu_type = "sh/sh7750", - .num_counters = NR_CNTRS, - .reg_setup = sh7750_ppc_reg_setup, - .cpu_setup = sh7750_ppc_cpu_setup, - .cpu_start = sh7750_ppc_cpu_start, - .cpu_stop = sh7750_ppc_cpu_stop, - .init = sh7750_ppc_init, - .exit = sh7750_ppc_exit, - .create_files = sh7750_ppc_create_files, -}; |
