| /* |
| * arch/xtensa/kernel/stacktrace.c |
| * |
| * 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. |
| * |
| * Copyright (C) 2001 - 2013 Tensilica Inc. |
| */ |
| #include <linux/export.h> |
| #include <linux/sched.h> |
| #include <linux/stacktrace.h> |
| |
| #include <asm/stacktrace.h> |
| #include <asm/traps.h> |
| |
| void walk_stackframe(unsigned long *sp, |
| int (*fn)(struct stackframe *frame, void *data), |
| void *data) |
| { |
| unsigned long a0, a1; |
| unsigned long sp_end; |
| |
| a1 = (unsigned long)sp; |
| sp_end = ALIGN(a1, THREAD_SIZE); |
| |
| spill_registers(); |
| |
| while (a1 < sp_end) { |
| struct stackframe frame; |
| |
| sp = (unsigned long *)a1; |
| |
| a0 = *(sp - 4); |
| a1 = *(sp - 3); |
| |
| if (a1 <= (unsigned long)sp) |
| break; |
| |
| frame.pc = MAKE_PC_FROM_RA(a0, a1); |
| frame.sp = a1; |
| |
| if (fn(&frame, data)) |
| return; |
| } |
| } |
| |
| #ifdef CONFIG_STACKTRACE |
| |
| struct stack_trace_data { |
| struct stack_trace *trace; |
| unsigned skip; |
| }; |
| |
| static int stack_trace_cb(struct stackframe *frame, void *data) |
| { |
| struct stack_trace_data *trace_data = data; |
| struct stack_trace *trace = trace_data->trace; |
| |
| if (trace_data->skip) { |
| --trace_data->skip; |
| return 0; |
| } |
| if (!kernel_text_address(frame->pc)) |
| return 0; |
| |
| trace->entries[trace->nr_entries++] = frame->pc; |
| return trace->nr_entries >= trace->max_entries; |
| } |
| |
| void save_stack_trace_tsk(struct task_struct *task, struct stack_trace *trace) |
| { |
| struct stack_trace_data trace_data = { |
| .trace = trace, |
| .skip = trace->skip, |
| }; |
| walk_stackframe(stack_pointer(task), stack_trace_cb, &trace_data); |
| } |
| EXPORT_SYMBOL_GPL(save_stack_trace_tsk); |
| |
| void save_stack_trace(struct stack_trace *trace) |
| { |
| save_stack_trace_tsk(current, trace); |
| } |
| EXPORT_SYMBOL_GPL(save_stack_trace); |
| |
| #endif |
| |
| #ifdef CONFIG_FRAME_POINTER |
| |
| struct return_addr_data { |
| unsigned long addr; |
| unsigned skip; |
| }; |
| |
| static int return_address_cb(struct stackframe *frame, void *data) |
| { |
| struct return_addr_data *r = data; |
| |
| if (r->skip) { |
| --r->skip; |
| return 0; |
| } |
| if (!kernel_text_address(frame->pc)) |
| return 0; |
| r->addr = frame->pc; |
| return 1; |
| } |
| |
| unsigned long return_address(unsigned level) |
| { |
| struct return_addr_data r = { |
| .skip = level + 1, |
| }; |
| walk_stackframe(stack_pointer(NULL), return_address_cb, &r); |
| return r.addr; |
| } |
| EXPORT_SYMBOL(return_address); |
| |
| #endif |