xref: /openbmc/linux/arch/loongarch/kernel/unwind_prologue.c (revision 1b0b4c4238c0bdcf1a8ffa4c5431ec1d3fb9a345)
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