| Matt Fleming | fad57fe | 2008-11-12 20:11:47 +0900 | [diff] [blame] | 1 | /* | 
| Matt Fleming | 7780b6a | 2009-06-11 09:26:43 +0100 | [diff] [blame] | 2 |  * Copyright (C) 2008 Matt Fleming <matt@console-pimps.org> | 
| Paul Mundt | b5cfeac | 2008-12-08 12:02:28 +0900 | [diff] [blame] | 3 |  * Copyright (C) 2008 Paul Mundt <lethal@linux-sh.org> | 
| Matt Fleming | fad57fe | 2008-11-12 20:11:47 +0900 | [diff] [blame] | 4 |  * | 
 | 5 |  * Code for replacing ftrace calls with jumps. | 
 | 6 |  * | 
 | 7 |  * Copyright (C) 2007-2008 Steven Rostedt <srostedt@redhat.com> | 
 | 8 |  * | 
 | 9 |  * Thanks goes to Ingo Molnar, for suggesting the idea. | 
 | 10 |  * Mathieu Desnoyers, for suggesting postponing the modifications. | 
 | 11 |  * Arjan van de Ven, for keeping me straight, and explaining to me | 
 | 12 |  * the dangers of modifying code on the run. | 
 | 13 |  */ | 
 | 14 | #include <linux/uaccess.h> | 
 | 15 | #include <linux/ftrace.h> | 
 | 16 | #include <linux/string.h> | 
 | 17 | #include <linux/init.h> | 
 | 18 | #include <linux/io.h> | 
 | 19 | #include <asm/ftrace.h> | 
 | 20 | #include <asm/cacheflush.h> | 
 | 21 |  | 
| Matt Fleming | fad57fe | 2008-11-12 20:11:47 +0900 | [diff] [blame] | 22 | static unsigned char ftrace_replaced_code[MCOUNT_INSN_SIZE]; | 
 | 23 |  | 
| Matt Fleming | 9e28c46 | 2009-06-10 22:07:53 +0100 | [diff] [blame] | 24 | static unsigned char ftrace_nop[4]; | 
 | 25 | /* | 
 | 26 |  * If we're trying to nop out a call to a function, we instead | 
 | 27 |  * place a call to the address after the memory table. | 
 | 28 |  * | 
 | 29 |  * 8c011060 <a>: | 
 | 30 |  * 8c011060:       02 d1           mov.l   8c01106c <a+0xc>,r1 | 
 | 31 |  * 8c011062:       22 4f           sts.l   pr,@-r15 | 
 | 32 |  * 8c011064:       02 c7           mova    8c011070 <a+0x10>,r0 | 
 | 33 |  * 8c011066:       2b 41           jmp     @r1 | 
 | 34 |  * 8c011068:       2a 40           lds     r0,pr | 
 | 35 |  * 8c01106a:       09 00           nop | 
 | 36 |  * 8c01106c:       68 24           .word 0x2468     <--- ip | 
 | 37 |  * 8c01106e:       1d 8c           .word 0x8c1d | 
 | 38 |  * 8c011070:       26 4f           lds.l   @r15+,pr <--- ip + MCOUNT_INSN_SIZE | 
 | 39 |  * | 
 | 40 |  * We write 0x8c011070 to 0x8c01106c so that on entry to a() we branch | 
 | 41 |  * past the _mcount call and continue executing code like normal. | 
 | 42 |  */ | 
 | 43 | static unsigned char *ftrace_nop_replace(unsigned long ip) | 
| Matt Fleming | fad57fe | 2008-11-12 20:11:47 +0900 | [diff] [blame] | 44 | { | 
| Matt Fleming | 9e28c46 | 2009-06-10 22:07:53 +0100 | [diff] [blame] | 45 | 	__raw_writel(ip + MCOUNT_INSN_SIZE, ftrace_nop); | 
| Matt Fleming | fad57fe | 2008-11-12 20:11:47 +0900 | [diff] [blame] | 46 | 	return ftrace_nop; | 
 | 47 | } | 
 | 48 |  | 
| Matt Fleming | 9e28c46 | 2009-06-10 22:07:53 +0100 | [diff] [blame] | 49 | static unsigned char *ftrace_call_replace(unsigned long ip, unsigned long addr) | 
| Matt Fleming | fad57fe | 2008-11-12 20:11:47 +0900 | [diff] [blame] | 50 | { | 
 | 51 | 	/* Place the address in the memory table. */ | 
| Matt Fleming | 9e28c46 | 2009-06-10 22:07:53 +0100 | [diff] [blame] | 52 | 	__raw_writel(addr, ftrace_replaced_code); | 
| Matt Fleming | fad57fe | 2008-11-12 20:11:47 +0900 | [diff] [blame] | 53 |  | 
 | 54 | 	/* | 
 | 55 | 	 * No locking needed, this must be called via kstop_machine | 
 | 56 | 	 * which in essence is like running on a uniprocessor machine. | 
 | 57 | 	 */ | 
 | 58 | 	return ftrace_replaced_code; | 
 | 59 | } | 
 | 60 |  | 
| Matt Fleming | 9e28c46 | 2009-06-10 22:07:53 +0100 | [diff] [blame] | 61 | static int ftrace_modify_code(unsigned long ip, unsigned char *old_code, | 
| Matt Fleming | fad57fe | 2008-11-12 20:11:47 +0900 | [diff] [blame] | 62 | 		       unsigned char *new_code) | 
 | 63 | { | 
 | 64 | 	unsigned char replaced[MCOUNT_INSN_SIZE]; | 
 | 65 |  | 
 | 66 | 	/* | 
 | 67 | 	 * Note: Due to modules and __init, code can | 
 | 68 | 	 *  disappear and change, we need to protect against faulting | 
 | 69 | 	 *  as well as code changing. We do this by using the | 
 | 70 | 	 *  probe_kernel_* functions. | 
 | 71 | 	 * | 
 | 72 | 	 * No real locking needed, this code is run through | 
 | 73 | 	 * kstop_machine, or before SMP starts. | 
 | 74 | 	 */ | 
 | 75 |  | 
| Matt Fleming | fad57fe | 2008-11-12 20:11:47 +0900 | [diff] [blame] | 76 | 	/* read the text we want to modify */ | 
 | 77 | 	if (probe_kernel_read(replaced, (void *)ip, MCOUNT_INSN_SIZE)) | 
 | 78 | 		return -EFAULT; | 
 | 79 |  | 
 | 80 | 	/* Make sure it is what we expect it to be */ | 
 | 81 | 	if (memcmp(replaced, old_code, MCOUNT_INSN_SIZE) != 0) | 
 | 82 | 		return -EINVAL; | 
 | 83 |  | 
 | 84 | 	/* replace the text with the new text */ | 
 | 85 | 	if (probe_kernel_write((void *)ip, new_code, MCOUNT_INSN_SIZE)) | 
 | 86 | 		return -EPERM; | 
 | 87 |  | 
 | 88 | 	flush_icache_range(ip, ip + MCOUNT_INSN_SIZE); | 
 | 89 |  | 
 | 90 | 	return 0; | 
 | 91 | } | 
 | 92 |  | 
 | 93 | int ftrace_update_ftrace_func(ftrace_func_t func) | 
 | 94 | { | 
| Matt Fleming | 9e28c46 | 2009-06-10 22:07:53 +0100 | [diff] [blame] | 95 | 	unsigned long ip = (unsigned long)(&ftrace_call) + MCOUNT_INSN_OFFSET; | 
| Matt Fleming | fad57fe | 2008-11-12 20:11:47 +0900 | [diff] [blame] | 96 | 	unsigned char old[MCOUNT_INSN_SIZE], *new; | 
 | 97 |  | 
| Matt Fleming | 9e28c46 | 2009-06-10 22:07:53 +0100 | [diff] [blame] | 98 | 	memcpy(old, (unsigned char *)ip, MCOUNT_INSN_SIZE); | 
| Matt Fleming | fad57fe | 2008-11-12 20:11:47 +0900 | [diff] [blame] | 99 | 	new = ftrace_call_replace(ip, (unsigned long)func); | 
 | 100 |  | 
| Matt Fleming | 9e28c46 | 2009-06-10 22:07:53 +0100 | [diff] [blame] | 101 | 	return ftrace_modify_code(ip, old, new); | 
| Matt Fleming | fad57fe | 2008-11-12 20:11:47 +0900 | [diff] [blame] | 102 | } | 
 | 103 |  | 
| Paul Mundt | b5cfeac | 2008-12-08 12:02:28 +0900 | [diff] [blame] | 104 | int ftrace_make_nop(struct module *mod, | 
 | 105 | 		    struct dyn_ftrace *rec, unsigned long addr) | 
 | 106 | { | 
 | 107 | 	unsigned char *new, *old; | 
 | 108 | 	unsigned long ip = rec->ip; | 
 | 109 |  | 
 | 110 | 	old = ftrace_call_replace(ip, addr); | 
| Matt Fleming | 9e28c46 | 2009-06-10 22:07:53 +0100 | [diff] [blame] | 111 | 	new = ftrace_nop_replace(ip); | 
| Paul Mundt | b5cfeac | 2008-12-08 12:02:28 +0900 | [diff] [blame] | 112 |  | 
 | 113 | 	return ftrace_modify_code(rec->ip, old, new); | 
 | 114 | } | 
 | 115 |  | 
 | 116 | int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) | 
 | 117 | { | 
 | 118 | 	unsigned char *new, *old; | 
 | 119 | 	unsigned long ip = rec->ip; | 
 | 120 |  | 
| Matt Fleming | 9e28c46 | 2009-06-10 22:07:53 +0100 | [diff] [blame] | 121 | 	old = ftrace_nop_replace(ip); | 
| Paul Mundt | b5cfeac | 2008-12-08 12:02:28 +0900 | [diff] [blame] | 122 | 	new = ftrace_call_replace(ip, addr); | 
 | 123 |  | 
 | 124 | 	return ftrace_modify_code(rec->ip, old, new); | 
 | 125 | } | 
 | 126 |  | 
| Matt Fleming | fad57fe | 2008-11-12 20:11:47 +0900 | [diff] [blame] | 127 | int __init ftrace_dyn_arch_init(void *data) | 
 | 128 | { | 
 | 129 | 	/* The return code is retured via data */ | 
 | 130 | 	__raw_writel(0, (unsigned long)data); | 
 | 131 |  | 
 | 132 | 	return 0; | 
 | 133 | } |