1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (C) 2022 Loongson Technology Corporation Limited
4  */
5 #include <linux/kallsyms.h>
6 
7 #include <asm/inst.h>
8 #include <asm/ptrace.h>
9 #include <asm/unwind.h>
10 
unwind_get_return_address(struct unwind_state * state)11 unsigned long unwind_get_return_address(struct unwind_state *state)
12 {
13 
14 	if (unwind_done(state))
15 		return 0;
16 	else if (state->type)
17 		return state->pc;
18 	else if (state->first)
19 		return state->pc;
20 
21 	return *(unsigned long *)(state->sp);
22 
23 }
24 EXPORT_SYMBOL_GPL(unwind_get_return_address);
25 
unwind_by_guess(struct unwind_state * state)26 static bool unwind_by_guess(struct unwind_state *state)
27 {
28 	struct stack_info *info = &state->stack_info;
29 	unsigned long addr;
30 
31 	for (state->sp += sizeof(unsigned long);
32 	     state->sp < info->end;
33 	     state->sp += sizeof(unsigned long)) {
34 		addr = *(unsigned long *)(state->sp);
35 		if (__kernel_text_address(addr))
36 			return true;
37 	}
38 
39 	return false;
40 }
41 
unwind_by_prologue(struct unwind_state * state)42 static bool unwind_by_prologue(struct unwind_state *state)
43 {
44 	struct stack_info *info = &state->stack_info;
45 	union loongarch_instruction *ip, *ip_end;
46 	long frame_ra = -1;
47 	unsigned long frame_size = 0;
48 	unsigned long size, offset, pc = state->pc;
49 
50 	if (state->sp >= info->end || state->sp < info->begin)
51 		return false;
52 
53 	if (!kallsyms_lookup_size_offset(pc, &size, &offset))
54 		return false;
55 
56 	ip = (union loongarch_instruction *)(pc - offset);
57 	ip_end = (union loongarch_instruction *)pc;
58 
59 	while (ip < ip_end) {
60 		if (is_stack_alloc_ins(ip)) {
61 			frame_size = (1 << 12) - ip->reg2i12_format.immediate;
62 			ip++;
63 			break;
64 		}
65 		ip++;
66 	}
67 
68 	if (!frame_size) {
69 		if (state->first)
70 			goto first;
71 
72 		return false;
73 	}
74 
75 	while (ip < ip_end) {
76 		if (is_ra_save_ins(ip)) {
77 			frame_ra = ip->reg2i12_format.immediate;
78 			break;
79 		}
80 		if (is_branch_ins(ip))
81 			break;
82 		ip++;
83 	}
84 
85 	if (frame_ra < 0) {
86 		if (state->first) {
87 			state->sp = state->sp + frame_size;
88 			goto first;
89 		}
90 		return false;
91 	}
92 
93 	if (state->first)
94 		state->first = false;
95 
96 	state->pc = *(unsigned long *)(state->sp + frame_ra);
97 	state->sp = state->sp + frame_size;
98 	return !!__kernel_text_address(state->pc);
99 
100 first:
101 	state->first = false;
102 	if (state->pc == state->ra)
103 		return false;
104 
105 	state->pc = state->ra;
106 
107 	return !!__kernel_text_address(state->ra);
108 }
109 
unwind_start(struct unwind_state * state,struct task_struct * task,struct pt_regs * regs)110 void unwind_start(struct unwind_state *state, struct task_struct *task,
111 		    struct pt_regs *regs)
112 {
113 	memset(state, 0, sizeof(*state));
114 
115 	if (regs &&  __kernel_text_address(regs->csr_era)) {
116 		state->pc = regs->csr_era;
117 		state->sp = regs->regs[3];
118 		state->ra = regs->regs[1];
119 		state->type = UNWINDER_PROLOGUE;
120 	}
121 
122 	state->task = task;
123 	state->first = true;
124 
125 	get_stack_info(state->sp, state->task, &state->stack_info);
126 
127 	if (!unwind_done(state) && !__kernel_text_address(state->pc))
128 		unwind_next_frame(state);
129 }
130 EXPORT_SYMBOL_GPL(unwind_start);
131 
unwind_next_frame(struct unwind_state * state)132 bool unwind_next_frame(struct unwind_state *state)
133 {
134 	struct stack_info *info = &state->stack_info;
135 	struct pt_regs *regs;
136 	unsigned long pc;
137 
138 	if (unwind_done(state))
139 		return false;
140 
141 	do {
142 		switch (state->type) {
143 		case UNWINDER_GUESS:
144 			state->first = false;
145 			if (unwind_by_guess(state))
146 				return true;
147 			break;
148 
149 		case UNWINDER_PROLOGUE:
150 			if (unwind_by_prologue(state))
151 				return true;
152 
153 			if (info->type == STACK_TYPE_IRQ &&
154 				info->end == state->sp) {
155 				regs = (struct pt_regs *)info->next_sp;
156 				pc = regs->csr_era;
157 
158 				if (user_mode(regs) || !__kernel_text_address(pc))
159 					return false;
160 
161 				state->pc = pc;
162 				state->sp = regs->regs[3];
163 				state->ra = regs->regs[1];
164 				state->first = true;
165 				get_stack_info(state->sp, state->task, info);
166 
167 				return true;
168 			}
169 		}
170 
171 		state->sp = info->next_sp;
172 
173 	} while (!get_stack_info(state->sp, state->task, info));
174 
175 	return false;
176 }
177 EXPORT_SYMBOL_GPL(unwind_next_frame);
178