162306a36Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0 */
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Most of this ideas comes from x86.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2022 Loongson Technology Corporation Limited
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#ifndef _ASM_UNWIND_H
862306a36Sopenharmony_ci#define _ASM_UNWIND_H
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/sched.h>
1162306a36Sopenharmony_ci#include <linux/ftrace.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <asm/ptrace.h>
1462306a36Sopenharmony_ci#include <asm/stacktrace.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cienum unwinder_type {
1762306a36Sopenharmony_ci	UNWINDER_GUESS,
1862306a36Sopenharmony_ci	UNWINDER_PROLOGUE,
1962306a36Sopenharmony_ci};
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistruct unwind_state {
2262306a36Sopenharmony_ci	char type; /* UNWINDER_XXX */
2362306a36Sopenharmony_ci	struct stack_info stack_info;
2462306a36Sopenharmony_ci	struct task_struct *task;
2562306a36Sopenharmony_ci	bool first, error, reset;
2662306a36Sopenharmony_ci	int graph_idx;
2762306a36Sopenharmony_ci	unsigned long sp, pc, ra;
2862306a36Sopenharmony_ci};
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cibool default_next_frame(struct unwind_state *state);
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_civoid unwind_start(struct unwind_state *state,
3362306a36Sopenharmony_ci		  struct task_struct *task, struct pt_regs *regs);
3462306a36Sopenharmony_cibool unwind_next_frame(struct unwind_state *state);
3562306a36Sopenharmony_ciunsigned long unwind_get_return_address(struct unwind_state *state);
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic inline bool unwind_done(struct unwind_state *state)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	return state->stack_info.type == STACK_TYPE_UNKNOWN;
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic inline bool unwind_error(struct unwind_state *state)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	return state->error;
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci#define GRAPH_FAKE_OFFSET (sizeof(struct pt_regs) - offsetof(struct pt_regs, regs[1]))
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic inline unsigned long unwind_graph_addr(struct unwind_state *state,
5062306a36Sopenharmony_ci					unsigned long pc, unsigned long cfa)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	return ftrace_graph_ret_addr(state->task, &state->graph_idx,
5362306a36Sopenharmony_ci				     pc, (unsigned long *)(cfa - GRAPH_FAKE_OFFSET));
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic __always_inline void __unwind_start(struct unwind_state *state,
5762306a36Sopenharmony_ci					struct task_struct *task, struct pt_regs *regs)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	memset(state, 0, sizeof(*state));
6062306a36Sopenharmony_ci	if (regs) {
6162306a36Sopenharmony_ci		state->sp = regs->regs[3];
6262306a36Sopenharmony_ci		state->pc = regs->csr_era;
6362306a36Sopenharmony_ci		state->ra = regs->regs[1];
6462306a36Sopenharmony_ci	} else if (task && task != current) {
6562306a36Sopenharmony_ci		state->sp = thread_saved_fp(task);
6662306a36Sopenharmony_ci		state->pc = thread_saved_ra(task);
6762306a36Sopenharmony_ci		state->ra = 0;
6862306a36Sopenharmony_ci	} else {
6962306a36Sopenharmony_ci		state->sp = (unsigned long)__builtin_frame_address(0);
7062306a36Sopenharmony_ci		state->pc = (unsigned long)__builtin_return_address(0);
7162306a36Sopenharmony_ci		state->ra = 0;
7262306a36Sopenharmony_ci	}
7362306a36Sopenharmony_ci	state->task = task;
7462306a36Sopenharmony_ci	get_stack_info(state->sp, state->task, &state->stack_info);
7562306a36Sopenharmony_ci	state->pc = unwind_graph_addr(state, state->pc, state->sp);
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic __always_inline unsigned long __unwind_get_return_address(struct unwind_state *state)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	return unwind_done(state) ? 0 : state->pc;
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ci#endif /* _ASM_UNWIND_H */
83