aboutsummaryrefslogtreecommitdiffstats
path: root/arch/mips
diff options
context:
space:
mode:
authorDeng-Cheng Zhu <dengcheng.zhu@gmail.com>2010-10-12 07:37:22 -0400
committerRalf Baechle <ralf@linux-mips.org>2010-10-29 14:08:48 -0400
commit14f7001284185bffeb796a181664906f7160f593 (patch)
tree5524b4a8bfdee982b2d5db68053ae0a8dd599b57 /arch/mips
parent7f788d2d53085815d474559cd51ef1f38b0a9bb8 (diff)
MIPS: add support for hardware performance events (skeleton)
This patch provides the skeleton of the HW perf event support. To enable this feature, we can not choose the SMTC kernel; Oprofile should be disabled; kernel performance events be selected. Then we can enable it in Kernel type menu. Oprofile for MIPS platforms initializes irq at arch init time. Currently we do not change this logic to allow PMU reservation. If a platform has EIC, we can use the irq base and perf counter irq offset defines for the interrupt controller in specific init_hw_perf_events(). Based on this skeleton patch, the 3 different kinds of MIPS PMU, namely, mipsxx/loongson2/rm9000, can be supported by adding corresponding lower level C files at the bottom. The suggested names of these files are perf_event_mipsxx.c/perf_event_loongson2.c/perf_event_rm9000.c. So, for example, we can do this by adding "#include perf_event_mipsxx.c" at the bottom of perf_event.c. In addition, PMUs with 64bit counters are also considered in this patch. Signed-off-by: Deng-Cheng Zhu <dengcheng.zhu@gmail.com> To: linux-mips@linux-mips.org Cc: a.p.zijlstra@chello.nl Cc: paulus@samba.org Cc: mingo@elte.hu Cc: acme@redhat.com Cc: jamie.iles@picochip.com Cc: ddaney@caviumnetworks.com Cc: matt@console-pimps.org Patchwork: https://patchwork.linux-mips.org/patch/1688/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'arch/mips')
-rw-r--r--arch/mips/Kconfig8
-rw-r--r--arch/mips/include/asm/perf_event.h25
-rw-r--r--arch/mips/kernel/Makefile2
-rw-r--r--arch/mips/kernel/perf_event.c488
4 files changed, 523 insertions, 0 deletions
diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index dc81f39c568e..e915750679b6 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -1935,6 +1935,14 @@ config NODES_SHIFT
1935 default "6" 1935 default "6"
1936 depends on NEED_MULTIPLE_NODES 1936 depends on NEED_MULTIPLE_NODES
1937 1937
1938config HW_PERF_EVENTS
1939 bool "Enable hardware performance counter support for perf events"
1940 depends on PERF_EVENTS && !MIPS_MT_SMTC && OPROFILE=n && CPU_MIPS32
1941 default y
1942 help
1943 Enable hardware performance counter support for perf events. If
1944 disabled, perf events will use software events only.
1945
1938source "mm/Kconfig" 1946source "mm/Kconfig"
1939 1947
1940config SMP 1948config SMP
diff --git a/arch/mips/include/asm/perf_event.h b/arch/mips/include/asm/perf_event.h
new file mode 100644
index 000000000000..e00007cf8162
--- /dev/null
+++ b/arch/mips/include/asm/perf_event.h
@@ -0,0 +1,25 @@
1/*
2 * linux/arch/mips/include/asm/perf_event.h
3 *
4 * Copyright (C) 2010 MIPS Technologies, Inc.
5 * Author: Deng-Cheng Zhu
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12#ifndef __MIPS_PERF_EVENT_H__
13#define __MIPS_PERF_EVENT_H__
14
15/*
16 * MIPS performance counters do not raise NMI upon overflow, a regular
17 * interrupt will be signaled. Hence we can do the pending perf event
18 * work at the tail of the irq handler.
19 */
20static inline void
21set_perf_event_pending(void)
22{
23}
24
25#endif /* __MIPS_PERF_EVENT_H__ */
diff --git a/arch/mips/kernel/Makefile b/arch/mips/kernel/Makefile
index 80884983270d..22b2e0e38617 100644
--- a/arch/mips/kernel/Makefile
+++ b/arch/mips/kernel/Makefile
@@ -104,4 +104,6 @@ obj-$(CONFIG_HAVE_STD_PC_SERIAL_PORT) += 8250-platform.o
104 104
105obj-$(CONFIG_MIPS_CPUFREQ) += cpufreq/ 105obj-$(CONFIG_MIPS_CPUFREQ) += cpufreq/
106 106
107obj-$(CONFIG_HW_PERF_EVENTS) += perf_event.o
108
107CPPFLAGS_vmlinux.lds := $(KBUILD_CFLAGS) 109CPPFLAGS_vmlinux.lds := $(KBUILD_CFLAGS)
diff --git a/arch/mips/kernel/perf_event.c b/arch/mips/kernel/perf_event.c
new file mode 100644
index 000000000000..eefdf60da2d7
--- /dev/null
+++ b/arch/mips/kernel/perf_event.c
@@ -0,0 +1,488 @@
1/*
2 * Linux performance counter support for MIPS.
3 *
4 * Copyright (C) 2010 MIPS Technologies, Inc.
5 * Author: Deng-Cheng Zhu
6 *
7 * This code is based on the implementation for ARM, which is in turn
8 * based on the sparc64 perf event code and the x86 code. Performance
9 * counter access is based on the MIPS Oprofile code.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
14 */
15
16#include <linux/cpumask.h>
17#include <linux/interrupt.h>
18#include <linux/smp.h>
19#include <linux/kernel.h>
20#include <linux/perf_event.h>
21#include <linux/uaccess.h>
22
23#include <asm/irq.h>
24#include <asm/irq_regs.h>
25#include <asm/stacktrace.h>
26#include <asm/time.h> /* For perf_irq */
27
28/* These are for 32bit counters. For 64bit ones, define them accordingly. */
29#define MAX_PERIOD ((1ULL << 32) - 1)
30#define VALID_COUNT 0x7fffffff
31#define TOTAL_BITS 32
32#define HIGHEST_BIT 31
33
34#define MIPS_MAX_HWEVENTS 4
35
36struct cpu_hw_events {
37 /* Array of events on this cpu. */
38 struct perf_event *events[MIPS_MAX_HWEVENTS];
39
40 /*
41 * Set the bit (indexed by the counter number) when the counter
42 * is used for an event.
43 */
44 unsigned long used_mask[BITS_TO_LONGS(MIPS_MAX_HWEVENTS)];
45
46 /*
47 * The borrowed MSB for the performance counter. A MIPS performance
48 * counter uses its bit 31 (for 32bit counters) or bit 63 (for 64bit
49 * counters) as a factor of determining whether a counter overflow
50 * should be signaled. So here we use a separate MSB for each
51 * counter to make things easy.
52 */
53 unsigned long msbs[BITS_TO_LONGS(MIPS_MAX_HWEVENTS)];
54
55 /*
56 * Software copy of the control register for each performance counter.
57 * MIPS CPUs vary in performance counters. They use this differently,
58 * and even may not use it.
59 */
60 unsigned int saved_ctrl[MIPS_MAX_HWEVENTS];
61};
62DEFINE_PER_CPU(struct cpu_hw_events, cpu_hw_events) = {
63 .saved_ctrl = {0},
64};
65
66/* The description of MIPS performance events. */
67struct mips_perf_event {
68 unsigned int event_id;
69 /*
70 * MIPS performance counters are indexed starting from 0.
71 * CNTR_EVEN indicates the indexes of the counters to be used are
72 * even numbers.
73 */
74 unsigned int cntr_mask;
75 #define CNTR_EVEN 0x55555555
76 #define CNTR_ODD 0xaaaaaaaa
77#ifdef CONFIG_MIPS_MT_SMP
78 enum {
79 T = 0,
80 V = 1,
81 P = 2,
82 } range;
83#else
84 #define T
85 #define V
86 #define P
87#endif
88};
89
90#define UNSUPPORTED_PERF_EVENT_ID 0xffffffff
91#define C(x) PERF_COUNT_HW_CACHE_##x
92
93struct mips_pmu {
94 const char *name;
95 int irq;
96 irqreturn_t (*handle_irq)(int irq, void *dev);
97 int (*handle_shared_irq)(void);
98 void (*start)(void);
99 void (*stop)(void);
100 int (*alloc_counter)(struct cpu_hw_events *cpuc,
101 struct hw_perf_event *hwc);
102 u64 (*read_counter)(unsigned int idx);
103 void (*write_counter)(unsigned int idx, u64 val);
104 void (*enable_event)(struct hw_perf_event *evt, int idx);
105 void (*disable_event)(int idx);
106 const struct mips_perf_event (*general_event_map)[PERF_COUNT_HW_MAX];
107 const struct mips_perf_event (*cache_event_map)
108 [PERF_COUNT_HW_CACHE_MAX]
109 [PERF_COUNT_HW_CACHE_OP_MAX]
110 [PERF_COUNT_HW_CACHE_RESULT_MAX];
111 unsigned int num_counters;
112};
113
114static const struct mips_pmu *mipspmu;
115
116static int
117mipspmu_event_set_period(struct perf_event *event,
118 struct hw_perf_event *hwc,
119 int idx)
120{
121 struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
122 s64 left = local64_read(&hwc->period_left);
123 s64 period = hwc->sample_period;
124 int ret = 0;
125 u64 uleft;
126 unsigned long flags;
127
128 if (unlikely(left <= -period)) {
129 left = period;
130 local64_set(&hwc->period_left, left);
131 hwc->last_period = period;
132 ret = 1;
133 }
134
135 if (unlikely(left <= 0)) {
136 left += period;
137 local64_set(&hwc->period_left, left);
138 hwc->last_period = period;
139 ret = 1;
140 }
141
142 if (left > (s64)MAX_PERIOD)
143 left = MAX_PERIOD;
144
145 local64_set(&hwc->prev_count, (u64)-left);
146
147 local_irq_save(flags);
148 uleft = (u64)(-left) & MAX_PERIOD;
149 uleft > VALID_COUNT ?
150 set_bit(idx, cpuc->msbs) : clear_bit(idx, cpuc->msbs);
151 mipspmu->write_counter(idx, (u64)(-left) & VALID_COUNT);
152 local_irq_restore(flags);
153
154 perf_event_update_userpage(event);
155
156 return ret;
157}
158
159static int mipspmu_enable(struct perf_event *event)
160{
161 struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
162 struct hw_perf_event *hwc = &event->hw;
163 int idx;
164 int err = 0;
165
166 /* To look for a free counter for this event. */
167 idx = mipspmu->alloc_counter(cpuc, hwc);
168 if (idx < 0) {
169 err = idx;
170 goto out;
171 }
172
173 /*
174 * If there is an event in the counter we are going to use then
175 * make sure it is disabled.
176 */
177 event->hw.idx = idx;
178 mipspmu->disable_event(idx);
179 cpuc->events[idx] = event;
180
181 /* Set the period for the event. */
182 mipspmu_event_set_period(event, hwc, idx);
183
184 /* Enable the event. */
185 mipspmu->enable_event(hwc, idx);
186
187 /* Propagate our changes to the userspace mapping. */
188 perf_event_update_userpage(event);
189
190out:
191 return err;
192}
193
194static void mipspmu_event_update(struct perf_event *event,
195 struct hw_perf_event *hwc,
196 int idx)
197{
198 struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
199 unsigned long flags;
200 int shift = 64 - TOTAL_BITS;
201 s64 prev_raw_count, new_raw_count;
202 s64 delta;
203
204again:
205 prev_raw_count = local64_read(&hwc->prev_count);
206 local_irq_save(flags);
207 /* Make the counter value be a "real" one. */
208 new_raw_count = mipspmu->read_counter(idx);
209 if (new_raw_count & (test_bit(idx, cpuc->msbs) << HIGHEST_BIT)) {
210 new_raw_count &= VALID_COUNT;
211 clear_bit(idx, cpuc->msbs);
212 } else
213 new_raw_count |= (test_bit(idx, cpuc->msbs) << HIGHEST_BIT);
214 local_irq_restore(flags);
215
216 if (local64_cmpxchg(&hwc->prev_count, prev_raw_count,
217 new_raw_count) != prev_raw_count)
218 goto again;
219
220 delta = (new_raw_count << shift) - (prev_raw_count << shift);
221 delta >>= shift;
222
223 local64_add(delta, &event->count);
224 local64_sub(delta, &hwc->period_left);
225
226 return;
227}
228
229static void mipspmu_disable(struct perf_event *event)
230{
231 struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
232 struct hw_perf_event *hwc = &event->hw;
233 int idx = hwc->idx;
234
235
236 WARN_ON(idx < 0 || idx >= mipspmu->num_counters);
237
238 /* We are working on a local event. */
239 mipspmu->disable_event(idx);
240
241 barrier();
242
243 mipspmu_event_update(event, hwc, idx);
244 cpuc->events[idx] = NULL;
245 clear_bit(idx, cpuc->used_mask);
246
247 perf_event_update_userpage(event);
248}
249
250static void mipspmu_unthrottle(struct perf_event *event)
251{
252 struct hw_perf_event *hwc = &event->hw;
253
254 mipspmu->enable_event(hwc, hwc->idx);
255}
256
257static void mipspmu_read(struct perf_event *event)
258{
259 struct hw_perf_event *hwc = &event->hw;
260
261 /* Don't read disabled counters! */
262 if (hwc->idx < 0)
263 return;
264
265 mipspmu_event_update(event, hwc, hwc->idx);
266}
267
268static struct pmu pmu = {
269 .enable = mipspmu_enable,
270 .disable = mipspmu_disable,
271 .unthrottle = mipspmu_unthrottle,
272 .read = mipspmu_read,
273};
274
275static atomic_t active_events = ATOMIC_INIT(0);
276static DEFINE_MUTEX(pmu_reserve_mutex);
277static int (*save_perf_irq)(void);
278
279static int mipspmu_get_irq(void)
280{
281 int err;
282
283 if (mipspmu->irq >= 0) {
284 /* Request my own irq handler. */
285 err = request_irq(mipspmu->irq, mipspmu->handle_irq,
286 IRQF_DISABLED | IRQF_NOBALANCING,
287 "mips_perf_pmu", NULL);
288 if (err) {
289 pr_warning("Unable to request IRQ%d for MIPS "
290 "performance counters!\n", mipspmu->irq);
291 }
292 } else if (cp0_perfcount_irq < 0) {
293 /*
294 * We are sharing the irq number with the timer interrupt.
295 */
296 save_perf_irq = perf_irq;
297 perf_irq = mipspmu->handle_shared_irq;
298 err = 0;
299 } else {
300 pr_warning("The platform hasn't properly defined its "
301 "interrupt controller.\n");
302 err = -ENOENT;
303 }
304
305 return err;
306}
307
308static void mipspmu_free_irq(void)
309{
310 if (mipspmu->irq >= 0)
311 free_irq(mipspmu->irq, NULL);
312 else if (cp0_perfcount_irq < 0)
313 perf_irq = save_perf_irq;
314}
315
316static inline unsigned int
317mipspmu_perf_event_encode(const struct mips_perf_event *pev)
318{
319/*
320 * Top 8 bits for range, next 16 bits for cntr_mask, lowest 8 bits for
321 * event_id.
322 */
323#ifdef CONFIG_MIPS_MT_SMP
324 return ((unsigned int)pev->range << 24) |
325 (pev->cntr_mask & 0xffff00) |
326 (pev->event_id & 0xff);
327#else
328 return (pev->cntr_mask & 0xffff00) |
329 (pev->event_id & 0xff);
330#endif
331}
332
333static const struct mips_perf_event *
334mipspmu_map_general_event(int idx)
335{
336 const struct mips_perf_event *pev;
337
338 pev = ((*mipspmu->general_event_map)[idx].event_id ==
339 UNSUPPORTED_PERF_EVENT_ID ? ERR_PTR(-EOPNOTSUPP) :
340 &(*mipspmu->general_event_map)[idx]);
341
342 return pev;
343}
344
345static const struct mips_perf_event *
346mipspmu_map_cache_event(u64 config)
347{
348 unsigned int cache_type, cache_op, cache_result;
349 const struct mips_perf_event *pev;
350
351 cache_type = (config >> 0) & 0xff;
352 if (cache_type >= PERF_COUNT_HW_CACHE_MAX)
353 return ERR_PTR(-EINVAL);
354
355 cache_op = (config >> 8) & 0xff;
356 if (cache_op >= PERF_COUNT_HW_CACHE_OP_MAX)
357 return ERR_PTR(-EINVAL);
358
359 cache_result = (config >> 16) & 0xff;
360 if (cache_result >= PERF_COUNT_HW_CACHE_RESULT_MAX)
361 return ERR_PTR(-EINVAL);
362
363 pev = &((*mipspmu->cache_event_map)
364 [cache_type]
365 [cache_op]
366 [cache_result]);
367
368 if (pev->event_id == UNSUPPORTED_PERF_EVENT_ID)
369 return ERR_PTR(-EOPNOTSUPP);
370
371 return pev;
372
373}
374
375static int validate_event(struct cpu_hw_events *cpuc,
376 struct perf_event *event)
377{
378 struct hw_perf_event fake_hwc = event->hw;
379
380 if (event->pmu && event->pmu != &pmu)
381 return 0;
382
383 return mipspmu->alloc_counter(cpuc, &fake_hwc) >= 0;
384}
385
386static int validate_group(struct perf_event *event)
387{
388 struct perf_event *sibling, *leader = event->group_leader;
389 struct cpu_hw_events fake_cpuc;
390
391 memset(&fake_cpuc, 0, sizeof(fake_cpuc));
392
393 if (!validate_event(&fake_cpuc, leader))
394 return -ENOSPC;
395
396 list_for_each_entry(sibling, &leader->sibling_list, group_entry) {
397 if (!validate_event(&fake_cpuc, sibling))
398 return -ENOSPC;
399 }
400
401 if (!validate_event(&fake_cpuc, event))
402 return -ENOSPC;
403
404 return 0;
405}
406
407/*
408 * mipsxx/rm9000/loongson2 have different performance counters, they have
409 * specific low-level init routines.
410 */
411static int __hw_perf_event_init(struct perf_event *event);
412
413static void hw_perf_event_destroy(struct perf_event *event)
414{
415 if (atomic_dec_and_mutex_lock(&active_events,
416 &pmu_reserve_mutex)) {
417 /*
418 * We must not call the destroy function with interrupts
419 * disabled.
420 */
421 on_each_cpu(reset_counters,
422 (void *)(long)mipspmu->num_counters, 1);
423 mipspmu_free_irq();
424 mutex_unlock(&pmu_reserve_mutex);
425 }
426}
427
428const struct pmu *hw_perf_event_init(struct perf_event *event)
429{
430 int err = 0;
431
432 if (!mipspmu || event->cpu >= nr_cpumask_bits ||
433 (event->cpu >= 0 && !cpu_online(event->cpu)))
434 return ERR_PTR(-ENODEV);
435
436 if (!atomic_inc_not_zero(&active_events)) {
437 if (atomic_read(&active_events) > MIPS_MAX_HWEVENTS) {
438 atomic_dec(&active_events);
439 return ERR_PTR(-ENOSPC);
440 }
441
442 mutex_lock(&pmu_reserve_mutex);
443 if (atomic_read(&active_events) == 0)
444 err = mipspmu_get_irq();
445
446 if (!err)
447 atomic_inc(&active_events);
448 mutex_unlock(&pmu_reserve_mutex);
449 }
450
451 if (err)
452 return ERR_PTR(err);
453
454 err = __hw_perf_event_init(event);
455 if (err)
456 hw_perf_event_destroy(event);
457
458 return err ? ERR_PTR(err) : &pmu;
459}
460
461void hw_perf_enable(void)
462{
463 if (mipspmu)
464 mipspmu->start();
465}
466
467void hw_perf_disable(void)
468{
469 if (mipspmu)
470 mipspmu->stop();
471}
472
473/* This is needed by specific irq handlers in perf_event_*.c */
474static void
475handle_associated_event(struct cpu_hw_events *cpuc,
476 int idx, struct perf_sample_data *data, struct pt_regs *regs)
477{
478 struct perf_event *event = cpuc->events[idx];
479 struct hw_perf_event *hwc = &event->hw;
480
481 mipspmu_event_update(event, hwc, idx);
482 data->period = event->hw.last_period;
483 if (!mipspmu_event_set_period(event, hwc, idx))
484 return;
485
486 if (perf_event_overflow(event, 0, data, regs))
487 mipspmu->disable_event(idx);
488}