]>
Commit | Line | Data |
---|---|---|
8a327f6d MM |
1 | /* |
2 | * Debug Store support - selftest | |
3 | * | |
4 | * | |
5 | * Copyright (C) 2009 Intel Corporation. | |
6 | * Markus Metzger <markus.t.metzger@intel.com>, 2009 | |
7 | */ | |
8 | ||
9 | #include "ds_selftest.h" | |
10 | ||
11 | #include <linux/kernel.h> | |
12 | #include <linux/string.h> | |
de79f54f | 13 | #include <linux/smp.h> |
01f6569e | 14 | #include <linux/cpu.h> |
8a327f6d MM |
15 | |
16 | #include <asm/ds.h> | |
17 | ||
18 | ||
150f5164 MM |
19 | #define BUFFER_SIZE 521 /* Intentionally chose an odd size. */ |
20 | #define SMALL_BUFFER_SIZE 24 /* A single bts entry. */ | |
8a327f6d | 21 | |
01f6569e MM |
22 | struct ds_selftest_bts_conf { |
23 | struct bts_tracer *tracer; | |
24 | int error; | |
25 | int (*suspend)(struct bts_tracer *); | |
26 | int (*resume)(struct bts_tracer *); | |
27 | }; | |
28 | ||
8a327f6d MM |
29 | static int ds_selftest_bts_consistency(const struct bts_trace *trace) |
30 | { | |
31 | int error = 0; | |
32 | ||
33 | if (!trace) { | |
34 | printk(KERN_CONT "failed to access trace..."); | |
35 | /* Bail out. Other tests are pointless. */ | |
36 | return -1; | |
37 | } | |
38 | ||
39 | if (!trace->read) { | |
40 | printk(KERN_CONT "bts read not available..."); | |
41 | error = -1; | |
42 | } | |
43 | ||
44 | /* Do some sanity checks on the trace configuration. */ | |
45 | if (!trace->ds.n) { | |
46 | printk(KERN_CONT "empty bts buffer..."); | |
47 | error = -1; | |
48 | } | |
49 | if (!trace->ds.size) { | |
50 | printk(KERN_CONT "bad bts trace setup..."); | |
51 | error = -1; | |
52 | } | |
53 | if (trace->ds.end != | |
54 | (char *)trace->ds.begin + (trace->ds.n * trace->ds.size)) { | |
55 | printk(KERN_CONT "bad bts buffer setup..."); | |
56 | error = -1; | |
57 | } | |
84f20113 MM |
58 | /* |
59 | * We allow top in [begin; end], since its not clear when the | |
60 | * overflow adjustment happens: after the increment or before the | |
61 | * write. | |
62 | */ | |
8a327f6d | 63 | if ((trace->ds.top < trace->ds.begin) || |
84f20113 | 64 | (trace->ds.end < trace->ds.top)) { |
8a327f6d MM |
65 | printk(KERN_CONT "bts top out of bounds..."); |
66 | error = -1; | |
67 | } | |
68 | ||
69 | return error; | |
70 | } | |
71 | ||
72 | static int ds_selftest_bts_read(struct bts_tracer *tracer, | |
73 | const struct bts_trace *trace, | |
74 | const void *from, const void *to) | |
75 | { | |
76 | const unsigned char *at; | |
77 | ||
78 | /* | |
79 | * Check a few things which do not belong to this test. | |
80 | * They should be covered by other tests. | |
81 | */ | |
82 | if (!trace) | |
83 | return -1; | |
84 | ||
85 | if (!trace->read) | |
86 | return -1; | |
87 | ||
88 | if (to < from) | |
89 | return -1; | |
90 | ||
91 | if (from < trace->ds.begin) | |
92 | return -1; | |
93 | ||
94 | if (trace->ds.end < to) | |
95 | return -1; | |
96 | ||
97 | if (!trace->ds.size) | |
98 | return -1; | |
99 | ||
100 | /* Now to the test itself. */ | |
101 | for (at = from; (void *)at < to; at += trace->ds.size) { | |
102 | struct bts_struct bts; | |
353afeea | 103 | unsigned long index; |
8a327f6d MM |
104 | int error; |
105 | ||
106 | if (((void *)at - trace->ds.begin) % trace->ds.size) { | |
107 | printk(KERN_CONT | |
108 | "read from non-integer index..."); | |
109 | return -1; | |
110 | } | |
111 | index = ((void *)at - trace->ds.begin) / trace->ds.size; | |
112 | ||
113 | memset(&bts, 0, sizeof(bts)); | |
114 | error = trace->read(tracer, at, &bts); | |
115 | if (error < 0) { | |
116 | printk(KERN_CONT | |
117 | "error reading bts trace at [%lu] (0x%p)...", | |
118 | index, at); | |
119 | return error; | |
120 | } | |
121 | ||
122 | switch (bts.qualifier) { | |
123 | case BTS_BRANCH: | |
124 | break; | |
125 | default: | |
126 | printk(KERN_CONT | |
127 | "unexpected bts entry %llu at [%lu] (0x%p)...", | |
128 | bts.qualifier, index, at); | |
129 | return -1; | |
130 | } | |
131 | } | |
132 | ||
133 | return 0; | |
134 | } | |
135 | ||
01f6569e | 136 | static void ds_selftest_bts_cpu(void *arg) |
8a327f6d | 137 | { |
01f6569e | 138 | struct ds_selftest_bts_conf *conf = arg; |
8a327f6d | 139 | const struct bts_trace *trace; |
8a327f6d | 140 | void *top; |
8a327f6d | 141 | |
01f6569e MM |
142 | if (IS_ERR(conf->tracer)) { |
143 | conf->error = PTR_ERR(conf->tracer); | |
144 | conf->tracer = NULL; | |
8a327f6d MM |
145 | |
146 | printk(KERN_CONT | |
01f6569e MM |
147 | "initialization failed (err: %d)...", conf->error); |
148 | return; | |
8a327f6d MM |
149 | } |
150 | ||
01f6569e MM |
151 | /* We should meanwhile have enough trace. */ |
152 | conf->error = conf->suspend(conf->tracer); | |
153 | if (conf->error < 0) | |
154 | return; | |
8a327f6d MM |
155 | |
156 | /* Let's see if we can access the trace. */ | |
01f6569e | 157 | trace = ds_read_bts(conf->tracer); |
8a327f6d | 158 | |
01f6569e MM |
159 | conf->error = ds_selftest_bts_consistency(trace); |
160 | if (conf->error < 0) | |
161 | return; | |
8a327f6d MM |
162 | |
163 | /* If everything went well, we should have a few trace entries. */ | |
164 | if (trace->ds.top == trace->ds.begin) { | |
165 | /* | |
166 | * It is possible but highly unlikely that we got a | |
167 | * buffer overflow and end up at exactly the same | |
168 | * position we started from. | |
169 | * Let's issue a warning, but continue. | |
170 | */ | |
171 | printk(KERN_CONT "no trace/overflow..."); | |
172 | } | |
173 | ||
174 | /* Let's try to read the trace we collected. */ | |
01f6569e MM |
175 | conf->error = |
176 | ds_selftest_bts_read(conf->tracer, trace, | |
8a327f6d | 177 | trace->ds.begin, trace->ds.top); |
01f6569e MM |
178 | if (conf->error < 0) |
179 | return; | |
8a327f6d MM |
180 | |
181 | /* | |
182 | * Let's read the trace again. | |
183 | * Since we suspended tracing, we should get the same result. | |
184 | */ | |
185 | top = trace->ds.top; | |
186 | ||
01f6569e MM |
187 | trace = ds_read_bts(conf->tracer); |
188 | conf->error = ds_selftest_bts_consistency(trace); | |
189 | if (conf->error < 0) | |
190 | return; | |
8a327f6d MM |
191 | |
192 | if (top != trace->ds.top) { | |
193 | printk(KERN_CONT "suspend not working..."); | |
01f6569e MM |
194 | conf->error = -1; |
195 | return; | |
8a327f6d MM |
196 | } |
197 | ||
198 | /* Let's collect some more trace - see if resume is working. */ | |
01f6569e MM |
199 | conf->error = conf->resume(conf->tracer); |
200 | if (conf->error < 0) | |
201 | return; | |
202 | ||
203 | conf->error = conf->suspend(conf->tracer); | |
204 | if (conf->error < 0) | |
205 | return; | |
8a327f6d | 206 | |
01f6569e | 207 | trace = ds_read_bts(conf->tracer); |
8a327f6d | 208 | |
01f6569e MM |
209 | conf->error = ds_selftest_bts_consistency(trace); |
210 | if (conf->error < 0) | |
211 | return; | |
8a327f6d MM |
212 | |
213 | if (trace->ds.top == top) { | |
214 | /* | |
215 | * It is possible but highly unlikely that we got a | |
216 | * buffer overflow and end up at exactly the same | |
217 | * position we started from. | |
218 | * Let's issue a warning and check the full trace. | |
219 | */ | |
220 | printk(KERN_CONT | |
221 | "no resume progress/overflow..."); | |
222 | ||
01f6569e MM |
223 | conf->error = |
224 | ds_selftest_bts_read(conf->tracer, trace, | |
8a327f6d MM |
225 | trace->ds.begin, trace->ds.end); |
226 | } else if (trace->ds.top < top) { | |
227 | /* | |
228 | * We had a buffer overflow - the entire buffer should | |
229 | * contain trace records. | |
230 | */ | |
01f6569e MM |
231 | conf->error = |
232 | ds_selftest_bts_read(conf->tracer, trace, | |
8a327f6d MM |
233 | trace->ds.begin, trace->ds.end); |
234 | } else { | |
235 | /* | |
236 | * It is quite likely that the buffer did not overflow. | |
237 | * Let's just check the delta trace. | |
238 | */ | |
01f6569e MM |
239 | conf->error = |
240 | ds_selftest_bts_read(conf->tracer, trace, top, | |
241 | trace->ds.top); | |
8a327f6d | 242 | } |
01f6569e MM |
243 | if (conf->error < 0) |
244 | return; | |
8a327f6d | 245 | |
01f6569e MM |
246 | conf->error = 0; |
247 | } | |
8a327f6d | 248 | |
01f6569e MM |
249 | static int ds_suspend_bts_wrap(struct bts_tracer *tracer) |
250 | { | |
251 | ds_suspend_bts(tracer); | |
252 | return 0; | |
253 | } | |
254 | ||
255 | static int ds_resume_bts_wrap(struct bts_tracer *tracer) | |
256 | { | |
257 | ds_resume_bts(tracer); | |
258 | return 0; | |
259 | } | |
8a327f6d | 260 | |
01f6569e MM |
261 | static void ds_release_bts_noirq_wrap(void *tracer) |
262 | { | |
263 | (void)ds_release_bts_noirq(tracer); | |
264 | } | |
8a327f6d | 265 | |
01f6569e MM |
266 | static int ds_selftest_bts_bad_release_noirq(int cpu, |
267 | struct bts_tracer *tracer) | |
268 | { | |
269 | int error = -EPERM; | |
270 | ||
271 | /* Try to release the tracer on the wrong cpu. */ | |
272 | get_cpu(); | |
273 | if (cpu != smp_processor_id()) { | |
274 | error = ds_release_bts_noirq(tracer); | |
275 | if (error != -EPERM) | |
276 | printk(KERN_CONT "release on wrong cpu..."); | |
277 | } | |
278 | put_cpu(); | |
279 | ||
280 | return error ? 0 : -1; | |
281 | } | |
282 | ||
3a68eef9 MM |
283 | static int ds_selftest_bts_bad_request_cpu(int cpu, void *buffer) |
284 | { | |
285 | struct bts_tracer *tracer; | |
286 | int error; | |
287 | ||
288 | /* Try to request cpu tracing while task tracing is active. */ | |
289 | tracer = ds_request_bts_cpu(cpu, buffer, BUFFER_SIZE, NULL, | |
290 | (size_t)-1, BTS_KERNEL); | |
291 | error = PTR_ERR(tracer); | |
292 | if (!IS_ERR(tracer)) { | |
293 | ds_release_bts(tracer); | |
294 | error = 0; | |
295 | } | |
296 | ||
297 | if (error != -EPERM) | |
298 | printk(KERN_CONT "cpu/task tracing overlap..."); | |
299 | ||
300 | return error ? 0 : -1; | |
301 | } | |
302 | ||
303 | static int ds_selftest_bts_bad_request_task(void *buffer) | |
304 | { | |
305 | struct bts_tracer *tracer; | |
306 | int error; | |
307 | ||
308 | /* Try to request cpu tracing while task tracing is active. */ | |
309 | tracer = ds_request_bts_task(current, buffer, BUFFER_SIZE, NULL, | |
310 | (size_t)-1, BTS_KERNEL); | |
311 | error = PTR_ERR(tracer); | |
312 | if (!IS_ERR(tracer)) { | |
313 | error = 0; | |
314 | ds_release_bts(tracer); | |
315 | } | |
316 | ||
317 | if (error != -EPERM) | |
318 | printk(KERN_CONT "task/cpu tracing overlap..."); | |
319 | ||
320 | return error ? 0 : -1; | |
321 | } | |
322 | ||
01f6569e MM |
323 | int ds_selftest_bts(void) |
324 | { | |
325 | struct ds_selftest_bts_conf conf; | |
782cc5ae | 326 | unsigned char buffer[BUFFER_SIZE], *small_buffer; |
3a68eef9 | 327 | unsigned long irq; |
01f6569e MM |
328 | int cpu; |
329 | ||
330 | printk(KERN_INFO "[ds] bts selftest..."); | |
331 | conf.error = 0; | |
332 | ||
782cc5ae MM |
333 | small_buffer = (unsigned char *)ALIGN((unsigned long)buffer, 8) + 8; |
334 | ||
01f6569e MM |
335 | get_online_cpus(); |
336 | for_each_online_cpu(cpu) { | |
337 | conf.suspend = ds_suspend_bts_wrap; | |
338 | conf.resume = ds_resume_bts_wrap; | |
339 | conf.tracer = | |
340 | ds_request_bts_cpu(cpu, buffer, BUFFER_SIZE, | |
341 | NULL, (size_t)-1, BTS_KERNEL); | |
342 | ds_selftest_bts_cpu(&conf); | |
3a68eef9 MM |
343 | if (conf.error >= 0) |
344 | conf.error = ds_selftest_bts_bad_request_task(buffer); | |
01f6569e MM |
345 | ds_release_bts(conf.tracer); |
346 | if (conf.error < 0) | |
347 | goto out; | |
348 | ||
349 | conf.suspend = ds_suspend_bts_noirq; | |
350 | conf.resume = ds_resume_bts_noirq; | |
351 | conf.tracer = | |
352 | ds_request_bts_cpu(cpu, buffer, BUFFER_SIZE, | |
353 | NULL, (size_t)-1, BTS_KERNEL); | |
354 | smp_call_function_single(cpu, ds_selftest_bts_cpu, &conf, 1); | |
355 | if (conf.error >= 0) { | |
356 | conf.error = | |
357 | ds_selftest_bts_bad_release_noirq(cpu, | |
358 | conf.tracer); | |
359 | /* We must not release the tracer twice. */ | |
360 | if (conf.error < 0) | |
361 | conf.tracer = NULL; | |
362 | } | |
3a68eef9 MM |
363 | if (conf.error >= 0) |
364 | conf.error = ds_selftest_bts_bad_request_task(buffer); | |
01f6569e MM |
365 | smp_call_function_single(cpu, ds_release_bts_noirq_wrap, |
366 | conf.tracer, 1); | |
367 | if (conf.error < 0) | |
368 | goto out; | |
369 | } | |
370 | ||
3a68eef9 MM |
371 | conf.suspend = ds_suspend_bts_wrap; |
372 | conf.resume = ds_resume_bts_wrap; | |
373 | conf.tracer = | |
374 | ds_request_bts_task(current, buffer, BUFFER_SIZE, | |
375 | NULL, (size_t)-1, BTS_KERNEL); | |
376 | ds_selftest_bts_cpu(&conf); | |
377 | if (conf.error >= 0) | |
378 | conf.error = ds_selftest_bts_bad_request_cpu(0, buffer); | |
379 | ds_release_bts(conf.tracer); | |
380 | if (conf.error < 0) | |
381 | goto out; | |
382 | ||
383 | conf.suspend = ds_suspend_bts_noirq; | |
384 | conf.resume = ds_resume_bts_noirq; | |
385 | conf.tracer = | |
782cc5ae | 386 | ds_request_bts_task(current, small_buffer, SMALL_BUFFER_SIZE, |
3a68eef9 MM |
387 | NULL, (size_t)-1, BTS_KERNEL); |
388 | local_irq_save(irq); | |
389 | ds_selftest_bts_cpu(&conf); | |
390 | if (conf.error >= 0) | |
391 | conf.error = ds_selftest_bts_bad_request_cpu(0, buffer); | |
392 | ds_release_bts_noirq(conf.tracer); | |
393 | local_irq_restore(irq); | |
394 | if (conf.error < 0) | |
395 | goto out; | |
396 | ||
01f6569e MM |
397 | conf.error = 0; |
398 | out: | |
399 | put_online_cpus(); | |
400 | printk(KERN_CONT "%s.\n", (conf.error ? "failed" : "passed")); | |
401 | ||
402 | return conf.error; | |
8a327f6d MM |
403 | } |
404 | ||
405 | int ds_selftest_pebs(void) | |
406 | { | |
407 | return 0; | |
408 | } |