162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2022 Loongson Technology Corporation Limited
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/init.h>
762306a36Sopenharmony_ci#include <linux/ftrace.h>
862306a36Sopenharmony_ci#include <linux/syscalls.h>
962306a36Sopenharmony_ci#include <linux/uaccess.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <asm/asm.h>
1262306a36Sopenharmony_ci#include <asm/asm-offsets.h>
1362306a36Sopenharmony_ci#include <asm/cacheflush.h>
1462306a36Sopenharmony_ci#include <asm/inst.h>
1562306a36Sopenharmony_ci#include <asm/loongarch.h>
1662306a36Sopenharmony_ci#include <asm/syscall.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include <asm-generic/sections.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#ifdef CONFIG_FUNCTION_GRAPH_TRACER
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci/*
2362306a36Sopenharmony_ci * As `call _mcount` follows LoongArch psABI, ra-saved operation and
2462306a36Sopenharmony_ci * stack operation can be found before this insn.
2562306a36Sopenharmony_ci */
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic int ftrace_get_parent_ra_addr(unsigned long insn_addr, int *ra_off)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	int limit = 32;
3062306a36Sopenharmony_ci	union loongarch_instruction *insn;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	insn = (union loongarch_instruction *)insn_addr;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	do {
3562306a36Sopenharmony_ci		insn--;
3662306a36Sopenharmony_ci		limit--;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci		if (is_ra_save_ins(insn))
3962306a36Sopenharmony_ci			*ra_off = -((1 << 12) - insn->reg2i12_format.immediate);
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	} while (!is_stack_alloc_ins(insn) && limit);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	if (!limit)
4462306a36Sopenharmony_ci		return -EINVAL;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	return 0;
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_civoid prepare_ftrace_return(unsigned long self_addr,
5062306a36Sopenharmony_ci		unsigned long callsite_sp, unsigned long old)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	int ra_off;
5362306a36Sopenharmony_ci	unsigned long return_hooker = (unsigned long)&return_to_handler;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	if (unlikely(ftrace_graph_is_dead()))
5662306a36Sopenharmony_ci		return;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	if (unlikely(atomic_read(&current->tracing_graph_pause)))
5962306a36Sopenharmony_ci		return;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	if (ftrace_get_parent_ra_addr(self_addr, &ra_off))
6262306a36Sopenharmony_ci		goto out;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	if (!function_graph_enter(old, self_addr, 0, NULL))
6562306a36Sopenharmony_ci		*(unsigned long *)(callsite_sp + ra_off) = return_hooker;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	return;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ciout:
7062306a36Sopenharmony_ci	ftrace_graph_stop();
7162306a36Sopenharmony_ci	WARN_ON(1);
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci#endif	/* CONFIG_FUNCTION_GRAPH_TRACER */
74