// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd. #include <linux/ftrace.h> #include <linux/uaccess.h> #include <linux/stop_machine.h> #include <asm/cacheflush.h> #ifdef CONFIG_DYNAMIC_FTRACE #define NOP 0x4000 #define NOP32_HI 0xc400 #define NOP32_LO 0x4820 #define PUSH_LR 0x14d0 #define MOVIH_LINK 0xea3a #define ORI_LINK 0xef5a #define JSR_LINK 0xe8fa #define BSR_LINK 0xe000 /* * Gcc-csky with -pg will insert stub in function prologue: * push lr * jbsr _mcount * nop32 * nop32 * * If the (callee - current_pc) is less then 64MB, we'll use bsr: * push lr * bsr _mcount * nop32 * nop32 * else we'll use (movih + ori + jsr): * push lr * movih r26, ... * ori r26, ... * jsr r26 * * (r26 is our reserved link-reg) * */ static inline void make_jbsr(unsigned long callee, unsigned long pc, uint16_t *call, bool nolr) { long offset; call[0] = nolr ? NOP : PUSH_LR; offset = (long) callee - (long) pc; if (unlikely(offset < -67108864 || offset > 67108864)) { call[1] = MOVIH_LINK; call[2] = callee >> 16; call[3] = ORI_LINK; call[4] = callee & 0xffff; call[5] = JSR_LINK; call[6] = 0; } else { offset = offset >> 1; call[1] = BSR_LINK | ((uint16_t)((unsigned long) offset >> 16) & 0x3ff); call[2] = (uint16_t)((unsigned long) offset & 0xffff); call[3] = call[5] = NOP32_HI; call[4] = call[6] = NOP32_LO; } } static uint16_t nops[7] = {NOP, NOP32_HI, NOP32_LO, NOP32_HI, NOP32_LO, NOP32_HI, NOP32_LO}; static int ftrace_check_current_nop(unsigned long hook) { uint16_t olds[7]; unsigned long hook_pos = hook - 2; if (copy_from_kernel_nofault((void *)olds, (void *)hook_pos, sizeof(nops))) return -EFAULT; if (memcmp((void *)nops, (void *)olds, sizeof(nops))) { pr_err("%p: nop but get (%04x %04x %04x %04x %04x %04x %04x)\n", (void *)hook_pos, olds[0], olds[1], olds[2], olds[3], olds[4], olds[5], olds[6]); return -EINVAL; } return 0; } static int ftrace_modify_code(unsigned long hook, unsigned long target, bool enable, bool nolr) { uint16_t call[7]; unsigned long hook_pos = hook - 2; int ret = 0; make_jbsr(target, hook, call, nolr); ret = copy_to_kernel_nofault((void *)hook_pos, enable ? call : nops, sizeof(nops)); if (ret) return -EPERM; flush_icache_range(hook_pos, hook_pos + MCOUNT_INSN_SIZE); return 0; } int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) { int ret = ftrace_check_current_nop(rec->ip); if (ret) return ret; return ftrace_modify_code(rec->ip, addr, true, false); } int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec, unsigned long addr) { return ftrace_modify_code(rec->ip, addr, false, false); } int ftrace_update_ftrace_func(ftrace_func_t func) { int ret = ftrace_modify_code((unsigned long)&ftrace_call, (unsigned long)func, true, true); if (!ret) ret = ftrace_modify_code((unsigned long)&ftrace_regs_call, (unsigned long)func, true, true); return ret; } #endif /* CONFIG_DYNAMIC_FTRACE */ #ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr, unsigned long addr) { return ftrace_modify_code(rec->ip, addr, true, true); } #endif #ifdef CONFIG_FUNCTION_GRAPH_TRACER void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr, unsigned long frame_pointer) { unsigned long return_hooker = (unsigned long)&return_to_handler; unsigned long old; if (unlikely(atomic_read(¤t->tracing_graph_pause))) return; old = *parent; if (!function_graph_enter(old, self_addr, *(unsigned long *)frame_pointer, parent)) { /* * For csky-gcc function has sub-call: * subi sp, sp, 8 * stw r8, (sp, 0) * mov r8, sp * st.w r15, (sp, 0x4) * push r15 * jl _mcount * We only need set *parent for resume * * For csky-gcc function has no sub-call: * subi sp, sp, 4 * stw r8, (sp, 0) * mov r8, sp * push r15 * jl _mcount * We need set *parent and *(frame_pointer + 4) for resume, * because lr is resumed twice. */ *parent = return_hooker; frame_pointer += 4; if (*(unsigned long *)frame_pointer == old) *(unsigned long *)frame_pointer = return_hooker; } } #ifdef CONFIG_DYNAMIC_FTRACE int ftrace_enable_ftrace_graph_caller(void) { return ftrace_modify_code((unsigned long)&ftrace_graph_call, (unsigned long)&ftrace_graph_caller, true, true); } int ftrace_disable_ftrace_graph_caller(void) { return ftrace_modify_code((unsigned long)&ftrace_graph_call, (unsigned long)&ftrace_graph_caller, false, true); } #endif /* CONFIG_DYNAMIC_FTRACE */ #endif /* CONFIG_FUNCTION_GRAPH_TRACER */ #ifdef CONFIG_DYNAMIC_FTRACE #ifndef CONFIG_CPU_HAS_ICACHE_INS struct ftrace_modify_param { int command; atomic_t cpu_count; }; static int __ftrace_modify_code(void *data) { struct ftrace_modify_param *param = data; if (atomic_inc_return(¶m->cpu_count) == 1) { ftrace_modify_all_code(param->command); atomic_inc(¶m->cpu_count); } else { while (atomic_read(¶m->cpu_count) <= num_online_cpus()) cpu_relax(); local_icache_inv_all(NULL); } return 0; } void arch_ftrace_update_code(int command) { struct ftrace_modify_param param = { command, ATOMIC_INIT(0) }; stop_machine(__ftrace_modify_code, ¶m, cpu_online_mask); } #endif #endif /* CONFIG_DYNAMIC_FTRACE */ /* _mcount is defined in abi's mcount.S */ EXPORT_SYMBOL(_mcount);