1230c77a5SGuo Ren // SPDX-License-Identifier: GPL-2.0
2230c77a5SGuo Ren // Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd.
3230c77a5SGuo Ren
4230c77a5SGuo Ren #include <linux/ftrace.h>
5230c77a5SGuo Ren #include <linux/uaccess.h>
6dd7c983eSGuo Ren #include <linux/stop_machine.h>
728bb030fSGuo Ren #include <asm/cacheflush.h>
828bb030fSGuo Ren
928bb030fSGuo Ren #ifdef CONFIG_DYNAMIC_FTRACE
1028bb030fSGuo Ren
1128bb030fSGuo Ren #define NOP 0x4000
1228bb030fSGuo Ren #define NOP32_HI 0xc400
1328bb030fSGuo Ren #define NOP32_LO 0x4820
1428bb030fSGuo Ren #define PUSH_LR 0x14d0
1528bb030fSGuo Ren #define MOVIH_LINK 0xea3a
1628bb030fSGuo Ren #define ORI_LINK 0xef5a
1728bb030fSGuo Ren #define JSR_LINK 0xe8fa
1828bb030fSGuo Ren #define BSR_LINK 0xe000
1928bb030fSGuo Ren
2028bb030fSGuo Ren /*
2128bb030fSGuo Ren * Gcc-csky with -pg will insert stub in function prologue:
2228bb030fSGuo Ren * push lr
2328bb030fSGuo Ren * jbsr _mcount
2428bb030fSGuo Ren * nop32
2528bb030fSGuo Ren * nop32
2628bb030fSGuo Ren *
2728bb030fSGuo Ren * If the (callee - current_pc) is less then 64MB, we'll use bsr:
2828bb030fSGuo Ren * push lr
2928bb030fSGuo Ren * bsr _mcount
3028bb030fSGuo Ren * nop32
3128bb030fSGuo Ren * nop32
3228bb030fSGuo Ren * else we'll use (movih + ori + jsr):
3328bb030fSGuo Ren * push lr
3428bb030fSGuo Ren * movih r26, ...
3528bb030fSGuo Ren * ori r26, ...
3628bb030fSGuo Ren * jsr r26
3728bb030fSGuo Ren *
3828bb030fSGuo Ren * (r26 is our reserved link-reg)
3928bb030fSGuo Ren *
4028bb030fSGuo Ren */
make_jbsr(unsigned long callee,unsigned long pc,uint16_t * call,bool nolr)4128bb030fSGuo Ren static inline void make_jbsr(unsigned long callee, unsigned long pc,
4228bb030fSGuo Ren uint16_t *call, bool nolr)
4328bb030fSGuo Ren {
4428bb030fSGuo Ren long offset;
4528bb030fSGuo Ren
4628bb030fSGuo Ren call[0] = nolr ? NOP : PUSH_LR;
4728bb030fSGuo Ren
4828bb030fSGuo Ren offset = (long) callee - (long) pc;
4928bb030fSGuo Ren
5028bb030fSGuo Ren if (unlikely(offset < -67108864 || offset > 67108864)) {
5128bb030fSGuo Ren call[1] = MOVIH_LINK;
5228bb030fSGuo Ren call[2] = callee >> 16;
5328bb030fSGuo Ren call[3] = ORI_LINK;
5428bb030fSGuo Ren call[4] = callee & 0xffff;
5528bb030fSGuo Ren call[5] = JSR_LINK;
5628bb030fSGuo Ren call[6] = 0;
5728bb030fSGuo Ren } else {
5828bb030fSGuo Ren offset = offset >> 1;
5928bb030fSGuo Ren
6028bb030fSGuo Ren call[1] = BSR_LINK |
6128bb030fSGuo Ren ((uint16_t)((unsigned long) offset >> 16) & 0x3ff);
6228bb030fSGuo Ren call[2] = (uint16_t)((unsigned long) offset & 0xffff);
6328bb030fSGuo Ren call[3] = call[5] = NOP32_HI;
6428bb030fSGuo Ren call[4] = call[6] = NOP32_LO;
6528bb030fSGuo Ren }
6628bb030fSGuo Ren }
6728bb030fSGuo Ren
6828bb030fSGuo Ren static uint16_t nops[7] = {NOP, NOP32_HI, NOP32_LO, NOP32_HI, NOP32_LO,
6928bb030fSGuo Ren NOP32_HI, NOP32_LO};
ftrace_check_current_nop(unsigned long hook)7028bb030fSGuo Ren static int ftrace_check_current_nop(unsigned long hook)
7128bb030fSGuo Ren {
7228bb030fSGuo Ren uint16_t olds[7];
7328bb030fSGuo Ren unsigned long hook_pos = hook - 2;
7428bb030fSGuo Ren
75*fe557319SChristoph Hellwig if (copy_from_kernel_nofault((void *)olds, (void *)hook_pos,
76*fe557319SChristoph Hellwig sizeof(nops)))
7728bb030fSGuo Ren return -EFAULT;
7828bb030fSGuo Ren
7928bb030fSGuo Ren if (memcmp((void *)nops, (void *)olds, sizeof(nops))) {
8028bb030fSGuo Ren pr_err("%p: nop but get (%04x %04x %04x %04x %04x %04x %04x)\n",
8128bb030fSGuo Ren (void *)hook_pos,
8228bb030fSGuo Ren olds[0], olds[1], olds[2], olds[3], olds[4], olds[5],
8328bb030fSGuo Ren olds[6]);
8428bb030fSGuo Ren
8528bb030fSGuo Ren return -EINVAL;
8628bb030fSGuo Ren }
8728bb030fSGuo Ren
8828bb030fSGuo Ren return 0;
8928bb030fSGuo Ren }
9028bb030fSGuo Ren
ftrace_modify_code(unsigned long hook,unsigned long target,bool enable,bool nolr)9128bb030fSGuo Ren static int ftrace_modify_code(unsigned long hook, unsigned long target,
9228bb030fSGuo Ren bool enable, bool nolr)
9328bb030fSGuo Ren {
9428bb030fSGuo Ren uint16_t call[7];
9528bb030fSGuo Ren
9628bb030fSGuo Ren unsigned long hook_pos = hook - 2;
9728bb030fSGuo Ren int ret = 0;
9828bb030fSGuo Ren
9928bb030fSGuo Ren make_jbsr(target, hook, call, nolr);
10028bb030fSGuo Ren
101*fe557319SChristoph Hellwig ret = copy_to_kernel_nofault((void *)hook_pos, enable ? call : nops,
10228bb030fSGuo Ren sizeof(nops));
10328bb030fSGuo Ren if (ret)
10428bb030fSGuo Ren return -EPERM;
10528bb030fSGuo Ren
10628bb030fSGuo Ren flush_icache_range(hook_pos, hook_pos + MCOUNT_INSN_SIZE);
10728bb030fSGuo Ren
10828bb030fSGuo Ren return 0;
10928bb030fSGuo Ren }
11028bb030fSGuo Ren
ftrace_make_call(struct dyn_ftrace * rec,unsigned long addr)11128bb030fSGuo Ren int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
11228bb030fSGuo Ren {
11328bb030fSGuo Ren int ret = ftrace_check_current_nop(rec->ip);
11428bb030fSGuo Ren
11528bb030fSGuo Ren if (ret)
11628bb030fSGuo Ren return ret;
11728bb030fSGuo Ren
11828bb030fSGuo Ren return ftrace_modify_code(rec->ip, addr, true, false);
11928bb030fSGuo Ren }
12028bb030fSGuo Ren
ftrace_make_nop(struct module * mod,struct dyn_ftrace * rec,unsigned long addr)12128bb030fSGuo Ren int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec,
12228bb030fSGuo Ren unsigned long addr)
12328bb030fSGuo Ren {
12428bb030fSGuo Ren return ftrace_modify_code(rec->ip, addr, false, false);
12528bb030fSGuo Ren }
12628bb030fSGuo Ren
ftrace_update_ftrace_func(ftrace_func_t func)12728bb030fSGuo Ren int ftrace_update_ftrace_func(ftrace_func_t func)
12828bb030fSGuo Ren {
12928bb030fSGuo Ren int ret = ftrace_modify_code((unsigned long)&ftrace_call,
13028bb030fSGuo Ren (unsigned long)func, true, true);
13189a3927aSGuo Ren if (!ret)
13289a3927aSGuo Ren ret = ftrace_modify_code((unsigned long)&ftrace_regs_call,
13389a3927aSGuo Ren (unsigned long)func, true, true);
13428bb030fSGuo Ren return ret;
13528bb030fSGuo Ren }
13628bb030fSGuo Ren #endif /* CONFIG_DYNAMIC_FTRACE */
137230c77a5SGuo Ren
13889a3927aSGuo Ren #ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
ftrace_modify_call(struct dyn_ftrace * rec,unsigned long old_addr,unsigned long addr)13989a3927aSGuo Ren int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
14089a3927aSGuo Ren unsigned long addr)
14189a3927aSGuo Ren {
14289a3927aSGuo Ren return ftrace_modify_code(rec->ip, addr, true, true);
14389a3927aSGuo Ren }
14489a3927aSGuo Ren #endif
14589a3927aSGuo Ren
146d7950be1SGuo Ren #ifdef CONFIG_FUNCTION_GRAPH_TRACER
prepare_ftrace_return(unsigned long * parent,unsigned long self_addr,unsigned long frame_pointer)147d7950be1SGuo Ren void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr,
148d7950be1SGuo Ren unsigned long frame_pointer)
149230c77a5SGuo Ren {
150d7950be1SGuo Ren unsigned long return_hooker = (unsigned long)&return_to_handler;
151d7950be1SGuo Ren unsigned long old;
152230c77a5SGuo Ren
153d7950be1SGuo Ren if (unlikely(atomic_read(¤t->tracing_graph_pause)))
154d7950be1SGuo Ren return;
155d7950be1SGuo Ren
156d7950be1SGuo Ren old = *parent;
157d7950be1SGuo Ren
158d7950be1SGuo Ren if (!function_graph_enter(old, self_addr,
159d7950be1SGuo Ren *(unsigned long *)frame_pointer, parent)) {
160d7950be1SGuo Ren /*
161d7950be1SGuo Ren * For csky-gcc function has sub-call:
162d7950be1SGuo Ren * subi sp, sp, 8
163d7950be1SGuo Ren * stw r8, (sp, 0)
164d7950be1SGuo Ren * mov r8, sp
165d7950be1SGuo Ren * st.w r15, (sp, 0x4)
166d7950be1SGuo Ren * push r15
167d7950be1SGuo Ren * jl _mcount
168d7950be1SGuo Ren * We only need set *parent for resume
169d7950be1SGuo Ren *
170d7950be1SGuo Ren * For csky-gcc function has no sub-call:
171d7950be1SGuo Ren * subi sp, sp, 4
172d7950be1SGuo Ren * stw r8, (sp, 0)
173d7950be1SGuo Ren * mov r8, sp
174d7950be1SGuo Ren * push r15
175d7950be1SGuo Ren * jl _mcount
176d7950be1SGuo Ren * We need set *parent and *(frame_pointer + 4) for resume,
177d7950be1SGuo Ren * because lr is resumed twice.
178d7950be1SGuo Ren */
179d7950be1SGuo Ren *parent = return_hooker;
180d7950be1SGuo Ren frame_pointer += 4;
181d7950be1SGuo Ren if (*(unsigned long *)frame_pointer == old)
182d7950be1SGuo Ren *(unsigned long *)frame_pointer = return_hooker;
183230c77a5SGuo Ren }
184d7950be1SGuo Ren }
18528bb030fSGuo Ren
18628bb030fSGuo Ren #ifdef CONFIG_DYNAMIC_FTRACE
ftrace_enable_ftrace_graph_caller(void)18728bb030fSGuo Ren int ftrace_enable_ftrace_graph_caller(void)
18828bb030fSGuo Ren {
18928bb030fSGuo Ren return ftrace_modify_code((unsigned long)&ftrace_graph_call,
19028bb030fSGuo Ren (unsigned long)&ftrace_graph_caller, true, true);
19128bb030fSGuo Ren }
19228bb030fSGuo Ren
ftrace_disable_ftrace_graph_caller(void)19328bb030fSGuo Ren int ftrace_disable_ftrace_graph_caller(void)
19428bb030fSGuo Ren {
19528bb030fSGuo Ren return ftrace_modify_code((unsigned long)&ftrace_graph_call,
19628bb030fSGuo Ren (unsigned long)&ftrace_graph_caller, false, true);
19728bb030fSGuo Ren }
19828bb030fSGuo Ren #endif /* CONFIG_DYNAMIC_FTRACE */
19928bb030fSGuo Ren #endif /* CONFIG_FUNCTION_GRAPH_TRACER */
200230c77a5SGuo Ren
201a13d5887SGuo Ren #ifdef CONFIG_DYNAMIC_FTRACE
202dd7c983eSGuo Ren #ifndef CONFIG_CPU_HAS_ICACHE_INS
203dd7c983eSGuo Ren struct ftrace_modify_param {
204dd7c983eSGuo Ren int command;
205dd7c983eSGuo Ren atomic_t cpu_count;
206dd7c983eSGuo Ren };
207dd7c983eSGuo Ren
__ftrace_modify_code(void * data)208dd7c983eSGuo Ren static int __ftrace_modify_code(void *data)
209dd7c983eSGuo Ren {
210dd7c983eSGuo Ren struct ftrace_modify_param *param = data;
211dd7c983eSGuo Ren
212dd7c983eSGuo Ren if (atomic_inc_return(¶m->cpu_count) == 1) {
213dd7c983eSGuo Ren ftrace_modify_all_code(param->command);
214dd7c983eSGuo Ren atomic_inc(¶m->cpu_count);
215dd7c983eSGuo Ren } else {
216dd7c983eSGuo Ren while (atomic_read(¶m->cpu_count) <= num_online_cpus())
217dd7c983eSGuo Ren cpu_relax();
218dd7c983eSGuo Ren local_icache_inv_all(NULL);
219dd7c983eSGuo Ren }
220dd7c983eSGuo Ren
221dd7c983eSGuo Ren return 0;
222dd7c983eSGuo Ren }
223dd7c983eSGuo Ren
arch_ftrace_update_code(int command)224dd7c983eSGuo Ren void arch_ftrace_update_code(int command)
225dd7c983eSGuo Ren {
226dd7c983eSGuo Ren struct ftrace_modify_param param = { command, ATOMIC_INIT(0) };
227dd7c983eSGuo Ren
228dd7c983eSGuo Ren stop_machine(__ftrace_modify_code, ¶m, cpu_online_mask);
229dd7c983eSGuo Ren }
230dd7c983eSGuo Ren #endif
231a13d5887SGuo Ren #endif /* CONFIG_DYNAMIC_FTRACE */
232dd7c983eSGuo Ren
233230c77a5SGuo Ren /* _mcount is defined in abi's mcount.S */
234230c77a5SGuo Ren EXPORT_SYMBOL(_mcount);
235