diff options
Diffstat (limited to 'kernel/trace/trace_bts.c')
| -rw-r--r-- | kernel/trace/trace_bts.c | 276 | 
1 files changed, 276 insertions, 0 deletions
diff --git a/kernel/trace/trace_bts.c b/kernel/trace/trace_bts.c new file mode 100644 index 00000000000..23b76e4690e --- /dev/null +++ b/kernel/trace/trace_bts.c @@ -0,0 +1,276 @@ +/* + * BTS tracer + * + * Copyright (C) 2008 Markus Metzger <markus.t.metzger@gmail.com> + * + */ + +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/debugfs.h> +#include <linux/ftrace.h> +#include <linux/kallsyms.h> + +#include <asm/ds.h> + +#include "trace.h" + + +#define SIZEOF_BTS (1 << 13) + +static DEFINE_PER_CPU(struct bts_tracer *, tracer); +static DEFINE_PER_CPU(unsigned char[SIZEOF_BTS], buffer); + +#define this_tracer per_cpu(tracer, smp_processor_id()) +#define this_buffer per_cpu(buffer, smp_processor_id()) + + +/* + * Information to interpret a BTS record. + * This will go into an in-kernel BTS interface. + */ +static unsigned char sizeof_field; +static unsigned long debugctl_mask; + +#define sizeof_bts (3 * sizeof_field) + +static void bts_trace_cpuinit(struct cpuinfo_x86 *c) +{ +	switch (c->x86) { +	case 0x6: +		switch (c->x86_model) { +		case 0x0 ... 0xC: +			break; +		case 0xD: +		case 0xE: /* Pentium M */ +			sizeof_field = sizeof(long); +			debugctl_mask = (1<<6)|(1<<7); +			break; +		default: +			sizeof_field = 8; +			debugctl_mask = (1<<6)|(1<<7); +			break; +		} +		break; +	case 0xF: +		switch (c->x86_model) { +		case 0x0: +		case 0x1: +		case 0x2: /* Netburst */ +			sizeof_field = sizeof(long); +			debugctl_mask = (1<<2)|(1<<3); +			break; +		default: +			/* sorry, don't know about them */ +			break; +		} +		break; +	default: +		/* sorry, don't know about them */ +		break; +	} +} + +static inline void bts_enable(void) +{ +	unsigned long debugctl; + +	rdmsrl(MSR_IA32_DEBUGCTLMSR, debugctl); +	wrmsrl(MSR_IA32_DEBUGCTLMSR, debugctl | debugctl_mask); +} + +static inline void bts_disable(void) +{ +	unsigned long debugctl; + +	rdmsrl(MSR_IA32_DEBUGCTLMSR, debugctl); +	wrmsrl(MSR_IA32_DEBUGCTLMSR, debugctl & ~debugctl_mask); +} + +static void bts_trace_reset(struct trace_array *tr) +{ +	int cpu; + +	tr->time_start = ftrace_now(tr->cpu); + +	for_each_online_cpu(cpu) +		tracing_reset(tr, cpu); +} + +static void bts_trace_start_cpu(void *arg) +{ +	this_tracer = +		ds_request_bts(/* task = */ NULL, this_buffer, SIZEOF_BTS, +			       /* ovfl = */ NULL, /* th = */ (size_t)-1); +	if (IS_ERR(this_tracer)) { +		this_tracer = NULL; +		return; +	} + +	bts_enable(); +} + +static void bts_trace_start(struct trace_array *tr) +{ +	int cpu; + +	bts_trace_reset(tr); + +	for_each_cpu_mask(cpu, cpu_possible_map) +		smp_call_function_single(cpu, bts_trace_start_cpu, NULL, 1); +} + +static void bts_trace_stop_cpu(void *arg) +{ +	if (this_tracer) { +		bts_disable(); + +		ds_release_bts(this_tracer); +		this_tracer = NULL; +	} +} + +static void bts_trace_stop(struct trace_array *tr) +{ +	int cpu; + +	for_each_cpu_mask(cpu, cpu_possible_map) +		smp_call_function_single(cpu, bts_trace_stop_cpu, NULL, 1); +} + +static int bts_trace_init(struct trace_array *tr) +{ +	bts_trace_cpuinit(&boot_cpu_data); +	bts_trace_reset(tr); +	bts_trace_start(tr); + +	return 0; +} + +static void bts_trace_print_header(struct seq_file *m) +{ +#ifdef __i386__ +	seq_puts(m, "# CPU#    FROM           TO     FUNCTION\n"); +	seq_puts(m, "#  |       |             |         |\n"); +#else +	seq_puts(m, +		 "# CPU#        FROM                   TO         FUNCTION\n"); +	seq_puts(m, +		 "#  |           |                     |             |\n"); +#endif +} + +static enum print_line_t bts_trace_print_line(struct trace_iterator *iter) +{ +	struct trace_entry *entry = iter->ent; +	struct trace_seq *seq = &iter->seq; +	struct bts_entry *it; + +	trace_assign_type(it, entry); + +	if (entry->type == TRACE_BTS) { +		int ret; +#ifdef CONFIG_KALLSYMS +		char function[KSYM_SYMBOL_LEN]; +		sprint_symbol(function, it->from); +#else +		char *function = "<unknown>"; +#endif + +		ret = trace_seq_printf(seq, "%4d  0x%lx -> 0x%lx [%s]\n", +				       entry->cpu, it->from, it->to, function); +		if (!ret) +			return TRACE_TYPE_PARTIAL_LINE;; +		return TRACE_TYPE_HANDLED; +	} +	return TRACE_TYPE_UNHANDLED; +} + +void trace_bts(struct trace_array *tr, unsigned long from, unsigned long to) +{ +	struct ring_buffer_event *event; +	struct bts_entry *entry; +	unsigned long irq; + +	event = ring_buffer_lock_reserve(tr->buffer, sizeof(*entry), &irq); +	if (!event) +		return; +	entry	= ring_buffer_event_data(event); +	tracing_generic_entry_update(&entry->ent, 0, from); +	entry->ent.type = TRACE_BTS; +	entry->ent.cpu = smp_processor_id(); +	entry->from = from; +	entry->to   = to; +	ring_buffer_unlock_commit(tr->buffer, event, irq); +} + +static void trace_bts_at(struct trace_array *tr, size_t index) +{ +	const void *raw = NULL; +	unsigned long from, to; +	int err; + +	err = ds_access_bts(this_tracer, index, &raw); +	if (err < 0) +		return; + +	from = *(const unsigned long *)raw; +	to = *(const unsigned long *)((const char *)raw + sizeof_field); + +	trace_bts(tr, from, to); +} + +static void trace_bts_cpu(void *arg) +{ +	struct trace_array *tr = (struct trace_array *) arg; +	size_t index = 0, end = 0, i; +	int err; + +	if (!this_tracer) +		return; + +	bts_disable(); + +	err = ds_get_bts_index(this_tracer, &index); +	if (err < 0) +		goto out; + +	err = ds_get_bts_end(this_tracer, &end); +	if (err < 0) +		goto out; + +	for (i = index; i < end; i++) +		trace_bts_at(tr, i); + +	for (i = 0; i < index; i++) +		trace_bts_at(tr, i); + +out: +	bts_enable(); +} + +static void trace_bts_prepare(struct trace_iterator *iter) +{ +	int cpu; + +	for_each_cpu_mask(cpu, cpu_possible_map) +		smp_call_function_single(cpu, trace_bts_cpu, iter->tr, 1); +} + +struct tracer bts_tracer __read_mostly = +{ +	.name		= "bts", +	.init		= bts_trace_init, +	.reset		= bts_trace_stop, +	.print_header	= bts_trace_print_header, +	.print_line	= bts_trace_print_line, +	.start		= bts_trace_start, +	.stop		= bts_trace_stop, +	.open		= trace_bts_prepare +}; + +__init static int init_bts_trace(void) +{ +	return register_tracer(&bts_tracer); +} +device_initcall(init_bts_trace);  |