1 /*
2  * trace event based perf event profiling/tracing
3  *
4  * Copyright (C) 2009 Red Hat Inc, Peter Zijlstra <pzijlstr@redhat.com>
5  * Copyright (C) 2009-2010 Frederic Weisbecker <fweisbec@gmail.com>
6  */
7 
8 #include <linux/module.h>
9 #include <linux/kprobes.h>
10 #include "trace.h"
11 
12 DEFINE_PER_CPU(struct pt_regs, perf_trace_regs);
13 EXPORT_PER_CPU_SYMBOL_GPL(perf_trace_regs);
14 
15 static char *perf_trace_buf;
16 static char *perf_trace_buf_nmi;
17 
18 typedef typeof(char [PERF_MAX_TRACE_SIZE]) perf_trace_t ;
19 
20 /* Count the events in use (per event id, not per instance) */
21 static int	total_ref_count;
22 
23 static int perf_trace_event_enable(struct ftrace_event_call *event)
24 {
25 	char *buf;
26 	int ret = -ENOMEM;
27 
28 	if (event->perf_refcount++ > 0)
29 		return 0;
30 
31 	if (!total_ref_count) {
32 		buf = (char *)alloc_percpu(perf_trace_t);
33 		if (!buf)
34 			goto fail_buf;
35 
36 		rcu_assign_pointer(perf_trace_buf, buf);
37 
38 		buf = (char *)alloc_percpu(perf_trace_t);
39 		if (!buf)
40 			goto fail_buf_nmi;
41 
42 		rcu_assign_pointer(perf_trace_buf_nmi, buf);
43 	}
44 
45 	ret = event->perf_event_enable(event);
46 	if (!ret) {
47 		total_ref_count++;
48 		return 0;
49 	}
50 
51 fail_buf_nmi:
52 	if (!total_ref_count) {
53 		free_percpu(perf_trace_buf_nmi);
54 		free_percpu(perf_trace_buf);
55 		perf_trace_buf_nmi = NULL;
56 		perf_trace_buf = NULL;
57 	}
58 fail_buf:
59 	event->perf_refcount--;
60 
61 	return ret;
62 }
63 
64 int perf_trace_enable(int event_id)
65 {
66 	struct ftrace_event_call *event;
67 	int ret = -EINVAL;
68 
69 	mutex_lock(&event_mutex);
70 	list_for_each_entry(event, &ftrace_events, list) {
71 		if (event->id == event_id && event->perf_event_enable &&
72 		    try_module_get(event->mod)) {
73 			ret = perf_trace_event_enable(event);
74 			break;
75 		}
76 	}
77 	mutex_unlock(&event_mutex);
78 
79 	return ret;
80 }
81 
82 static void perf_trace_event_disable(struct ftrace_event_call *event)
83 {
84 	char *buf, *nmi_buf;
85 
86 	if (--event->perf_refcount > 0)
87 		return;
88 
89 	event->perf_event_disable(event);
90 
91 	if (!--total_ref_count) {
92 		buf = perf_trace_buf;
93 		rcu_assign_pointer(perf_trace_buf, NULL);
94 
95 		nmi_buf = perf_trace_buf_nmi;
96 		rcu_assign_pointer(perf_trace_buf_nmi, NULL);
97 
98 		/*
99 		 * Ensure every events in profiling have finished before
100 		 * releasing the buffers
101 		 */
102 		synchronize_sched();
103 
104 		free_percpu(buf);
105 		free_percpu(nmi_buf);
106 	}
107 }
108 
109 void perf_trace_disable(int event_id)
110 {
111 	struct ftrace_event_call *event;
112 
113 	mutex_lock(&event_mutex);
114 	list_for_each_entry(event, &ftrace_events, list) {
115 		if (event->id == event_id) {
116 			perf_trace_event_disable(event);
117 			module_put(event->mod);
118 			break;
119 		}
120 	}
121 	mutex_unlock(&event_mutex);
122 }
123 
124 __kprobes void *perf_trace_buf_prepare(int size, unsigned short type,
125 				       int *rctxp, unsigned long *irq_flags)
126 {
127 	struct trace_entry *entry;
128 	char *trace_buf, *raw_data;
129 	int pc, cpu;
130 
131 	pc = preempt_count();
132 
133 	/* Protect the per cpu buffer, begin the rcu read side */
134 	local_irq_save(*irq_flags);
135 
136 	*rctxp = perf_swevent_get_recursion_context();
137 	if (*rctxp < 0)
138 		goto err_recursion;
139 
140 	cpu = smp_processor_id();
141 
142 	if (in_nmi())
143 		trace_buf = rcu_dereference(perf_trace_buf_nmi);
144 	else
145 		trace_buf = rcu_dereference(perf_trace_buf);
146 
147 	if (!trace_buf)
148 		goto err;
149 
150 	raw_data = per_cpu_ptr(trace_buf, cpu);
151 
152 	/* zero the dead bytes from align to not leak stack to user */
153 	*(u64 *)(&raw_data[size - sizeof(u64)]) = 0ULL;
154 
155 	entry = (struct trace_entry *)raw_data;
156 	tracing_generic_entry_update(entry, *irq_flags, pc);
157 	entry->type = type;
158 
159 	return raw_data;
160 err:
161 	perf_swevent_put_recursion_context(*rctxp);
162 err_recursion:
163 	local_irq_restore(*irq_flags);
164 	return NULL;
165 }
166 EXPORT_SYMBOL_GPL(perf_trace_buf_prepare);
167