diff options
-rw-r--r-- | arch/xtensa/Kconfig | 1 | ||||
-rw-r--r-- | arch/xtensa/Makefile | 1 | ||||
-rw-r--r-- | arch/xtensa/kernel/entry.S | 2 | ||||
-rw-r--r-- | arch/xtensa/oprofile/Makefile | 9 | ||||
-rw-r--r-- | arch/xtensa/oprofile/backtrace.c | 171 | ||||
-rw-r--r-- | arch/xtensa/oprofile/init.c | 26 |
6 files changed, 209 insertions, 1 deletions
diff --git a/arch/xtensa/Kconfig b/arch/xtensa/Kconfig index f83780f8f290..f61f7362c8f6 100644 --- a/arch/xtensa/Kconfig +++ b/arch/xtensa/Kconfig | |||
@@ -17,6 +17,7 @@ config XTENSA | |||
17 | select ARCH_WANT_OPTIONAL_GPIOLIB | 17 | select ARCH_WANT_OPTIONAL_GPIOLIB |
18 | select CLONE_BACKWARDS | 18 | select CLONE_BACKWARDS |
19 | select IRQ_DOMAIN | 19 | select IRQ_DOMAIN |
20 | select HAVE_OPROFILE | ||
20 | help | 21 | help |
21 | Xtensa processors are 32-bit RISC machines designed by Tensilica | 22 | Xtensa processors are 32-bit RISC machines designed by Tensilica |
22 | primarily for embedded systems. These processors are both | 23 | primarily for embedded systems. These processors are both |
diff --git a/arch/xtensa/Makefile b/arch/xtensa/Makefile index afec8f000c4a..136224b74d4f 100644 --- a/arch/xtensa/Makefile +++ b/arch/xtensa/Makefile | |||
@@ -87,6 +87,7 @@ core-y += arch/xtensa/kernel/ arch/xtensa/mm/ | |||
87 | core-y += $(buildvar) $(buildplf) | 87 | core-y += $(buildvar) $(buildplf) |
88 | 88 | ||
89 | libs-y += arch/xtensa/lib/ $(LIBGCC) | 89 | libs-y += arch/xtensa/lib/ $(LIBGCC) |
90 | drivers-$(CONFIG_OPROFILE) += arch/xtensa/oprofile/ | ||
90 | 91 | ||
91 | ifneq ($(CONFIG_BUILTIN_DTB),"") | 92 | ifneq ($(CONFIG_BUILTIN_DTB),"") |
92 | core-$(CONFIG_OF) += arch/xtensa/boot/dts/ | 93 | core-$(CONFIG_OF) += arch/xtensa/boot/dts/ |
diff --git a/arch/xtensa/kernel/entry.S b/arch/xtensa/kernel/entry.S index 0ace2acbbad0..70d5a9e33573 100644 --- a/arch/xtensa/kernel/entry.S +++ b/arch/xtensa/kernel/entry.S | |||
@@ -399,7 +399,7 @@ common_exception: | |||
399 | callx4 a4 | 399 | callx4 a4 |
400 | 400 | ||
401 | /* Jump here for exception exit */ | 401 | /* Jump here for exception exit */ |
402 | 402 | .global common_exception_return | |
403 | common_exception_return: | 403 | common_exception_return: |
404 | 404 | ||
405 | /* Jump if we are returning from kernel exceptions. */ | 405 | /* Jump if we are returning from kernel exceptions. */ |
diff --git a/arch/xtensa/oprofile/Makefile b/arch/xtensa/oprofile/Makefile new file mode 100644 index 000000000000..69ffbe80f184 --- /dev/null +++ b/arch/xtensa/oprofile/Makefile | |||
@@ -0,0 +1,9 @@ | |||
1 | obj-$(CONFIG_OPROFILE) += oprofile.o | ||
2 | |||
3 | DRIVER_OBJS = $(addprefix ../../../drivers/oprofile/, \ | ||
4 | oprof.o cpu_buffer.o buffer_sync.o \ | ||
5 | event_buffer.o oprofile_files.o \ | ||
6 | oprofilefs.o oprofile_stats.o \ | ||
7 | timer_int.o ) | ||
8 | |||
9 | oprofile-y := $(DRIVER_OBJS) init.o backtrace.o | ||
diff --git a/arch/xtensa/oprofile/backtrace.c b/arch/xtensa/oprofile/backtrace.c new file mode 100644 index 000000000000..66f32ee2c982 --- /dev/null +++ b/arch/xtensa/oprofile/backtrace.c | |||
@@ -0,0 +1,171 @@ | |||
1 | /** | ||
2 | * @file backtrace.c | ||
3 | * | ||
4 | * @remark Copyright 2008 Tensilica Inc. | ||
5 | * @remark Read the file COPYING | ||
6 | * | ||
7 | */ | ||
8 | |||
9 | #include <linux/oprofile.h> | ||
10 | #include <linux/sched.h> | ||
11 | #include <linux/mm.h> | ||
12 | #include <asm/ptrace.h> | ||
13 | #include <asm/uaccess.h> | ||
14 | #include <asm/traps.h> | ||
15 | |||
16 | /* Address of common_exception_return, used to check the | ||
17 | * transition from kernel to user space. | ||
18 | */ | ||
19 | extern int common_exception_return; | ||
20 | |||
21 | /* A struct that maps to the part of the frame containing the a0 and | ||
22 | * a1 registers. | ||
23 | */ | ||
24 | struct frame_start { | ||
25 | unsigned long a0; | ||
26 | unsigned long a1; | ||
27 | }; | ||
28 | |||
29 | static void xtensa_backtrace_user(struct pt_regs *regs, unsigned int depth) | ||
30 | { | ||
31 | unsigned long windowstart = regs->windowstart; | ||
32 | unsigned long windowbase = regs->windowbase; | ||
33 | unsigned long a0 = regs->areg[0]; | ||
34 | unsigned long a1 = regs->areg[1]; | ||
35 | unsigned long pc = MAKE_PC_FROM_RA(a0, regs->pc); | ||
36 | int index; | ||
37 | |||
38 | /* First add the current PC to the trace. */ | ||
39 | if (pc != 0 && pc <= TASK_SIZE) | ||
40 | oprofile_add_trace(pc); | ||
41 | else | ||
42 | return; | ||
43 | |||
44 | /* Two steps: | ||
45 | * | ||
46 | * 1. Look through the register window for the | ||
47 | * previous PCs in the call trace. | ||
48 | * | ||
49 | * 2. Look on the stack. | ||
50 | */ | ||
51 | |||
52 | /* Step 1. */ | ||
53 | /* Rotate WINDOWSTART to move the bit corresponding to | ||
54 | * the current window to the bit #0. | ||
55 | */ | ||
56 | windowstart = (windowstart << WSBITS | windowstart) >> windowbase; | ||
57 | |||
58 | /* Look for bits that are set, they correspond to | ||
59 | * valid windows. | ||
60 | */ | ||
61 | for (index = WSBITS - 1; (index > 0) && depth; depth--, index--) | ||
62 | if (windowstart & (1 << index)) { | ||
63 | /* Read a0 and a1 from the | ||
64 | * corresponding position in AREGs. | ||
65 | */ | ||
66 | a0 = regs->areg[index * 4]; | ||
67 | a1 = regs->areg[index * 4 + 1]; | ||
68 | /* Get the PC from a0 and a1. */ | ||
69 | pc = MAKE_PC_FROM_RA(a0, pc); | ||
70 | |||
71 | /* Add the PC to the trace. */ | ||
72 | if (pc != 0 && pc <= TASK_SIZE) | ||
73 | oprofile_add_trace(pc); | ||
74 | else | ||
75 | return; | ||
76 | } | ||
77 | |||
78 | /* Step 2. */ | ||
79 | /* We are done with the register window, we need to | ||
80 | * look through the stack. | ||
81 | */ | ||
82 | if (depth > 0) { | ||
83 | /* Start from the a1 register. */ | ||
84 | /* a1 = regs->areg[1]; */ | ||
85 | while (a0 != 0 && depth--) { | ||
86 | |||
87 | struct frame_start frame_start; | ||
88 | /* Get the location for a1, a0 for the | ||
89 | * previous frame from the current a1. | ||
90 | */ | ||
91 | unsigned long *psp = (unsigned long *)a1; | ||
92 | psp -= 4; | ||
93 | |||
94 | /* Check if the region is OK to access. */ | ||
95 | if (!access_ok(VERIFY_READ, psp, sizeof(frame_start))) | ||
96 | return; | ||
97 | /* Copy a1, a0 from user space stack frame. */ | ||
98 | if (__copy_from_user_inatomic(&frame_start, psp, | ||
99 | sizeof(frame_start))) | ||
100 | return; | ||
101 | |||
102 | a0 = frame_start.a0; | ||
103 | a1 = frame_start.a1; | ||
104 | pc = MAKE_PC_FROM_RA(a0, pc); | ||
105 | |||
106 | if (pc != 0 && pc <= TASK_SIZE) | ||
107 | oprofile_add_trace(pc); | ||
108 | else | ||
109 | return; | ||
110 | } | ||
111 | } | ||
112 | } | ||
113 | |||
114 | static void xtensa_backtrace_kernel(struct pt_regs *regs, unsigned int depth) | ||
115 | { | ||
116 | unsigned long pc = regs->pc; | ||
117 | unsigned long *psp; | ||
118 | unsigned long sp_start, sp_end; | ||
119 | unsigned long a0 = regs->areg[0]; | ||
120 | unsigned long a1 = regs->areg[1]; | ||
121 | |||
122 | sp_start = a1 & ~(THREAD_SIZE-1); | ||
123 | sp_end = sp_start + THREAD_SIZE; | ||
124 | |||
125 | /* Spill the register window to the stack first. */ | ||
126 | spill_registers(); | ||
127 | |||
128 | /* Read the stack frames one by one and create the PC | ||
129 | * from the a0 and a1 registers saved there. | ||
130 | */ | ||
131 | while (a1 > sp_start && a1 < sp_end && depth--) { | ||
132 | pc = MAKE_PC_FROM_RA(a0, pc); | ||
133 | |||
134 | /* Add the PC to the trace. */ | ||
135 | if (kernel_text_address(pc)) | ||
136 | oprofile_add_trace(pc); | ||
137 | |||
138 | if (pc == (unsigned long) &common_exception_return) { | ||
139 | regs = (struct pt_regs *)a1; | ||
140 | if (user_mode(regs)) { | ||
141 | pc = regs->pc; | ||
142 | if (pc != 0 && pc <= TASK_SIZE) | ||
143 | oprofile_add_trace(pc); | ||
144 | else | ||
145 | return; | ||
146 | return xtensa_backtrace_user(regs, depth); | ||
147 | } | ||
148 | a0 = regs->areg[0]; | ||
149 | a1 = regs->areg[1]; | ||
150 | continue; | ||
151 | } | ||
152 | |||
153 | psp = (unsigned long *)a1; | ||
154 | |||
155 | a0 = *(psp - 4); | ||
156 | a1 = *(psp - 3); | ||
157 | |||
158 | if (a1 <= (unsigned long)psp) | ||
159 | return; | ||
160 | |||
161 | } | ||
162 | return; | ||
163 | } | ||
164 | |||
165 | void xtensa_backtrace(struct pt_regs * const regs, unsigned int depth) | ||
166 | { | ||
167 | if (user_mode(regs)) | ||
168 | xtensa_backtrace_user(regs, depth); | ||
169 | else | ||
170 | xtensa_backtrace_kernel(regs, depth); | ||
171 | } | ||
diff --git a/arch/xtensa/oprofile/init.c b/arch/xtensa/oprofile/init.c new file mode 100644 index 000000000000..a67eea379766 --- /dev/null +++ b/arch/xtensa/oprofile/init.c | |||
@@ -0,0 +1,26 @@ | |||
1 | /** | ||
2 | * @file init.c | ||
3 | * | ||
4 | * @remark Copyright 2008 Tensilica Inc. | ||
5 | * @remark Read the file COPYING | ||
6 | * | ||
7 | */ | ||
8 | |||
9 | #include <linux/kernel.h> | ||
10 | #include <linux/oprofile.h> | ||
11 | #include <linux/errno.h> | ||
12 | #include <linux/init.h> | ||
13 | |||
14 | |||
15 | extern void xtensa_backtrace(struct pt_regs *const regs, unsigned int depth); | ||
16 | |||
17 | int __init oprofile_arch_init(struct oprofile_operations *ops) | ||
18 | { | ||
19 | ops->backtrace = xtensa_backtrace; | ||
20 | return -ENODEV; | ||
21 | } | ||
22 | |||
23 | |||
24 | void oprofile_arch_exit(void) | ||
25 | { | ||
26 | } | ||