root/arch/loongarch/kernel/unwind_prologue.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2022 Loongson Technology Corporation Limited
 */
#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);
}

/*
 * 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->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;

        /*
         * 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);