409 lines
9.2 KiB
C
409 lines
9.2 KiB
C
|
/*
|
||
|
* 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;
|
||
|
}
|