1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2022 Loongson Technology Corporation Limited
4 */
5#include <linux/cpumask.h>
6#include <linux/ftrace.h>
7#include <linux/kallsyms.h>
8
9#include <asm/inst.h>
10#include <asm/loongson.h>
11#include <asm/ptrace.h>
12#include <asm/setup.h>
13#include <asm/unwind.h>
14
15extern const int unwind_hint_ade;
16extern const int unwind_hint_ale;
17extern const int unwind_hint_bp;
18extern const int unwind_hint_fpe;
19extern const int unwind_hint_fpu;
20extern const int unwind_hint_lsx;
21extern const int unwind_hint_lasx;
22extern const int unwind_hint_lbt;
23extern const int unwind_hint_ri;
24extern const int unwind_hint_watch;
25extern unsigned long eentry;
26#ifdef CONFIG_NUMA
27extern unsigned long pcpu_handlers[NR_CPUS];
28#endif
29
30static inline bool scan_handlers(unsigned long entry_offset)
31{
32 int idx, offset;
33
34 if (entry_offset >= EXCCODE_INT_START * VECSIZE)
35 return false;
36
37 idx = entry_offset / VECSIZE;
38 offset = entry_offset % VECSIZE;
39 switch (idx) {
40 case EXCCODE_ADE:
41 return offset == unwind_hint_ade;
42 case EXCCODE_ALE:
43 return offset == unwind_hint_ale;
44 case EXCCODE_BP:
45 return offset == unwind_hint_bp;
46 case EXCCODE_FPE:
47 return offset == unwind_hint_fpe;
48 case EXCCODE_FPDIS:
49 return offset == unwind_hint_fpu;
50 case EXCCODE_LSXDIS:
51 return offset == unwind_hint_lsx;
52 case EXCCODE_LASXDIS:
53 return offset == unwind_hint_lasx;
54 case EXCCODE_BTDIS:
55 return offset == unwind_hint_lbt;
56 case EXCCODE_INE:
57 return offset == unwind_hint_ri;
58 case EXCCODE_WATCH:
59 return offset == unwind_hint_watch;
60 default:
61 return false;
62 }
63}
64
65static inline bool fix_exception(unsigned long pc)
66{
67#ifdef CONFIG_NUMA
68 int cpu;
69
70 for_each_possible_cpu(cpu) {
71 if (!pcpu_handlers[cpu])
72 continue;
73 if (scan_handlers(entry_offset: pc - pcpu_handlers[cpu]))
74 return true;
75 }
76#endif
77 return scan_handlers(entry_offset: pc - eentry);
78}
79
80/*
81 * As we meet ftrace_regs_entry, reset first flag like first doing
82 * tracing. Prologue analysis will stop soon because PC is at entry.
83 */
84static inline bool fix_ftrace(unsigned long pc)
85{
86#ifdef CONFIG_DYNAMIC_FTRACE
87 return pc == (unsigned long)ftrace_call + LOONGARCH_INSN_SIZE;
88#else
89 return false;
90#endif
91}
92
93static inline bool unwind_state_fixup(struct unwind_state *state)
94{
95 if (!fix_exception(pc: state->pc) && !fix_ftrace(pc: state->pc))
96 return false;
97
98 state->reset = true;
99 return true;
100}
101
102/*
103 * LoongArch function prologue is like follows,
104 * [instructions not use stack var]
105 * addi.d sp, sp, -imm
106 * st.d xx, sp, offset <- save callee saved regs and
107 * st.d yy, sp, offset save ra if function is nest.
108 * [others instructions]
109 */
110static bool unwind_by_prologue(struct unwind_state *state)
111{
112 long frame_ra = -1;
113 unsigned long frame_size = 0;
114 unsigned long size, offset, pc;
115 struct pt_regs *regs;
116 struct stack_info *info = &state->stack_info;
117 union loongarch_instruction *ip, *ip_end;
118
119 if (state->sp >= info->end || state->sp < info->begin)
120 return false;
121
122 if (state->reset) {
123 regs = (struct pt_regs *)state->sp;
124 state->first = true;
125 state->reset = false;
126 state->pc = regs->csr_era;
127 state->ra = regs->regs[1];
128 state->sp = regs->regs[3];
129 return true;
130 }
131
132 /*
133 * When first is not set, the PC is a return address in the previous frame.
134 * We need to adjust its value in case overflow to the next symbol.
135 */
136 pc = state->pc - (state->first ? 0 : LOONGARCH_INSN_SIZE);
137 if (!kallsyms_lookup_size_offset(addr: pc, symbolsize: &size, offset: &offset))
138 return false;
139
140 ip = (union loongarch_instruction *)(pc - offset);
141 ip_end = (union loongarch_instruction *)pc;
142
143 while (ip < ip_end) {
144 if (is_stack_alloc_ins(ip)) {
145 frame_size = (1 << 12) - ip->reg2i12_format.immediate;
146 ip++;
147 break;
148 }
149 ip++;
150 }
151
152 /*
153 * Can't find stack alloc action, PC may be in a leaf function. Only the
154 * first being true is reasonable, otherwise indicate analysis is broken.
155 */
156 if (!frame_size) {
157 if (state->first)
158 goto first;
159
160 return false;
161 }
162
163 while (ip < ip_end) {
164 if (is_ra_save_ins(ip)) {
165 frame_ra = ip->reg2i12_format.immediate;
166 break;
167 }
168 if (is_branch_ins(ip))
169 break;
170 ip++;
171 }
172
173 /* Can't find save $ra action, PC may be in a leaf function, too. */
174 if (frame_ra < 0) {
175 if (state->first) {
176 state->sp = state->sp + frame_size;
177 goto first;
178 }
179 return false;
180 }
181
182 state->pc = *(unsigned long *)(state->sp + frame_ra);
183 state->sp = state->sp + frame_size;
184 goto out;
185
186first:
187 state->pc = state->ra;
188
189out:
190 state->first = false;
191 return unwind_state_fixup(state) || __kernel_text_address(addr: state->pc);
192}
193
194static bool next_frame(struct unwind_state *state)
195{
196 unsigned long pc;
197 struct pt_regs *regs;
198 struct stack_info *info = &state->stack_info;
199
200 if (unwind_done(state))
201 return false;
202
203 do {
204 if (unwind_by_prologue(state)) {
205 state->pc = unwind_graph_addr(state, state->pc, state->sp);
206 return true;
207 }
208
209 if (info->type == STACK_TYPE_IRQ && info->end == state->sp) {
210 regs = (struct pt_regs *)info->next_sp;
211 pc = regs->csr_era;
212
213 if (user_mode(regs) || !__kernel_text_address(addr: pc))
214 goto out;
215
216 state->first = true;
217 state->pc = pc;
218 state->ra = regs->regs[1];
219 state->sp = regs->regs[3];
220 get_stack_info(state->sp, state->task, info);
221
222 return true;
223 }
224
225 state->sp = info->next_sp;
226
227 } while (!get_stack_info(state->sp, state->task, info));
228
229out:
230 state->stack_info.type = STACK_TYPE_UNKNOWN;
231 return false;
232}
233
234unsigned long unwind_get_return_address(struct unwind_state *state)
235{
236 return __unwind_get_return_address(state);
237}
238EXPORT_SYMBOL_GPL(unwind_get_return_address);
239
240void unwind_start(struct unwind_state *state, struct task_struct *task,
241 struct pt_regs *regs)
242{
243 __unwind_start(state, task, regs);
244 state->type = UNWINDER_PROLOGUE;
245 state->first = true;
246
247 /*
248 * The current PC is not kernel text address, we cannot find its
249 * relative symbol. Thus, prologue analysis will be broken. Luckily,
250 * we can use the default_next_frame().
251 */
252 if (!__kernel_text_address(addr: state->pc)) {
253 state->type = UNWINDER_GUESS;
254 if (!unwind_done(state))
255 unwind_next_frame(state);
256 }
257}
258EXPORT_SYMBOL_GPL(unwind_start);
259
260bool unwind_next_frame(struct unwind_state *state)
261{
262 return state->type == UNWINDER_PROLOGUE ?
263 next_frame(state) : default_next_frame(state);
264}
265EXPORT_SYMBOL_GPL(unwind_next_frame);
266

source code of linux/arch/loongarch/kernel/unwind_prologue.c