Blame view

kernel/events/callchain.c 4.33 KB
9251f904f   Borislav Petkov   perf: Carve out c...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
  /*
   * Performance events callchain code, extracted from core.c:
   *
   *  Copyright (C) 2008 Thomas Gleixner <tglx@linutronix.de>
   *  Copyright (C) 2008-2011 Red Hat, Inc., Ingo Molnar
   *  Copyright (C) 2008-2011 Red Hat, Inc., Peter Zijlstra <pzijlstr@redhat.com>
   *  Copyright  ©  2009 Paul Mackerras, IBM Corp. <paulus@au1.ibm.com>
   *
   * For licensing details see kernel-base/COPYING
   */
  
  #include <linux/perf_event.h>
  #include <linux/slab.h>
  #include "internal.h"
  
  struct callchain_cpus_entries {
  	struct rcu_head			rcu_head;
  	struct perf_callchain_entry	*cpu_entries[0];
  };
  
  static DEFINE_PER_CPU(int, callchain_recursion[PERF_NR_CONTEXTS]);
  static atomic_t nr_callchain_events;
  static DEFINE_MUTEX(callchain_mutex);
  static struct callchain_cpus_entries *callchain_cpus_entries;
  
  
  __weak void perf_callchain_kernel(struct perf_callchain_entry *entry,
  				  struct pt_regs *regs)
  {
  }
  
  __weak void perf_callchain_user(struct perf_callchain_entry *entry,
  				struct pt_regs *regs)
  {
  }
  
  static void release_callchain_buffers_rcu(struct rcu_head *head)
  {
  	struct callchain_cpus_entries *entries;
  	int cpu;
  
  	entries = container_of(head, struct callchain_cpus_entries, rcu_head);
  
  	for_each_possible_cpu(cpu)
  		kfree(entries->cpu_entries[cpu]);
  
  	kfree(entries);
  }
  
  static void release_callchain_buffers(void)
  {
  	struct callchain_cpus_entries *entries;
  
  	entries = callchain_cpus_entries;
e0455e194   Andreea-Cristina Bernat   perf/callchain: R...
55
  	RCU_INIT_POINTER(callchain_cpus_entries, NULL);
9251f904f   Borislav Petkov   perf: Carve out c...
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
  	call_rcu(&entries->rcu_head, release_callchain_buffers_rcu);
  }
  
  static int alloc_callchain_buffers(void)
  {
  	int cpu;
  	int size;
  	struct callchain_cpus_entries *entries;
  
  	/*
  	 * We can't use the percpu allocation API for data that can be
  	 * accessed from NMI. Use a temporary manual per cpu allocation
  	 * until that gets sorted out.
  	 */
  	size = offsetof(struct callchain_cpus_entries, cpu_entries[nr_cpu_ids]);
  
  	entries = kzalloc(size, GFP_KERNEL);
  	if (!entries)
  		return -ENOMEM;
  
  	size = sizeof(struct perf_callchain_entry) * PERF_NR_CONTEXTS;
  
  	for_each_possible_cpu(cpu) {
  		entries->cpu_entries[cpu] = kmalloc_node(size, GFP_KERNEL,
  							 cpu_to_node(cpu));
  		if (!entries->cpu_entries[cpu])
  			goto fail;
  	}
  
  	rcu_assign_pointer(callchain_cpus_entries, entries);
  
  	return 0;
  
  fail:
  	for_each_possible_cpu(cpu)
  		kfree(entries->cpu_entries[cpu]);
  	kfree(entries);
  
  	return -ENOMEM;
  }
  
  int get_callchain_buffers(void)
  {
  	int err = 0;
  	int count;
  
  	mutex_lock(&callchain_mutex);
  
  	count = atomic_inc_return(&nr_callchain_events);
  	if (WARN_ON_ONCE(count < 1)) {
  		err = -EINVAL;
  		goto exit;
  	}
  
  	if (count > 1) {
  		/* If the allocation failed, give up */
  		if (!callchain_cpus_entries)
  			err = -ENOMEM;
  		goto exit;
  	}
  
  	err = alloc_callchain_buffers();
9251f904f   Borislav Petkov   perf: Carve out c...
118
  exit:
90983b160   Frederic Weisbecker   perf: Sanitize ge...
119
120
  	if (err)
  		atomic_dec(&nr_callchain_events);
9251f904f   Borislav Petkov   perf: Carve out c...
121

fc3b86d67   Frederic Weisbecker   perf: Roll back c...
122
  	mutex_unlock(&callchain_mutex);
9251f904f   Borislav Petkov   perf: Carve out c...
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
  	return err;
  }
  
  void put_callchain_buffers(void)
  {
  	if (atomic_dec_and_mutex_lock(&nr_callchain_events, &callchain_mutex)) {
  		release_callchain_buffers();
  		mutex_unlock(&callchain_mutex);
  	}
  }
  
  static struct perf_callchain_entry *get_callchain_entry(int *rctx)
  {
  	int cpu;
  	struct callchain_cpus_entries *entries;
4a32fea9d   Christoph Lameter   scheduler: Replac...
138
  	*rctx = get_recursion_context(this_cpu_ptr(callchain_recursion));
9251f904f   Borislav Petkov   perf: Carve out c...
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
  	if (*rctx == -1)
  		return NULL;
  
  	entries = rcu_dereference(callchain_cpus_entries);
  	if (!entries)
  		return NULL;
  
  	cpu = smp_processor_id();
  
  	return &entries->cpu_entries[cpu][*rctx];
  }
  
  static void
  put_callchain_entry(int rctx)
  {
4a32fea9d   Christoph Lameter   scheduler: Replac...
154
  	put_recursion_context(this_cpu_ptr(callchain_recursion), rctx);
9251f904f   Borislav Petkov   perf: Carve out c...
155
  }
e6dab5ffa   Andrew Vagin   perf/trace: Add a...
156
157
  struct perf_callchain_entry *
  perf_callchain(struct perf_event *event, struct pt_regs *regs)
9251f904f   Borislav Petkov   perf: Carve out c...
158
159
160
  {
  	int rctx;
  	struct perf_callchain_entry *entry;
d07752648   Frederic Weisbecker   perf: Add attribu...
161
162
163
164
165
  	int kernel = !event->attr.exclude_callchain_kernel;
  	int user   = !event->attr.exclude_callchain_user;
  
  	if (!kernel && !user)
  		return NULL;
9251f904f   Borislav Petkov   perf: Carve out c...
166
167
168
169
170
171
172
173
174
  
  	entry = get_callchain_entry(&rctx);
  	if (rctx == -1)
  		return NULL;
  
  	if (!entry)
  		goto exit_put;
  
  	entry->nr = 0;
d07752648   Frederic Weisbecker   perf: Add attribu...
175
  	if (kernel && !user_mode(regs)) {
9251f904f   Borislav Petkov   perf: Carve out c...
176
177
  		perf_callchain_store(entry, PERF_CONTEXT_KERNEL);
  		perf_callchain_kernel(entry, regs);
9251f904f   Borislav Petkov   perf: Carve out c...
178
  	}
d07752648   Frederic Weisbecker   perf: Add attribu...
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
  	if (user) {
  		if (!user_mode(regs)) {
  			if  (current->mm)
  				regs = task_pt_regs(current);
  			else
  				regs = NULL;
  		}
  
  		if (regs) {
  			/*
  			 * Disallow cross-task user callchains.
  			 */
  			if (event->ctx->task && event->ctx->task != current)
  				goto exit_put;
  
  			perf_callchain_store(entry, PERF_CONTEXT_USER);
  			perf_callchain_user(entry, regs);
  		}
9251f904f   Borislav Petkov   perf: Carve out c...
197
198
199
200
201
202
203
  	}
  
  exit_put:
  	put_callchain_entry(rctx);
  
  	return entry;
  }