|  | /* | 
|  | * ftrace graph code | 
|  | * | 
|  | * Copyright (C) 2009-2010 Analog Devices Inc. | 
|  | * Licensed under the GPL-2 or later. | 
|  | */ | 
|  |  | 
|  | #include <linux/ftrace.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/sched.h> | 
|  | #include <linux/uaccess.h> | 
|  | #include <asm/atomic.h> | 
|  | #include <asm/cacheflush.h> | 
|  |  | 
|  | #ifdef CONFIG_DYNAMIC_FTRACE | 
|  |  | 
|  | static const unsigned char mnop[] = { | 
|  | 0x03, 0xc0, 0x00, 0x18, /* MNOP; */ | 
|  | 0x03, 0xc0, 0x00, 0x18, /* MNOP; */ | 
|  | }; | 
|  |  | 
|  | static void bfin_make_pcrel24(unsigned char *insn, unsigned long src, | 
|  | unsigned long dst) | 
|  | { | 
|  | uint32_t pcrel = (dst - src) >> 1; | 
|  | insn[0] = pcrel >> 16; | 
|  | insn[1] = 0xe3; | 
|  | insn[2] = pcrel; | 
|  | insn[3] = pcrel >> 8; | 
|  | } | 
|  | #define bfin_make_pcrel24(insn, src, dst) bfin_make_pcrel24(insn, src, (unsigned long)(dst)) | 
|  |  | 
|  | static int ftrace_modify_code(unsigned long ip, const unsigned char *code, | 
|  | unsigned long len) | 
|  | { | 
|  | int ret = probe_kernel_write((void *)ip, (void *)code, len); | 
|  | flush_icache_range(ip, ip + len); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec, | 
|  | unsigned long addr) | 
|  | { | 
|  | /* Turn the mcount call site into two MNOPs as those are 32bit insns */ | 
|  | return ftrace_modify_code(rec->ip, mnop, sizeof(mnop)); | 
|  | } | 
|  |  | 
|  | int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) | 
|  | { | 
|  | /* Restore the mcount call site */ | 
|  | unsigned char call[8]; | 
|  | call[0] = 0x67; /* [--SP] = RETS; */ | 
|  | call[1] = 0x01; | 
|  | bfin_make_pcrel24(&call[2], rec->ip + 2, addr); | 
|  | call[6] = 0x27; /* RETS = [SP++]; */ | 
|  | call[7] = 0x01; | 
|  | return ftrace_modify_code(rec->ip, call, sizeof(call)); | 
|  | } | 
|  |  | 
|  | int ftrace_update_ftrace_func(ftrace_func_t func) | 
|  | { | 
|  | unsigned char call[4]; | 
|  | unsigned long ip = (unsigned long)&ftrace_call; | 
|  | bfin_make_pcrel24(call, ip, func); | 
|  | return ftrace_modify_code(ip, call, sizeof(call)); | 
|  | } | 
|  |  | 
|  | int __init ftrace_dyn_arch_init(void *data) | 
|  | { | 
|  | /* return value is done indirectly via data */ | 
|  | *(unsigned long *)data = 0; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  | #ifdef CONFIG_FUNCTION_GRAPH_TRACER | 
|  |  | 
|  | # ifdef CONFIG_DYNAMIC_FTRACE | 
|  |  | 
|  | extern void ftrace_graph_call(void); | 
|  |  | 
|  | int ftrace_enable_ftrace_graph_caller(void) | 
|  | { | 
|  | unsigned long ip = (unsigned long)&ftrace_graph_call; | 
|  | uint16_t jump_pcrel12 = ((unsigned long)&ftrace_graph_caller - ip) >> 1; | 
|  | jump_pcrel12 |= 0x2000; | 
|  | return ftrace_modify_code(ip, (void *)&jump_pcrel12, sizeof(jump_pcrel12)); | 
|  | } | 
|  |  | 
|  | int ftrace_disable_ftrace_graph_caller(void) | 
|  | { | 
|  | return ftrace_modify_code((unsigned long)&ftrace_graph_call, empty_zero_page, 2); | 
|  | } | 
|  |  | 
|  | # endif | 
|  |  | 
|  | /* | 
|  | * Hook the return address and push it in the stack of return addrs | 
|  | * in current thread info. | 
|  | */ | 
|  | void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr, | 
|  | unsigned long frame_pointer) | 
|  | { | 
|  | struct ftrace_graph_ent trace; | 
|  | unsigned long return_hooker = (unsigned long)&return_to_handler; | 
|  |  | 
|  | if (unlikely(atomic_read(¤t->tracing_graph_pause))) | 
|  | return; | 
|  |  | 
|  | if (ftrace_push_return_trace(*parent, self_addr, &trace.depth, | 
|  | frame_pointer) == -EBUSY) | 
|  | return; | 
|  |  | 
|  | trace.func = self_addr; | 
|  |  | 
|  | /* Only trace if the calling function expects to */ | 
|  | if (!ftrace_graph_entry(&trace)) { | 
|  | current->curr_ret_stack--; | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* all is well in the world !  hijack RETS ... */ | 
|  | *parent = return_hooker; | 
|  | } | 
|  |  | 
|  | #endif |