aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/oprofile/oprofile_perf.c
diff options
context:
space:
mode:
authorMatt Fleming <matt@console-pimps.org>2010-09-27 15:45:08 -0400
committerRobert Richter <robert.richter@amd.com>2010-10-11 11:46:16 -0400
commit3d90a00763b51e1db344a7430c966be723b67a29 (patch)
tree16d089339f161eb1e719b4b9b61e59e572587656 /drivers/oprofile/oprofile_perf.c
parent58850e210cd207399cf6461326e322541b2ec81c (diff)
oprofile: Abstract the perf-events backend
Move the perf-events backend from arch/arm/oprofile into drivers/oprofile so that the code can be shared between architectures. This allows each architecture to maintain only a single copy of the PMU accessor functions instead of one for both perf and OProfile. It also becomes possible for other architectures to delete much of their OProfile code in favour of the common code now available in drivers/oprofile/oprofile_perf.c. Signed-off-by: Matt Fleming <matt@console-pimps.org> Tested-by: Will Deacon <will.deacon@arm.com> Signed-off-by: Robert Richter <robert.richter@amd.com>
Diffstat (limited to 'drivers/oprofile/oprofile_perf.c')
-rw-r--r--drivers/oprofile/oprofile_perf.c326
1 files changed, 326 insertions, 0 deletions
diff --git a/drivers/oprofile/oprofile_perf.c b/drivers/oprofile/oprofile_perf.c
new file mode 100644
index 000000000000..ebb40cb87474
--- /dev/null
+++ b/drivers/oprofile/oprofile_perf.c
@@ -0,0 +1,326 @@
1/*
2 * Copyright 2010 ARM Ltd.
3 *
4 * Perf-events backend for OProfile.
5 */
6#include <linux/perf_event.h>
7#include <linux/oprofile.h>
8#include <linux/slab.h>
9
10/*
11 * Per performance monitor configuration as set via oprofilefs.
12 */
13struct op_counter_config {
14 unsigned long count;
15 unsigned long enabled;
16 unsigned long event;
17 unsigned long unit_mask;
18 unsigned long kernel;
19 unsigned long user;
20 struct perf_event_attr attr;
21};
22
23static int oprofile_perf_enabled;
24static DEFINE_MUTEX(oprofile_perf_mutex);
25
26static struct op_counter_config *counter_config;
27static struct perf_event **perf_events[nr_cpumask_bits];
28static int num_counters;
29
30/*
31 * Overflow callback for oprofile.
32 */
33static void op_overflow_handler(struct perf_event *event, int unused,
34 struct perf_sample_data *data, struct pt_regs *regs)
35{
36 int id;
37 u32 cpu = smp_processor_id();
38
39 for (id = 0; id < num_counters; ++id)
40 if (perf_events[cpu][id] == event)
41 break;
42
43 if (id != num_counters)
44 oprofile_add_sample(regs, id);
45 else
46 pr_warning("oprofile: ignoring spurious overflow "
47 "on cpu %u\n", cpu);
48}
49
50/*
51 * Called by oprofile_perf_setup to create perf attributes to mirror the oprofile
52 * settings in counter_config. Attributes are created as `pinned' events and
53 * so are permanently scheduled on the PMU.
54 */
55static void op_perf_setup(void)
56{
57 int i;
58 u32 size = sizeof(struct perf_event_attr);
59 struct perf_event_attr *attr;
60
61 for (i = 0; i < num_counters; ++i) {
62 attr = &counter_config[i].attr;
63 memset(attr, 0, size);
64 attr->type = PERF_TYPE_RAW;
65 attr->size = size;
66 attr->config = counter_config[i].event;
67 attr->sample_period = counter_config[i].count;
68 attr->pinned = 1;
69 }
70}
71
72static int op_create_counter(int cpu, int event)
73{
74 int ret = 0;
75 struct perf_event *pevent;
76
77 if (!counter_config[event].enabled || (perf_events[cpu][event] != NULL))
78 return ret;
79
80 pevent = perf_event_create_kernel_counter(&counter_config[event].attr,
81 cpu, -1,
82 op_overflow_handler);
83
84 if (IS_ERR(pevent)) {
85 ret = PTR_ERR(pevent);
86 } else if (pevent->state != PERF_EVENT_STATE_ACTIVE) {
87 pr_warning("oprofile: failed to enable event %d "
88 "on CPU %d\n", event, cpu);
89 ret = -EBUSY;
90 } else {
91 perf_events[cpu][event] = pevent;
92 }
93
94 return ret;
95}
96
97static void op_destroy_counter(int cpu, int event)
98{
99 struct perf_event *pevent = perf_events[cpu][event];
100
101 if (pevent) {
102 perf_event_release_kernel(pevent);
103 perf_events[cpu][event] = NULL;
104 }
105}
106
107/*
108 * Called by oprofile_perf_start to create active perf events based on the
109 * perviously configured attributes.
110 */
111static int op_perf_start(void)
112{
113 int cpu, event, ret = 0;
114
115 for_each_online_cpu(cpu) {
116 for (event = 0; event < num_counters; ++event) {
117 ret = op_create_counter(cpu, event);
118 if (ret)
119 goto out;
120 }
121 }
122
123out:
124 return ret;
125}
126
127/*
128 * Called by oprofile_perf_stop at the end of a profiling run.
129 */
130static void op_perf_stop(void)
131{
132 int cpu, event;
133
134 for_each_online_cpu(cpu)
135 for (event = 0; event < num_counters; ++event)
136 op_destroy_counter(cpu, event);
137}
138
139static int oprofile_perf_create_files(struct super_block *sb, struct dentry *root)
140{
141 unsigned int i;
142
143 for (i = 0; i < num_counters; i++) {
144 struct dentry *dir;
145 char buf[4];
146
147 snprintf(buf, sizeof buf, "%d", i);
148 dir = oprofilefs_mkdir(sb, root, buf);
149 oprofilefs_create_ulong(sb, dir, "enabled", &counter_config[i].enabled);
150 oprofilefs_create_ulong(sb, dir, "event", &counter_config[i].event);
151 oprofilefs_create_ulong(sb, dir, "count", &counter_config[i].count);
152 oprofilefs_create_ulong(sb, dir, "unit_mask", &counter_config[i].unit_mask);
153 oprofilefs_create_ulong(sb, dir, "kernel", &counter_config[i].kernel);
154 oprofilefs_create_ulong(sb, dir, "user", &counter_config[i].user);
155 }
156
157 return 0;
158}
159
160static int oprofile_perf_setup(void)
161{
162 spin_lock(&oprofilefs_lock);
163 op_perf_setup();
164 spin_unlock(&oprofilefs_lock);
165 return 0;
166}
167
168static int oprofile_perf_start(void)
169{
170 int ret = -EBUSY;
171
172 mutex_lock(&oprofile_perf_mutex);
173 if (!oprofile_perf_enabled) {
174 ret = 0;
175 op_perf_start();
176 oprofile_perf_enabled = 1;
177 }
178 mutex_unlock(&oprofile_perf_mutex);
179 return ret;
180}
181
182static void oprofile_perf_stop(void)
183{
184 mutex_lock(&oprofile_perf_mutex);
185 if (oprofile_perf_enabled)
186 op_perf_stop();
187 oprofile_perf_enabled = 0;
188 mutex_unlock(&oprofile_perf_mutex);
189}
190
191#ifdef CONFIG_PM
192static int oprofile_perf_suspend(struct platform_device *dev, pm_message_t state)
193{
194 mutex_lock(&oprofile_perf_mutex);
195 if (oprofile_perf_enabled)
196 op_perf_stop();
197 mutex_unlock(&oprofile_perf_mutex);
198 return 0;
199}
200
201static int oprofile_perf_resume(struct platform_device *dev)
202{
203 mutex_lock(&oprofile_perf_mutex);
204 if (oprofile_perf_enabled && op_perf_start())
205 oprofile_perf_enabled = 0;
206 mutex_unlock(&oprofile_perf_mutex);
207 return 0;
208}
209
210static struct platform_driver oprofile_driver = {
211 .driver = {
212 .name = "oprofile-perf",
213 },
214 .resume = oprofile_perf_resume,
215 .suspend = oprofile_perf_suspend,
216};
217
218static struct platform_device *oprofile_pdev;
219
220static int __init init_driverfs(void)
221{
222 int ret;
223
224 ret = platform_driver_register(&oprofile_driver);
225 if (ret)
226 goto out;
227
228 oprofile_pdev = platform_device_register_simple(
229 oprofile_driver.driver.name, 0, NULL, 0);
230 if (IS_ERR(oprofile_pdev)) {
231 ret = PTR_ERR(oprofile_pdev);
232 platform_driver_unregister(&oprofile_driver);
233 }
234
235out:
236 return ret;
237}
238
239static void __exit exit_driverfs(void)
240{
241 platform_device_unregister(oprofile_pdev);
242 platform_driver_unregister(&oprofile_driver);
243}
244#else
245static int __init init_driverfs(void) { return 0; }
246#define exit_driverfs() do { } while (0)
247#endif /* CONFIG_PM */
248
249int __init oprofile_perf_init(struct oprofile_operations *ops)
250{
251 int cpu, ret = 0;
252
253 memset(&perf_events, 0, sizeof(perf_events));
254
255 num_counters = perf_num_counters();
256 if (num_counters <= 0) {
257 pr_info("oprofile: no performance counters\n");
258 ret = -ENODEV;
259 goto out;
260 }
261
262 counter_config = kcalloc(num_counters,
263 sizeof(struct op_counter_config), GFP_KERNEL);
264
265 if (!counter_config) {
266 pr_info("oprofile: failed to allocate %d "
267 "counters\n", num_counters);
268 ret = -ENOMEM;
269 goto out;
270 }
271
272 ret = init_driverfs();
273 if (ret)
274 goto out;
275
276 for_each_possible_cpu(cpu) {
277 perf_events[cpu] = kcalloc(num_counters,
278 sizeof(struct perf_event *), GFP_KERNEL);
279 if (!perf_events[cpu]) {
280 pr_info("oprofile: failed to allocate %d perf events "
281 "for cpu %d\n", num_counters, cpu);
282 ret = -ENOMEM;
283 goto out;
284 }
285 }
286
287 ops->create_files = oprofile_perf_create_files;
288 ops->setup = oprofile_perf_setup;
289 ops->start = oprofile_perf_start;
290 ops->stop = oprofile_perf_stop;
291 ops->shutdown = oprofile_perf_stop;
292 ops->cpu_type = op_name_from_perf_id();
293
294 if (!ops->cpu_type)
295 ret = -ENODEV;
296 else
297 pr_info("oprofile: using %s\n", ops->cpu_type);
298
299out:
300 if (ret) {
301 for_each_possible_cpu(cpu)
302 kfree(perf_events[cpu]);
303 kfree(counter_config);
304 }
305
306 return ret;
307}
308
309void __exit oprofile_perf_exit(void)
310{
311 int cpu, id;
312 struct perf_event *event;
313
314 for_each_possible_cpu(cpu) {
315 for (id = 0; id < num_counters; ++id) {
316 event = perf_events[cpu][id];
317 if (event)
318 perf_event_release_kernel(event);
319 }
320
321 kfree(perf_events[cpu]);
322 }
323
324 kfree(counter_config);
325 exit_driverfs();
326}