Commit | Line | Data |
---|---|---|
d7ac4e28 JP |
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> | |
1618fdd9 | 14 | #include <linux/err.h> |
d7ac4e28 JP |
15 | #include <linux/oprofile.h> |
16 | #include <linux/interrupt.h> | |
17 | #include <linux/irq.h> | |
18 | #include <linux/smp.h> | |
19 | ||
1618fdd9 JI |
20 | #include <asm/pmu.h> |
21 | ||
d7ac4e28 JP |
22 | #include "op_counter.h" |
23 | #include "op_arm_model.h" | |
24 | #include "op_model_v7.h" | |
25 | ||
26 | /* #define DEBUG */ | |
27 | ||
28 | ||
29 | /* | |
30 | * ARM V7 PMNC support | |
31 | */ | |
32 | ||
33 | static u32 cnt_en[CNTMAX]; | |
34 | ||
35 | static inline void armv7_pmnc_write(u32 val) | |
36 | { | |
37 | val &= PMNC_MASK; | |
38 | asm volatile("mcr p15, 0, %0, c9, c12, 0" : : "r" (val)); | |
39 | } | |
40 | ||
41 | static inline u32 armv7_pmnc_read(void) | |
42 | { | |
43 | u32 val; | |
44 | ||
45 | asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r" (val)); | |
46 | return val; | |
47 | } | |
48 | ||
49 | static inline u32 armv7_pmnc_enable_counter(unsigned int cnt) | |
50 | { | |
51 | u32 val; | |
52 | ||
53 | if (cnt >= CNTMAX) { | |
54 | printk(KERN_ERR "oprofile: CPU%u enabling wrong PMNC counter" | |
55 | " %d\n", smp_processor_id(), cnt); | |
56 | return -1; | |
57 | } | |
58 | ||
59 | if (cnt == CCNT) | |
60 | val = CNTENS_C; | |
61 | else | |
62 | val = (1 << (cnt - CNT0)); | |
63 | ||
64 | val &= CNTENS_MASK; | |
65 | asm volatile("mcr p15, 0, %0, c9, c12, 1" : : "r" (val)); | |
66 | ||
67 | return cnt; | |
68 | } | |
69 | ||
70 | static inline u32 armv7_pmnc_disable_counter(unsigned int cnt) | |
71 | { | |
72 | u32 val; | |
73 | ||
74 | if (cnt >= CNTMAX) { | |
75 | printk(KERN_ERR "oprofile: CPU%u disabling wrong PMNC counter" | |
76 | " %d\n", smp_processor_id(), cnt); | |
77 | return -1; | |
78 | } | |
79 | ||
80 | if (cnt == CCNT) | |
81 | val = CNTENC_C; | |
82 | else | |
83 | val = (1 << (cnt - CNT0)); | |
84 | ||
85 | val &= CNTENC_MASK; | |
86 | asm volatile("mcr p15, 0, %0, c9, c12, 2" : : "r" (val)); | |
87 | ||
88 | return cnt; | |
89 | } | |
90 | ||
91 | static inline u32 armv7_pmnc_enable_intens(unsigned int cnt) | |
92 | { | |
93 | u32 val; | |
94 | ||
95 | if (cnt >= CNTMAX) { | |
96 | printk(KERN_ERR "oprofile: CPU%u enabling wrong PMNC counter" | |
97 | " interrupt enable %d\n", smp_processor_id(), cnt); | |
98 | return -1; | |
99 | } | |
100 | ||
101 | if (cnt == CCNT) | |
102 | val = INTENS_C; | |
103 | else | |
104 | val = (1 << (cnt - CNT0)); | |
105 | ||
106 | val &= INTENS_MASK; | |
107 | asm volatile("mcr p15, 0, %0, c9, c14, 1" : : "r" (val)); | |
108 | ||
109 | return cnt; | |
110 | } | |
111 | ||
112 | static inline u32 armv7_pmnc_getreset_flags(void) | |
113 | { | |
114 | u32 val; | |
115 | ||
116 | /* Read */ | |
117 | asm volatile("mrc p15, 0, %0, c9, c12, 3" : "=r" (val)); | |
118 | ||
119 | /* Write to clear flags */ | |
120 | val &= FLAG_MASK; | |
121 | asm volatile("mcr p15, 0, %0, c9, c12, 3" : : "r" (val)); | |
122 | ||
123 | return val; | |
124 | } | |
125 | ||
126 | static inline int armv7_pmnc_select_counter(unsigned int cnt) | |
127 | { | |
128 | u32 val; | |
129 | ||
130 | if ((cnt == CCNT) || (cnt >= CNTMAX)) { | |
131 | printk(KERN_ERR "oprofile: CPU%u selecting wrong PMNC counteri" | |
132 | " %d\n", smp_processor_id(), cnt); | |
133 | return -1; | |
134 | } | |
135 | ||
136 | val = (cnt - CNT0) & SELECT_MASK; | |
137 | asm volatile("mcr p15, 0, %0, c9, c12, 5" : : "r" (val)); | |
138 | ||
139 | return cnt; | |
140 | } | |
141 | ||
142 | static inline void armv7_pmnc_write_evtsel(unsigned int cnt, u32 val) | |
143 | { | |
144 | if (armv7_pmnc_select_counter(cnt) == cnt) { | |
145 | val &= EVTSEL_MASK; | |
146 | asm volatile("mcr p15, 0, %0, c9, c13, 1" : : "r" (val)); | |
147 | } | |
148 | } | |
149 | ||
150 | static void armv7_pmnc_reset_counter(unsigned int cnt) | |
151 | { | |
152 | u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), cnt); | |
153 | u32 val = -(u32)counter_config[cpu_cnt].count; | |
154 | ||
155 | switch (cnt) { | |
156 | case CCNT: | |
157 | armv7_pmnc_disable_counter(cnt); | |
158 | ||
159 | asm volatile("mcr p15, 0, %0, c9, c13, 0" : : "r" (val)); | |
160 | ||
161 | if (cnt_en[cnt] != 0) | |
162 | armv7_pmnc_enable_counter(cnt); | |
163 | ||
164 | break; | |
165 | ||
166 | case CNT0: | |
167 | case CNT1: | |
168 | case CNT2: | |
169 | case CNT3: | |
170 | armv7_pmnc_disable_counter(cnt); | |
171 | ||
172 | if (armv7_pmnc_select_counter(cnt) == cnt) | |
173 | asm volatile("mcr p15, 0, %0, c9, c13, 2" : : "r" (val)); | |
174 | ||
175 | if (cnt_en[cnt] != 0) | |
176 | armv7_pmnc_enable_counter(cnt); | |
177 | ||
178 | break; | |
179 | ||
180 | default: | |
181 | printk(KERN_ERR "oprofile: CPU%u resetting wrong PMNC counter" | |
182 | " %d\n", smp_processor_id(), cnt); | |
183 | break; | |
184 | } | |
185 | } | |
186 | ||
187 | int armv7_setup_pmnc(void) | |
188 | { | |
189 | unsigned int cnt; | |
190 | ||
191 | if (armv7_pmnc_read() & PMNC_E) { | |
192 | printk(KERN_ERR "oprofile: CPU%u PMNC still enabled when setup" | |
193 | " new event counter.\n", smp_processor_id()); | |
194 | return -EBUSY; | |
195 | } | |
196 | ||
3321c2bc JP |
197 | /* Initialize & Reset PMNC: C bit and P bit */ |
198 | armv7_pmnc_write(PMNC_P | PMNC_C); | |
d7ac4e28 JP |
199 | |
200 | ||
201 | for (cnt = CCNT; cnt < CNTMAX; cnt++) { | |
202 | unsigned long event; | |
203 | u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), cnt); | |
204 | ||
205 | /* | |
206 | * Disable counter | |
207 | */ | |
208 | armv7_pmnc_disable_counter(cnt); | |
209 | cnt_en[cnt] = 0; | |
210 | ||
211 | if (!counter_config[cpu_cnt].enabled) | |
212 | continue; | |
213 | ||
214 | event = counter_config[cpu_cnt].event & 255; | |
215 | ||
216 | /* | |
217 | * Set event (if destined for PMNx counters) | |
218 | * We don't need to set the event if it's a cycle count | |
219 | */ | |
220 | if (cnt != CCNT) | |
221 | armv7_pmnc_write_evtsel(cnt, event); | |
222 | ||
223 | /* | |
224 | * Enable interrupt for this counter | |
225 | */ | |
226 | armv7_pmnc_enable_intens(cnt); | |
227 | ||
228 | /* | |
229 | * Reset counter | |
230 | */ | |
231 | armv7_pmnc_reset_counter(cnt); | |
232 | ||
233 | /* | |
234 | * Enable counter | |
235 | */ | |
236 | armv7_pmnc_enable_counter(cnt); | |
237 | cnt_en[cnt] = 1; | |
238 | } | |
239 | ||
240 | return 0; | |
241 | } | |
242 | ||
243 | static inline void armv7_start_pmnc(void) | |
244 | { | |
245 | armv7_pmnc_write(armv7_pmnc_read() | PMNC_E); | |
246 | } | |
247 | ||
248 | static inline void armv7_stop_pmnc(void) | |
249 | { | |
250 | armv7_pmnc_write(armv7_pmnc_read() & ~PMNC_E); | |
251 | } | |
252 | ||
253 | /* | |
254 | * CPU counters' IRQ handler (one IRQ per CPU) | |
255 | */ | |
256 | static irqreturn_t armv7_pmnc_interrupt(int irq, void *arg) | |
257 | { | |
258 | struct pt_regs *regs = get_irq_regs(); | |
259 | unsigned int cnt; | |
260 | u32 flags; | |
261 | ||
262 | ||
263 | /* | |
264 | * Stop IRQ generation | |
265 | */ | |
266 | armv7_stop_pmnc(); | |
267 | ||
268 | /* | |
269 | * Get and reset overflow status flags | |
270 | */ | |
271 | flags = armv7_pmnc_getreset_flags(); | |
272 | ||
273 | /* | |
274 | * Cycle counter | |
275 | */ | |
276 | if (flags & FLAG_C) { | |
277 | u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), CCNT); | |
278 | armv7_pmnc_reset_counter(CCNT); | |
279 | oprofile_add_sample(regs, cpu_cnt); | |
280 | } | |
281 | ||
282 | /* | |
283 | * PMNC counters 0:3 | |
284 | */ | |
285 | for (cnt = CNT0; cnt < CNTMAX; cnt++) { | |
286 | if (flags & (1 << (cnt - CNT0))) { | |
287 | u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), cnt); | |
288 | armv7_pmnc_reset_counter(cnt); | |
289 | oprofile_add_sample(regs, cpu_cnt); | |
290 | } | |
291 | } | |
292 | ||
293 | /* | |
294 | * Allow IRQ generation | |
295 | */ | |
296 | armv7_start_pmnc(); | |
297 | ||
298 | return IRQ_HANDLED; | |
299 | } | |
300 | ||
1618fdd9 | 301 | int armv7_request_interrupts(const int *irqs, int nr) |
d7ac4e28 JP |
302 | { |
303 | unsigned int i; | |
304 | int ret = 0; | |
305 | ||
306 | for (i = 0; i < nr; i++) { | |
307 | ret = request_irq(irqs[i], armv7_pmnc_interrupt, | |
308 | IRQF_DISABLED, "CP15 PMNC", NULL); | |
309 | if (ret != 0) { | |
310 | printk(KERN_ERR "oprofile: unable to request IRQ%u" | |
311 | " for ARMv7\n", | |
312 | irqs[i]); | |
313 | break; | |
314 | } | |
315 | } | |
316 | ||
317 | if (i != nr) | |
318 | while (i-- != 0) | |
319 | free_irq(irqs[i], NULL); | |
320 | ||
321 | return ret; | |
322 | } | |
323 | ||
1618fdd9 | 324 | void armv7_release_interrupts(const int *irqs, int nr) |
d7ac4e28 JP |
325 | { |
326 | unsigned int i; | |
327 | ||
328 | for (i = 0; i < nr; i++) | |
329 | free_irq(irqs[i], NULL); | |
330 | } | |
331 | ||
332 | #ifdef DEBUG | |
333 | static void armv7_pmnc_dump_regs(void) | |
334 | { | |
335 | u32 val; | |
336 | unsigned int cnt; | |
337 | ||
338 | printk(KERN_INFO "PMNC registers dump:\n"); | |
339 | ||
340 | asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r" (val)); | |
341 | printk(KERN_INFO "PMNC =0x%08x\n", val); | |
342 | ||
343 | asm volatile("mrc p15, 0, %0, c9, c12, 1" : "=r" (val)); | |
344 | printk(KERN_INFO "CNTENS=0x%08x\n", val); | |
345 | ||
346 | asm volatile("mrc p15, 0, %0, c9, c14, 1" : "=r" (val)); | |
347 | printk(KERN_INFO "INTENS=0x%08x\n", val); | |
348 | ||
349 | asm volatile("mrc p15, 0, %0, c9, c12, 3" : "=r" (val)); | |
350 | printk(KERN_INFO "FLAGS =0x%08x\n", val); | |
351 | ||
352 | asm volatile("mrc p15, 0, %0, c9, c12, 5" : "=r" (val)); | |
353 | printk(KERN_INFO "SELECT=0x%08x\n", val); | |
354 | ||
355 | asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r" (val)); | |
356 | printk(KERN_INFO "CCNT =0x%08x\n", val); | |
357 | ||
358 | for (cnt = CNT0; cnt < CNTMAX; cnt++) { | |
359 | armv7_pmnc_select_counter(cnt); | |
360 | asm volatile("mrc p15, 0, %0, c9, c13, 2" : "=r" (val)); | |
361 | printk(KERN_INFO "CNT[%d] count =0x%08x\n", cnt-CNT0, val); | |
362 | asm volatile("mrc p15, 0, %0, c9, c13, 1" : "=r" (val)); | |
363 | printk(KERN_INFO "CNT[%d] evtsel=0x%08x\n", cnt-CNT0, val); | |
364 | } | |
365 | } | |
366 | #endif | |
367 | ||
1618fdd9 | 368 | static const struct pmu_irqs *pmu_irqs; |
d7ac4e28 JP |
369 | |
370 | static void armv7_pmnc_stop(void) | |
371 | { | |
372 | #ifdef DEBUG | |
373 | armv7_pmnc_dump_regs(); | |
374 | #endif | |
375 | armv7_stop_pmnc(); | |
1618fdd9 JI |
376 | armv7_release_interrupts(pmu_irqs->irqs, pmu_irqs->num_irqs); |
377 | release_pmu(pmu_irqs); | |
378 | pmu_irqs = NULL; | |
d7ac4e28 JP |
379 | } |
380 | ||
381 | static int armv7_pmnc_start(void) | |
382 | { | |
383 | int ret; | |
384 | ||
1618fdd9 JI |
385 | pmu_irqs = reserve_pmu(); |
386 | if (IS_ERR(pmu_irqs)) | |
387 | return PTR_ERR(pmu_irqs); | |
388 | ||
d7ac4e28 JP |
389 | #ifdef DEBUG |
390 | armv7_pmnc_dump_regs(); | |
391 | #endif | |
1618fdd9 JI |
392 | ret = armv7_request_interrupts(pmu_irqs->irqs, pmu_irqs->num_irqs); |
393 | if (ret >= 0) { | |
d7ac4e28 | 394 | armv7_start_pmnc(); |
1618fdd9 JI |
395 | } else { |
396 | release_pmu(pmu_irqs); | |
397 | pmu_irqs = NULL; | |
398 | } | |
d7ac4e28 JP |
399 | |
400 | return ret; | |
401 | } | |
402 | ||
403 | static int armv7_detect_pmnc(void) | |
404 | { | |
405 | return 0; | |
406 | } | |
407 | ||
408 | struct op_arm_model_spec op_armv7_spec = { | |
409 | .init = armv7_detect_pmnc, | |
410 | .num_counters = 5, | |
411 | .setup_ctrs = armv7_setup_pmnc, | |
412 | .start = armv7_pmnc_start, | |
413 | .stop = armv7_pmnc_stop, | |
414 | .name = "arm/armv7", | |
415 | }; |