|  | /* | 
|  | * trace event based perf counter profiling | 
|  | * | 
|  | * Copyright (C) 2009 Red Hat Inc, Peter Zijlstra <pzijlstr@redhat.com> | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include "trace.h" | 
|  |  | 
|  |  | 
|  | char *perf_trace_buf; | 
|  | EXPORT_SYMBOL_GPL(perf_trace_buf); | 
|  |  | 
|  | char *perf_trace_buf_nmi; | 
|  | EXPORT_SYMBOL_GPL(perf_trace_buf_nmi); | 
|  |  | 
|  | typedef typeof(char [FTRACE_MAX_PROFILE_SIZE]) perf_trace_t ; | 
|  |  | 
|  | /* Count the events in use (per event id, not per instance) */ | 
|  | static int	total_profile_count; | 
|  |  | 
|  | static int ftrace_profile_enable_event(struct ftrace_event_call *event) | 
|  | { | 
|  | char *buf; | 
|  | int ret = -ENOMEM; | 
|  |  | 
|  | if (event->profile_count++ > 0) | 
|  | return 0; | 
|  |  | 
|  | if (!total_profile_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->profile_enable(event); | 
|  | if (!ret) { | 
|  | total_profile_count++; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | fail_buf_nmi: | 
|  | if (!total_profile_count) { | 
|  | free_percpu(perf_trace_buf_nmi); | 
|  | free_percpu(perf_trace_buf); | 
|  | perf_trace_buf_nmi = NULL; | 
|  | perf_trace_buf = NULL; | 
|  | } | 
|  | fail_buf: | 
|  | event->profile_count--; | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int ftrace_profile_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->profile_enable && | 
|  | try_module_get(event->mod)) { | 
|  | ret = ftrace_profile_enable_event(event); | 
|  | break; | 
|  | } | 
|  | } | 
|  | mutex_unlock(&event_mutex); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void ftrace_profile_disable_event(struct ftrace_event_call *event) | 
|  | { | 
|  | char *buf, *nmi_buf; | 
|  |  | 
|  | if (--event->profile_count > 0) | 
|  | return; | 
|  |  | 
|  | event->profile_disable(event); | 
|  |  | 
|  | if (!--total_profile_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 ftrace_profile_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) { | 
|  | ftrace_profile_disable_event(event); | 
|  | module_put(event->mod); | 
|  | break; | 
|  | } | 
|  | } | 
|  | mutex_unlock(&event_mutex); | 
|  | } |