diff options
-rw-r--r-- | arch/powerpc/include/asm/ftrace.h | 2 | ||||
-rw-r--r-- | arch/powerpc/include/asm/module.h | 11 | ||||
-rw-r--r-- | arch/powerpc/kernel/ftrace.c | 278 | ||||
-rw-r--r-- | arch/powerpc/kernel/module_64.c | 13 |
4 files changed, 293 insertions, 11 deletions
diff --git a/arch/powerpc/include/asm/ftrace.h b/arch/powerpc/include/asm/ftrace.h index 17efecc2bf03..e5f2ae8362f7 100644 --- a/arch/powerpc/include/asm/ftrace.h +++ b/arch/powerpc/include/asm/ftrace.h | |||
@@ -16,7 +16,7 @@ static inline unsigned long ftrace_call_adjust(unsigned long addr) | |||
16 | } | 16 | } |
17 | 17 | ||
18 | struct dyn_arch_ftrace { | 18 | struct dyn_arch_ftrace { |
19 | /* nothing yet */ | 19 | struct module *mod; |
20 | }; | 20 | }; |
21 | #endif /* CONFIG_DYNAMIC_FTRACE */ | 21 | #endif /* CONFIG_DYNAMIC_FTRACE */ |
22 | #endif /* __ASSEMBLY__ */ | 22 | #endif /* __ASSEMBLY__ */ |
diff --git a/arch/powerpc/include/asm/module.h b/arch/powerpc/include/asm/module.h index e5f14b13ccf0..340bc699b620 100644 --- a/arch/powerpc/include/asm/module.h +++ b/arch/powerpc/include/asm/module.h | |||
@@ -34,6 +34,11 @@ struct mod_arch_specific { | |||
34 | #ifdef __powerpc64__ | 34 | #ifdef __powerpc64__ |
35 | unsigned int stubs_section; /* Index of stubs section in module */ | 35 | unsigned int stubs_section; /* Index of stubs section in module */ |
36 | unsigned int toc_section; /* What section is the TOC? */ | 36 | unsigned int toc_section; /* What section is the TOC? */ |
37 | #ifdef CONFIG_DYNAMIC_FTRACE | ||
38 | unsigned long toc; | ||
39 | unsigned long tramp; | ||
40 | #endif | ||
41 | |||
37 | #else | 42 | #else |
38 | /* Indices of PLT sections within module. */ | 43 | /* Indices of PLT sections within module. */ |
39 | unsigned int core_plt_section; | 44 | unsigned int core_plt_section; |
@@ -68,6 +73,12 @@ struct mod_arch_specific { | |||
68 | # endif /* MODULE */ | 73 | # endif /* MODULE */ |
69 | #endif | 74 | #endif |
70 | 75 | ||
76 | #ifdef CONFIG_DYNAMIC_FTRACE | ||
77 | # ifdef MODULE | ||
78 | asm(".section .ftrace.tramp,\"ax\",@nobits; .align 3; .previous"); | ||
79 | # endif /* MODULE */ | ||
80 | #endif | ||
81 | |||
71 | 82 | ||
72 | struct exception_table_entry; | 83 | struct exception_table_entry; |
73 | void sort_ex_table(struct exception_table_entry *start, | 84 | void sort_ex_table(struct exception_table_entry *start, |
diff --git a/arch/powerpc/kernel/ftrace.c b/arch/powerpc/kernel/ftrace.c index 1adfbb268d8e..1aec559bdfcb 100644 --- a/arch/powerpc/kernel/ftrace.c +++ b/arch/powerpc/kernel/ftrace.c | |||
@@ -10,22 +10,29 @@ | |||
10 | #include <linux/spinlock.h> | 10 | #include <linux/spinlock.h> |
11 | #include <linux/hardirq.h> | 11 | #include <linux/hardirq.h> |
12 | #include <linux/uaccess.h> | 12 | #include <linux/uaccess.h> |
13 | #include <linux/module.h> | ||
13 | #include <linux/ftrace.h> | 14 | #include <linux/ftrace.h> |
14 | #include <linux/percpu.h> | 15 | #include <linux/percpu.h> |
15 | #include <linux/init.h> | 16 | #include <linux/init.h> |
16 | #include <linux/list.h> | 17 | #include <linux/list.h> |
17 | 18 | ||
18 | #include <asm/cacheflush.h> | 19 | #include <asm/cacheflush.h> |
20 | #include <asm/code-patching.h> | ||
19 | #include <asm/ftrace.h> | 21 | #include <asm/ftrace.h> |
20 | 22 | ||
23 | #if 0 | ||
24 | #define DEBUGP printk | ||
25 | #else | ||
26 | #define DEBUGP(fmt , ...) do { } while (0) | ||
27 | #endif | ||
21 | 28 | ||
22 | static unsigned int ftrace_nop = 0x60000000; | 29 | static unsigned int ftrace_nop = PPC_NOP_INSTR; |
23 | 30 | ||
24 | #ifdef CONFIG_PPC32 | 31 | #ifdef CONFIG_PPC32 |
25 | # define GET_ADDR(addr) addr | 32 | # define GET_ADDR(addr) addr |
26 | #else | 33 | #else |
27 | /* PowerPC64's functions are data that points to the functions */ | 34 | /* PowerPC64's functions are data that points to the functions */ |
28 | # define GET_ADDR(addr) *(unsigned long *)addr | 35 | # define GET_ADDR(addr) (*(unsigned long *)addr) |
29 | #endif | 36 | #endif |
30 | 37 | ||
31 | 38 | ||
@@ -102,6 +109,9 @@ ftrace_modify_code(unsigned long ip, unsigned char *old_code, | |||
102 | return 0; | 109 | return 0; |
103 | } | 110 | } |
104 | 111 | ||
112 | /* | ||
113 | * Helper functions that are the same for both PPC64 and PPC32. | ||
114 | */ | ||
105 | static int test_24bit_addr(unsigned long ip, unsigned long addr) | 115 | static int test_24bit_addr(unsigned long ip, unsigned long addr) |
106 | { | 116 | { |
107 | long diff; | 117 | long diff; |
@@ -119,43 +129,292 @@ static int test_24bit_addr(unsigned long ip, unsigned long addr) | |||
119 | return (diff < (1 << 25)) && (diff > (-1 << 26)); | 129 | return (diff < (1 << 25)) && (diff > (-1 << 26)); |
120 | } | 130 | } |
121 | 131 | ||
132 | static int is_bl_op(unsigned int op) | ||
133 | { | ||
134 | return (op & 0xfc000003) == 0x48000001; | ||
135 | } | ||
136 | |||
137 | static int test_offset(unsigned long offset) | ||
138 | { | ||
139 | return (offset + 0x2000000 > 0x3ffffff) || ((offset & 3) != 0); | ||
140 | } | ||
141 | |||
142 | static unsigned long find_bl_target(unsigned long ip, unsigned int op) | ||
143 | { | ||
144 | static int offset; | ||
145 | |||
146 | offset = (op & 0x03fffffc); | ||
147 | /* make it signed */ | ||
148 | if (offset & 0x02000000) | ||
149 | offset |= 0xfe000000; | ||
150 | |||
151 | return ip + (long)offset; | ||
152 | } | ||
153 | |||
154 | static unsigned int branch_offset(unsigned long offset) | ||
155 | { | ||
156 | /* return "bl ip+offset" */ | ||
157 | return 0x48000001 | (offset & 0x03fffffc); | ||
158 | } | ||
159 | |||
160 | #ifdef CONFIG_PPC64 | ||
161 | static int | ||
162 | __ftrace_make_nop(struct module *mod, | ||
163 | struct dyn_ftrace *rec, unsigned long addr) | ||
164 | { | ||
165 | unsigned char replaced[MCOUNT_INSN_SIZE * 2]; | ||
166 | unsigned int *op = (unsigned *)&replaced; | ||
167 | unsigned char jmp[8]; | ||
168 | unsigned long *ptr = (unsigned long *)&jmp; | ||
169 | unsigned long ip = rec->ip; | ||
170 | unsigned long tramp; | ||
171 | int offset; | ||
172 | |||
173 | /* read where this goes */ | ||
174 | if (probe_kernel_read(replaced, (void *)ip, MCOUNT_INSN_SIZE)) | ||
175 | return -EFAULT; | ||
176 | |||
177 | /* Make sure that that this is still a 24bit jump */ | ||
178 | if (!is_bl_op(*op)) { | ||
179 | printk(KERN_ERR "Not expected bl: opcode is %x\n", *op); | ||
180 | return -EINVAL; | ||
181 | } | ||
182 | |||
183 | /* lets find where the pointer goes */ | ||
184 | tramp = find_bl_target(ip, *op); | ||
185 | |||
186 | /* | ||
187 | * On PPC64 the trampoline looks like: | ||
188 | * 0x3d, 0x82, 0x00, 0x00, addis r12,r2, <high> | ||
189 | * 0x39, 0x8c, 0x00, 0x00, addi r12,r12, <low> | ||
190 | * Where the bytes 2,3,6 and 7 make up the 32bit offset | ||
191 | * to the TOC that holds the pointer. | ||
192 | * to jump to. | ||
193 | * 0xf8, 0x41, 0x00, 0x28, std r2,40(r1) | ||
194 | * 0xe9, 0x6c, 0x00, 0x20, ld r11,32(r12) | ||
195 | * The actually address is 32 bytes from the offset | ||
196 | * into the TOC. | ||
197 | * 0xe8, 0x4c, 0x00, 0x28, ld r2,40(r12) | ||
198 | */ | ||
199 | |||
200 | DEBUGP("ip:%lx jumps to %lx r2: %lx", ip, tramp, mod->arch.toc); | ||
201 | |||
202 | /* Find where the trampoline jumps to */ | ||
203 | if (probe_kernel_read(jmp, (void *)tramp, 8)) { | ||
204 | printk(KERN_ERR "Failed to read %lx\n", tramp); | ||
205 | return -EFAULT; | ||
206 | } | ||
207 | |||
208 | DEBUGP(" %08x %08x", | ||
209 | (unsigned)(*ptr >> 32), | ||
210 | (unsigned)*ptr); | ||
211 | |||
212 | offset = (unsigned)jmp[2] << 24 | | ||
213 | (unsigned)jmp[3] << 16 | | ||
214 | (unsigned)jmp[6] << 8 | | ||
215 | (unsigned)jmp[7]; | ||
216 | |||
217 | DEBUGP(" %x ", offset); | ||
218 | |||
219 | /* get the address this jumps too */ | ||
220 | tramp = mod->arch.toc + offset + 32; | ||
221 | DEBUGP("toc: %lx", tramp); | ||
222 | |||
223 | if (probe_kernel_read(jmp, (void *)tramp, 8)) { | ||
224 | printk(KERN_ERR "Failed to read %lx\n", tramp); | ||
225 | return -EFAULT; | ||
226 | } | ||
227 | |||
228 | DEBUGP(" %08x %08x\n", | ||
229 | (unsigned)(*ptr >> 32), | ||
230 | (unsigned)*ptr); | ||
231 | |||
232 | /* This should match what was called */ | ||
233 | if (*ptr != GET_ADDR(addr)) { | ||
234 | printk(KERN_ERR "addr does not match %lx\n", *ptr); | ||
235 | return -EINVAL; | ||
236 | } | ||
237 | |||
238 | /* | ||
239 | * We want to nop the line, but the next line is | ||
240 | * 0xe8, 0x41, 0x00, 0x28 ld r2,40(r1) | ||
241 | * This needs to be turned to a nop too. | ||
242 | */ | ||
243 | if (probe_kernel_read(replaced, (void *)(ip+4), MCOUNT_INSN_SIZE)) | ||
244 | return -EFAULT; | ||
245 | |||
246 | if (*op != 0xe8410028) { | ||
247 | printk(KERN_ERR "Next line is not ld! (%08x)\n", *op); | ||
248 | return -EINVAL; | ||
249 | } | ||
250 | |||
251 | /* | ||
252 | * Milton Miller pointed out that we can not blindly do nops. | ||
253 | * If a task was preempted when calling a trace function, | ||
254 | * the nops will remove the way to restore the TOC in r2 | ||
255 | * and the r2 TOC will get corrupted. | ||
256 | */ | ||
257 | |||
258 | /* | ||
259 | * Replace: | ||
260 | * bl <tramp> <==== will be replaced with "b 1f" | ||
261 | * ld r2,40(r1) | ||
262 | * 1: | ||
263 | */ | ||
264 | op[0] = 0x48000008; /* b +8 */ | ||
265 | |||
266 | if (probe_kernel_write((void *)ip, replaced, MCOUNT_INSN_SIZE)) | ||
267 | return -EPERM; | ||
268 | |||
269 | return 0; | ||
270 | } | ||
271 | |||
272 | #else /* !PPC64 */ | ||
273 | static int | ||
274 | __ftrace_make_nop(struct module *mod, | ||
275 | struct dyn_ftrace *rec, unsigned long addr) | ||
276 | { | ||
277 | /* Ignore modules for PPC32 (for now) */ | ||
278 | return 0; | ||
279 | } | ||
280 | #endif /* PPC64 */ | ||
281 | |||
122 | int ftrace_make_nop(struct module *mod, | 282 | int ftrace_make_nop(struct module *mod, |
123 | struct dyn_ftrace *rec, unsigned long addr) | 283 | struct dyn_ftrace *rec, unsigned long addr) |
124 | { | 284 | { |
125 | unsigned char *old, *new; | 285 | unsigned char *old, *new; |
286 | unsigned long ip = rec->ip; | ||
126 | 287 | ||
127 | /* | 288 | /* |
128 | * If the calling address is more that 24 bits away, | 289 | * If the calling address is more that 24 bits away, |
129 | * then we had to use a trampoline to make the call. | 290 | * then we had to use a trampoline to make the call. |
130 | * Otherwise just update the call site. | 291 | * Otherwise just update the call site. |
131 | */ | 292 | */ |
132 | if (test_24bit_addr(rec->ip, addr)) { | 293 | if (test_24bit_addr(ip, addr)) { |
133 | /* within range */ | 294 | /* within range */ |
134 | old = ftrace_call_replace(rec->ip, addr); | 295 | old = ftrace_call_replace(ip, addr); |
135 | new = ftrace_nop_replace(); | 296 | new = ftrace_nop_replace(); |
136 | return ftrace_modify_code(rec->ip, old, new); | 297 | return ftrace_modify_code(ip, old, new); |
298 | } | ||
299 | |||
300 | #ifdef CONFIG_PPC64 | ||
301 | /* | ||
302 | * Out of range jumps are called from modules. | ||
303 | * We should either already have a pointer to the module | ||
304 | * or it has been passed in. | ||
305 | */ | ||
306 | if (!rec->arch.mod) { | ||
307 | if (!mod) { | ||
308 | printk(KERN_ERR "No module loaded addr=%lx\n", | ||
309 | addr); | ||
310 | return -EFAULT; | ||
311 | } | ||
312 | rec->arch.mod = mod; | ||
313 | } else if (mod) { | ||
314 | if (mod != rec->arch.mod) { | ||
315 | printk(KERN_ERR | ||
316 | "Record mod %p not equal to passed in mod %p\n", | ||
317 | rec->arch.mod, mod); | ||
318 | return -EINVAL; | ||
319 | } | ||
320 | /* nothing to do if mod == rec->arch.mod */ | ||
321 | } else | ||
322 | mod = rec->arch.mod; | ||
323 | #endif /* CONFIG_PPC64 */ | ||
324 | |||
325 | return __ftrace_make_nop(mod, rec, addr); | ||
326 | |||
327 | } | ||
328 | |||
329 | #ifdef CONFIG_PPC64 | ||
330 | static int | ||
331 | __ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) | ||
332 | { | ||
333 | unsigned char replaced[MCOUNT_INSN_SIZE * 2]; | ||
334 | unsigned int *op = (unsigned *)&replaced; | ||
335 | unsigned long ip = rec->ip; | ||
336 | unsigned long offset; | ||
337 | |||
338 | /* read where this goes */ | ||
339 | if (probe_kernel_read(replaced, (void *)ip, MCOUNT_INSN_SIZE * 2)) | ||
340 | return -EFAULT; | ||
341 | |||
342 | /* | ||
343 | * It should be pointing to two nops or | ||
344 | * b +8; ld r2,40(r1) | ||
345 | */ | ||
346 | if (((op[0] != 0x48000008) || (op[1] != 0xe8410028)) && | ||
347 | ((op[0] != PPC_NOP_INSTR) || (op[1] != PPC_NOP_INSTR))) { | ||
348 | printk(KERN_ERR "Expected NOPs but have %x %x\n", op[0], op[1]); | ||
349 | return -EINVAL; | ||
350 | } | ||
351 | |||
352 | /* If we never set up a trampoline to ftrace_caller, then bail */ | ||
353 | if (!rec->arch.mod->arch.tramp) { | ||
354 | printk(KERN_ERR "No ftrace trampoline\n"); | ||
355 | return -EINVAL; | ||
356 | } | ||
357 | |||
358 | /* now calculate a jump to the ftrace caller trampoline */ | ||
359 | offset = rec->arch.mod->arch.tramp - ip; | ||
360 | |||
361 | if (test_offset(offset)) { | ||
362 | printk(KERN_ERR "REL24 %li out of range!\n", | ||
363 | (long int)offset); | ||
364 | return -EINVAL; | ||
137 | } | 365 | } |
138 | 366 | ||
367 | /* Set to "bl addr" */ | ||
368 | op[0] = branch_offset(offset); | ||
369 | /* ld r2,40(r1) */ | ||
370 | op[1] = 0xe8410028; | ||
371 | |||
372 | DEBUGP("write to %lx\n", rec->ip); | ||
373 | |||
374 | if (probe_kernel_write((void *)ip, replaced, MCOUNT_INSN_SIZE * 2)) | ||
375 | return -EPERM; | ||
376 | |||
139 | return 0; | 377 | return 0; |
140 | } | 378 | } |
379 | #else | ||
380 | static int | ||
381 | __ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) | ||
382 | { | ||
383 | /* PPC32 ignores modules for now */ | ||
384 | return 0; | ||
385 | } | ||
386 | #endif /* CONFIG_PPC64 */ | ||
141 | 387 | ||
142 | int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) | 388 | int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr) |
143 | { | 389 | { |
144 | unsigned char *old, *new; | 390 | unsigned char *old, *new; |
391 | unsigned long ip = rec->ip; | ||
145 | 392 | ||
146 | /* | 393 | /* |
147 | * If the calling address is more that 24 bits away, | 394 | * If the calling address is more that 24 bits away, |
148 | * then we had to use a trampoline to make the call. | 395 | * then we had to use a trampoline to make the call. |
149 | * Otherwise just update the call site. | 396 | * Otherwise just update the call site. |
150 | */ | 397 | */ |
151 | if (test_24bit_addr(rec->ip, addr)) { | 398 | if (test_24bit_addr(ip, addr)) { |
152 | /* within range */ | 399 | /* within range */ |
153 | old = ftrace_nop_replace(); | 400 | old = ftrace_nop_replace(); |
154 | new = ftrace_call_replace(rec->ip, addr); | 401 | new = ftrace_call_replace(ip, addr); |
155 | return ftrace_modify_code(rec->ip, old, new); | 402 | return ftrace_modify_code(ip, old, new); |
156 | } | 403 | } |
157 | 404 | ||
158 | return 0; | 405 | #ifdef CONFIG_PPC64 |
406 | /* | ||
407 | * Out of range jumps are called from modules. | ||
408 | * Being that we are converting from nop, it had better | ||
409 | * already have a module defined. | ||
410 | */ | ||
411 | if (!rec->arch.mod) { | ||
412 | printk(KERN_ERR "No module loaded\n"); | ||
413 | return -EINVAL; | ||
414 | } | ||
415 | #endif | ||
416 | |||
417 | return __ftrace_make_call(rec, addr); | ||
159 | } | 418 | } |
160 | 419 | ||
161 | int ftrace_update_ftrace_func(ftrace_func_t func) | 420 | int ftrace_update_ftrace_func(ftrace_func_t func) |
@@ -180,4 +439,3 @@ int __init ftrace_dyn_arch_init(void *data) | |||
180 | 439 | ||
181 | return 0; | 440 | return 0; |
182 | } | 441 | } |
183 | |||
diff --git a/arch/powerpc/kernel/module_64.c b/arch/powerpc/kernel/module_64.c index 1af2377e4992..8992b031a7b6 100644 --- a/arch/powerpc/kernel/module_64.c +++ b/arch/powerpc/kernel/module_64.c | |||
@@ -20,6 +20,7 @@ | |||
20 | #include <linux/moduleloader.h> | 20 | #include <linux/moduleloader.h> |
21 | #include <linux/err.h> | 21 | #include <linux/err.h> |
22 | #include <linux/vmalloc.h> | 22 | #include <linux/vmalloc.h> |
23 | #include <linux/ftrace.h> | ||
23 | #include <linux/bug.h> | 24 | #include <linux/bug.h> |
24 | #include <asm/module.h> | 25 | #include <asm/module.h> |
25 | #include <asm/firmware.h> | 26 | #include <asm/firmware.h> |
@@ -163,6 +164,11 @@ static unsigned long get_stubs_size(const Elf64_Ehdr *hdr, | |||
163 | } | 164 | } |
164 | } | 165 | } |
165 | 166 | ||
167 | #ifdef CONFIG_DYNAMIC_FTRACE | ||
168 | /* make the trampoline to the ftrace_caller */ | ||
169 | relocs++; | ||
170 | #endif | ||
171 | |||
166 | DEBUGP("Looks like a total of %lu stubs, max\n", relocs); | 172 | DEBUGP("Looks like a total of %lu stubs, max\n", relocs); |
167 | return relocs * sizeof(struct ppc64_stub_entry); | 173 | return relocs * sizeof(struct ppc64_stub_entry); |
168 | } | 174 | } |
@@ -441,5 +447,12 @@ int apply_relocate_add(Elf64_Shdr *sechdrs, | |||
441 | } | 447 | } |
442 | } | 448 | } |
443 | 449 | ||
450 | #ifdef CONFIG_DYNAMIC_FTRACE | ||
451 | me->arch.toc = my_r2(sechdrs, me); | ||
452 | me->arch.tramp = stub_for_addr(sechdrs, | ||
453 | (unsigned long)ftrace_caller, | ||
454 | me); | ||
455 | #endif | ||
456 | |||
444 | return 0; | 457 | return 0; |
445 | } | 458 | } |