// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2022 Loongson Technology Corporation Limited */ #include <linux/cpumask.h> #include <linux/ftrace.h> #include <linux/kallsyms.h> #include <asm/inst.h> #include <asm/loongson.h> #include <asm/ptrace.h> #include <asm/setup.h> #include <asm/unwind.h> extern const int unwind_hint_ade; extern const int unwind_hint_ale; extern const int unwind_hint_bp; extern const int unwind_hint_fpe; extern const int unwind_hint_fpu; extern const int unwind_hint_lsx; extern const int unwind_hint_lasx; extern const int unwind_hint_lbt; extern const int unwind_hint_ri; extern const int unwind_hint_watch; extern unsigned long eentry; #ifdef CONFIG_NUMA extern unsigned long pcpu_handlers[NR_CPUS]; #endif static inline bool scan_handlers(unsigned long entry_offset) { int idx, offset; if (entry_offset >= EXCCODE_INT_START * VECSIZE) return false; idx = entry_offset / VECSIZE; offset = entry_offset % VECSIZE; switch (idx) { case EXCCODE_ADE: return offset == unwind_hint_ade; case EXCCODE_ALE: return offset == unwind_hint_ale; case EXCCODE_BP: return offset == unwind_hint_bp; case EXCCODE_FPE: return offset == unwind_hint_fpe; case EXCCODE_FPDIS: return offset == unwind_hint_fpu; case EXCCODE_LSXDIS: return offset == unwind_hint_lsx; case EXCCODE_LASXDIS: return offset == unwind_hint_lasx; case EXCCODE_BTDIS: return offset == unwind_hint_lbt; case EXCCODE_INE: return offset == unwind_hint_ri; case EXCCODE_WATCH: return offset == unwind_hint_watch; default: return false; } } static inline bool fix_exception(unsigned long pc) { #ifdef CONFIG_NUMA int cpu; for_each_possible_cpu(cpu) { if (!pcpu_handlers[cpu]) continue; if (scan_handlers(pc - pcpu_handlers[cpu])) return true; } #endif return scan_handlers(pc - eentry); } /* * As we meet ftrace_regs_entry, reset first flag like first doing * tracing. Prologue analysis will stop soon because PC is at entry. */ static inline bool fix_ftrace(unsigned long pc) { #ifdef CONFIG_DYNAMIC_FTRACE return pc == (unsigned long)ftrace_call + LOONGARCH_INSN_SIZE; #else return false; #endif } static inline bool unwind_state_fixup(struct unwind_state *state) { if (!fix_exception(state->pc) && !fix_ftrace(state->pc)) return false; state->reset = true; return true; } /* * LoongArch function prologue is like follows, * [instructions not use stack var] * addi.d sp, sp, -imm * st.d xx, sp, offset <- save callee saved regs and * st.d yy, sp, offset save ra if function is nest. * [others instructions] */ static bool unwind_by_prologue(struct unwind_state *state) { long frame_ra = -1; unsigned long frame_size = 0; unsigned long size, offset, pc; struct pt_regs *regs; struct stack_info *info = &state->stack_info; union loongarch_instruction *ip, *ip_end; if (state->sp >= info->end || state->sp < info->begin) return false; if (state->reset) { regs = (struct pt_regs *)state->sp; state->first = true; state->reset = false; state->pc = regs->csr_era; state->ra = regs->regs[1]; state->sp = regs->regs[3]; return true; } /* * When first is not set, the PC is a return address in the previous frame. * We need to adjust its value in case overflow to the next symbol. */ pc = state->pc - (state->first ? 0 : LOONGARCH_INSN_SIZE); if (!kallsyms_lookup_size_offset(pc, &size, &offset)) return false; ip = (union loongarch_instruction *)(pc - offset); ip_end = (union loongarch_instruction *)pc; while (ip < ip_end) { if (is_stack_alloc_ins(ip)) { frame_size = (1 << 12) - ip->reg2i12_format.immediate; ip++; break; } ip++; } /* * Can't find stack alloc action, PC may be in a leaf function. Only the * first being true is reasonable, otherwise indicate analysis is broken. */ if (!frame_size) { if (state->first) goto first; return false; } while (ip < ip_end) { if (is_ra_save_ins(ip)) { frame_ra = ip->reg2i12_format.immediate; break; } if (is_branch_ins(ip)) break; ip++; } /* Can't find save $ra action, PC may be in a leaf function, too. */ if (frame_ra < 0) { if (state->first) { state->sp = state->sp + frame_size; goto first; } return false; } state->pc = *(unsigned long *)(state->sp + frame_ra); state->sp = state->sp + frame_size; goto out; first: state->pc = state->ra; out: state->first = false; return unwind_state_fixup(state) || __kernel_text_address(state->pc); } static bool next_frame(struct unwind_state *state) { unsigned long pc; struct pt_regs *regs; struct stack_info *info = &state->stack_info; if (unwind_done(state)) return false; do { if (unwind_by_prologue(state)) { state->pc = unwind_graph_addr(state, state->pc, state->sp); return true; } if (info->type == STACK_TYPE_IRQ && info->end == state->sp) { regs = (struct pt_regs *)info->next_sp; pc = regs->csr_era; if (user_mode(regs) || !__kernel_text_address(pc)) goto out; state->first = true; state->pc = pc; state->ra = regs->regs[1]; state->sp = regs->regs[3]; get_stack_info(state->sp, state->task, info); return true; } state->sp = info->next_sp; } while (!get_stack_info(state->sp, state->task, info)); out: state->error = true; return false; } unsigned long unwind_get_return_address(struct unwind_state *state) { return __unwind_get_return_address(state); } EXPORT_SYMBOL_GPL(unwind_get_return_address); void unwind_start(struct unwind_state *state, struct task_struct *task, struct pt_regs *regs) { __unwind_start(state, task, regs); state->type = UNWINDER_PROLOGUE; state->first = true; /* * The current PC is not kernel text address, we cannot find its * relative symbol. Thus, prologue analysis will be broken. Luckily, * we can use the default_next_frame(). */ if (!__kernel_text_address(state->pc)) { state->type = UNWINDER_GUESS; if (!unwind_done(state)) unwind_next_frame(state); } } EXPORT_SYMBOL_GPL(unwind_start); bool unwind_next_frame(struct unwind_state *state) { return state->type == UNWINDER_PROLOGUE ? next_frame(state) : default_next_frame(state); } EXPORT_SYMBOL_GPL(unwind_next_frame);