Root/
1 | /* |
2 | * Copyright 2010 ARM Ltd. |
3 | * Copyright 2012 Advanced Micro Devices, Inc., Robert Richter |
4 | * |
5 | * Perf-events backend for OProfile. |
6 | */ |
7 | #include <linux/perf_event.h> |
8 | #include <linux/platform_device.h> |
9 | #include <linux/oprofile.h> |
10 | #include <linux/slab.h> |
11 | |
12 | /* |
13 | * Per performance monitor configuration as set via oprofilefs. |
14 | */ |
15 | struct op_counter_config { |
16 | unsigned long count; |
17 | unsigned long enabled; |
18 | unsigned long event; |
19 | unsigned long unit_mask; |
20 | unsigned long kernel; |
21 | unsigned long user; |
22 | struct perf_event_attr attr; |
23 | }; |
24 | |
25 | static int oprofile_perf_enabled; |
26 | static DEFINE_MUTEX(oprofile_perf_mutex); |
27 | |
28 | static struct op_counter_config *counter_config; |
29 | static DEFINE_PER_CPU(struct perf_event **, perf_events); |
30 | static int num_counters; |
31 | |
32 | /* |
33 | * Overflow callback for oprofile. |
34 | */ |
35 | static void op_overflow_handler(struct perf_event *event, |
36 | struct perf_sample_data *data, struct pt_regs *regs) |
37 | { |
38 | int id; |
39 | u32 cpu = smp_processor_id(); |
40 | |
41 | for (id = 0; id < num_counters; ++id) |
42 | if (per_cpu(perf_events, cpu)[id] == event) |
43 | break; |
44 | |
45 | if (id != num_counters) |
46 | oprofile_add_sample(regs, id); |
47 | else |
48 | pr_warning("oprofile: ignoring spurious overflow " |
49 | "on cpu %u\n", cpu); |
50 | } |
51 | |
52 | /* |
53 | * Called by oprofile_perf_setup to create perf attributes to mirror the oprofile |
54 | * settings in counter_config. Attributes are created as `pinned' events and |
55 | * so are permanently scheduled on the PMU. |
56 | */ |
57 | static void op_perf_setup(void) |
58 | { |
59 | int i; |
60 | u32 size = sizeof(struct perf_event_attr); |
61 | struct perf_event_attr *attr; |
62 | |
63 | for (i = 0; i < num_counters; ++i) { |
64 | attr = &counter_config[i].attr; |
65 | memset(attr, 0, size); |
66 | attr->type = PERF_TYPE_RAW; |
67 | attr->size = size; |
68 | attr->config = counter_config[i].event; |
69 | attr->sample_period = counter_config[i].count; |
70 | attr->pinned = 1; |
71 | } |
72 | } |
73 | |
74 | static int op_create_counter(int cpu, int event) |
75 | { |
76 | struct perf_event *pevent; |
77 | |
78 | if (!counter_config[event].enabled || per_cpu(perf_events, cpu)[event]) |
79 | return 0; |
80 | |
81 | pevent = perf_event_create_kernel_counter(&counter_config[event].attr, |
82 | cpu, NULL, |
83 | op_overflow_handler, NULL); |
84 | |
85 | if (IS_ERR(pevent)) |
86 | return PTR_ERR(pevent); |
87 | |
88 | if (pevent->state != PERF_EVENT_STATE_ACTIVE) { |
89 | perf_event_release_kernel(pevent); |
90 | pr_warning("oprofile: failed to enable event %d " |
91 | "on CPU %d\n", event, cpu); |
92 | return -EBUSY; |
93 | } |
94 | |
95 | per_cpu(perf_events, cpu)[event] = pevent; |
96 | |
97 | return 0; |
98 | } |
99 | |
100 | static void op_destroy_counter(int cpu, int event) |
101 | { |
102 | struct perf_event *pevent = per_cpu(perf_events, cpu)[event]; |
103 | |
104 | if (pevent) { |
105 | perf_event_release_kernel(pevent); |
106 | per_cpu(perf_events, cpu)[event] = NULL; |
107 | } |
108 | } |
109 | |
110 | /* |
111 | * Called by oprofile_perf_start to create active perf events based on the |
112 | * perviously configured attributes. |
113 | */ |
114 | static int op_perf_start(void) |
115 | { |
116 | int cpu, event, ret = 0; |
117 | |
118 | for_each_online_cpu(cpu) { |
119 | for (event = 0; event < num_counters; ++event) { |
120 | ret = op_create_counter(cpu, event); |
121 | if (ret) |
122 | return ret; |
123 | } |
124 | } |
125 | |
126 | return ret; |
127 | } |
128 | |
129 | /* |
130 | * Called by oprofile_perf_stop at the end of a profiling run. |
131 | */ |
132 | static void op_perf_stop(void) |
133 | { |
134 | int cpu, event; |
135 | |
136 | for_each_online_cpu(cpu) |
137 | for (event = 0; event < num_counters; ++event) |
138 | op_destroy_counter(cpu, event); |
139 | } |
140 | |
141 | static int oprofile_perf_create_files(struct super_block *sb, struct dentry *root) |
142 | { |
143 | unsigned int i; |
144 | |
145 | for (i = 0; i < num_counters; i++) { |
146 | struct dentry *dir; |
147 | char buf[4]; |
148 | |
149 | snprintf(buf, sizeof buf, "%d", i); |
150 | dir = oprofilefs_mkdir(sb, root, buf); |
151 | oprofilefs_create_ulong(sb, dir, "enabled", &counter_config[i].enabled); |
152 | oprofilefs_create_ulong(sb, dir, "event", &counter_config[i].event); |
153 | oprofilefs_create_ulong(sb, dir, "count", &counter_config[i].count); |
154 | oprofilefs_create_ulong(sb, dir, "unit_mask", &counter_config[i].unit_mask); |
155 | oprofilefs_create_ulong(sb, dir, "kernel", &counter_config[i].kernel); |
156 | oprofilefs_create_ulong(sb, dir, "user", &counter_config[i].user); |
157 | } |
158 | |
159 | return 0; |
160 | } |
161 | |
162 | static int oprofile_perf_setup(void) |
163 | { |
164 | raw_spin_lock(&oprofilefs_lock); |
165 | op_perf_setup(); |
166 | raw_spin_unlock(&oprofilefs_lock); |
167 | return 0; |
168 | } |
169 | |
170 | static int oprofile_perf_start(void) |
171 | { |
172 | int ret = -EBUSY; |
173 | |
174 | mutex_lock(&oprofile_perf_mutex); |
175 | if (!oprofile_perf_enabled) { |
176 | ret = 0; |
177 | op_perf_start(); |
178 | oprofile_perf_enabled = 1; |
179 | } |
180 | mutex_unlock(&oprofile_perf_mutex); |
181 | return ret; |
182 | } |
183 | |
184 | static void oprofile_perf_stop(void) |
185 | { |
186 | mutex_lock(&oprofile_perf_mutex); |
187 | if (oprofile_perf_enabled) |
188 | op_perf_stop(); |
189 | oprofile_perf_enabled = 0; |
190 | mutex_unlock(&oprofile_perf_mutex); |
191 | } |
192 | |
193 | #ifdef CONFIG_PM |
194 | |
195 | static int oprofile_perf_suspend(struct platform_device *dev, pm_message_t state) |
196 | { |
197 | mutex_lock(&oprofile_perf_mutex); |
198 | if (oprofile_perf_enabled) |
199 | op_perf_stop(); |
200 | mutex_unlock(&oprofile_perf_mutex); |
201 | return 0; |
202 | } |
203 | |
204 | static int oprofile_perf_resume(struct platform_device *dev) |
205 | { |
206 | mutex_lock(&oprofile_perf_mutex); |
207 | if (oprofile_perf_enabled && op_perf_start()) |
208 | oprofile_perf_enabled = 0; |
209 | mutex_unlock(&oprofile_perf_mutex); |
210 | return 0; |
211 | } |
212 | |
213 | static struct platform_driver oprofile_driver = { |
214 | .driver = { |
215 | .name = "oprofile-perf", |
216 | }, |
217 | .resume = oprofile_perf_resume, |
218 | .suspend = oprofile_perf_suspend, |
219 | }; |
220 | |
221 | static struct platform_device *oprofile_pdev; |
222 | |
223 | static int __init init_driverfs(void) |
224 | { |
225 | int ret; |
226 | |
227 | ret = platform_driver_register(&oprofile_driver); |
228 | if (ret) |
229 | return ret; |
230 | |
231 | oprofile_pdev = platform_device_register_simple( |
232 | oprofile_driver.driver.name, 0, NULL, 0); |
233 | if (IS_ERR(oprofile_pdev)) { |
234 | ret = PTR_ERR(oprofile_pdev); |
235 | platform_driver_unregister(&oprofile_driver); |
236 | } |
237 | |
238 | return ret; |
239 | } |
240 | |
241 | static void exit_driverfs(void) |
242 | { |
243 | platform_device_unregister(oprofile_pdev); |
244 | platform_driver_unregister(&oprofile_driver); |
245 | } |
246 | |
247 | #else |
248 | |
249 | static inline int init_driverfs(void) { return 0; } |
250 | static inline void exit_driverfs(void) { } |
251 | |
252 | #endif /* CONFIG_PM */ |
253 | |
254 | void oprofile_perf_exit(void) |
255 | { |
256 | int cpu, id; |
257 | struct perf_event *event; |
258 | |
259 | for_each_possible_cpu(cpu) { |
260 | for (id = 0; id < num_counters; ++id) { |
261 | event = per_cpu(perf_events, cpu)[id]; |
262 | if (event) |
263 | perf_event_release_kernel(event); |
264 | } |
265 | |
266 | kfree(per_cpu(perf_events, cpu)); |
267 | } |
268 | |
269 | kfree(counter_config); |
270 | exit_driverfs(); |
271 | } |
272 | |
273 | int __init oprofile_perf_init(struct oprofile_operations *ops) |
274 | { |
275 | int cpu, ret = 0; |
276 | |
277 | ret = init_driverfs(); |
278 | if (ret) |
279 | return ret; |
280 | |
281 | num_counters = perf_num_counters(); |
282 | if (num_counters <= 0) { |
283 | pr_info("oprofile: no performance counters\n"); |
284 | ret = -ENODEV; |
285 | goto out; |
286 | } |
287 | |
288 | counter_config = kcalloc(num_counters, |
289 | sizeof(struct op_counter_config), GFP_KERNEL); |
290 | |
291 | if (!counter_config) { |
292 | pr_info("oprofile: failed to allocate %d " |
293 | "counters\n", num_counters); |
294 | ret = -ENOMEM; |
295 | num_counters = 0; |
296 | goto out; |
297 | } |
298 | |
299 | for_each_possible_cpu(cpu) { |
300 | per_cpu(perf_events, cpu) = kcalloc(num_counters, |
301 | sizeof(struct perf_event *), GFP_KERNEL); |
302 | if (!per_cpu(perf_events, cpu)) { |
303 | pr_info("oprofile: failed to allocate %d perf events " |
304 | "for cpu %d\n", num_counters, cpu); |
305 | ret = -ENOMEM; |
306 | goto out; |
307 | } |
308 | } |
309 | |
310 | ops->create_files = oprofile_perf_create_files; |
311 | ops->setup = oprofile_perf_setup; |
312 | ops->start = oprofile_perf_start; |
313 | ops->stop = oprofile_perf_stop; |
314 | ops->shutdown = oprofile_perf_stop; |
315 | ops->cpu_type = op_name_from_perf_id(); |
316 | |
317 | if (!ops->cpu_type) |
318 | ret = -ENODEV; |
319 | else |
320 | pr_info("oprofile: using %s\n", ops->cpu_type); |
321 | |
322 | out: |
323 | if (ret) |
324 | oprofile_perf_exit(); |
325 | |
326 | return ret; |
327 | } |
328 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9