#include <linux/cpumask.h>
#include <linux/export.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;
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)
{
#if defined(CONFIG_NUMA) && !defined(CONFIG_PREEMPT_RT)
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);
}
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;
}
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;
}
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++;
}
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++;
}
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->stack_info.type = STACK_TYPE_UNKNOWN;
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;
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);