diff options
-rw-r--r-- | arch/sh/oprofile/backtrace.c | 118 | ||||
-rw-r--r-- | arch/sh/oprofile/common.c | 9 |
2 files changed, 127 insertions, 0 deletions
diff --git a/arch/sh/oprofile/backtrace.c b/arch/sh/oprofile/backtrace.c new file mode 100644 index 000000000000..418e834654c1 --- /dev/null +++ b/arch/sh/oprofile/backtrace.c | |||
@@ -0,0 +1,118 @@ | |||
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. */ | ||
25 | static int backtrace_limit = 20; | ||
26 | |||
27 | static unsigned long * | ||
28 | user_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 | */ | ||
69 | static 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 | |||
77 | static unsigned long * | ||
78 | kernel_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 | |||
89 | if (__kernel_text_address(addr)) { | ||
90 | oprofile_add_trace(addr); | ||
91 | break; | ||
92 | } | ||
93 | } while (valid_kernel_stack(stackaddr, regs)); | ||
94 | |||
95 | return stackaddr; | ||
96 | } | ||
97 | |||
98 | void sh_backtrace(struct pt_regs * const regs, unsigned int depth) | ||
99 | { | ||
100 | unsigned long *stackaddr; | ||
101 | |||
102 | /* | ||
103 | * Paranoia - clip max depth as we could get lost in the weeds. | ||
104 | */ | ||
105 | if (depth > backtrace_limit) | ||
106 | depth = backtrace_limit; | ||
107 | |||
108 | stackaddr = (unsigned long *)regs->regs[15]; | ||
109 | if (!user_mode(regs)) { | ||
110 | while (depth-- && valid_kernel_stack(stackaddr, regs)) | ||
111 | stackaddr = kernel_backtrace(stackaddr, regs); | ||
112 | |||
113 | return; | ||
114 | } | ||
115 | |||
116 | while (depth-- && (stackaddr != NULL)) | ||
117 | stackaddr = user_backtrace(stackaddr, regs); | ||
118 | } | ||
diff --git a/arch/sh/oprofile/common.c b/arch/sh/oprofile/common.c index a4d8c0c9a63c..1d97d64cb95f 100644 --- a/arch/sh/oprofile/common.c +++ b/arch/sh/oprofile/common.c | |||
@@ -27,6 +27,8 @@ static struct op_sh_model *model; | |||
27 | 27 | ||
28 | static struct op_counter_config ctr[20]; | 28 | static struct op_counter_config ctr[20]; |
29 | 29 | ||
30 | extern void sh_backtrace(struct pt_regs * const regs, unsigned int depth); | ||
31 | |||
30 | static int op_sh_setup(void) | 32 | static int op_sh_setup(void) |
31 | { | 33 | { |
32 | /* Pre-compute the values to stuff in the hardware registers. */ | 34 | /* Pre-compute the values to stuff in the hardware registers. */ |
@@ -85,6 +87,13 @@ int __init oprofile_arch_init(struct oprofile_operations *ops) | |||
85 | struct op_sh_model *lmodel = NULL; | 87 | struct op_sh_model *lmodel = NULL; |
86 | int ret; | 88 | int ret; |
87 | 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 | |||
88 | switch (current_cpu_data.type) { | 97 | switch (current_cpu_data.type) { |
89 | /* SH-4 types */ | 98 | /* SH-4 types */ |
90 | case CPU_SH7750: | 99 | case CPU_SH7750: |