/* * BTS tracer * * Copyright (C) 2008 Markus Metzger * */ #include #include #include #include #include #include #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 = ""; #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);