aboutsummaryrefslogtreecommitdiffstats
path: root/arch/sh/oprofile
diff options
context:
space:
mode:
Diffstat (limited to 'arch/sh/oprofile')
-rw-r--r--arch/sh/oprofile/Makefile13
-rw-r--r--arch/sh/oprofile/backtrace.c114
-rw-r--r--arch/sh/oprofile/common.c150
-rw-r--r--arch/sh/oprofile/op_impl.h33
-rw-r--r--arch/sh/oprofile/op_model_null.c23
-rw-r--r--arch/sh/oprofile/op_model_sh7750.c172
6 files changed, 379 insertions, 126 deletions
diff --git a/arch/sh/oprofile/Makefile b/arch/sh/oprofile/Makefile
index 2efc2e79fd29..8e6eec91c14c 100644
--- a/arch/sh/oprofile/Makefile
+++ b/arch/sh/oprofile/Makefile
@@ -6,13 +6,8 @@ DRIVER_OBJS = $(addprefix ../../../drivers/oprofile/, \
6 oprofilefs.o oprofile_stats.o \ 6 oprofilefs.o oprofile_stats.o \
7 timer_int.o ) 7 timer_int.o )
8 8
9profdrvr-y := op_model_null.o 9oprofile-y := $(DRIVER_OBJS) common.o backtrace.o
10 10
11# SH7750-style performance counters exist across 7750/7750S and 7091. 11oprofile-$(CONFIG_CPU_SUBTYPE_SH7750S) += op_model_sh7750.o
12profdrvr-$(CONFIG_CPU_SUBTYPE_SH7750S) := op_model_sh7750.o 12oprofile-$(CONFIG_CPU_SUBTYPE_SH7750) += op_model_sh7750.o
13profdrvr-$(CONFIG_CPU_SUBTYPE_SH7750) := op_model_sh7750.o 13oprofile-$(CONFIG_CPU_SUBTYPE_SH7091) += op_model_sh7750.o
14profdrvr-$(CONFIG_CPU_SUBTYPE_SH7091) := op_model_sh7750.o
15
16oprofile-y := $(DRIVER_OBJS) $(profdrvr-y)
17
18EXTRA_CFLAGS += -Werror
diff --git a/arch/sh/oprofile/backtrace.c b/arch/sh/oprofile/backtrace.c
new file mode 100644
index 000000000000..9499a2914f89
--- /dev/null
+++ b/arch/sh/oprofile/backtrace.c
@@ -0,0 +1,114 @@
1/*
2 * SH specific backtracing code for oprofile
3 *
4 * Copyright 2007 STMicroelectronics Ltd.
5 *
6 * Author: Dave Peverley <dpeverley@mpc-data.co.uk>
7 *
8 * Based on ARM oprofile backtrace code by Richard Purdie and in turn, i386
9 * oprofile backtrace code by John Levon, David Smith
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
14 *
15 */
16#include <linux/oprofile.h>
17#include <linux/sched.h>
18#include <linux/kallsyms.h>
19#include <linux/mm.h>
20#include <asm/ptrace.h>
21#include <asm/uaccess.h>
22#include <asm/sections.h>
23
24/* Limit to stop backtracing too far. */
25static int backtrace_limit = 20;
26
27static unsigned long *
28user_backtrace(unsigned long *stackaddr, struct pt_regs *regs)
29{
30 unsigned long buf_stack;
31
32 /* Also check accessibility of address */
33 if (!access_ok(VERIFY_READ, stackaddr, sizeof(unsigned long)))
34 return NULL;
35
36 if (__copy_from_user_inatomic(&buf_stack, stackaddr, sizeof(unsigned long)))
37 return NULL;
38
39 /* Quick paranoia check */
40 if (buf_stack & 3)
41 return NULL;
42
43 oprofile_add_trace(buf_stack);
44
45 stackaddr++;
46
47 return stackaddr;
48}
49
50/*
51 * | | /\ Higher addresses
52 * | |
53 * --------------- stack base (address of current_thread_info)
54 * | thread info |
55 * . .
56 * | stack |
57 * --------------- saved regs->regs[15] value if valid
58 * . .
59 * --------------- struct pt_regs stored on stack (struct pt_regs *)
60 * | |
61 * . .
62 * | |
63 * --------------- ???
64 * | |
65 * | | \/ Lower addresses
66 *
67 * Thus, &pt_regs <-> stack base restricts the valid(ish) fp values
68 */
69static int valid_kernel_stack(unsigned long *stackaddr, struct pt_regs *regs)
70{
71 unsigned long stack = (unsigned long)regs;
72 unsigned long stack_base = (stack & ~(THREAD_SIZE - 1)) + THREAD_SIZE;
73
74 return ((unsigned long)stackaddr > stack) && ((unsigned long)stackaddr < stack_base);
75}
76
77static unsigned long *
78kernel_backtrace(unsigned long *stackaddr, struct pt_regs *regs)
79{
80 unsigned long addr;
81
82 /*
83 * If not a valid kernel address, keep going till we find one
84 * or the SP stops being a valid address.
85 */
86 do {
87 addr = *stackaddr++;
88 oprofile_add_trace(addr);
89 } while (valid_kernel_stack(stackaddr, regs));
90
91 return stackaddr;
92}
93
94void sh_backtrace(struct pt_regs * const regs, unsigned int depth)
95{
96 unsigned long *stackaddr;
97
98 /*
99 * Paranoia - clip max depth as we could get lost in the weeds.
100 */
101 if (depth > backtrace_limit)
102 depth = backtrace_limit;
103
104 stackaddr = (unsigned long *)regs->regs[15];
105 if (!user_mode(regs)) {
106 while (depth-- && valid_kernel_stack(stackaddr, regs))
107 stackaddr = kernel_backtrace(stackaddr, regs);
108
109 return;
110 }
111
112 while (depth-- && (stackaddr != NULL))
113 stackaddr = user_backtrace(stackaddr, regs);
114}
diff --git a/arch/sh/oprofile/common.c b/arch/sh/oprofile/common.c
new file mode 100644
index 000000000000..1d97d64cb95f
--- /dev/null
+++ b/arch/sh/oprofile/common.c
@@ -0,0 +1,150 @@
1/*
2 * arch/sh/oprofile/init.c
3 *
4 * Copyright (C) 2003 - 2008 Paul Mundt
5 *
6 * Based on arch/mips/oprofile/common.c:
7 *
8 * Copyright (C) 2004, 2005 Ralf Baechle
9 * Copyright (C) 2005 MIPS Technologies, Inc.
10 *
11 * This file is subject to the terms and conditions of the GNU General Public
12 * License. See the file "COPYING" in the main directory of this archive
13 * for more details.
14 */
15#include <linux/kernel.h>
16#include <linux/oprofile.h>
17#include <linux/init.h>
18#include <linux/errno.h>
19#include <linux/smp.h>
20#include <asm/processor.h>
21#include "op_impl.h"
22
23extern struct op_sh_model op_model_sh7750_ops __weak;
24extern struct op_sh_model op_model_sh4a_ops __weak;
25
26static struct op_sh_model *model;
27
28static struct op_counter_config ctr[20];
29
30extern void sh_backtrace(struct pt_regs * const regs, unsigned int depth);
31
32static int op_sh_setup(void)
33{
34 /* Pre-compute the values to stuff in the hardware registers. */
35 model->reg_setup(ctr);
36
37 /* Configure the registers on all cpus. */
38 on_each_cpu(model->cpu_setup, NULL, 1);
39
40 return 0;
41}
42
43static int op_sh_create_files(struct super_block *sb, struct dentry *root)
44{
45 int i, ret = 0;
46
47 for (i = 0; i < model->num_counters; i++) {
48 struct dentry *dir;
49 char buf[4];
50
51 snprintf(buf, sizeof(buf), "%d", i);
52 dir = oprofilefs_mkdir(sb, root, buf);
53
54 ret |= oprofilefs_create_ulong(sb, dir, "enabled", &ctr[i].enabled);
55 ret |= oprofilefs_create_ulong(sb, dir, "event", &ctr[i].event);
56 ret |= oprofilefs_create_ulong(sb, dir, "kernel", &ctr[i].kernel);
57 ret |= oprofilefs_create_ulong(sb, dir, "user", &ctr[i].user);
58
59 if (model->create_files)
60 ret |= model->create_files(sb, dir);
61 else
62 ret |= oprofilefs_create_ulong(sb, dir, "count", &ctr[i].count);
63
64 /* Dummy entries */
65 ret |= oprofilefs_create_ulong(sb, dir, "unit_mask", &ctr[i].unit_mask);
66 }
67
68 return ret;
69}
70
71static int op_sh_start(void)
72{
73 /* Enable performance monitoring for all counters. */
74 on_each_cpu(model->cpu_start, NULL, 1);
75
76 return 0;
77}
78
79static void op_sh_stop(void)
80{
81 /* Disable performance monitoring for all counters. */
82 on_each_cpu(model->cpu_stop, NULL, 1);
83}
84
85int __init oprofile_arch_init(struct oprofile_operations *ops)
86{
87 struct op_sh_model *lmodel = NULL;
88 int ret;
89
90 /*
91 * Always assign the backtrace op. If the counter initialization
92 * fails, we fall back to the timer which will still make use of
93 * this.
94 */
95 ops->backtrace = sh_backtrace;
96
97 switch (current_cpu_data.type) {
98 /* SH-4 types */
99 case CPU_SH7750:
100 case CPU_SH7750S:
101 lmodel = &op_model_sh7750_ops;
102 break;
103
104 /* SH-4A types */
105 case CPU_SH7763:
106 case CPU_SH7770:
107 case CPU_SH7780:
108 case CPU_SH7781:
109 case CPU_SH7785:
110 case CPU_SH7723:
111 case CPU_SHX3:
112 lmodel = &op_model_sh4a_ops;
113 break;
114
115 /* SH4AL-DSP types */
116 case CPU_SH7343:
117 case CPU_SH7722:
118 case CPU_SH7366:
119 lmodel = &op_model_sh4a_ops;
120 break;
121 }
122
123 if (!lmodel)
124 return -ENODEV;
125 if (!(current_cpu_data.flags & CPU_HAS_PERF_COUNTER))
126 return -ENODEV;
127
128 ret = lmodel->init();
129 if (unlikely(ret != 0))
130 return ret;
131
132 model = lmodel;
133
134 ops->setup = op_sh_setup;
135 ops->create_files = op_sh_create_files;
136 ops->start = op_sh_start;
137 ops->stop = op_sh_stop;
138 ops->cpu_type = lmodel->cpu_type;
139
140 printk(KERN_INFO "oprofile: using %s performance monitoring.\n",
141 lmodel->cpu_type);
142
143 return 0;
144}
145
146void oprofile_arch_exit(void)
147{
148 if (model && model->exit)
149 model->exit();
150}
diff --git a/arch/sh/oprofile/op_impl.h b/arch/sh/oprofile/op_impl.h
new file mode 100644
index 000000000000..4d509975eba6
--- /dev/null
+++ b/arch/sh/oprofile/op_impl.h
@@ -0,0 +1,33 @@
1#ifndef __OP_IMPL_H
2#define __OP_IMPL_H
3
4/* Per-counter configuration as set via oprofilefs. */
5struct op_counter_config {
6 unsigned long enabled;
7 unsigned long event;
8
9 unsigned long long count;
10
11 /* Dummy values for userspace tool compliance */
12 unsigned long kernel;
13 unsigned long user;
14 unsigned long unit_mask;
15};
16
17/* Per-architecture configury and hooks. */
18struct op_sh_model {
19 void (*reg_setup)(struct op_counter_config *);
20 int (*create_files)(struct super_block *sb, struct dentry *dir);
21 void (*cpu_setup)(void *dummy);
22 int (*init)(void);
23 void (*exit)(void);
24 void (*cpu_start)(void *args);
25 void (*cpu_stop)(void *args);
26 char *cpu_type;
27 unsigned char num_counters;
28};
29
30/* arch/sh/oprofile/common.c */
31extern void sh_backtrace(struct pt_regs * const regs, unsigned int depth);
32
33#endif /* __OP_IMPL_H */
diff --git a/arch/sh/oprofile/op_model_null.c b/arch/sh/oprofile/op_model_null.c
deleted file mode 100644
index a845b088edb4..000000000000
--- a/arch/sh/oprofile/op_model_null.c
+++ /dev/null
@@ -1,23 +0,0 @@
1/*
2 * arch/sh/oprofile/op_model_null.c
3 *
4 * Copyright (C) 2003 Paul Mundt
5 *
6 * This file is subject to the terms and conditions of the GNU General Public
7 * License. See the file "COPYING" in the main directory of this archive
8 * for more details.
9 */
10#include <linux/kernel.h>
11#include <linux/oprofile.h>
12#include <linux/init.h>
13#include <linux/errno.h>
14
15int __init oprofile_arch_init(struct oprofile_operations *ops)
16{
17 return -ENODEV;
18}
19
20void oprofile_arch_exit(void)
21{
22}
23
diff --git a/arch/sh/oprofile/op_model_sh7750.c b/arch/sh/oprofile/op_model_sh7750.c
index 008b3b03750a..c892c7c30c2f 100644
--- a/arch/sh/oprofile/op_model_sh7750.c
+++ b/arch/sh/oprofile/op_model_sh7750.c
@@ -3,7 +3,7 @@
3 * 3 *
4 * OProfile support for SH7750/SH7750S Performance Counters 4 * OProfile support for SH7750/SH7750S Performance Counters
5 * 5 *
6 * Copyright (C) 2003, 2004 Paul Mundt 6 * Copyright (C) 2003 - 2008 Paul Mundt
7 * 7 *
8 * This file is subject to the terms and conditions of the GNU General Public 8 * This file is subject to the terms and conditions of the GNU General Public
9 * License. See the file "COPYING" in the main directory of this archive 9 * License. See the file "COPYING" in the main directory of this archive
@@ -15,19 +15,16 @@
15#include <linux/init.h> 15#include <linux/init.h>
16#include <linux/errno.h> 16#include <linux/errno.h>
17#include <linux/interrupt.h> 17#include <linux/interrupt.h>
18#include <linux/io.h>
18#include <linux/fs.h> 19#include <linux/fs.h>
19#include <asm/uaccess.h> 20#include "op_impl.h"
20#include <asm/io.h>
21 21
22#define PM_CR_BASE 0xff000084 /* 16-bit */ 22#define PM_CR_BASE 0xff000084 /* 16-bit */
23#define PM_CTR_BASE 0xff100004 /* 32-bit */ 23#define PM_CTR_BASE 0xff100004 /* 32-bit */
24 24
25#define PMCR1 (PM_CR_BASE + 0x00) 25#define PMCR(n) (PM_CR_BASE + ((n) * 0x04))
26#define PMCR2 (PM_CR_BASE + 0x04) 26#define PMCTRH(n) (PM_CTR_BASE + 0x00 + ((n) * 0x08))
27#define PMCTR1H (PM_CTR_BASE + 0x00) 27#define PMCTRL(n) (PM_CTR_BASE + 0x04 + ((n) * 0x08))
28#define PMCTR1L (PM_CTR_BASE + 0x04)
29#define PMCTR2H (PM_CTR_BASE + 0x08)
30#define PMCTR2L (PM_CTR_BASE + 0x0c)
31 28
32#define PMCR_PMM_MASK 0x0000003f 29#define PMCR_PMM_MASK 0x0000003f
33 30
@@ -36,25 +33,15 @@
36#define PMCR_PMST 0x00004000 33#define PMCR_PMST 0x00004000
37#define PMCR_PMEN 0x00008000 34#define PMCR_PMEN 0x00008000
38 35
39#define PMCR_ENABLE (PMCR_PMST | PMCR_PMEN) 36struct op_sh_model op_model_sh7750_ops;
40 37
41/*
42 * SH7750/SH7750S have 2 perf counters
43 */
44#define NR_CNTRS 2 38#define NR_CNTRS 2
45 39
46struct op_counter_config { 40static struct sh7750_ppc_register_config {
47 unsigned long enabled; 41 unsigned int ctrl;
48 unsigned long event; 42 unsigned long cnt_hi;
49 unsigned long count; 43 unsigned long cnt_lo;
50 44} regcache[NR_CNTRS];
51 /* Dummy values for userspace tool compliance */
52 unsigned long kernel;
53 unsigned long user;
54 unsigned long unit_mask;
55};
56
57static struct op_counter_config ctr[NR_CNTRS];
58 45
59/* 46/*
60 * There are a number of events supported by each counter (33 in total). 47 * There are a number of events supported by each counter (33 in total).
@@ -116,12 +103,8 @@ static int sh7750_timer_notify(struct pt_regs *regs)
116 103
117static u64 sh7750_read_counter(int counter) 104static u64 sh7750_read_counter(int counter)
118{ 105{
119 u32 hi, lo; 106 return (u64)((u64)(__raw_readl(PMCTRH(counter)) & 0xffff) << 32) |
120 107 __raw_readl(PMCTRL(counter));
121 hi = (counter == 0) ? ctrl_inl(PMCTR1H) : ctrl_inl(PMCTR2H);
122 lo = (counter == 0) ? ctrl_inl(PMCTR1L) : ctrl_inl(PMCTR2L);
123
124 return (u64)((u64)(hi & 0xffff) << 32) | lo;
125} 108}
126 109
127/* 110/*
@@ -170,11 +153,7 @@ static ssize_t sh7750_write_count(struct file *file, const char __user *buf,
170 */ 153 */
171 WARN_ON(val != 0); 154 WARN_ON(val != 0);
172 155
173 if (counter == 0) { 156 __raw_writew(__raw_readw(PMCR(counter)) | PMCR_PMCLR, PMCR(counter));
174 ctrl_outw(ctrl_inw(PMCR1) | PMCR_PMCLR, PMCR1);
175 } else {
176 ctrl_outw(ctrl_inw(PMCR2) | PMCR_PMCLR, PMCR2);
177 }
178 157
179 return count; 158 return count;
180} 159}
@@ -184,88 +163,93 @@ static const struct file_operations count_fops = {
184 .write = sh7750_write_count, 163 .write = sh7750_write_count,
185}; 164};
186 165
187static int sh7750_perf_counter_create_files(struct super_block *sb, struct dentry *root) 166static int sh7750_ppc_create_files(struct super_block *sb, struct dentry *dir)
188{ 167{
189 int i; 168 return oprofilefs_create_file(sb, dir, "count", &count_fops);
169}
190 170
191 for (i = 0; i < NR_CNTRS; i++) { 171static void sh7750_ppc_reg_setup(struct op_counter_config *ctr)
192 struct dentry *dir; 172{
193 char buf[4]; 173 unsigned int counters = op_model_sh7750_ops.num_counters;
174 int i;
194 175
195 snprintf(buf, sizeof(buf), "%d", i); 176 for (i = 0; i < counters; i++) {
196 dir = oprofilefs_mkdir(sb, root, buf); 177 regcache[i].ctrl = 0;
178 regcache[i].cnt_hi = 0;
179 regcache[i].cnt_lo = 0;
197 180
198 oprofilefs_create_ulong(sb, dir, "enabled", &ctr[i].enabled); 181 if (!ctr[i].enabled)
199 oprofilefs_create_ulong(sb, dir, "event", &ctr[i].event); 182 continue;
200 oprofilefs_create_file(sb, dir, "count", &count_fops);
201 183
202 /* Dummy entries */ 184 regcache[i].ctrl |= ctr[i].event | PMCR_PMEN | PMCR_PMST;
203 oprofilefs_create_ulong(sb, dir, "kernel", &ctr[i].kernel); 185 regcache[i].cnt_hi = (unsigned long)((ctr->count >> 32) & 0xffff);
204 oprofilefs_create_ulong(sb, dir, "user", &ctr[i].user); 186 regcache[i].cnt_lo = (unsigned long)(ctr->count & 0xffffffff);
205 oprofilefs_create_ulong(sb, dir, "unit_mask", &ctr[i].unit_mask);
206 } 187 }
207
208 return 0;
209} 188}
210 189
211static int sh7750_perf_counter_start(void) 190static void sh7750_ppc_cpu_setup(void *args)
212{ 191{
213 u16 pmcr; 192 unsigned int counters = op_model_sh7750_ops.num_counters;
214 193 int i;
215 /* Enable counter 1 */
216 if (ctr[0].enabled) {
217 pmcr = ctrl_inw(PMCR1);
218 WARN_ON(pmcr & PMCR_PMEN);
219
220 pmcr &= ~PMCR_PMM_MASK;
221 pmcr |= ctr[0].event;
222 ctrl_outw(pmcr | PMCR_ENABLE, PMCR1);
223 }
224
225 /* Enable counter 2 */
226 if (ctr[1].enabled) {
227 pmcr = ctrl_inw(PMCR2);
228 WARN_ON(pmcr & PMCR_PMEN);
229 194
230 pmcr &= ~PMCR_PMM_MASK; 195 for (i = 0; i < counters; i++) {
231 pmcr |= ctr[1].event; 196 __raw_writew(0, PMCR(i));
232 ctrl_outw(pmcr | PMCR_ENABLE, PMCR2); 197 __raw_writel(regcache[i].cnt_hi, PMCTRH(i));
198 __raw_writel(regcache[i].cnt_lo, PMCTRL(i));
233 } 199 }
234
235 return register_timer_hook(sh7750_timer_notify);
236} 200}
237 201
238static void sh7750_perf_counter_stop(void) 202static void sh7750_ppc_cpu_start(void *args)
239{ 203{
240 ctrl_outw(ctrl_inw(PMCR1) & ~PMCR_PMEN, PMCR1); 204 unsigned int counters = op_model_sh7750_ops.num_counters;
241 ctrl_outw(ctrl_inw(PMCR2) & ~PMCR_PMEN, PMCR2); 205 int i;
242 206
243 unregister_timer_hook(sh7750_timer_notify); 207 for (i = 0; i < counters; i++)
208 __raw_writew(regcache[i].ctrl, PMCR(i));
244} 209}
245 210
246static struct oprofile_operations sh7750_perf_counter_ops = { 211static void sh7750_ppc_cpu_stop(void *args)
247 .create_files = sh7750_perf_counter_create_files,
248 .start = sh7750_perf_counter_start,
249 .stop = sh7750_perf_counter_stop,
250};
251
252int __init oprofile_arch_init(struct oprofile_operations *ops)
253{ 212{
254 if (!(current_cpu_data.flags & CPU_HAS_PERF_COUNTER)) 213 unsigned int counters = op_model_sh7750_ops.num_counters;
255 return -ENODEV; 214 int i;
256 215
257 ops = &sh7750_perf_counter_ops; 216 /* Disable the counters */
258 ops->cpu_type = "sh/sh7750"; 217 for (i = 0; i < counters; i++)
218 __raw_writew(__raw_readw(PMCR(i)) & ~PMCR_PMEN, PMCR(i));
219}
259 220
260 printk(KERN_INFO "oprofile: using SH-4 performance monitoring.\n"); 221static inline void sh7750_ppc_reset(void)
222{
223 unsigned int counters = op_model_sh7750_ops.num_counters;
224 int i;
261 225
262 /* Clear the counters */ 226 /* Clear the counters */
263 ctrl_outw(ctrl_inw(PMCR1) | PMCR_PMCLR, PMCR1); 227 for (i = 0; i < counters; i++)
264 ctrl_outw(ctrl_inw(PMCR2) | PMCR_PMCLR, PMCR2); 228 __raw_writew(__raw_readw(PMCR(i)) | PMCR_PMCLR, PMCR(i));
229}
265 230
266 return 0; 231static int sh7750_ppc_init(void)
232{
233 sh7750_ppc_reset();
234
235 return register_timer_hook(sh7750_timer_notify);
267} 236}
268 237
269void oprofile_arch_exit(void) 238static void sh7750_ppc_exit(void)
270{ 239{
240 unregister_timer_hook(sh7750_timer_notify);
241
242 sh7750_ppc_reset();
271} 243}
244
245struct op_sh_model op_model_sh7750_ops = {
246 .cpu_type = "sh/sh7750",
247 .num_counters = NR_CNTRS,
248 .reg_setup = sh7750_ppc_reg_setup,
249 .cpu_setup = sh7750_ppc_cpu_setup,
250 .cpu_start = sh7750_ppc_cpu_start,
251 .cpu_stop = sh7750_ppc_cpu_stop,
252 .init = sh7750_ppc_init,
253 .exit = sh7750_ppc_exit,
254 .create_files = sh7750_ppc_create_files,
255};