diff options
Diffstat (limited to 'arch/xtensa/kernel/stacktrace.c')
| -rw-r--r-- | arch/xtensa/kernel/stacktrace.c | 120 | 
1 files changed, 120 insertions, 0 deletions
diff --git a/arch/xtensa/kernel/stacktrace.c b/arch/xtensa/kernel/stacktrace.c new file mode 100644 index 00000000000..7d2c317bd98 --- /dev/null +++ b/arch/xtensa/kernel/stacktrace.c @@ -0,0 +1,120 @@ +/* + * 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  | 
