1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Code for replacing ftrace calls with jumps.
4 *
5 * Author: Huacai Chen <chenhuacai@loongson.cn>
6 * Copyright (C) 2020 Loongson Technology Corporation Limited
7 *
8 * Thanks goes to Steven Rostedt for writing the original x86 version.
9 */
10
11 #include <linux/uaccess.h>
12 #include <linux/init.h>
13 #include <linux/ftrace.h>
14 #include <linux/syscalls.h>
15
16 #include <asm/asm.h>
17 #include <asm/asm-offsets.h>
18 #include <asm/cacheflush.h>
19 #include <asm/inst.h>
20 #include <asm/loongarchregs.h>
21 #include <asm/syscall.h>
22 #include <asm/unistd.h>
23
24 #include <asm-generic/sections.h>
25
26 #ifdef CONFIG_FUNCTION_GRAPH_TRACER
27
28 /*
29 * As `call _mcount` follows LoongArch psABI, ra-saved operation and
30 * stack operation can be found before this insn.
31 */
is_ra_save_ins(union loongarch_instruction * ip)32 static inline bool is_ra_save_ins(union loongarch_instruction *ip)
33 {
34 /* st.d $ra, $sp, offset */
35 return ip->reg2i12_format.opcode == std_op &&
36 ip->reg2i12_format.rj == 3 &&
37 ip->reg2i12_format.rd == 1;
38 }
39
is_stack_open_ins(union loongarch_instruction * ip)40 static inline bool is_stack_open_ins(union loongarch_instruction *ip)
41 {
42 /* addi.d $sp, $sp, -imm */
43 return ip->reg2i12_format.opcode == addid_op &&
44 ip->reg2i12_format.rj == 3 &&
45 ip->reg2i12_format.rd == 3 &&
46 ip->reg2i12_format.simmediate < 0;
47 }
48
ftrace_get_parent_ra_addr(unsigned long insn_addr,int * ra_off)49 static int ftrace_get_parent_ra_addr(unsigned long insn_addr, int *ra_off)
50 {
51 union loongarch_instruction *insn;
52 int limit = 32;
53
54 insn = (union loongarch_instruction *)insn_addr;
55
56 do {
57 insn--;
58 limit--;
59
60 if (is_ra_save_ins(insn))
61 *ra_off = insn->reg2i12_format.simmediate;
62
63 } while (!is_stack_open_ins(insn) && limit);
64
65 if (!limit)
66 return -EINVAL;
67
68 return 0;
69 }
70
prepare_ftrace_return(unsigned long self_addr,unsigned long callsite_sp,unsigned long old)71 void prepare_ftrace_return(unsigned long self_addr,
72 unsigned long callsite_sp, unsigned long old)
73 {
74 int ra_off;
75 unsigned long return_hooker = (unsigned long)&return_to_handler;
76
77 if (unlikely(ftrace_graph_is_dead()))
78 return;
79
80 if (unlikely(atomic_read(¤t->tracing_graph_pause)))
81 return;
82
83 if (ftrace_get_parent_ra_addr(self_addr, &ra_off))
84 goto out;
85
86 if (!function_graph_enter(old, self_addr, 0, NULL)) {
87 *(unsigned long *)(callsite_sp + ra_off) = return_hooker;
88 }
89
90 return;
91
92 out:
93 ftrace_graph_stop();
94 WARN_ON(1);
95 return;
96 }
97 #endif /* CONFIG_FUNCTION_GRAPH_TRACER */
98