Merge branch 'viafb-next' of git://github.com/schandinat/linux-2.6
[sfrench/cifs-2.6.git] / arch / arm / kernel / ftrace.c
1 /*
2  * Dynamic function tracing support.
3  *
4  * Copyright (C) 2008 Abhishek Sagar <sagar.abhishek@gmail.com>
5  * Copyright (C) 2010 Rabin Vincent <rabin@rab.in>
6  *
7  * For licencing details, see COPYING.
8  *
9  * Defines low-level handling of mcount calls when the kernel
10  * is compiled with the -pg flag. When using dynamic ftrace, the
11  * mcount call-sites get patched with NOP till they are enabled.
12  * All code mutation routines here are called under stop_machine().
13  */
14
15 #include <linux/ftrace.h>
16 #include <linux/uaccess.h>
17
18 #include <asm/cacheflush.h>
19 #include <asm/ftrace.h>
20
21 #ifdef CONFIG_THUMB2_KERNEL
22 #define NOP             0xeb04f85d      /* pop.w {lr} */
23 #else
24 #define NOP             0xe8bd4000      /* pop {lr} */
25 #endif
26
27 #ifdef CONFIG_OLD_MCOUNT
28 #define OLD_MCOUNT_ADDR ((unsigned long) mcount)
29 #define OLD_FTRACE_ADDR ((unsigned long) ftrace_caller_old)
30
31 #define OLD_NOP         0xe1a00000      /* mov r0, r0 */
32
33 static unsigned long ftrace_nop_replace(struct dyn_ftrace *rec)
34 {
35         return rec->arch.old_mcount ? OLD_NOP : NOP;
36 }
37
38 static unsigned long adjust_address(struct dyn_ftrace *rec, unsigned long addr)
39 {
40         if (!rec->arch.old_mcount)
41                 return addr;
42
43         if (addr == MCOUNT_ADDR)
44                 addr = OLD_MCOUNT_ADDR;
45         else if (addr == FTRACE_ADDR)
46                 addr = OLD_FTRACE_ADDR;
47
48         return addr;
49 }
50 #else
51 static unsigned long ftrace_nop_replace(struct dyn_ftrace *rec)
52 {
53         return NOP;
54 }
55
56 static unsigned long adjust_address(struct dyn_ftrace *rec, unsigned long addr)
57 {
58         return addr;
59 }
60 #endif
61
62 /* construct a branch (BL) instruction to addr */
63 #ifdef CONFIG_THUMB2_KERNEL
64 static unsigned long ftrace_call_replace(unsigned long pc, unsigned long addr)
65 {
66         unsigned long s, j1, j2, i1, i2, imm10, imm11;
67         unsigned long first, second;
68         long offset;
69
70         offset = (long)addr - (long)(pc + 4);
71         if (offset < -16777216 || offset > 16777214) {
72                 WARN_ON_ONCE(1);
73                 return 0;
74         }
75
76         s       = (offset >> 24) & 0x1;
77         i1      = (offset >> 23) & 0x1;
78         i2      = (offset >> 22) & 0x1;
79         imm10   = (offset >> 12) & 0x3ff;
80         imm11   = (offset >>  1) & 0x7ff;
81
82         j1 = (!i1) ^ s;
83         j2 = (!i2) ^ s;
84
85         first = 0xf000 | (s << 10) | imm10;
86         second = 0xd000 | (j1 << 13) | (j2 << 11) | imm11;
87
88         return (second << 16) | first;
89 }
90 #else
91 static unsigned long ftrace_call_replace(unsigned long pc, unsigned long addr)
92 {
93         long offset;
94
95         offset = (long)addr - (long)(pc + 8);
96         if (unlikely(offset < -33554432 || offset > 33554428)) {
97                 /* Can't generate branches that far (from ARM ARM). Ftrace
98                  * doesn't generate branches outside of kernel text.
99                  */
100                 WARN_ON_ONCE(1);
101                 return 0;
102         }
103
104         offset = (offset >> 2) & 0x00ffffff;
105
106         return 0xeb000000 | offset;
107 }
108 #endif
109
110 static int ftrace_modify_code(unsigned long pc, unsigned long old,
111                               unsigned long new)
112 {
113         unsigned long replaced;
114
115         if (probe_kernel_read(&replaced, (void *)pc, MCOUNT_INSN_SIZE))
116                 return -EFAULT;
117
118         if (replaced != old)
119                 return -EINVAL;
120
121         if (probe_kernel_write((void *)pc, &new, MCOUNT_INSN_SIZE))
122                 return -EPERM;
123
124         flush_icache_range(pc, pc + MCOUNT_INSN_SIZE);
125
126         return 0;
127 }
128
129 int ftrace_update_ftrace_func(ftrace_func_t func)
130 {
131         unsigned long pc, old;
132         unsigned long new;
133         int ret;
134
135         pc = (unsigned long)&ftrace_call;
136         memcpy(&old, &ftrace_call, MCOUNT_INSN_SIZE);
137         new = ftrace_call_replace(pc, (unsigned long)func);
138
139         ret = ftrace_modify_code(pc, old, new);
140
141 #ifdef CONFIG_OLD_MCOUNT
142         if (!ret) {
143                 pc = (unsigned long)&ftrace_call_old;
144                 memcpy(&old, &ftrace_call_old, MCOUNT_INSN_SIZE);
145                 new = ftrace_call_replace(pc, (unsigned long)func);
146
147                 ret = ftrace_modify_code(pc, old, new);
148         }
149 #endif
150
151         return ret;
152 }
153
154 int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
155 {
156         unsigned long new, old;
157         unsigned long ip = rec->ip;
158
159         old = ftrace_nop_replace(rec);
160         new = ftrace_call_replace(ip, adjust_address(rec, addr));
161
162         return ftrace_modify_code(rec->ip, old, new);
163 }
164
165 int ftrace_make_nop(struct module *mod,
166                     struct dyn_ftrace *rec, unsigned long addr)
167 {
168         unsigned long ip = rec->ip;
169         unsigned long old;
170         unsigned long new;
171         int ret;
172
173         old = ftrace_call_replace(ip, adjust_address(rec, addr));
174         new = ftrace_nop_replace(rec);
175         ret = ftrace_modify_code(ip, old, new);
176
177 #ifdef CONFIG_OLD_MCOUNT
178         if (ret == -EINVAL && addr == MCOUNT_ADDR) {
179                 rec->arch.old_mcount = true;
180
181                 old = ftrace_call_replace(ip, adjust_address(rec, addr));
182                 new = ftrace_nop_replace(rec);
183                 ret = ftrace_modify_code(ip, old, new);
184         }
185 #endif
186
187         return ret;
188 }
189
190 int __init ftrace_dyn_arch_init(void *data)
191 {
192         *(unsigned long *)data = 0;
193
194         return 0;
195 }