149aef111SQing Zhang // SPDX-License-Identifier: GPL-2.0
249aef111SQing Zhang /*
349aef111SQing Zhang * Copyright (C) 2022 Loongson Technology Corporation Limited
449aef111SQing Zhang */
5dc74a9e8SJinyang He #include <linux/cpumask.h>
6a51ac524SQing Zhang #include <linux/ftrace.h>
749aef111SQing Zhang #include <linux/kallsyms.h>
849aef111SQing Zhang
949aef111SQing Zhang #include <asm/inst.h>
10dc74a9e8SJinyang He #include <asm/loongson.h>
1149aef111SQing Zhang #include <asm/ptrace.h>
12dc74a9e8SJinyang He #include <asm/setup.h>
1349aef111SQing Zhang #include <asm/unwind.h>
1449aef111SQing Zhang
15dc74a9e8SJinyang He extern const int unwind_hint_ade;
16dc74a9e8SJinyang He extern const int unwind_hint_ale;
17dc74a9e8SJinyang He extern const int unwind_hint_bp;
18dc74a9e8SJinyang He extern const int unwind_hint_fpe;
19dc74a9e8SJinyang He extern const int unwind_hint_fpu;
20dc74a9e8SJinyang He extern const int unwind_hint_lsx;
21dc74a9e8SJinyang He extern const int unwind_hint_lasx;
22dc74a9e8SJinyang He extern const int unwind_hint_lbt;
23dc74a9e8SJinyang He extern const int unwind_hint_ri;
24dc74a9e8SJinyang He extern const int unwind_hint_watch;
25dc74a9e8SJinyang He extern unsigned long eentry;
26dc74a9e8SJinyang He #ifdef CONFIG_NUMA
27dc74a9e8SJinyang He extern unsigned long pcpu_handlers[NR_CPUS];
28dc74a9e8SJinyang He #endif
29dc74a9e8SJinyang He
scan_handlers(unsigned long entry_offset)30dc74a9e8SJinyang He static inline bool scan_handlers(unsigned long entry_offset)
31dc74a9e8SJinyang He {
32dc74a9e8SJinyang He int idx, offset;
33dc74a9e8SJinyang He
34dc74a9e8SJinyang He if (entry_offset >= EXCCODE_INT_START * VECSIZE)
35dc74a9e8SJinyang He return false;
36dc74a9e8SJinyang He
37dc74a9e8SJinyang He idx = entry_offset / VECSIZE;
38dc74a9e8SJinyang He offset = entry_offset % VECSIZE;
39dc74a9e8SJinyang He switch (idx) {
40dc74a9e8SJinyang He case EXCCODE_ADE:
41dc74a9e8SJinyang He return offset == unwind_hint_ade;
42dc74a9e8SJinyang He case EXCCODE_ALE:
43dc74a9e8SJinyang He return offset == unwind_hint_ale;
44dc74a9e8SJinyang He case EXCCODE_BP:
45dc74a9e8SJinyang He return offset == unwind_hint_bp;
46dc74a9e8SJinyang He case EXCCODE_FPE:
47dc74a9e8SJinyang He return offset == unwind_hint_fpe;
48dc74a9e8SJinyang He case EXCCODE_FPDIS:
49dc74a9e8SJinyang He return offset == unwind_hint_fpu;
50dc74a9e8SJinyang He case EXCCODE_LSXDIS:
51dc74a9e8SJinyang He return offset == unwind_hint_lsx;
52dc74a9e8SJinyang He case EXCCODE_LASXDIS:
53dc74a9e8SJinyang He return offset == unwind_hint_lasx;
54dc74a9e8SJinyang He case EXCCODE_BTDIS:
55dc74a9e8SJinyang He return offset == unwind_hint_lbt;
56dc74a9e8SJinyang He case EXCCODE_INE:
57dc74a9e8SJinyang He return offset == unwind_hint_ri;
58dc74a9e8SJinyang He case EXCCODE_WATCH:
59dc74a9e8SJinyang He return offset == unwind_hint_watch;
60dc74a9e8SJinyang He default:
61dc74a9e8SJinyang He return false;
62dc74a9e8SJinyang He }
63dc74a9e8SJinyang He }
64dc74a9e8SJinyang He
fix_exception(unsigned long pc)65dc74a9e8SJinyang He static inline bool fix_exception(unsigned long pc)
66dc74a9e8SJinyang He {
67dc74a9e8SJinyang He #ifdef CONFIG_NUMA
68dc74a9e8SJinyang He int cpu;
69dc74a9e8SJinyang He
70dc74a9e8SJinyang He for_each_possible_cpu(cpu) {
71dc74a9e8SJinyang He if (!pcpu_handlers[cpu])
72dc74a9e8SJinyang He continue;
73dc74a9e8SJinyang He if (scan_handlers(pc - pcpu_handlers[cpu]))
74dc74a9e8SJinyang He return true;
75dc74a9e8SJinyang He }
76dc74a9e8SJinyang He #endif
77dc74a9e8SJinyang He return scan_handlers(pc - eentry);
78dc74a9e8SJinyang He }
79dc74a9e8SJinyang He
80dc74a9e8SJinyang He /*
81dc74a9e8SJinyang He * As we meet ftrace_regs_entry, reset first flag like first doing
82dc74a9e8SJinyang He * tracing. Prologue analysis will stop soon because PC is at entry.
83dc74a9e8SJinyang He */
fix_ftrace(unsigned long pc)84dc74a9e8SJinyang He static inline bool fix_ftrace(unsigned long pc)
854733f09dSQing Zhang {
864733f09dSQing Zhang #ifdef CONFIG_DYNAMIC_FTRACE
87dc74a9e8SJinyang He return pc == (unsigned long)ftrace_call + LOONGARCH_INSN_SIZE;
88dc74a9e8SJinyang He #else
89dc74a9e8SJinyang He return false;
904733f09dSQing Zhang #endif
914733f09dSQing Zhang }
924733f09dSQing Zhang
unwind_state_fixup(struct unwind_state * state)93dc74a9e8SJinyang He static inline bool unwind_state_fixup(struct unwind_state *state)
94dc74a9e8SJinyang He {
95dc74a9e8SJinyang He if (!fix_exception(state->pc) && !fix_ftrace(state->pc))
96dc74a9e8SJinyang He return false;
97dc74a9e8SJinyang He
98dc74a9e8SJinyang He state->reset = true;
99dc74a9e8SJinyang He return true;
100dc74a9e8SJinyang He }
101dc74a9e8SJinyang He
102c5ac25e0SJinyang He /*
103c5ac25e0SJinyang He * LoongArch function prologue is like follows,
104c5ac25e0SJinyang He * [instructions not use stack var]
105c5ac25e0SJinyang He * addi.d sp, sp, -imm
106c5ac25e0SJinyang He * st.d xx, sp, offset <- save callee saved regs and
107c5ac25e0SJinyang He * st.d yy, sp, offset save ra if function is nest.
108c5ac25e0SJinyang He * [others instructions]
109c5ac25e0SJinyang He */
unwind_by_prologue(struct unwind_state * state)11049aef111SQing Zhang static bool unwind_by_prologue(struct unwind_state *state)
11149aef111SQing Zhang {
112b96e74bbSKaiLong Wang long frame_ra = -1;
113b96e74bbSKaiLong Wang unsigned long frame_size = 0;
114e2f27392SJinyang He unsigned long size, offset, pc;
1154733f09dSQing Zhang struct pt_regs *regs;
1164733f09dSQing Zhang struct stack_info *info = &state->stack_info;
1174733f09dSQing Zhang union loongarch_instruction *ip, *ip_end;
11849aef111SQing Zhang
11949aef111SQing Zhang if (state->sp >= info->end || state->sp < info->begin)
12049aef111SQing Zhang return false;
12149aef111SQing Zhang
122dc74a9e8SJinyang He if (state->reset) {
1234733f09dSQing Zhang regs = (struct pt_regs *)state->sp;
1244733f09dSQing Zhang state->first = true;
125dc74a9e8SJinyang He state->reset = false;
1264733f09dSQing Zhang state->pc = regs->csr_era;
1274733f09dSQing Zhang state->ra = regs->regs[1];
1284733f09dSQing Zhang state->sp = regs->regs[3];
1294733f09dSQing Zhang return true;
1304733f09dSQing Zhang }
1314733f09dSQing Zhang
132e2f27392SJinyang He /*
133e2f27392SJinyang He * When first is not set, the PC is a return address in the previous frame.
134e2f27392SJinyang He * We need to adjust its value in case overflow to the next symbol.
135e2f27392SJinyang He */
136e2f27392SJinyang He pc = state->pc - (state->first ? 0 : LOONGARCH_INSN_SIZE);
13749aef111SQing Zhang if (!kallsyms_lookup_size_offset(pc, &size, &offset))
13849aef111SQing Zhang return false;
13949aef111SQing Zhang
14049aef111SQing Zhang ip = (union loongarch_instruction *)(pc - offset);
14149aef111SQing Zhang ip_end = (union loongarch_instruction *)pc;
14249aef111SQing Zhang
14349aef111SQing Zhang while (ip < ip_end) {
14449aef111SQing Zhang if (is_stack_alloc_ins(ip)) {
14549aef111SQing Zhang frame_size = (1 << 12) - ip->reg2i12_format.immediate;
14649aef111SQing Zhang ip++;
14749aef111SQing Zhang break;
14849aef111SQing Zhang }
14949aef111SQing Zhang ip++;
15049aef111SQing Zhang }
15149aef111SQing Zhang
152c5ac25e0SJinyang He /*
153c5ac25e0SJinyang He * Can't find stack alloc action, PC may be in a leaf function. Only the
154c5ac25e0SJinyang He * first being true is reasonable, otherwise indicate analysis is broken.
155c5ac25e0SJinyang He */
15649aef111SQing Zhang if (!frame_size) {
15749aef111SQing Zhang if (state->first)
15849aef111SQing Zhang goto first;
15949aef111SQing Zhang
16049aef111SQing Zhang return false;
16149aef111SQing Zhang }
16249aef111SQing Zhang
16349aef111SQing Zhang while (ip < ip_end) {
16449aef111SQing Zhang if (is_ra_save_ins(ip)) {
16549aef111SQing Zhang frame_ra = ip->reg2i12_format.immediate;
16649aef111SQing Zhang break;
16749aef111SQing Zhang }
16849aef111SQing Zhang if (is_branch_ins(ip))
16949aef111SQing Zhang break;
17049aef111SQing Zhang ip++;
17149aef111SQing Zhang }
17249aef111SQing Zhang
173c5ac25e0SJinyang He /* Can't find save $ra action, PC may be in a leaf function, too. */
17449aef111SQing Zhang if (frame_ra < 0) {
17549aef111SQing Zhang if (state->first) {
17649aef111SQing Zhang state->sp = state->sp + frame_size;
17749aef111SQing Zhang goto first;
17849aef111SQing Zhang }
17949aef111SQing Zhang return false;
18049aef111SQing Zhang }
18149aef111SQing Zhang
18249aef111SQing Zhang state->pc = *(unsigned long *)(state->sp + frame_ra);
18349aef111SQing Zhang state->sp = state->sp + frame_size;
1844733f09dSQing Zhang goto out;
18549aef111SQing Zhang
18649aef111SQing Zhang first:
18749aef111SQing Zhang state->pc = state->ra;
18849aef111SQing Zhang
1894733f09dSQing Zhang out:
190c5ac25e0SJinyang He state->first = false;
191dc74a9e8SJinyang He return unwind_state_fixup(state) || __kernel_text_address(state->pc);
19249aef111SQing Zhang }
19349aef111SQing Zhang
next_frame(struct unwind_state * state)194c5ac25e0SJinyang He static bool next_frame(struct unwind_state *state)
19549aef111SQing Zhang {
19649aef111SQing Zhang unsigned long pc;
197c5ac25e0SJinyang He struct pt_regs *regs;
198c5ac25e0SJinyang He struct stack_info *info = &state->stack_info;
19949aef111SQing Zhang
20049aef111SQing Zhang if (unwind_done(state))
20149aef111SQing Zhang return false;
20249aef111SQing Zhang
20349aef111SQing Zhang do {
204a51ac524SQing Zhang if (unwind_by_prologue(state)) {
2055bb8d344SJinyang He state->pc = unwind_graph_addr(state, state->pc, state->sp);
20649aef111SQing Zhang return true;
207a51ac524SQing Zhang }
20849aef111SQing Zhang
209c5ac25e0SJinyang He if (info->type == STACK_TYPE_IRQ && info->end == state->sp) {
21049aef111SQing Zhang regs = (struct pt_regs *)info->next_sp;
21149aef111SQing Zhang pc = regs->csr_era;
21249aef111SQing Zhang
21349aef111SQing Zhang if (user_mode(regs) || !__kernel_text_address(pc))
214370a3b8fSTiezhu Yang goto out;
21549aef111SQing Zhang
21649aef111SQing Zhang state->first = true;
217c5ac25e0SJinyang He state->pc = pc;
218a51ac524SQing Zhang state->ra = regs->regs[1];
219a51ac524SQing Zhang state->sp = regs->regs[3];
22049aef111SQing Zhang get_stack_info(state->sp, state->task, info);
22149aef111SQing Zhang
22249aef111SQing Zhang return true;
22349aef111SQing Zhang }
22449aef111SQing Zhang
22549aef111SQing Zhang state->sp = info->next_sp;
22649aef111SQing Zhang
22749aef111SQing Zhang } while (!get_stack_info(state->sp, state->task, info));
22849aef111SQing Zhang
229370a3b8fSTiezhu Yang out:
230*4d8121aaSJinyang He state->stack_info.type = STACK_TYPE_UNKNOWN;
23149aef111SQing Zhang return false;
23249aef111SQing Zhang }
233c5ac25e0SJinyang He
unwind_get_return_address(struct unwind_state * state)234c5ac25e0SJinyang He unsigned long unwind_get_return_address(struct unwind_state *state)
235c5ac25e0SJinyang He {
236c5ac25e0SJinyang He return __unwind_get_return_address(state);
237c5ac25e0SJinyang He }
238c5ac25e0SJinyang He EXPORT_SYMBOL_GPL(unwind_get_return_address);
239c5ac25e0SJinyang He
unwind_start(struct unwind_state * state,struct task_struct * task,struct pt_regs * regs)240c5ac25e0SJinyang He void unwind_start(struct unwind_state *state, struct task_struct *task,
241c5ac25e0SJinyang He struct pt_regs *regs)
242c5ac25e0SJinyang He {
243c5ac25e0SJinyang He __unwind_start(state, task, regs);
244c5ac25e0SJinyang He state->type = UNWINDER_PROLOGUE;
245c5ac25e0SJinyang He state->first = true;
246c5ac25e0SJinyang He
247c5ac25e0SJinyang He /*
248c5ac25e0SJinyang He * The current PC is not kernel text address, we cannot find its
249c5ac25e0SJinyang He * relative symbol. Thus, prologue analysis will be broken. Luckily,
250c5ac25e0SJinyang He * we can use the default_next_frame().
251c5ac25e0SJinyang He */
252c5ac25e0SJinyang He if (!__kernel_text_address(state->pc)) {
253c5ac25e0SJinyang He state->type = UNWINDER_GUESS;
254c5ac25e0SJinyang He if (!unwind_done(state))
255c5ac25e0SJinyang He unwind_next_frame(state);
256c5ac25e0SJinyang He }
257c5ac25e0SJinyang He }
258c5ac25e0SJinyang He EXPORT_SYMBOL_GPL(unwind_start);
259c5ac25e0SJinyang He
unwind_next_frame(struct unwind_state * state)260c5ac25e0SJinyang He bool unwind_next_frame(struct unwind_state *state)
261c5ac25e0SJinyang He {
262c5ac25e0SJinyang He return state->type == UNWINDER_PROLOGUE ?
263c5ac25e0SJinyang He next_frame(state) : default_next_frame(state);
264c5ac25e0SJinyang He }
26549aef111SQing Zhang EXPORT_SYMBOL_GPL(unwind_next_frame);
266