| /* |
| * trace event based perf event profiling/tracing |
| * |
| * Copyright (C) 2009 Red Hat Inc, Peter Zijlstra <pzijlstr@redhat.com> |
| * Copyright (C) 2009-2010 Frederic Weisbecker <fweisbec@gmail.com> |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kprobes.h> |
| #include "trace.h" |
| |
| DEFINE_PER_CPU(struct pt_regs, perf_trace_regs); |
| EXPORT_PER_CPU_SYMBOL_GPL(perf_trace_regs); |
| |
| EXPORT_SYMBOL_GPL(perf_arch_fetch_caller_regs); |
| |
| static char *perf_trace_buf; |
| static char *perf_trace_buf_nmi; |
| |
| /* |
| * Force it to be aligned to unsigned long to avoid misaligned accesses |
| * suprises |
| */ |
| typedef typeof(unsigned long [PERF_MAX_TRACE_SIZE / sizeof(unsigned long)]) |
| perf_trace_t; |
| |
| /* Count the events in use (per event id, not per instance) */ |
| static int total_ref_count; |
| |
| static int perf_trace_event_enable(struct ftrace_event_call *event) |
| { |
| char *buf; |
| int ret = -ENOMEM; |
| |
| if (event->perf_refcount++ > 0) |
| return 0; |
| |
| if (!total_ref_count) { |
| buf = (char *)alloc_percpu(perf_trace_t); |
| if (!buf) |
| goto fail_buf; |
| |
| rcu_assign_pointer(perf_trace_buf, buf); |
| |
| buf = (char *)alloc_percpu(perf_trace_t); |
| if (!buf) |
| goto fail_buf_nmi; |
| |
| rcu_assign_pointer(perf_trace_buf_nmi, buf); |
| } |
| |
| ret = event->perf_event_enable(event); |
| if (!ret) { |
| total_ref_count++; |
| return 0; |
| } |
| |
| fail_buf_nmi: |
| if (!total_ref_count) { |
| free_percpu(perf_trace_buf_nmi); |
| free_percpu(perf_trace_buf); |
| perf_trace_buf_nmi = NULL; |
| perf_trace_buf = NULL; |
| } |
| fail_buf: |
| event->perf_refcount--; |
| |
| return ret; |
| } |
| |
| int perf_trace_enable(int event_id) |
| { |
| struct ftrace_event_call *event; |
| int ret = -EINVAL; |
| |
| mutex_lock(&event_mutex); |
| list_for_each_entry(event, &ftrace_events, list) { |
| if (event->id == event_id && event->perf_event_enable && |
| try_module_get(event->mod)) { |
| ret = perf_trace_event_enable(event); |
| break; |
| } |
| } |
| mutex_unlock(&event_mutex); |
| |
| return ret; |
| } |
| |
| static void perf_trace_event_disable(struct ftrace_event_call *event) |
| { |
| char *buf, *nmi_buf; |
| |
| if (--event->perf_refcount > 0) |
| return; |
| |
| event->perf_event_disable(event); |
| |
| if (!--total_ref_count) { |
| buf = perf_trace_buf; |
| rcu_assign_pointer(perf_trace_buf, NULL); |
| |
| nmi_buf = perf_trace_buf_nmi; |
| rcu_assign_pointer(perf_trace_buf_nmi, NULL); |
| |
| /* |
| * Ensure every events in profiling have finished before |
| * releasing the buffers |
| */ |
| synchronize_sched(); |
| |
| free_percpu(buf); |
| free_percpu(nmi_buf); |
| } |
| } |
| |
| void perf_trace_disable(int event_id) |
| { |
| struct ftrace_event_call *event; |
| |
| mutex_lock(&event_mutex); |
| list_for_each_entry(event, &ftrace_events, list) { |
| if (event->id == event_id) { |
| perf_trace_event_disable(event); |
| module_put(event->mod); |
| break; |
| } |
| } |
| mutex_unlock(&event_mutex); |
| } |
| |
| __kprobes void *perf_trace_buf_prepare(int size, unsigned short type, |
| int *rctxp, unsigned long *irq_flags) |
| { |
| struct trace_entry *entry; |
| char *trace_buf, *raw_data; |
| int pc, cpu; |
| |
| BUILD_BUG_ON(PERF_MAX_TRACE_SIZE % sizeof(unsigned long)); |
| |
| pc = preempt_count(); |
| |
| /* Protect the per cpu buffer, begin the rcu read side */ |
| local_irq_save(*irq_flags); |
| |
| *rctxp = perf_swevent_get_recursion_context(); |
| if (*rctxp < 0) |
| goto err_recursion; |
| |
| cpu = smp_processor_id(); |
| |
| if (in_nmi()) |
| trace_buf = rcu_dereference_sched(perf_trace_buf_nmi); |
| else |
| trace_buf = rcu_dereference_sched(perf_trace_buf); |
| |
| if (!trace_buf) |
| goto err; |
| |
| raw_data = per_cpu_ptr(trace_buf, cpu); |
| |
| /* zero the dead bytes from align to not leak stack to user */ |
| memset(&raw_data[size - sizeof(u64)], 0, sizeof(u64)); |
| |
| entry = (struct trace_entry *)raw_data; |
| tracing_generic_entry_update(entry, *irq_flags, pc); |
| entry->type = type; |
| |
| return raw_data; |
| err: |
| perf_swevent_put_recursion_context(*rctxp); |
| err_recursion: |
| local_irq_restore(*irq_flags); |
| return NULL; |
| } |
| EXPORT_SYMBOL_GPL(perf_trace_buf_prepare); |