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;
 | 
						|
}
 |