Commit | Line | Data |
---|---|---|
3e4196a5 MF |
1 | /* |
2 | * arch/xtensa/kernel/stacktrace.c | |
3 | * | |
4 | * This file is subject to the terms and conditions of the GNU General Public | |
5 | * License. See the file "COPYING" in the main directory of this archive | |
6 | * for more details. | |
7 | * | |
8 | * Copyright (C) 2001 - 2013 Tensilica Inc. | |
9 | */ | |
10 | #include <linux/export.h> | |
11 | #include <linux/sched.h> | |
12 | #include <linux/stacktrace.h> | |
13 | ||
14 | #include <asm/stacktrace.h> | |
15 | #include <asm/traps.h> | |
16 | ||
17 | void walk_stackframe(unsigned long *sp, | |
18 | int (*fn)(struct stackframe *frame, void *data), | |
19 | void *data) | |
20 | { | |
21 | unsigned long a0, a1; | |
22 | unsigned long sp_end; | |
23 | ||
24 | a1 = (unsigned long)sp; | |
25 | sp_end = ALIGN(a1, THREAD_SIZE); | |
26 | ||
27 | spill_registers(); | |
28 | ||
29 | while (a1 < sp_end) { | |
30 | struct stackframe frame; | |
31 | ||
32 | sp = (unsigned long *)a1; | |
33 | ||
34 | a0 = *(sp - 4); | |
35 | a1 = *(sp - 3); | |
36 | ||
37 | if (a1 <= (unsigned long)sp) | |
38 | break; | |
39 | ||
40 | frame.pc = MAKE_PC_FROM_RA(a0, a1); | |
41 | frame.sp = a1; | |
42 | ||
43 | if (fn(&frame, data)) | |
44 | return; | |
45 | } | |
46 | } | |
47 | ||
48 | #ifdef CONFIG_STACKTRACE | |
49 | ||
50 | struct stack_trace_data { | |
51 | struct stack_trace *trace; | |
52 | unsigned skip; | |
53 | }; | |
54 | ||
55 | static int stack_trace_cb(struct stackframe *frame, void *data) | |
56 | { | |
57 | struct stack_trace_data *trace_data = data; | |
58 | struct stack_trace *trace = trace_data->trace; | |
59 | ||
60 | if (trace_data->skip) { | |
61 | --trace_data->skip; | |
62 | return 0; | |
63 | } | |
64 | if (!kernel_text_address(frame->pc)) | |
65 | return 0; | |
66 | ||
67 | trace->entries[trace->nr_entries++] = frame->pc; | |
68 | return trace->nr_entries >= trace->max_entries; | |
69 | } | |
70 | ||
71 | void save_stack_trace_tsk(struct task_struct *task, struct stack_trace *trace) | |
72 | { | |
73 | struct stack_trace_data trace_data = { | |
74 | .trace = trace, | |
75 | .skip = trace->skip, | |
76 | }; | |
77 | walk_stackframe(stack_pointer(task), stack_trace_cb, &trace_data); | |
78 | } | |
79 | EXPORT_SYMBOL_GPL(save_stack_trace_tsk); | |
80 | ||
81 | void save_stack_trace(struct stack_trace *trace) | |
82 | { | |
83 | save_stack_trace_tsk(current, trace); | |
84 | } | |
85 | EXPORT_SYMBOL_GPL(save_stack_trace); | |
86 | ||
87 | #endif | |
3ae908c9 MF |
88 | |
89 | #ifdef CONFIG_FRAME_POINTER | |
90 | ||
91 | struct return_addr_data { | |
92 | unsigned long addr; | |
93 | unsigned skip; | |
94 | }; | |
95 | ||
96 | static int return_address_cb(struct stackframe *frame, void *data) | |
97 | { | |
98 | struct return_addr_data *r = data; | |
99 | ||
100 | if (r->skip) { | |
101 | --r->skip; | |
102 | return 0; | |
103 | } | |
104 | if (!kernel_text_address(frame->pc)) | |
105 | return 0; | |
106 | r->addr = frame->pc; | |
107 | return 1; | |
108 | } | |
109 | ||
110 | unsigned long return_address(unsigned level) | |
111 | { | |
112 | struct return_addr_data r = { | |
113 | .skip = level + 1, | |
114 | }; | |
115 | walk_stackframe(stack_pointer(NULL), return_address_cb, &r); | |
116 | return r.addr; | |
117 | } | |
118 | EXPORT_SYMBOL(return_address); | |
119 | ||
120 | #endif |