1/*
2 * Stack tracing support
3 *
4 * Copyright (C) 2012 ARM Ltd.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18#include <linux/kernel.h>
19#include <linux/export.h>
20#include <linux/sched.h>
21#include <linux/stacktrace.h>
22
23#include <asm/stacktrace.h>
24
25/*
26 * AArch64 PCS assigns the frame pointer to x29.
27 *
28 * A simple function prologue looks like this:
29 * 	sub	sp, sp, #0x10
30 *   	stp	x29, x30, [sp]
31 *	mov	x29, sp
32 *
33 * A simple function epilogue looks like this:
34 *	mov	sp, x29
35 *	ldp	x29, x30, [sp]
36 *	add	sp, sp, #0x10
37 */
38int notrace unwind_frame(struct stackframe *frame)
39{
40	unsigned long high, low;
41	unsigned long fp = frame->fp;
42
43	low  = frame->sp;
44	high = ALIGN(low, THREAD_SIZE);
45
46	if (fp < low || fp > high - 0x18 || fp & 0xf)
47		return -EINVAL;
48
49	frame->sp = fp + 0x10;
50	frame->fp = *(unsigned long *)(fp);
51	frame->pc = *(unsigned long *)(fp + 8);
52
53	return 0;
54}
55
56void notrace walk_stackframe(struct stackframe *frame,
57		     int (*fn)(struct stackframe *, void *), void *data)
58{
59	while (1) {
60		int ret;
61
62		if (fn(frame, data))
63			break;
64		ret = unwind_frame(frame);
65		if (ret < 0)
66			break;
67	}
68}
69EXPORT_SYMBOL(walk_stackframe);
70
71#ifdef CONFIG_STACKTRACE
72struct stack_trace_data {
73	struct stack_trace *trace;
74	unsigned int no_sched_functions;
75	unsigned int skip;
76};
77
78static int save_trace(struct stackframe *frame, void *d)
79{
80	struct stack_trace_data *data = d;
81	struct stack_trace *trace = data->trace;
82	unsigned long addr = frame->pc;
83
84	if (data->no_sched_functions && in_sched_functions(addr))
85		return 0;
86	if (data->skip) {
87		data->skip--;
88		return 0;
89	}
90
91	trace->entries[trace->nr_entries++] = addr;
92
93	return trace->nr_entries >= trace->max_entries;
94}
95
96void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
97{
98	struct stack_trace_data data;
99	struct stackframe frame;
100
101	data.trace = trace;
102	data.skip = trace->skip;
103
104	if (tsk != current) {
105		data.no_sched_functions = 1;
106		frame.fp = thread_saved_fp(tsk);
107		frame.sp = thread_saved_sp(tsk);
108		frame.pc = thread_saved_pc(tsk);
109	} else {
110		data.no_sched_functions = 0;
111		frame.fp = (unsigned long)__builtin_frame_address(0);
112		frame.sp = current_stack_pointer;
113		frame.pc = (unsigned long)save_stack_trace_tsk;
114	}
115
116	walk_stackframe(&frame, save_trace, &data);
117	if (trace->nr_entries < trace->max_entries)
118		trace->entries[trace->nr_entries++] = ULONG_MAX;
119}
120
121void save_stack_trace(struct stack_trace *trace)
122{
123	save_stack_trace_tsk(current, trace);
124}
125EXPORT_SYMBOL_GPL(save_stack_trace);
126#endif
127