Merge branches 'smbus' and 'fujitsu-fix' into release-2.6.27
[linux-2.6] / arch / arm / kernel / ftrace.c
1 /*
2  * Dynamic function tracing support.
3  *
4  * Copyright (C) 2008 Abhishek Sagar <sagar.abhishek@gmail.com>
5  *
6  * For licencing details, see COPYING.
7  *
8  * Defines low-level handling of mcount calls when the kernel
9  * is compiled with the -pg flag. When using dynamic ftrace, the
10  * mcount call-sites get patched lazily with NOP till they are
11  * enabled. All code mutation routines here take effect atomically.
12  */
13
14 #include <linux/ftrace.h>
15
16 #include <asm/cacheflush.h>
17 #include <asm/ftrace.h>
18
19 #define PC_OFFSET      8
20 #define BL_OPCODE      0xeb000000
21 #define BL_OFFSET_MASK 0x00ffffff
22
23 static unsigned long bl_insn;
24 static const unsigned long NOP = 0xe1a00000; /* mov r0, r0 */
25
26 unsigned char *ftrace_nop_replace(void)
27 {
28         return (char *)&NOP;
29 }
30
31 /* construct a branch (BL) instruction to addr */
32 unsigned char *ftrace_call_replace(unsigned long pc, unsigned long addr)
33 {
34         long offset;
35
36         offset = (long)addr - (long)(pc + PC_OFFSET);
37         if (unlikely(offset < -33554432 || offset > 33554428)) {
38                 /* Can't generate branches that far (from ARM ARM). Ftrace
39                  * doesn't generate branches outside of kernel text.
40                  */
41                 WARN_ON_ONCE(1);
42                 return NULL;
43         }
44         offset = (offset >> 2) & BL_OFFSET_MASK;
45         bl_insn = BL_OPCODE | offset;
46         return (unsigned char *)&bl_insn;
47 }
48
49 int ftrace_modify_code(unsigned long pc, unsigned char *old_code,
50                        unsigned char *new_code)
51 {
52         unsigned long err = 0, replaced = 0, old, new;
53
54         old = *(unsigned long *)old_code;
55         new = *(unsigned long *)new_code;
56
57         __asm__ __volatile__ (
58                 "1:  ldr    %1, [%2]  \n"
59                 "    cmp    %1, %4    \n"
60                 "2:  streq  %3, [%2]  \n"
61                 "    cmpne  %1, %3    \n"
62                 "    movne  %0, #2    \n"
63                 "3:\n"
64
65                 ".section .fixup, \"ax\"\n"
66                 "4:  mov  %0, #1  \n"
67                 "    b    3b      \n"
68                 ".previous\n"
69
70                 ".section __ex_table, \"a\"\n"
71                 "    .long 1b, 4b \n"
72                 "    .long 2b, 4b \n"
73                 ".previous\n"
74
75                 : "=r"(err), "=r"(replaced)
76                 : "r"(pc), "r"(new), "r"(old), "0"(err), "1"(replaced)
77                 : "memory");
78
79         if (!err && (replaced == old))
80                 flush_icache_range(pc, pc + MCOUNT_INSN_SIZE);
81
82         return err;
83 }
84
85 int ftrace_update_ftrace_func(ftrace_func_t func)
86 {
87         int ret;
88         unsigned long pc, old;
89         unsigned char *new;
90
91         pc = (unsigned long)&ftrace_call;
92         memcpy(&old, &ftrace_call, MCOUNT_INSN_SIZE);
93         new = ftrace_call_replace(pc, (unsigned long)func);
94         ret = ftrace_modify_code(pc, (unsigned char *)&old, new);
95         return ret;
96 }
97
98 int ftrace_mcount_set(unsigned long *data)
99 {
100         unsigned long pc, old;
101         unsigned long *addr = data;
102         unsigned char *new;
103
104         pc = (unsigned long)&mcount_call;
105         memcpy(&old, &mcount_call, MCOUNT_INSN_SIZE);
106         new = ftrace_call_replace(pc, *addr);
107         *addr = ftrace_modify_code(pc, (unsigned char *)&old, new);
108         return 0;
109 }
110
111 /* run from kstop_machine */
112 int __init ftrace_dyn_arch_init(void *data)
113 {
114         ftrace_mcount_set(data);
115         return 0;
116 }