Commit d7ac4e28ccc63ed6b4d67bd9c4a67cb9533eeb45
Committed by
Russell King
1 parent
46097c7dd8
Exists in
master
and in
7 other branches
[ARM] 5195/1: ARMv7 Oprofile support
Add Oprofile kernel support for ARMv7. Tested on OMAP3430 and OMAP3530 chipsets (Cortex-A8). Signed-off-by: Jean Pihet <jpihet@mvista.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Showing 7 changed files with 527 additions and 0 deletions Side-by-side Diff
arch/arm/Kconfig
arch/arm/oprofile/Makefile
arch/arm/oprofile/common.c
arch/arm/oprofile/op_arm_model.h
arch/arm/oprofile/op_model_v7.c
1 | +/** | |
2 | + * op_model_v7.c | |
3 | + * ARM V7 (Cortex A8) Event Monitor Driver | |
4 | + * | |
5 | + * Copyright 2008 Jean Pihet <jpihet@mvista.com> | |
6 | + * Copyright 2004 ARM SMP Development Team | |
7 | + * | |
8 | + * This program is free software; you can redistribute it and/or modify | |
9 | + * it under the terms of the GNU General Public License version 2 as | |
10 | + * published by the Free Software Foundation. | |
11 | + */ | |
12 | +#include <linux/types.h> | |
13 | +#include <linux/errno.h> | |
14 | +#include <linux/oprofile.h> | |
15 | +#include <linux/interrupt.h> | |
16 | +#include <linux/irq.h> | |
17 | +#include <linux/smp.h> | |
18 | + | |
19 | +#include "op_counter.h" | |
20 | +#include "op_arm_model.h" | |
21 | +#include "op_model_v7.h" | |
22 | + | |
23 | +/* #define DEBUG */ | |
24 | + | |
25 | + | |
26 | +/* | |
27 | + * ARM V7 PMNC support | |
28 | + */ | |
29 | + | |
30 | +static u32 cnt_en[CNTMAX]; | |
31 | + | |
32 | +static inline void armv7_pmnc_write(u32 val) | |
33 | +{ | |
34 | + val &= PMNC_MASK; | |
35 | + asm volatile("mcr p15, 0, %0, c9, c12, 0" : : "r" (val)); | |
36 | +} | |
37 | + | |
38 | +static inline u32 armv7_pmnc_read(void) | |
39 | +{ | |
40 | + u32 val; | |
41 | + | |
42 | + asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r" (val)); | |
43 | + return val; | |
44 | +} | |
45 | + | |
46 | +static inline u32 armv7_pmnc_enable_counter(unsigned int cnt) | |
47 | +{ | |
48 | + u32 val; | |
49 | + | |
50 | + if (cnt >= CNTMAX) { | |
51 | + printk(KERN_ERR "oprofile: CPU%u enabling wrong PMNC counter" | |
52 | + " %d\n", smp_processor_id(), cnt); | |
53 | + return -1; | |
54 | + } | |
55 | + | |
56 | + if (cnt == CCNT) | |
57 | + val = CNTENS_C; | |
58 | + else | |
59 | + val = (1 << (cnt - CNT0)); | |
60 | + | |
61 | + val &= CNTENS_MASK; | |
62 | + asm volatile("mcr p15, 0, %0, c9, c12, 1" : : "r" (val)); | |
63 | + | |
64 | + return cnt; | |
65 | +} | |
66 | + | |
67 | +static inline u32 armv7_pmnc_disable_counter(unsigned int cnt) | |
68 | +{ | |
69 | + u32 val; | |
70 | + | |
71 | + if (cnt >= CNTMAX) { | |
72 | + printk(KERN_ERR "oprofile: CPU%u disabling wrong PMNC counter" | |
73 | + " %d\n", smp_processor_id(), cnt); | |
74 | + return -1; | |
75 | + } | |
76 | + | |
77 | + if (cnt == CCNT) | |
78 | + val = CNTENC_C; | |
79 | + else | |
80 | + val = (1 << (cnt - CNT0)); | |
81 | + | |
82 | + val &= CNTENC_MASK; | |
83 | + asm volatile("mcr p15, 0, %0, c9, c12, 2" : : "r" (val)); | |
84 | + | |
85 | + return cnt; | |
86 | +} | |
87 | + | |
88 | +static inline u32 armv7_pmnc_enable_intens(unsigned int cnt) | |
89 | +{ | |
90 | + u32 val; | |
91 | + | |
92 | + if (cnt >= CNTMAX) { | |
93 | + printk(KERN_ERR "oprofile: CPU%u enabling wrong PMNC counter" | |
94 | + " interrupt enable %d\n", smp_processor_id(), cnt); | |
95 | + return -1; | |
96 | + } | |
97 | + | |
98 | + if (cnt == CCNT) | |
99 | + val = INTENS_C; | |
100 | + else | |
101 | + val = (1 << (cnt - CNT0)); | |
102 | + | |
103 | + val &= INTENS_MASK; | |
104 | + asm volatile("mcr p15, 0, %0, c9, c14, 1" : : "r" (val)); | |
105 | + | |
106 | + return cnt; | |
107 | +} | |
108 | + | |
109 | +static inline u32 armv7_pmnc_getreset_flags(void) | |
110 | +{ | |
111 | + u32 val; | |
112 | + | |
113 | + /* Read */ | |
114 | + asm volatile("mrc p15, 0, %0, c9, c12, 3" : "=r" (val)); | |
115 | + | |
116 | + /* Write to clear flags */ | |
117 | + val &= FLAG_MASK; | |
118 | + asm volatile("mcr p15, 0, %0, c9, c12, 3" : : "r" (val)); | |
119 | + | |
120 | + return val; | |
121 | +} | |
122 | + | |
123 | +static inline int armv7_pmnc_select_counter(unsigned int cnt) | |
124 | +{ | |
125 | + u32 val; | |
126 | + | |
127 | + if ((cnt == CCNT) || (cnt >= CNTMAX)) { | |
128 | + printk(KERN_ERR "oprofile: CPU%u selecting wrong PMNC counteri" | |
129 | + " %d\n", smp_processor_id(), cnt); | |
130 | + return -1; | |
131 | + } | |
132 | + | |
133 | + val = (cnt - CNT0) & SELECT_MASK; | |
134 | + asm volatile("mcr p15, 0, %0, c9, c12, 5" : : "r" (val)); | |
135 | + | |
136 | + return cnt; | |
137 | +} | |
138 | + | |
139 | +static inline void armv7_pmnc_write_evtsel(unsigned int cnt, u32 val) | |
140 | +{ | |
141 | + if (armv7_pmnc_select_counter(cnt) == cnt) { | |
142 | + val &= EVTSEL_MASK; | |
143 | + asm volatile("mcr p15, 0, %0, c9, c13, 1" : : "r" (val)); | |
144 | + } | |
145 | +} | |
146 | + | |
147 | +static void armv7_pmnc_reset_counter(unsigned int cnt) | |
148 | +{ | |
149 | + u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), cnt); | |
150 | + u32 val = -(u32)counter_config[cpu_cnt].count; | |
151 | + | |
152 | + switch (cnt) { | |
153 | + case CCNT: | |
154 | + armv7_pmnc_disable_counter(cnt); | |
155 | + | |
156 | + asm volatile("mcr p15, 0, %0, c9, c13, 0" : : "r" (val)); | |
157 | + | |
158 | + if (cnt_en[cnt] != 0) | |
159 | + armv7_pmnc_enable_counter(cnt); | |
160 | + | |
161 | + break; | |
162 | + | |
163 | + case CNT0: | |
164 | + case CNT1: | |
165 | + case CNT2: | |
166 | + case CNT3: | |
167 | + armv7_pmnc_disable_counter(cnt); | |
168 | + | |
169 | + if (armv7_pmnc_select_counter(cnt) == cnt) | |
170 | + asm volatile("mcr p15, 0, %0, c9, c13, 2" : : "r" (val)); | |
171 | + | |
172 | + if (cnt_en[cnt] != 0) | |
173 | + armv7_pmnc_enable_counter(cnt); | |
174 | + | |
175 | + break; | |
176 | + | |
177 | + default: | |
178 | + printk(KERN_ERR "oprofile: CPU%u resetting wrong PMNC counter" | |
179 | + " %d\n", smp_processor_id(), cnt); | |
180 | + break; | |
181 | + } | |
182 | +} | |
183 | + | |
184 | +int armv7_setup_pmnc(void) | |
185 | +{ | |
186 | + unsigned int cnt; | |
187 | + | |
188 | + if (armv7_pmnc_read() & PMNC_E) { | |
189 | + printk(KERN_ERR "oprofile: CPU%u PMNC still enabled when setup" | |
190 | + " new event counter.\n", smp_processor_id()); | |
191 | + return -EBUSY; | |
192 | + } | |
193 | + | |
194 | + /* | |
195 | + * Initialize & Reset PMNC: C bit, D bit and P bit. | |
196 | + * Note: Using a slower count for CCNT (D bit: divide by 64) results | |
197 | + * in a more stable system | |
198 | + */ | |
199 | + armv7_pmnc_write(PMNC_P | PMNC_C | PMNC_D); | |
200 | + | |
201 | + | |
202 | + for (cnt = CCNT; cnt < CNTMAX; cnt++) { | |
203 | + unsigned long event; | |
204 | + u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), cnt); | |
205 | + | |
206 | + /* | |
207 | + * Disable counter | |
208 | + */ | |
209 | + armv7_pmnc_disable_counter(cnt); | |
210 | + cnt_en[cnt] = 0; | |
211 | + | |
212 | + if (!counter_config[cpu_cnt].enabled) | |
213 | + continue; | |
214 | + | |
215 | + event = counter_config[cpu_cnt].event & 255; | |
216 | + | |
217 | + /* | |
218 | + * Set event (if destined for PMNx counters) | |
219 | + * We don't need to set the event if it's a cycle count | |
220 | + */ | |
221 | + if (cnt != CCNT) | |
222 | + armv7_pmnc_write_evtsel(cnt, event); | |
223 | + | |
224 | + /* | |
225 | + * Enable interrupt for this counter | |
226 | + */ | |
227 | + armv7_pmnc_enable_intens(cnt); | |
228 | + | |
229 | + /* | |
230 | + * Reset counter | |
231 | + */ | |
232 | + armv7_pmnc_reset_counter(cnt); | |
233 | + | |
234 | + /* | |
235 | + * Enable counter | |
236 | + */ | |
237 | + armv7_pmnc_enable_counter(cnt); | |
238 | + cnt_en[cnt] = 1; | |
239 | + } | |
240 | + | |
241 | + return 0; | |
242 | +} | |
243 | + | |
244 | +static inline void armv7_start_pmnc(void) | |
245 | +{ | |
246 | + armv7_pmnc_write(armv7_pmnc_read() | PMNC_E); | |
247 | +} | |
248 | + | |
249 | +static inline void armv7_stop_pmnc(void) | |
250 | +{ | |
251 | + armv7_pmnc_write(armv7_pmnc_read() & ~PMNC_E); | |
252 | +} | |
253 | + | |
254 | +/* | |
255 | + * CPU counters' IRQ handler (one IRQ per CPU) | |
256 | + */ | |
257 | +static irqreturn_t armv7_pmnc_interrupt(int irq, void *arg) | |
258 | +{ | |
259 | + struct pt_regs *regs = get_irq_regs(); | |
260 | + unsigned int cnt; | |
261 | + u32 flags; | |
262 | + | |
263 | + | |
264 | + /* | |
265 | + * Stop IRQ generation | |
266 | + */ | |
267 | + armv7_stop_pmnc(); | |
268 | + | |
269 | + /* | |
270 | + * Get and reset overflow status flags | |
271 | + */ | |
272 | + flags = armv7_pmnc_getreset_flags(); | |
273 | + | |
274 | + /* | |
275 | + * Cycle counter | |
276 | + */ | |
277 | + if (flags & FLAG_C) { | |
278 | + u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), CCNT); | |
279 | + armv7_pmnc_reset_counter(CCNT); | |
280 | + oprofile_add_sample(regs, cpu_cnt); | |
281 | + } | |
282 | + | |
283 | + /* | |
284 | + * PMNC counters 0:3 | |
285 | + */ | |
286 | + for (cnt = CNT0; cnt < CNTMAX; cnt++) { | |
287 | + if (flags & (1 << (cnt - CNT0))) { | |
288 | + u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), cnt); | |
289 | + armv7_pmnc_reset_counter(cnt); | |
290 | + oprofile_add_sample(regs, cpu_cnt); | |
291 | + } | |
292 | + } | |
293 | + | |
294 | + /* | |
295 | + * Allow IRQ generation | |
296 | + */ | |
297 | + armv7_start_pmnc(); | |
298 | + | |
299 | + return IRQ_HANDLED; | |
300 | +} | |
301 | + | |
302 | +int armv7_request_interrupts(int *irqs, int nr) | |
303 | +{ | |
304 | + unsigned int i; | |
305 | + int ret = 0; | |
306 | + | |
307 | + for (i = 0; i < nr; i++) { | |
308 | + ret = request_irq(irqs[i], armv7_pmnc_interrupt, | |
309 | + IRQF_DISABLED, "CP15 PMNC", NULL); | |
310 | + if (ret != 0) { | |
311 | + printk(KERN_ERR "oprofile: unable to request IRQ%u" | |
312 | + " for ARMv7\n", | |
313 | + irqs[i]); | |
314 | + break; | |
315 | + } | |
316 | + } | |
317 | + | |
318 | + if (i != nr) | |
319 | + while (i-- != 0) | |
320 | + free_irq(irqs[i], NULL); | |
321 | + | |
322 | + return ret; | |
323 | +} | |
324 | + | |
325 | +void armv7_release_interrupts(int *irqs, int nr) | |
326 | +{ | |
327 | + unsigned int i; | |
328 | + | |
329 | + for (i = 0; i < nr; i++) | |
330 | + free_irq(irqs[i], NULL); | |
331 | +} | |
332 | + | |
333 | +#ifdef DEBUG | |
334 | +static void armv7_pmnc_dump_regs(void) | |
335 | +{ | |
336 | + u32 val; | |
337 | + unsigned int cnt; | |
338 | + | |
339 | + printk(KERN_INFO "PMNC registers dump:\n"); | |
340 | + | |
341 | + asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r" (val)); | |
342 | + printk(KERN_INFO "PMNC =0x%08x\n", val); | |
343 | + | |
344 | + asm volatile("mrc p15, 0, %0, c9, c12, 1" : "=r" (val)); | |
345 | + printk(KERN_INFO "CNTENS=0x%08x\n", val); | |
346 | + | |
347 | + asm volatile("mrc p15, 0, %0, c9, c14, 1" : "=r" (val)); | |
348 | + printk(KERN_INFO "INTENS=0x%08x\n", val); | |
349 | + | |
350 | + asm volatile("mrc p15, 0, %0, c9, c12, 3" : "=r" (val)); | |
351 | + printk(KERN_INFO "FLAGS =0x%08x\n", val); | |
352 | + | |
353 | + asm volatile("mrc p15, 0, %0, c9, c12, 5" : "=r" (val)); | |
354 | + printk(KERN_INFO "SELECT=0x%08x\n", val); | |
355 | + | |
356 | + asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r" (val)); | |
357 | + printk(KERN_INFO "CCNT =0x%08x\n", val); | |
358 | + | |
359 | + for (cnt = CNT0; cnt < CNTMAX; cnt++) { | |
360 | + armv7_pmnc_select_counter(cnt); | |
361 | + asm volatile("mrc p15, 0, %0, c9, c13, 2" : "=r" (val)); | |
362 | + printk(KERN_INFO "CNT[%d] count =0x%08x\n", cnt-CNT0, val); | |
363 | + asm volatile("mrc p15, 0, %0, c9, c13, 1" : "=r" (val)); | |
364 | + printk(KERN_INFO "CNT[%d] evtsel=0x%08x\n", cnt-CNT0, val); | |
365 | + } | |
366 | +} | |
367 | +#endif | |
368 | + | |
369 | + | |
370 | +static int irqs[] = { | |
371 | +#ifdef CONFIG_ARCH_OMAP3 | |
372 | + INT_34XX_BENCH_MPU_EMUL, | |
373 | +#endif | |
374 | +}; | |
375 | + | |
376 | +static void armv7_pmnc_stop(void) | |
377 | +{ | |
378 | +#ifdef DEBUG | |
379 | + armv7_pmnc_dump_regs(); | |
380 | +#endif | |
381 | + armv7_stop_pmnc(); | |
382 | + armv7_release_interrupts(irqs, ARRAY_SIZE(irqs)); | |
383 | +} | |
384 | + | |
385 | +static int armv7_pmnc_start(void) | |
386 | +{ | |
387 | + int ret; | |
388 | + | |
389 | +#ifdef DEBUG | |
390 | + armv7_pmnc_dump_regs(); | |
391 | +#endif | |
392 | + ret = armv7_request_interrupts(irqs, ARRAY_SIZE(irqs)); | |
393 | + if (ret >= 0) | |
394 | + armv7_start_pmnc(); | |
395 | + | |
396 | + return ret; | |
397 | +} | |
398 | + | |
399 | +static int armv7_detect_pmnc(void) | |
400 | +{ | |
401 | + return 0; | |
402 | +} | |
403 | + | |
404 | +struct op_arm_model_spec op_armv7_spec = { | |
405 | + .init = armv7_detect_pmnc, | |
406 | + .num_counters = 5, | |
407 | + .setup_ctrs = armv7_setup_pmnc, | |
408 | + .start = armv7_pmnc_start, | |
409 | + .stop = armv7_pmnc_stop, | |
410 | + .name = "arm/armv7", | |
411 | +}; |
arch/arm/oprofile/op_model_v7.h
1 | +/** | |
2 | + * op_model_v7.h | |
3 | + * ARM v7 (Cortex A8) Event Monitor Driver | |
4 | + * | |
5 | + * Copyright 2008 Jean Pihet <jpihet@mvista.com> | |
6 | + * Copyright 2004 ARM SMP Development Team | |
7 | + * Copyright 2000-2004 Deepak Saxena <dsaxena@mvista.com> | |
8 | + * Copyright 2000-2004 MontaVista Software Inc | |
9 | + * Copyright 2004 Dave Jiang <dave.jiang@intel.com> | |
10 | + * Copyright 2004 Intel Corporation | |
11 | + * Copyright 2004 Zwane Mwaikambo <zwane@arm.linux.org.uk> | |
12 | + * Copyright 2004 Oprofile Authors | |
13 | + * | |
14 | + * Read the file COPYING | |
15 | + * | |
16 | + * This program is free software; you can redistribute it and/or modify | |
17 | + * it under the terms of the GNU General Public License version 2 as | |
18 | + * published by the Free Software Foundation. | |
19 | + */ | |
20 | +#ifndef OP_MODEL_V7_H | |
21 | +#define OP_MODEL_V7_H | |
22 | + | |
23 | +/* | |
24 | + * Per-CPU PMNC: config reg | |
25 | + */ | |
26 | +#define PMNC_E (1 << 0) /* Enable all counters */ | |
27 | +#define PMNC_P (1 << 1) /* Reset all counters */ | |
28 | +#define PMNC_C (1 << 2) /* Cycle counter reset */ | |
29 | +#define PMNC_D (1 << 3) /* CCNT counts every 64th cpu cycle */ | |
30 | +#define PMNC_X (1 << 4) /* Export to ETM */ | |
31 | +#define PMNC_DP (1 << 5) /* Disable CCNT if non-invasive debug*/ | |
32 | +#define PMNC_MASK 0x3f /* Mask for writable bits */ | |
33 | + | |
34 | +/* | |
35 | + * Available counters | |
36 | + */ | |
37 | +#define CCNT 0 | |
38 | +#define CNT0 1 | |
39 | +#define CNT1 2 | |
40 | +#define CNT2 3 | |
41 | +#define CNT3 4 | |
42 | +#define CNTMAX 5 | |
43 | + | |
44 | +#define CPU_COUNTER(cpu, counter) ((cpu) * CNTMAX + (counter)) | |
45 | + | |
46 | +/* | |
47 | + * CNTENS: counters enable reg | |
48 | + */ | |
49 | +#define CNTENS_P0 (1 << 0) | |
50 | +#define CNTENS_P1 (1 << 1) | |
51 | +#define CNTENS_P2 (1 << 2) | |
52 | +#define CNTENS_P3 (1 << 3) | |
53 | +#define CNTENS_C (1 << 31) | |
54 | +#define CNTENS_MASK 0x8000000f /* Mask for writable bits */ | |
55 | + | |
56 | +/* | |
57 | + * CNTENC: counters disable reg | |
58 | + */ | |
59 | +#define CNTENC_P0 (1 << 0) | |
60 | +#define CNTENC_P1 (1 << 1) | |
61 | +#define CNTENC_P2 (1 << 2) | |
62 | +#define CNTENC_P3 (1 << 3) | |
63 | +#define CNTENC_C (1 << 31) | |
64 | +#define CNTENC_MASK 0x8000000f /* Mask for writable bits */ | |
65 | + | |
66 | +/* | |
67 | + * INTENS: counters overflow interrupt enable reg | |
68 | + */ | |
69 | +#define INTENS_P0 (1 << 0) | |
70 | +#define INTENS_P1 (1 << 1) | |
71 | +#define INTENS_P2 (1 << 2) | |
72 | +#define INTENS_P3 (1 << 3) | |
73 | +#define INTENS_C (1 << 31) | |
74 | +#define INTENS_MASK 0x8000000f /* Mask for writable bits */ | |
75 | + | |
76 | +/* | |
77 | + * EVTSEL: Event selection reg | |
78 | + */ | |
79 | +#define EVTSEL_MASK 0x7f /* Mask for writable bits */ | |
80 | + | |
81 | +/* | |
82 | + * SELECT: Counter selection reg | |
83 | + */ | |
84 | +#define SELECT_MASK 0x1f /* Mask for writable bits */ | |
85 | + | |
86 | +/* | |
87 | + * FLAG: counters overflow flag status reg | |
88 | + */ | |
89 | +#define FLAG_P0 (1 << 0) | |
90 | +#define FLAG_P1 (1 << 1) | |
91 | +#define FLAG_P2 (1 << 2) | |
92 | +#define FLAG_P3 (1 << 3) | |
93 | +#define FLAG_C (1 << 31) | |
94 | +#define FLAG_MASK 0x8000000f /* Mask for writable bits */ | |
95 | + | |
96 | + | |
97 | +int armv7_setup_pmu(void); | |
98 | +int armv7_start_pmu(void); | |
99 | +int armv7_stop_pmu(void); | |
100 | +int armv7_request_interrupts(int *, int); | |
101 | +void armv7_release_interrupts(int *, int); | |
102 | + | |
103 | +#endif |
arch/arm/plat-omap/include/mach/irqs.h