x86, bts, ftrace: a BTS ftrace plug-in prototype
[linux-2.6.git] / kernel / trace / trace_bts.c
1 /*
2  * BTS tracer
3  *
4  * Copyright (C) 2008 Markus Metzger <markus.t.metzger@gmail.com>
5  *
6  */
7
8 #include <linux/module.h>
9 #include <linux/fs.h>
10 #include <linux/debugfs.h>
11 #include <linux/ftrace.h>
12 #include <linux/kallsyms.h>
13
14 #include <asm/ds.h>
15
16 #include "trace.h"
17
18
19 #define SIZEOF_BTS (1 << 13)
20
21 static DEFINE_PER_CPU(struct bts_tracer *, tracer);
22 static DEFINE_PER_CPU(unsigned char[SIZEOF_BTS], buffer);
23
24 #define this_tracer per_cpu(tracer, smp_processor_id())
25 #define this_buffer per_cpu(buffer, smp_processor_id())
26
27
28 /*
29  * Information to interpret a BTS record.
30  * This will go into an in-kernel BTS interface.
31  */
32 static unsigned char sizeof_field;
33 static unsigned long debugctl_mask;
34
35 #define sizeof_bts (3 * sizeof_field)
36
37 static void bts_trace_cpuinit(struct cpuinfo_x86 *c)
38 {
39         switch (c->x86) {
40         case 0x6:
41                 switch (c->x86_model) {
42                 case 0x0 ... 0xC:
43                         break;
44                 case 0xD:
45                 case 0xE: /* Pentium M */
46                         sizeof_field = sizeof(long);
47                         debugctl_mask = (1<<6)|(1<<7);
48                         break;
49                 default:
50                         sizeof_field = 8;
51                         debugctl_mask = (1<<6)|(1<<7);
52                         break;
53                 }
54                 break;
55         case 0xF:
56                 switch (c->x86_model) {
57                 case 0x0:
58                 case 0x1:
59                 case 0x2: /* Netburst */
60                         sizeof_field = sizeof(long);
61                         debugctl_mask = (1<<2)|(1<<3);
62                         break;
63                 default:
64                         /* sorry, don't know about them */
65                         break;
66                 }
67                 break;
68         default:
69                 /* sorry, don't know about them */
70                 break;
71         }
72 }
73
74 static inline void bts_enable(void)
75 {
76         unsigned long debugctl;
77
78         rdmsrl(MSR_IA32_DEBUGCTLMSR, debugctl);
79         wrmsrl(MSR_IA32_DEBUGCTLMSR, debugctl | debugctl_mask);
80 }
81
82 static inline void bts_disable(void)
83 {
84         unsigned long debugctl;
85
86         rdmsrl(MSR_IA32_DEBUGCTLMSR, debugctl);
87         wrmsrl(MSR_IA32_DEBUGCTLMSR, debugctl & ~debugctl_mask);
88 }
89
90 static void bts_trace_reset(struct trace_array *tr)
91 {
92         int cpu;
93
94         tr->time_start = ftrace_now(tr->cpu);
95
96         for_each_online_cpu(cpu)
97                 tracing_reset(tr, cpu);
98 }
99
100 static void bts_trace_start_cpu(void *arg)
101 {
102         this_tracer =
103                 ds_request_bts(/* task = */ NULL, this_buffer, SIZEOF_BTS,
104                                /* ovfl = */ NULL, /* th = */ (size_t)-1);
105         if (IS_ERR(this_tracer)) {
106                 this_tracer = NULL;
107                 return;
108         }
109
110         bts_enable();
111 }
112
113 static void bts_trace_start(struct trace_array *tr)
114 {
115         int cpu;
116
117         bts_trace_reset(tr);
118
119         for_each_cpu_mask(cpu, cpu_possible_map)
120                 smp_call_function_single(cpu, bts_trace_start_cpu, NULL, 1);
121 }
122
123 static void bts_trace_stop_cpu(void *arg)
124 {
125         if (this_tracer) {
126                 bts_disable();
127
128                 ds_release_bts(this_tracer);
129                 this_tracer = NULL;
130         }
131 }
132
133 static void bts_trace_stop(struct trace_array *tr)
134 {
135         int cpu;
136
137         for_each_cpu_mask(cpu, cpu_possible_map)
138                 smp_call_function_single(cpu, bts_trace_stop_cpu, NULL, 1);
139 }
140
141 static int bts_trace_init(struct trace_array *tr)
142 {
143         bts_trace_cpuinit(&boot_cpu_data);
144         bts_trace_reset(tr);
145         bts_trace_start(tr);
146
147         return 0;
148 }
149
150 static void bts_trace_print_header(struct seq_file *m)
151 {
152 #ifdef __i386__
153         seq_puts(m, "# CPU#    FROM           TO     FUNCTION\n");
154         seq_puts(m, "#  |       |             |         |\n");
155 #else
156         seq_puts(m,
157                  "# CPU#        FROM                   TO         FUNCTION\n");
158         seq_puts(m,
159                  "#  |           |                     |             |\n");
160 #endif
161 }
162
163 static enum print_line_t bts_trace_print_line(struct trace_iterator *iter)
164 {
165         struct trace_entry *entry = iter->ent;
166         struct trace_seq *seq = &iter->seq;
167         struct bts_entry *it;
168
169         trace_assign_type(it, entry);
170
171         if (entry->type == TRACE_BTS) {
172                 int ret;
173 #ifdef CONFIG_KALLSYMS
174                 char function[KSYM_SYMBOL_LEN];
175                 sprint_symbol(function, it->from);
176 #else
177                 char *function = "<unknown>";
178 #endif
179
180                 ret = trace_seq_printf(seq, "%4d  0x%lx -> 0x%lx [%s]\n",
181                                        entry->cpu, it->from, it->to, function);
182                 if (!ret)
183                         return TRACE_TYPE_PARTIAL_LINE;;
184                 return TRACE_TYPE_HANDLED;
185         }
186         return TRACE_TYPE_UNHANDLED;
187 }
188
189 void trace_bts(struct trace_array *tr, unsigned long from, unsigned long to)
190 {
191         struct ring_buffer_event *event;
192         struct bts_entry *entry;
193         unsigned long irq;
194
195         event = ring_buffer_lock_reserve(tr->buffer, sizeof(*entry), &irq);
196         if (!event)
197                 return;
198         entry   = ring_buffer_event_data(event);
199         tracing_generic_entry_update(&entry->ent, 0, from);
200         entry->ent.type = TRACE_BTS;
201         entry->ent.cpu = smp_processor_id();
202         entry->from = from;
203         entry->to   = to;
204         ring_buffer_unlock_commit(tr->buffer, event, irq);
205 }
206
207 static void trace_bts_at(struct trace_array *tr, size_t index)
208 {
209         const void *raw = NULL;
210         unsigned long from, to;
211         int err;
212
213         err = ds_access_bts(this_tracer, index, &raw);
214         if (err < 0)
215                 return;
216
217         from = *(const unsigned long *)raw;
218         to = *(const unsigned long *)((const char *)raw + sizeof_field);
219
220         trace_bts(tr, from, to);
221 }
222
223 static void trace_bts_cpu(void *arg)
224 {
225         struct trace_array *tr = (struct trace_array *) arg;
226         size_t index = 0, end = 0, i;
227         int err;
228
229         if (!this_tracer)
230                 return;
231
232         bts_disable();
233
234         err = ds_get_bts_index(this_tracer, &index);
235         if (err < 0)
236                 goto out;
237
238         err = ds_get_bts_end(this_tracer, &end);
239         if (err < 0)
240                 goto out;
241
242         for (i = index; i < end; i++)
243                 trace_bts_at(tr, i);
244
245         for (i = 0; i < index; i++)
246                 trace_bts_at(tr, i);
247
248 out:
249         bts_enable();
250 }
251
252 static void trace_bts_prepare(struct trace_iterator *iter)
253 {
254         int cpu;
255
256         for_each_cpu_mask(cpu, cpu_possible_map)
257                 smp_call_function_single(cpu, trace_bts_cpu, iter->tr, 1);
258 }
259
260 struct tracer bts_tracer __read_mostly =
261 {
262         .name           = "bts",
263         .init           = bts_trace_init,
264         .reset          = bts_trace_stop,
265         .print_header   = bts_trace_print_header,
266         .print_line     = bts_trace_print_line,
267         .start          = bts_trace_start,
268         .stop           = bts_trace_stop,
269         .open           = trace_bts_prepare
270 };
271
272 __init static int init_bts_trace(void)
273 {
274         return register_tracer(&bts_tracer);
275 }
276 device_initcall(init_bts_trace);