|  | /* | 
|  | * Debug Store support - selftest | 
|  | * | 
|  | * | 
|  | * Copyright (C) 2009 Intel Corporation. | 
|  | * Markus Metzger <markus.t.metzger@intel.com>, 2009 | 
|  | */ | 
|  |  | 
|  | #include "ds_selftest.h" | 
|  |  | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/string.h> | 
|  | #include <linux/smp.h> | 
|  | #include <linux/cpu.h> | 
|  |  | 
|  | #include <asm/ds.h> | 
|  |  | 
|  |  | 
|  | #define BUFFER_SIZE		521	/* Intentionally chose an odd size. */ | 
|  | #define SMALL_BUFFER_SIZE	24	/* A single bts entry. */ | 
|  |  | 
|  | 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], *small_buffer; | 
|  | unsigned long irq; | 
|  | int cpu; | 
|  |  | 
|  | printk(KERN_INFO "[ds] bts selftest..."); | 
|  | conf.error = 0; | 
|  |  | 
|  | small_buffer = (unsigned char *)ALIGN((unsigned long)buffer, 8) + 8; | 
|  |  | 
|  | 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, small_buffer, SMALL_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; | 
|  | } |