xref: /openbmc/linux/arch/csky/kernel/ftrace.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
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(&current->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(&param->cpu_count) == 1) {
213dd7c983eSGuo Ren 		ftrace_modify_all_code(param->command);
214dd7c983eSGuo Ren 		atomic_inc(&param->cpu_count);
215dd7c983eSGuo Ren 	} else {
216dd7c983eSGuo Ren 		while (atomic_read(&param->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, &param, 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