diff options
Diffstat (limited to 'arch/metag/kernel')
-rw-r--r-- | arch/metag/kernel/ftrace.c | 126 | ||||
-rw-r--r-- | arch/metag/kernel/ftrace_stub.S | 76 | ||||
-rw-r--r-- | arch/metag/kernel/metag_ksyms.c | 5 |
3 files changed, 207 insertions, 0 deletions
diff --git a/arch/metag/kernel/ftrace.c b/arch/metag/kernel/ftrace.c new file mode 100644 index 0000000..a774f32 --- /dev/null +++ b/arch/metag/kernel/ftrace.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2008 Imagination Technologies Ltd. + * Licensed under the GPL + * + * Dynamic ftrace support. + */ + +#include <linux/ftrace.h> +#include <linux/io.h> +#include <linux/uaccess.h> + +#include <asm/cacheflush.h> + +#define D04_MOVT_TEMPLATE 0x02200005 +#define D04_CALL_TEMPLATE 0xAC200005 +#define D1RTP_MOVT_TEMPLATE 0x03200005 +#define D1RTP_CALL_TEMPLATE 0xAC200006 + +static const unsigned long NOP[2] = {0xa0fffffe, 0xa0fffffe}; +static unsigned long movt_and_call_insn[2]; + +static unsigned char *ftrace_nop_replace(void) +{ + return (char *)&NOP[0]; +} + +static unsigned char *ftrace_call_replace(unsigned long pc, unsigned long addr) +{ + unsigned long hi16, low16; + + hi16 = (addr & 0xffff0000) >> 13; + low16 = (addr & 0x0000ffff) << 3; + + /* + * The compiler makes the call to mcount_wrapper() + * (Meta's wrapper around mcount()) through the register + * D0.4. So whenever we're patching one of those compiler-generated + * calls we also need to go through D0.4. Otherwise use D1RtP. + */ + if (pc == (unsigned long)&ftrace_call) { + writel(D1RTP_MOVT_TEMPLATE | hi16, &movt_and_call_insn[0]); + writel(D1RTP_CALL_TEMPLATE | low16, &movt_and_call_insn[1]); + } else { + writel(D04_MOVT_TEMPLATE | hi16, &movt_and_call_insn[0]); + writel(D04_CALL_TEMPLATE | low16, &movt_and_call_insn[1]); + } + + return (unsigned char *)&movt_and_call_insn[0]; +} + +static int ftrace_modify_code(unsigned long pc, unsigned char *old_code, + unsigned char *new_code) +{ + unsigned char replaced[MCOUNT_INSN_SIZE]; + + /* + * Note: Due to modules and __init, code can + * disappear and change, we need to protect against faulting + * as well as code changing. + * + * No real locking needed, this code is run through + * kstop_machine. + */ + + /* read the text we want to modify */ + if (probe_kernel_read(replaced, (void *)pc, MCOUNT_INSN_SIZE)) + return -EFAULT; + + /* Make sure it is what we expect it to be */ + if (memcmp(replaced, old_code, MCOUNT_INSN_SIZE) != 0) + return -EINVAL; + + /* replace the text with the new text */ + if (probe_kernel_write((void *)pc, new_code, MCOUNT_INSN_SIZE)) + return -EPERM; + + flush_icache_range(pc, pc + MCOUNT_INSN_SIZE); + + return 0; +} + +int ftrace_update_ftrace_func(ftrace_func_t func) +{ + int ret; + unsigned long pc; + unsigned char old[MCOUNT_INSN_SIZE], *new; + + pc = (unsigned long)&ftrace_call; + memcpy(old, &ftrace_call, MCOUNT_INSN_SIZE); + new = ftrace_call_replace(pc, (unsigned long)func); + ret = ftrace_modify_code(pc, old, new); + + return ret; +} + +int ftrace_make_nop(struct module *mod, + struct dyn_ftrace *rec, unsigned long addr) +{ + unsigned char *new, *old; + unsigned long ip = rec->ip; + + old = ftrace_call_replace(ip, addr); + new = ftrace_nop_replace(); + + return ftrace_modify_code(ip, old, new); +} + +int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) +{ + unsigned char *new, *old; + unsigned long ip = rec->ip; + + old = ftrace_nop_replace(); + new = ftrace_call_replace(ip, addr); + + return ftrace_modify_code(ip, old, new); +} + +/* run from kstop_machine */ +int __init ftrace_dyn_arch_init(void *data) +{ + /* The return code is returned via data */ + writel(0, data); + + return 0; +} diff --git a/arch/metag/kernel/ftrace_stub.S b/arch/metag/kernel/ftrace_stub.S new file mode 100644 index 0000000..e70bff7 --- /dev/null +++ b/arch/metag/kernel/ftrace_stub.S @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2008 Imagination Technologies Ltd. + * Licensed under the GPL + * + */ + +#include <asm/ftrace.h> + + .text +#ifdef CONFIG_DYNAMIC_FTRACE + .global _mcount_wrapper + .type _mcount_wrapper,function +_mcount_wrapper: + MOV PC,D0.4 + + .global _ftrace_caller + .type _ftrace_caller,function +_ftrace_caller: + MOVT D0Re0,#HI(_function_trace_stop) + ADD D0Re0,D0Re0,#LO(_function_trace_stop) + GETD D0Re0,[D0Re0] + CMP D0Re0,#0 + BEQ $Lcall_stub + MOV PC,D0.4 +$Lcall_stub: + MSETL [A0StP], D0Ar6, D0Ar4, D0Ar2, D0.4 + MOV D1Ar1, D0.4 + MOV D0Ar2, D1RtP + SUB D1Ar1,D1Ar1,#MCOUNT_INSN_SIZE + + .global _ftrace_call +_ftrace_call: + MOVT D1RtP,#HI(_ftrace_stub) + CALL D1RtP,#LO(_ftrace_stub) + GETL D0.4, D1RtP, [A0StP++#(-8)] + GETL D0Ar2, D1Ar1, [A0StP++#(-8)] + GETL D0Ar4, D1Ar3, [A0StP++#(-8)] + GETL D0Ar6, D1Ar5, [A0StP++#(-8)] + MOV PC, D0.4 +#else + + .global _mcount_wrapper + .type _mcount_wrapper,function +_mcount_wrapper: + MOVT D0Re0,#HI(_function_trace_stop) + ADD D0Re0,D0Re0,#LO(_function_trace_stop) + GETD D0Re0,[D0Re0] + CMP D0Re0,#0 + BEQ $Lcall_mcount + MOV PC,D0.4 +$Lcall_mcount: + MSETL [A0StP], D0Ar6, D0Ar4, D0Ar2, D0.4 + MOV D1Ar1, D0.4 + MOV D0Ar2, D1RtP + MOVT D0Re0,#HI(_ftrace_trace_function) + ADD D0Re0,D0Re0,#LO(_ftrace_trace_function) + GET D1Ar3,[D0Re0] + MOVT D1Re0,#HI(_ftrace_stub) + ADD D1Re0,D1Re0,#LO(_ftrace_stub) + CMP D1Ar3,D1Re0 + BEQ $Ltrace_exit + MOV D1RtP,D1Ar3 + SUB D1Ar1,D1Ar1,#MCOUNT_INSN_SIZE + SWAP PC,D1RtP +$Ltrace_exit: + GETL D0.4, D1RtP, [A0StP++#(-8)] + GETL D0Ar2, D1Ar1, [A0StP++#(-8)] + GETL D0Ar4, D1Ar3, [A0StP++#(-8)] + GETL D0Ar6, D1Ar5, [A0StP++#(-8)] + MOV PC, D0.4 + +#endif /* CONFIG_DYNAMIC_FTRACE */ + + .global _ftrace_stub +_ftrace_stub: + MOV PC,D1RtP diff --git a/arch/metag/kernel/metag_ksyms.c b/arch/metag/kernel/metag_ksyms.c index c73ebd3..3cc4d1d 100644 --- a/arch/metag/kernel/metag_ksyms.c +++ b/arch/metag/kernel/metag_ksyms.c @@ -10,6 +10,7 @@ #include <asm/checksum.h> #include <asm/uaccess.h> #include <asm/traps.h> +#include <asm/ftrace.h> #include <asm/tbx.h> /* uaccess symbols */ @@ -73,3 +74,7 @@ EXPORT_SYMBOL(div_s64); EXPORT_SYMBOL(memcpy); EXPORT_SYMBOL(memset); EXPORT_SYMBOL(memmove); + +#ifdef CONFIG_FUNCTION_TRACER +EXPORT_SYMBOL(mcount_wrapper); +#endif |