/* * Debug Store support - selftest * * * Copyright (C) 2009 Intel Corporation. * Markus Metzger , 2009 */ #include "ds_selftest.h" #include #include #include #include #include #define BUFFER_SIZE 521 /* Intentionally chose an odd size. */ struct ds_selftest_bts_conf { struct bts_tracer *tracer; int error; int (*suspend)(struct bts_tracer *); int (*resume)(struct bts_tracer *); }; static int ds_selftest_bts_consistency(const struct bts_trace *trace) { int error = 0; if (!trace) { printk(KERN_CONT "failed to access trace..."); /* Bail out. Other tests are pointless. */ return -1; } if (!trace->read) { printk(KERN_CONT "bts read not available..."); error = -1; } /* Do some sanity checks on the trace configuration. */ if (!trace->ds.n) { printk(KERN_CONT "empty bts buffer..."); error = -1; } if (!trace->ds.size) { printk(KERN_CONT "bad bts trace setup..."); error = -1; } if (trace->ds.end != (char *)trace->ds.begin + (trace->ds.n * trace->ds.size)) { printk(KERN_CONT "bad bts buffer setup..."); error = -1; } /* * We allow top in [begin; end], since its not clear when the * overflow adjustment happens: after the increment or before the * write. */ if ((trace->ds.top < trace->ds.begin) || (trace->ds.end < trace->ds.top)) { printk(KERN_CONT "bts top out of bounds..."); error = -1; } return error; } static int ds_selftest_bts_read(struct bts_tracer *tracer, const struct bts_trace *trace, const void *from, const void *to) { const unsigned char *at; /* * Check a few things which do not belong to this test. * They should be covered by other tests. */ if (!trace) return -1; if (!trace->read) return -1; if (to < from) return -1; if (from < trace->ds.begin) return -1; if (trace->ds.end < to) return -1; if (!trace->ds.size) return -1; /* Now to the test itself. */ for (at = from; (void *)at < to; at += trace->ds.size) { struct bts_struct bts; unsigned long index; int error; if (((void *)at - trace->ds.begin) % trace->ds.size) { printk(KERN_CONT "read from non-integer index..."); return -1; } index = ((void *)at - trace->ds.begin) / trace->ds.size; memset(&bts, 0, sizeof(bts)); error = trace->read(tracer, at, &bts); if (error < 0) { printk(KERN_CONT "error reading bts trace at [%lu] (0x%p)...", index, at); return error; } switch (bts.qualifier) { case BTS_BRANCH: break; default: printk(KERN_CONT "unexpected bts entry %llu at [%lu] (0x%p)...", bts.qualifier, index, at); return -1; } } return 0; } static void ds_selftest_bts_cpu(void *arg) { struct ds_selftest_bts_conf *conf = arg; const struct bts_trace *trace; void *top; if (IS_ERR(conf->tracer)) { conf->error = PTR_ERR(conf->tracer); conf->tracer = NULL; printk(KERN_CONT "initialization failed (err: %d)...", conf->error); return; } /* We should meanwhile have enough trace. */ conf->error = conf->suspend(conf->tracer); if (conf->error < 0) return; /* Let's see if we can access the trace. */ trace = ds_read_bts(conf->tracer); conf->error = ds_selftest_bts_consistency(trace); if (conf->error < 0) return; /* If everything went well, we should have a few trace entries. */ if (trace->ds.top == trace->ds.begin) { /* * It is possible but highly unlikely that we got a * buffer overflow and end up at exactly the same * position we started from. * Let's issue a warning, but continue. */ printk(KERN_CONT "no trace/overflow..."); } /* Let's try to read the trace we collected. */ conf->error = ds_selftest_bts_read(conf->tracer, trace, trace->ds.begin, trace->ds.top); if (conf->error < 0) return; /* * Let's read the trace again. * Since we suspended tracing, we should get the same result. */ top = trace->ds.top; trace = ds_read_bts(conf->tracer); conf->error = ds_selftest_bts_consistency(trace); if (conf->error < 0) return; if (top != trace->ds.top) { printk(KERN_CONT "suspend not working..."); conf->error = -1; return; } /* Let's collect some more trace - see if resume is working. */ conf->error = conf->resume(conf->tracer); if (conf->error < 0) return; conf->error = conf->suspend(conf->tracer); if (conf->error < 0) return; trace = ds_read_bts(conf->tracer); conf->error = ds_selftest_bts_consistency(trace); if (conf->error < 0) return; if (trace->ds.top == top) { /* * It is possible but highly unlikely that we got a * buffer overflow and end up at exactly the same * position we started from. * Let's issue a warning and check the full trace. */ printk(KERN_CONT "no resume progress/overflow..."); conf->error = ds_selftest_bts_read(conf->tracer, trace, trace->ds.begin, trace->ds.end); } else if (trace->ds.top < top) { /* * We had a buffer overflow - the entire buffer should * contain trace records. */ conf->error = ds_selftest_bts_read(conf->tracer, trace, trace->ds.begin, trace->ds.end); } else { /* * It is quite likely that the buffer did not overflow. * Let's just check the delta trace. */ conf->error = ds_selftest_bts_read(conf->tracer, trace, top, trace->ds.top); } if (conf->error < 0) return; conf->error = 0; } static int ds_suspend_bts_wrap(struct bts_tracer *tracer) { ds_suspend_bts(tracer); return 0; } static int ds_resume_bts_wrap(struct bts_tracer *tracer) { ds_resume_bts(tracer); return 0; } static void ds_release_bts_noirq_wrap(void *tracer) { (void)ds_release_bts_noirq(tracer); } static int ds_selftest_bts_bad_release_noirq(int cpu, struct bts_tracer *tracer) { int error = -EPERM; /* Try to release the tracer on the wrong cpu. */ get_cpu(); if (cpu != smp_processor_id()) { error = ds_release_bts_noirq(tracer); if (error != -EPERM) printk(KERN_CONT "release on wrong cpu..."); } put_cpu(); return error ? 0 : -1; } static int ds_selftest_bts_bad_request_cpu(int cpu, void *buffer) { struct bts_tracer *tracer; int error; /* Try to request cpu tracing while task tracing is active. */ tracer = ds_request_bts_cpu(cpu, buffer, BUFFER_SIZE, NULL, (size_t)-1, BTS_KERNEL); error = PTR_ERR(tracer); if (!IS_ERR(tracer)) { ds_release_bts(tracer); error = 0; } if (error != -EPERM) printk(KERN_CONT "cpu/task tracing overlap..."); return error ? 0 : -1; } static int ds_selftest_bts_bad_request_task(void *buffer) { struct bts_tracer *tracer; int error; /* Try to request cpu tracing while task tracing is active. */ tracer = ds_request_bts_task(current, buffer, BUFFER_SIZE, NULL, (size_t)-1, BTS_KERNEL); error = PTR_ERR(tracer); if (!IS_ERR(tracer)) { error = 0; ds_release_bts(tracer); } if (error != -EPERM) printk(KERN_CONT "task/cpu tracing overlap..."); return error ? 0 : -1; } int ds_selftest_bts(void) { struct ds_selftest_bts_conf conf; unsigned char buffer[BUFFER_SIZE]; unsigned long irq; int cpu; printk(KERN_INFO "[ds] bts selftest..."); conf.error = 0; get_online_cpus(); for_each_online_cpu(cpu) { conf.suspend = ds_suspend_bts_wrap; conf.resume = ds_resume_bts_wrap; conf.tracer = ds_request_bts_cpu(cpu, buffer, BUFFER_SIZE, NULL, (size_t)-1, BTS_KERNEL); ds_selftest_bts_cpu(&conf); if (conf.error >= 0) conf.error = ds_selftest_bts_bad_request_task(buffer); ds_release_bts(conf.tracer); if (conf.error < 0) goto out; conf.suspend = ds_suspend_bts_noirq; conf.resume = ds_resume_bts_noirq; conf.tracer = ds_request_bts_cpu(cpu, buffer, BUFFER_SIZE, NULL, (size_t)-1, BTS_KERNEL); smp_call_function_single(cpu, ds_selftest_bts_cpu, &conf, 1); if (conf.error >= 0) { conf.error = ds_selftest_bts_bad_release_noirq(cpu, conf.tracer); /* We must not release the tracer twice. */ if (conf.error < 0) conf.tracer = NULL; } if (conf.error >= 0) conf.error = ds_selftest_bts_bad_request_task(buffer); smp_call_function_single(cpu, ds_release_bts_noirq_wrap, conf.tracer, 1); if (conf.error < 0) goto out; } conf.suspend = ds_suspend_bts_wrap; conf.resume = ds_resume_bts_wrap; conf.tracer = ds_request_bts_task(current, buffer, BUFFER_SIZE, NULL, (size_t)-1, BTS_KERNEL); ds_selftest_bts_cpu(&conf); if (conf.error >= 0) conf.error = ds_selftest_bts_bad_request_cpu(0, buffer); ds_release_bts(conf.tracer); if (conf.error < 0) goto out; conf.suspend = ds_suspend_bts_noirq; conf.resume = ds_resume_bts_noirq; conf.tracer = ds_request_bts_task(current, buffer, BUFFER_SIZE, NULL, (size_t)-1, BTS_KERNEL); local_irq_save(irq); ds_selftest_bts_cpu(&conf); if (conf.error >= 0) conf.error = ds_selftest_bts_bad_request_cpu(0, buffer); ds_release_bts_noirq(conf.tracer); local_irq_restore(irq); if (conf.error < 0) goto out; conf.error = 0; out: put_online_cpus(); printk(KERN_CONT "%s.\n", (conf.error ? "failed" : "passed")); return conf.error; } int ds_selftest_pebs(void) { return 0; }