| // 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); |