linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* perf events for ARMv6
@ 2009-12-11  9:44 Jamie Iles
  2009-12-11  9:44 ` [PATCH 1/4] arm: provide a mechanism to reserve performance counters Jamie Iles
  0 siblings, 1 reply; 6+ messages in thread
From: Jamie Iles @ 2009-12-11  9:44 UTC (permalink / raw)
  To: linux-arm-kernel

This patch series provides support for software perf events on all
ARM platforms and hardware support for v6 CPUs. In addition to adding
support for the hardware counters, these patches provide a way of
arbitrating access to the PMU and patches the oprofile code to use this.

Jamie

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/4] arm: provide a mechanism to reserve performance counters
  2009-12-11  9:44 perf events for ARMv6 Jamie Iles
@ 2009-12-11  9:44 ` Jamie Iles
  2009-12-11  9:44   ` [PATCH 2/4] arm/oprofile: reserve the PMU when starting Jamie Iles
  0 siblings, 1 reply; 6+ messages in thread
From: Jamie Iles @ 2009-12-11  9:44 UTC (permalink / raw)
  To: linux-arm-kernel

To add support for perf events and to allow the hardware
counters to be shared with oprofile, we need a way to reserve
access to the pmu (performance monitor unit).

Signed-off-by: Jamie Iles <jamie.iles@picochip.com>
---
 arch/arm/include/asm/pmu.h |   54 ++++++++++++++++++++++++++++++++++++++++++++
 arch/arm/kernel/Makefile   |    1 +
 arch/arm/kernel/pmu.c      |   32 ++++++++++++++++++++++++++
 arch/arm/mm/Kconfig        |    4 +++
 4 files changed, 91 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/include/asm/pmu.h
 create mode 100644 arch/arm/kernel/pmu.c

diff --git a/arch/arm/include/asm/pmu.h b/arch/arm/include/asm/pmu.h
new file mode 100644
index 0000000..94d6c67
--- /dev/null
+++ b/arch/arm/include/asm/pmu.h
@@ -0,0 +1,54 @@
+/*
+ *  linux/arch/arm/include/asm/pmu.h
+ *
+ *  Copyright (C) 2009 picoChip Designs Ltd, Jamie Iles
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __ARM_PMU_H__
+#define __ARM_PMU_H__
+
+#ifdef CONFIG_CPU_HAS_PMU
+
+/**
+ * reserve_pmu() - Reserve the hardware performance counters
+ *
+ * Reserve the hardware performance counters in the system for exclusive use.
+ * Note: success here does not guarantee exclusive access - callers must
+ * respect the return value.
+ *
+ * Returns zero on success, negative error on failure.
+ */
+extern int
+reserve_pmu(void);
+
+/**
+ * release_pmu() - Relinquish control of the performance counters
+ *
+ * Release the performance counters and allow someone else to use them.
+ * Callers must have disabled the counters and released IRQs before calling
+ * this.
+ */
+extern void
+release_pmu(void);
+
+#else /* CONFIG_CPU_HAS_PMU */
+
+static inline int
+reserve_pmu(void)
+{
+	return -ENODEV;
+}
+
+static inline void
+release_pmu(void)
+{
+}
+
+#endif /* CONFIG_CPU_HAS_PMU */
+
+#endif /* __ARM_PMU_H__ */
diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile
index e7ccf7e..286a276 100644
--- a/arch/arm/kernel/Makefile
+++ b/arch/arm/kernel/Makefile
@@ -46,6 +46,7 @@ obj-$(CONFIG_CPU_XSCALE)	+= xscale-cp0.o
 obj-$(CONFIG_CPU_XSC3)		+= xscale-cp0.o
 obj-$(CONFIG_CPU_MOHAWK)	+= xscale-cp0.o
 obj-$(CONFIG_IWMMXT)		+= iwmmxt.o
+obj-$(CONFIG_CPU_HAS_PMU)	+= pmu.o
 AFLAGS_iwmmxt.o			:= -Wa,-mcpu=iwmmxt
 
 ifneq ($(CONFIG_ARCH_EBSA110),y)
diff --git a/arch/arm/kernel/pmu.c b/arch/arm/kernel/pmu.c
new file mode 100644
index 0000000..a2b1a26
--- /dev/null
+++ b/arch/arm/kernel/pmu.c
@@ -0,0 +1,32 @@
+/*
+ *  linux/arch/arm/kernel/pmu.c
+ *
+ *  Copyright (C) 2009 picoChip Designs Ltd, Jamie Iles
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/semaphore.h>
+
+#include <asm/pmu.h>
+
+static DECLARE_MUTEX(pmu_mutex);
+
+int
+reserve_pmu(void)
+{
+	return down_trylock(&pmu_mutex) ? -EBUSY : 0;
+}
+EXPORT_SYMBOL_GPL(reserve_pmu);
+
+void
+release_pmu(void)
+{
+	up(&pmu_mutex);
+}
+EXPORT_SYMBOL_GPL(release_pmu);
diff --git a/arch/arm/mm/Kconfig b/arch/arm/mm/Kconfig
index dd4698c..60edbfe 100644
--- a/arch/arm/mm/Kconfig
+++ b/arch/arm/mm/Kconfig
@@ -398,6 +398,7 @@ config CPU_V6
 	select CPU_HAS_ASID if MMU
 	select CPU_COPY_V6 if MMU
 	select CPU_TLB_V6 if MMU
+	select CPU_HAS_PMU
 
 # ARMv6k
 config CPU_32v6K
@@ -536,6 +537,9 @@ config CPU_COPY_FA
 config CPU_COPY_V6
 	bool
 
+config CPU_HAS_PMU
+	bool
+
 # This selects the TLB model
 config CPU_TLB_V3
 	bool
-- 
1.6.5.4

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/4] arm/oprofile: reserve the PMU when starting
  2009-12-11  9:44 ` [PATCH 1/4] arm: provide a mechanism to reserve performance counters Jamie Iles
@ 2009-12-11  9:44   ` Jamie Iles
  2009-12-11  9:44     ` [PATCH 3/4] arm: enable support for software perf events Jamie Iles
  0 siblings, 1 reply; 6+ messages in thread
From: Jamie Iles @ 2009-12-11  9:44 UTC (permalink / raw)
  To: linux-arm-kernel

Make sure that we have access to the performance counters and
that they aren't being used by perf events or anything else.

Signed-off-by: Jamie Iles <jamie.iles@picochip.com>
---
 arch/arm/oprofile/op_model_mpcore.c |    9 ++++++++-
 arch/arm/oprofile/op_model_v6.c     |    8 +++++++-
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/arch/arm/oprofile/op_model_mpcore.c b/arch/arm/oprofile/op_model_mpcore.c
index 4ce0f98..b14e2d9 100644
--- a/arch/arm/oprofile/op_model_mpcore.c
+++ b/arch/arm/oprofile/op_model_mpcore.c
@@ -43,6 +43,7 @@
 #include <mach/hardware.h>
 #include <mach/board-eb.h>
 #include <asm/system.h>
+#include <asm/pmu.h>
 
 #include "op_counter.h"
 #include "op_arm_model.h"
@@ -234,7 +235,10 @@ static int arm11_irqs[] = {
 
 static int em_start(void)
 {
-	int ret;
+	int ret = reserve_pmu();
+
+	if (ret)
+		goto out;
 
 	ret = arm11_request_interrupts(arm11_irqs, ARRAY_SIZE(arm11_irqs));
 	if (ret == 0) {
@@ -244,6 +248,8 @@ static int em_start(void)
 		if (ret)
 			arm11_release_interrupts(arm11_irqs, ARRAY_SIZE(arm11_irqs));
 	}
+
+out:
 	return ret;
 }
 
@@ -252,6 +258,7 @@ static void em_stop(void)
 	em_call_function(arm11_stop_pmu);
 	arm11_release_interrupts(arm11_irqs, ARRAY_SIZE(arm11_irqs));
 	scu_stop();
+	release_pmu();
 }
 
 /*
diff --git a/arch/arm/oprofile/op_model_v6.c b/arch/arm/oprofile/op_model_v6.c
index e468017..e37720a 100644
--- a/arch/arm/oprofile/op_model_v6.c
+++ b/arch/arm/oprofile/op_model_v6.c
@@ -24,6 +24,7 @@
 #include <linux/interrupt.h>
 #include <asm/irq.h>
 #include <asm/system.h>
+#include <asm/pmu.h>
 
 #include "op_counter.h"
 #include "op_arm_model.h"
@@ -45,16 +46,21 @@ static void armv6_pmu_stop(void)
 {
 	arm11_stop_pmu();
 	arm11_release_interrupts(irqs, ARRAY_SIZE(irqs));
+	release_pmu();
 }
 
 static int armv6_pmu_start(void)
 {
-	int ret;
+	int ret = reserve_pmu();
+
+	if (ret)
+		goto out;
 
 	ret = arm11_request_interrupts(irqs, ARRAY_SIZE(irqs));
 	if (ret >= 0)
 		ret = arm11_start_pmu();
 
+out:
 	return ret;
 }
 
-- 
1.6.5.4

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 3/4] arm: enable support for software perf events
  2009-12-11  9:44   ` [PATCH 2/4] arm/oprofile: reserve the PMU when starting Jamie Iles
@ 2009-12-11  9:44     ` Jamie Iles
  2009-12-11  9:44       ` [PATCH 4/4] arm/perfevents: implement perf event support for ARMv6 Jamie Iles
  0 siblings, 1 reply; 6+ messages in thread
From: Jamie Iles @ 2009-12-11  9:44 UTC (permalink / raw)
  To: linux-arm-kernel

The perf events subsystem allows counting of both hardware and
software events. This patch implements the bare minimum for software
performance events.

Signed-off-by: Jamie Iles <jamie.iles@picochip.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ingo Molnar <mingo@elte.hu>
---
 arch/arm/Kconfig                  |    3 ++
 arch/arm/include/asm/atomic.h     |    1 +
 arch/arm/include/asm/perf_event.h |   38 +++++++++++++++++++++++++++++++++++++
 arch/arm/mm/fault.c               |    7 ++++++
 4 files changed, 49 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/include/asm/perf_event.h

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index c40efec..1e35f21 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -18,6 +18,9 @@ config ARM
 	select HAVE_KRETPROBES if (HAVE_KPROBES)
 	select HAVE_FUNCTION_TRACER if (!XIP_KERNEL)
 	select HAVE_GENERIC_DMA_COHERENT
+	select HAVE_PERF_EVENTS
+	select GENERIC_ATOMIC64
+	select PERF_USE_VMALLOC
 	help
 	  The ARM series is a line of low-power-consumption RISC chip designs
 	  licensed by ARM Ltd and targeted at embedded applications and
diff --git a/arch/arm/include/asm/atomic.h b/arch/arm/include/asm/atomic.h
index d0daeab..5861cc4 100644
--- a/arch/arm/include/asm/atomic.h
+++ b/arch/arm/include/asm/atomic.h
@@ -14,6 +14,7 @@
 #include <linux/compiler.h>
 #include <linux/types.h>
 #include <asm/system.h>
+#include <asm-generic/atomic64.h>
 
 #define ATOMIC_INIT(i)	{ (i) }
 
diff --git a/arch/arm/include/asm/perf_event.h b/arch/arm/include/asm/perf_event.h
new file mode 100644
index 0000000..32a66ac
--- /dev/null
+++ b/arch/arm/include/asm/perf_event.h
@@ -0,0 +1,38 @@
+/*
+ *  linux/arch/arm/include/asm/perf_event.h
+ *
+ *  Copyright (C) 2009 picoChip Designs Ltd, Jamie Iles
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef __ARM_PERF_EVENT_H__
+#define __ARM_PERF_EVENT_H__
+
+/*
+ * NOP: on *most* (read: all supported) ARM platforms, the performance
+ * counter interrupts are regular interrupts and not an NMI. This
+ * means that when we receive the interrupt we can call
+ * perf_event_do_pending() that handles all of the work with
+ * interrupts enabled.
+ */
+static inline void
+set_perf_event_pending(void)
+{
+}
+
+/* Get the PC. Make sure that we have a 64bit value with the upper 32 cleared.
+ */
+#define perf_instruction_pointer(_regs) \
+	((u64)instruction_pointer(regs) & 0xFFFFFFFFLU)
+#define perf_misc_flags(regs)   (user_mode(regs) ? PERF_RECORD_MISC_USER : \
+                                 PERF_RECORD_MISC_KERNEL)
+
+/* ARM performance counters start from 1 (in the cp15 accesses) so use the
+ * same indexes here for consistency. */
+#define PERF_EVENT_INDEX_OFFSET 1
+
+#endif /* __ARM_PERF_EVENT_H__ */
diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c
index 10e0680..9d40c34 100644
--- a/arch/arm/mm/fault.c
+++ b/arch/arm/mm/fault.c
@@ -18,6 +18,7 @@
 #include <linux/page-flags.h>
 #include <linux/sched.h>
 #include <linux/highmem.h>
+#include <linux/perf_event.h>
 
 #include <asm/system.h>
 #include <asm/pgtable.h>
@@ -302,6 +303,12 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
 	fault = __do_page_fault(mm, addr, fsr, tsk);
 	up_read(&mm->mmap_sem);
 
+	perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, 0, regs, addr);
+	if (fault & VM_FAULT_MAJOR)
+		perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1, 0, regs, addr);
+	else if (fault & VM_FAULT_MINOR)
+		perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1, 0, regs, addr);
+
 	/*
 	 * Handle the "normal" case first - VM_FAULT_MAJOR / VM_FAULT_MINOR
 	 */
-- 
1.6.5.4

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 4/4] arm/perfevents: implement perf event support for ARMv6
  2009-12-11  9:44     ` [PATCH 3/4] arm: enable support for software perf events Jamie Iles
@ 2009-12-11  9:44       ` Jamie Iles
  2009-12-11 10:19         ` Ingo Molnar
  0 siblings, 1 reply; 6+ messages in thread
From: Jamie Iles @ 2009-12-11  9:44 UTC (permalink / raw)
  To: linux-arm-kernel

This patch implements support for ARMv6 performance counters in the
Linux performance events subsystem. ARMv6 architectures that have the
performance counters should enable ARMV6_PERF_EVENTS and define the
interrupts for the counters in arch/arm/kernel/perf_event.c

Signed-off-by: Jamie Iles <jamie.iles@picochip.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ingo Molnar <mingo@elte.hu>
---
 arch/arm/Kconfig                  |    5 +
 arch/arm/include/asm/perf_event.h |    2 -
 arch/arm/kernel/Makefile          |    1 +
 arch/arm/kernel/perf_event.c      |  993 +++++++++++++++++++++++++++++++++++++
 4 files changed, 999 insertions(+), 2 deletions(-)
 create mode 100644 arch/arm/kernel/perf_event.c

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 1e35f21..1a1b53b 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -162,6 +162,11 @@ config ARCH_MTD_XIP
 config GENERIC_HARDIRQS_NO__DO_IRQ
 	def_bool y
 
+config HW_PERF_EVENTS
+	bool
+	depends on PERF_EVENTS && CPU_HAS_PMU
+	default y
+
 if OPROFILE
 
 config OPROFILE_ARMV6
diff --git a/arch/arm/include/asm/perf_event.h b/arch/arm/include/asm/perf_event.h
index 32a66ac..c29f84e 100644
--- a/arch/arm/include/asm/perf_event.h
+++ b/arch/arm/include/asm/perf_event.h
@@ -31,8 +31,6 @@ set_perf_event_pending(void)
 #define perf_misc_flags(regs)   (user_mode(regs) ? PERF_RECORD_MISC_USER : \
                                  PERF_RECORD_MISC_KERNEL)
 
-/* ARM performance counters start from 1 (in the cp15 accesses) so use the
- * same indexes here for consistency. */
 #define PERF_EVENT_INDEX_OFFSET 1
 
 #endif /* __ARM_PERF_EVENT_H__ */
diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile
index 286a276..44ebf36 100644
--- a/arch/arm/kernel/Makefile
+++ b/arch/arm/kernel/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_CPU_XSC3)		+= xscale-cp0.o
 obj-$(CONFIG_CPU_MOHAWK)	+= xscale-cp0.o
 obj-$(CONFIG_IWMMXT)		+= iwmmxt.o
 obj-$(CONFIG_CPU_HAS_PMU)	+= pmu.o
+obj-$(CONFIG_HW_PERF_EVENTS)	+= perf_event.o
 AFLAGS_iwmmxt.o			:= -Wa,-mcpu=iwmmxt
 
 ifneq ($(CONFIG_ARCH_EBSA110),y)
diff --git a/arch/arm/kernel/perf_event.c b/arch/arm/kernel/perf_event.c
new file mode 100644
index 0000000..f28ff09
--- /dev/null
+++ b/arch/arm/kernel/perf_event.c
@@ -0,0 +1,993 @@
+#undef DEBUG
+
+/*
+ * ARMv6 performance counter support.
+ *
+ * Copyright (C) 2009 picoChip Designs, Ltd., Jamie Iles
+ *
+ * This code is based on the sparc64 perf event code, which is in turn based
+ * on the x86 code. Callchain code is based on the ARM OProfile backtrace
+ * code.
+ */
+#define pr_fmt(fmt) "armv6_perfctr: " fmt
+
+#include <linux/kernel.h>
+#include <linux/perf_event.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+
+#include <asm/irq_regs.h>
+#include <asm/stacktrace.h>
+#include <asm/irq.h>
+#include <asm/pmu.h>
+
+/*
+ * ARMv6 has 2 configurable performance counters and a single cycle counter.
+ * They all share a single reset bit but can be written to zero so we can use
+ * that for a reset.
+ *
+ * The counters can't be individually enabled or disabled so when we remove
+ * one event and replace it with another we could get spurious counts from the
+ * wrong event. However, we can take advantage of the fact that the
+ * performance counters can export events to the event bus, and the event bus
+ * itself can be monitored. This requires that we *don't* export the events to
+ * the event bus. The procedure for disabling a configurable counter is:
+ *	- change the counter to count the ETMEXTOUT[0] signal (0x20). This
+ *	  effectively stops the counter from counting.
+ *	- disable the counter's interrupt generation (each counter has it's
+ *	  own interrupt enable bit).
+ * Once stopped, the counter value can be written as 0 to reset.
+ *
+ * To enable a counter:
+ *	- enable the counter's interrupt generation.
+ *	- set the new event type.
+ *
+ * Note: the dedicated cycle counter only counts cycles and can't be
+ * enabled/disabled independently of the others. When we want to disable the
+ * cycle counter, we have to just disable the interrupt reporting and start
+ * ignoring that counter. When re-enabling, we have to reset the value and
+ * enable the interrupt.
+ */
+
+#define MAX_PMU_IRQS	    8
+
+/* Define the IRQs for the system. We could use something like a platform
+ * device but that seems fairly heavyweight for this. Also, the performance
+ * counters can't be removed or hotplugged. */
+static const struct {
+	int		irqs[MAX_PMU_IRQS];
+	unsigned    	num_irqs;
+} pmu_irqs = {
+#ifdef CONFIG_ARCH_PC3XX
+	.irqs	    = { IRQ_NPMUIRQ },
+	.num_irqs   = 1,
+#else
+#warning "ARM performance counters enabled, but no IRQ's defined"
+#endif
+};
+
+/* Hardware lock to serialize accesses to PMU registers. Needed for the
+ * read/modify/write sequences. */
+DEFINE_SPINLOCK(pmu_lock);
+
+enum arm_perf_types {
+	ARM_PERFCTR_ICACHE_MISS		= 0x0,
+	ARM_PERFCTR_IBUF_STALL		= 0x1,
+	ARM_PERFCTR_DDEP_STALL		= 0x2,
+	ARM_PERFCTR_ITLB_MISS		= 0x3,
+	ARM_PERFCTR_DTLB_MISS		= 0x4,
+	ARM_PERFCTR_BR_EXEC		= 0x5,
+	ARM_PERFCTR_BR_MISPREDICT	= 0x6,
+	ARM_PERFCTR_INSTR_EXEC		= 0x7,
+	ARM_PERFCTR_DCACHE_HIT		= 0x9,
+	ARM_PERFCTR_DCACHE_ACCESS	= 0xA,
+	ARM_PERFCTR_DCACHE_MISS		= 0xB,
+	ARM_PERFCTR_DCACHE_WBACK	= 0xC,
+	ARM_PERFCTR_SW_PC_CHANGE	= 0xD,
+	ARM_PERFCTR_MAIN_TLB_MISS	= 0xF,
+	ARM_PERFCTR_EXPL_D_ACCESS	= 0x10,
+	ARM_PERFCTR_LSU_FULL_STALL	= 0x11,
+	ARM_PERFCTR_WBUF_DRAINED	= 0x12,
+	ARM_PERFCTR_CPU_CYCLES		= 0xFF,
+	ARM_PERFCTR_NOP			= 0x20,
+};
+
+/* We support using the full 32 bits of each counter. */
+#define MAX_PERIOD			((1LLU << 32) - 1)
+
+enum armv6_counters {
+	ARMV6_CYCLE_COUNTER = 1,
+	ARMV6_COUNTER0,
+	ARMV6_COUNTER1,
+};
+
+/* We support 3 simultaneous events, but they begin from 1. */
+#define ARMV6_MAX_HWEVENTS		4
+
+/* The events for a given CPU. */
+struct cpu_hw_events {
+	/* The events that are active on the CPU for the given index. Index 0
+	 * is reserved. */
+	struct perf_event	*events[ARMV6_MAX_HWEVENTS];
+
+	/* A 1 bit for an index indicates that the counter is being used for
+	 * an event. A 0 means that the counter can be used. */
+	unsigned long		used_mask[BITS_TO_LONGS(ARMV6_MAX_HWEVENTS)];
+
+	/* A 1 bit for an index indicates that the counter is actively being
+	 * used. */
+	unsigned long		active_mask[BITS_TO_LONGS(ARMV6_MAX_HWEVENTS)];
+};
+DEFINE_PER_CPU(struct cpu_hw_events, cpu_hw_events);
+
+#define HW_OP_UNSUPPORTED		    0xFFFF
+
+/* The hardware events that we support. We do support cache operations but
+ * we have harvard caches and no way to combine instruction and data
+ * accesses/misses in hardware. */
+static const unsigned v6_perf_map[PERF_COUNT_HW_MAX] = {
+	[PERF_COUNT_HW_CPU_CYCLES]	    = ARM_PERFCTR_CPU_CYCLES,
+	[PERF_COUNT_HW_INSTRUCTIONS]	    = ARM_PERFCTR_INSTR_EXEC,
+	[PERF_COUNT_HW_CACHE_REFERENCES]    = HW_OP_UNSUPPORTED,
+	[PERF_COUNT_HW_CACHE_MISSES]	    = HW_OP_UNSUPPORTED,
+	[PERF_COUNT_HW_BRANCH_INSTRUCTIONS] = ARM_PERFCTR_BR_EXEC,
+	[PERF_COUNT_HW_BRANCH_MISSES]	    = ARM_PERFCTR_BR_MISPREDICT,
+	[PERF_COUNT_HW_BUS_CYCLES]	    = HW_OP_UNSUPPORTED,
+};
+
+static inline int
+armv6_map_hw_event(u64 config)
+{
+	int mapping = v6_perf_map[config];
+	if (HW_OP_UNSUPPORTED == mapping)
+		mapping = -EOPNOTSUPP;
+	return mapping;
+}
+
+#define C(_x) \
+	PERF_COUNT_HW_CACHE_##_x
+
+#define CACHE_OP_UNSUPPORTED		0xFFFF
+
+static const unsigned v6_perf_cache_map[PERF_COUNT_HW_CACHE_MAX]
+				       [PERF_COUNT_HW_CACHE_OP_MAX]
+				       [PERF_COUNT_HW_CACHE_RESULT_MAX] = {
+	[C(L1D)] = {
+		/* The performance counters don't differentiate between read
+		 * and write accesses/misses so this isn't strictly correct,
+		 * but it's the best we can do. Writes and reads get
+		 * combined. */
+		[C(OP_READ)] = {
+			[C(RESULT_ACCESS)]	= ARM_PERFCTR_DCACHE_ACCESS,
+			[C(RESULT_MISS)]	= ARM_PERFCTR_DCACHE_MISS,
+		},
+		[C(OP_WRITE)] = {
+			[C(RESULT_ACCESS)]	= ARM_PERFCTR_DCACHE_ACCESS,
+			[C(RESULT_MISS)]	= ARM_PERFCTR_DCACHE_MISS,
+		},
+		[C(OP_PREFETCH)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,
+		},
+	},
+	[C(L1I)] = {
+		[C(OP_READ)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= ARM_PERFCTR_ICACHE_MISS,
+		},
+		[C(OP_WRITE)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= ARM_PERFCTR_ICACHE_MISS,
+		},
+		[C(OP_PREFETCH)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,
+		},
+	},
+	[C(LL)] = {
+		[C(OP_READ)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,
+		},
+		[C(OP_WRITE)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,
+		},
+		[C(OP_PREFETCH)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,
+		},
+	},
+	[C(DTLB)] = {
+		/* The ARM performance counters can count micro DTLB misses,
+		 * micro ITLB misses and main TLB misses. There isn't an event
+		 * for TLB misses, so use the micro misses here and if users
+		 * want the main TLB misses they can use a raw counter. */
+		[C(OP_READ)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= ARM_PERFCTR_DTLB_MISS,
+		},
+		[C(OP_WRITE)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= ARM_PERFCTR_DTLB_MISS,
+		},
+		[C(OP_PREFETCH)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,
+		},
+	},
+	[C(ITLB)] = {
+		[C(OP_READ)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= ARM_PERFCTR_ITLB_MISS,
+		},
+		[C(OP_WRITE)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= ARM_PERFCTR_ITLB_MISS,
+		},
+		[C(OP_PREFETCH)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,
+		},
+	},
+	[C(BPU)] = {
+		[C(OP_READ)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,
+		},
+		[C(OP_WRITE)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,
+		},
+		[C(OP_PREFETCH)] = {
+			[C(RESULT_ACCESS)]	= CACHE_OP_UNSUPPORTED,
+			[C(RESULT_MISS)]	= CACHE_OP_UNSUPPORTED,
+		},
+	},
+};
+
+static const int
+armv6_map_cache_event(u64 config)
+{
+	unsigned int cache_type, cache_op, cache_result, ret;
+
+	cache_type = (config >>  0) & 0xff;
+	if (cache_type >= PERF_COUNT_HW_CACHE_MAX)
+		return -EINVAL;
+
+	cache_op = (config >>  8) & 0xff;
+	if (cache_op >= PERF_COUNT_HW_CACHE_OP_MAX)
+		return -EINVAL;
+
+	cache_result = (config >> 16) & 0xff;
+	if (cache_result >= PERF_COUNT_HW_CACHE_RESULT_MAX)
+		return -EINVAL;
+
+	ret = (int)v6_perf_cache_map[cache_type][cache_op][cache_result];
+
+	if (ret == CACHE_OP_UNSUPPORTED)
+		return -ENOENT;
+
+	return ret;
+}
+
+#define PMCR_ENABLE		(1 << 0)
+#define PMCR_CTR01_RESET    	(1 << 1)
+#define PMCR_CCOUNT_RESET   	(1 << 2)
+#define PMCR_CCOUNT_DIV	    	(1 << 3)
+#define PMCR_COUNT0_IEN	    	(1 << 4)
+#define PMCR_COUNT1_IEN	    	(1 << 5)
+#define PMCR_CCOUNT_IEN	    	(1 << 6)
+#define PMCR_COUNT0_OVERFLOW	(1 << 8)
+#define PMCR_COUNT1_OVERFLOW	(1 << 9)
+#define PMCR_CCOUNT_OVERFLOW	(1 << 10)
+#define PMCR_EVT_COUNT0_SHIFT	20
+#define PMCR_EVT_COUNT0_MASK	(0xFF << PMCR_EVT_COUNT0_SHIFT)
+#define PMCR_EVT_COUNT1_SHIFT	12
+#define PMCR_EVT_COUNT1_MASK	(0xFF << PMCR_EVT_COUNT1_SHIFT)
+
+static inline unsigned long
+pmcr_read(void)
+{
+	u32 val;
+	asm volatile("mrc   p15, 0, %0, c15, c12, 0" : "=r"(val));
+	return val;
+}
+
+static inline void
+pmcr_write(unsigned long val)
+{
+	asm volatile("mcr   p15, 0, %0, c15, c12, 0" : : "r"(val));
+}
+
+#define PMCR_OVERFLOWED_MASK \
+	(PMCR_COUNT0_OVERFLOW | PMCR_COUNT1_OVERFLOW | PMCR_CCOUNT_OVERFLOW)
+
+static inline int
+pmcr_has_overflowed(unsigned long pmcr)
+{
+	return (pmcr & PMCR_OVERFLOWED_MASK);
+}
+
+static inline int
+pmcr_counter_has_overflowed(unsigned long pmcr,
+			    enum armv6_counters counter)
+{
+	int ret;
+
+	if (ARMV6_CYCLE_COUNTER == counter)
+		ret = pmcr & PMCR_CCOUNT_OVERFLOW;
+	else if (ARMV6_COUNTER0 == counter)
+		ret = pmcr & PMCR_COUNT0_OVERFLOW;
+	else if (ARMV6_COUNTER1 == counter)
+		ret = pmcr & PMCR_COUNT1_OVERFLOW;
+	else
+		BUG();
+
+	return ret;
+}
+
+static inline unsigned long
+armv6pmu_read_counter(enum armv6_counters counter)
+{
+	unsigned long value;
+
+	if (ARMV6_CYCLE_COUNTER == counter)
+		asm volatile("mrc   p15, 0, %0, c15, c12, 1" : "=r"(value));
+	else if (ARMV6_COUNTER0 == counter)
+		asm volatile("mrc   p15, 0, %0, c15, c12, 2" : "=r"(value));
+	else if (ARMV6_COUNTER1 == counter)
+		asm volatile("mrc   p15, 0, %0, c15, c12, 3" : "=r"(value));
+	else
+		BUG();
+
+	return value;
+}
+
+static inline void
+armv6pmu_write_counter(enum armv6_counters counter,
+		       unsigned long value)
+{
+	if (ARMV6_CYCLE_COUNTER == counter)
+		asm volatile("mcr   p15, 0, %0, c15, c12, 1" : : "r"(value));
+	else if (ARMV6_COUNTER0 == counter)
+		asm volatile("mcr   p15, 0, %0, c15, c12, 2" : : "r"(value));
+	else if (ARMV6_COUNTER1 == counter)
+		asm volatile("mcr   p15, 0, %0, c15, c12, 3" : : "r"(value));
+	else
+		BUG();
+}
+
+static int
+armv6pmu_place_event(struct cpu_hw_events *cpuc,
+		     struct perf_event *event)
+{
+	/* Always place a cycle counter into the cycle counter. */
+	if (ARM_PERFCTR_CPU_CYCLES == event->hw.config_base) {
+		if (test_and_set_bit(ARMV6_CYCLE_COUNTER, cpuc->used_mask))
+			return -EAGAIN;
+
+		event->hw.idx = ARMV6_CYCLE_COUNTER;
+		return 0;
+	} else {
+		/* For anything other than a cycle counter, try and use
+		 * counter0 and counter1. */
+		if (!test_and_set_bit(ARMV6_COUNTER1, cpuc->used_mask)) {
+			event->hw.idx = ARMV6_COUNTER1;
+			return 0;
+		}
+
+		if (!test_and_set_bit(ARMV6_COUNTER0, cpuc->used_mask)) {
+			event->hw.idx = ARMV6_COUNTER0;
+			return 0;
+		}
+
+		/* The counters are all in use. */
+		return -EAGAIN;
+	}
+}
+
+static void
+armv6pmu_disable_event(struct cpu_hw_events *cpuc,
+		       struct hw_perf_event *hwc,
+		       int idx)
+{
+	unsigned long val, mask, evt, flags;
+
+	if (ARMV6_CYCLE_COUNTER == idx) {
+		mask	= PMCR_CCOUNT_IEN;
+		evt	= 0;
+	} else if (ARMV6_COUNTER0 == idx) {
+		mask	= PMCR_COUNT0_IEN | PMCR_EVT_COUNT0_MASK;
+		evt	= ARM_PERFCTR_NOP << PMCR_EVT_COUNT0_SHIFT;
+	} else if (ARMV6_COUNTER1 == idx) {
+		mask	= PMCR_COUNT1_IEN | PMCR_EVT_COUNT1_MASK;
+		evt	= ARM_PERFCTR_NOP << PMCR_EVT_COUNT1_SHIFT;
+	} else {
+		BUG();
+	}
+
+	/* Mask out the current event and set the counter to count the number
+	 * of ETM bus signal assertion cycles. The external reporting should
+	 * be disabled and so this should never increment. */
+	spin_lock_irqsave(&pmu_lock, flags);
+	val = pmcr_read();
+	val &= ~mask;
+	val |= evt;
+	pmcr_write(val);
+	spin_unlock_irqrestore(&pmu_lock, flags);
+}
+
+static void
+armv6pmu_enable_event(struct cpu_hw_events *cpuc,
+		      struct hw_perf_event *hwc,
+		      int idx)
+{
+	unsigned long val, mask, evt, flags;
+
+	if (ARMV6_CYCLE_COUNTER == idx) {
+		mask	= 0;
+		evt	= PMCR_CCOUNT_IEN;
+	} else if (ARMV6_COUNTER0 == idx) {
+		mask	= PMCR_EVT_COUNT0_MASK;
+		evt	= (hwc->config_base << PMCR_EVT_COUNT0_SHIFT) |
+			  PMCR_COUNT0_IEN;
+	} else if (ARMV6_COUNTER1 == idx) {
+		mask	= PMCR_EVT_COUNT1_MASK;
+		evt	= (hwc->config_base << PMCR_EVT_COUNT1_SHIFT) |
+			  PMCR_COUNT1_IEN;
+	} else {
+		BUG();
+	}
+
+	/* Mask out the current event and set the counter to count the event
+	 * that we're interested in. */
+	spin_lock_irqsave(&pmu_lock, flags);
+	val = pmcr_read();
+	val &= ~mask;
+	val |= evt;
+	pmcr_write(val);
+	spin_unlock_irqrestore(&pmu_lock, flags);
+}
+
+static int
+armv6pmu_event_set_period(struct perf_event *event,
+			  struct hw_perf_event *hwc,
+			  int idx)
+{
+	s64 left = atomic64_read(&hwc->period_left);
+	s64 period = hwc->sample_period;
+	int ret = 0;
+
+	if (unlikely(left <= -period)) {
+		left = period;
+		atomic64_set(&hwc->period_left, left);
+		hwc->last_period = period;
+		ret = 1;
+	}
+
+	if (unlikely(left <= 0)) {
+		left += period;
+		atomic64_set(&hwc->period_left, left);
+		hwc->last_period = period;
+		ret = 1;
+	}
+
+	if (left > MAX_PERIOD)
+		left = MAX_PERIOD;
+
+	atomic64_set(&hwc->prev_count, (u64)-left);
+
+	armv6pmu_write_counter(idx, (u64)(-left) & 0xffffffff);
+
+	perf_event_update_userpage(event);
+
+	return ret;
+}
+
+static int
+armv6pmu_enable(struct perf_event *event)
+{
+	struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
+	struct hw_perf_event *hwc = &event->hw;
+	int idx;
+	int err = 0;
+
+	/* If we don't have a space for the counter then finish early. */
+	err = armv6pmu_place_event(cpuc, event);
+	if (err)
+		goto out;
+
+	/* If there is an event in the counter we are going to use then make
+	 * sure it is disabled. */
+	idx = event->hw.idx;
+	armv6pmu_disable_event(cpuc, hwc, idx);
+	cpuc->events[idx] = event;
+	set_bit(idx, cpuc->active_mask);
+
+	/* Set the period for the event. */
+	armv6pmu_event_set_period(event, hwc, idx);
+
+	/* Enable the event. */
+	armv6pmu_enable_event(cpuc, hwc, idx);
+
+	/* Propagate our changes to the userspace mapping. */
+	perf_event_update_userpage(event);
+
+out:
+	return err;
+}
+
+static u64
+armv6pmu_event_update(struct perf_event *event,
+		      struct hw_perf_event *hwc,
+		      int idx)
+{
+	int shift = 64 - 32;
+	u64 prev_raw_count, new_raw_count;
+	s64 delta;
+
+again:
+	prev_raw_count = atomic64_read(&hwc->prev_count);
+	new_raw_count = armv6pmu_read_counter(idx);
+
+	if (atomic64_cmpxchg(&hwc->prev_count, prev_raw_count,
+			     new_raw_count) != prev_raw_count)
+		goto again;
+
+	delta = (new_raw_count << shift) - (prev_raw_count << shift);
+	delta >>= shift;
+
+	atomic64_add(delta, &event->count);
+	atomic64_sub(delta, &hwc->period_left);
+
+	return new_raw_count;
+}
+
+static void
+armv6pmu_disable(struct perf_event *event)
+{
+	struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
+	struct hw_perf_event *hwc = &event->hw;
+	int idx = hwc->idx;
+
+	WARN_ON(idx < 0);
+
+	clear_bit(idx, cpuc->active_mask);
+	armv6pmu_disable_event(cpuc, hwc, idx);
+
+	barrier();
+
+	armv6pmu_event_update(event, hwc, idx);
+	cpuc->events[idx] = NULL;
+	clear_bit(idx, cpuc->used_mask);
+
+	perf_event_update_userpage(event);
+}
+
+static void
+armv6pmu_read(struct perf_event *event)
+{
+	struct hw_perf_event *hwc = &event->hw;
+
+	/* Don't read disabled counters! */
+	if (hwc->idx < 0)
+		return;
+
+	armv6pmu_event_update(event, hwc, hwc->idx);
+}
+
+static void
+armv6pmu_unthrottle(struct perf_event *event)
+{
+	struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
+	struct hw_perf_event *hwc = &event->hw;
+
+	armv6pmu_enable_event(cpuc, hwc, hwc->idx);
+}
+
+static irqreturn_t
+armv6_perfcounters_irq(int irq_num,
+		       void *dev)
+{
+	unsigned long pmcr = pmcr_read();
+	struct perf_sample_data data;
+	struct cpu_hw_events *cpuc;
+	struct pt_regs *regs;
+	int idx;
+
+	if (!pmcr_has_overflowed(pmcr))
+		return IRQ_NONE;
+
+	regs = get_irq_regs();
+
+	/*
+	 * The interrupts are cleared by writing the overflow flags back to
+	 * the control register. All of the other bits don't have any effect
+	 * if they are rewritten, so write the whole value back.
+	 */
+	pmcr_write(pmcr);
+
+	data.addr = 0;
+
+	cpuc = &__get_cpu_var(cpu_hw_events);
+	for (idx = 0; idx < ARMV6_MAX_HWEVENTS; ++idx) {
+		struct perf_event *event = cpuc->events[idx];
+		struct hw_perf_event *hwc;
+
+		if (!test_bit(idx, cpuc->active_mask))
+			continue;
+
+		/* We have a single interrupt for all counters. Check that
+		 * each counter has overflowed before we process it. */
+		if (!pmcr_counter_has_overflowed(pmcr, idx))
+			continue;
+
+		hwc = &event->hw;
+		armv6pmu_event_update(event, hwc, idx);
+		data.period = event->hw.last_period;
+		if (!armv6pmu_event_set_period(event, hwc, idx))
+			continue;
+
+		if (perf_event_overflow(event, 0, &data, regs))
+			armv6pmu_disable_event(cpuc, hwc, idx);
+	}
+
+	/*
+	 * Handle the pending perf events.
+	 *
+	 * Note: this call *must* be run with interrupts enabled. For
+	 * platforms that can have the PMU interrupts raised as a PMI, this
+	 * will not work.
+	 */
+	perf_event_do_pending();
+
+	return IRQ_HANDLED;
+}
+
+void
+hw_perf_enable(void)
+{
+	/* Enable all of the perf events on hardware. */
+	int idx;
+	struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
+	unsigned long flags, val;
+
+	for (idx = 0; idx < ARMV6_MAX_HWEVENTS; ++idx) {
+		struct perf_event *event = cpuc->events[idx];
+
+		if (!event)
+			continue;
+
+		armv6pmu_enable_event(cpuc, &event->hw, idx);
+	}
+
+	spin_lock_irqsave(&pmu_lock, flags);
+	val = pmcr_read();
+	val |= PMCR_ENABLE;
+	pmcr_write(val);
+	spin_unlock_irqrestore(&pmu_lock, flags);
+}
+
+void
+hw_perf_disable(void)
+{
+	unsigned long flags, val;
+
+	spin_lock_irqsave(&pmu_lock, flags);
+	val = pmcr_read();
+	val &= ~PMCR_ENABLE;
+	pmcr_write(val);
+	spin_unlock_irqrestore(&pmu_lock, flags);
+}
+
+static const struct pmu armv6pmu = {
+	.enable		= armv6pmu_enable,
+	.disable    	= armv6pmu_disable,
+	.read	    	= armv6pmu_read,
+	.unthrottle 	= armv6pmu_unthrottle,
+};
+
+/* Check that a group of events can take another hardware event. */
+static int
+check_group_space(struct perf_event *group,
+	          int max_count)
+{
+	struct perf_event *event;
+	int n = 0;
+
+	if (!is_software_event(group)) {
+		if (n >= max_count)
+			return -1;
+		++n;
+	}
+
+	list_for_each_entry(event, &group->sibling_list, group_entry) {
+		if (!is_software_event(event) &&
+		    event->state != PERF_EVENT_STATE_OFF) {
+			if (n >= max_count)
+				return -1;
+			++n;
+		}
+	}
+
+	return 0;
+}
+
+static int
+armv6_hw_perf_event_init(struct perf_event *event)
+{
+	struct hw_perf_event *hwc = &event->hw;
+	int mapping, err;
+
+	/* Decode the generic type into an ARM event identifier. */
+	if (PERF_TYPE_HARDWARE == event->attr.type) {
+		mapping = armv6_map_hw_event(event->attr.config);
+	} else if (PERF_TYPE_HW_CACHE == event->attr.type) {
+		mapping = armv6_map_cache_event(event->attr.config);
+	} else if (PERF_TYPE_RAW == event->attr.type) {
+		/* The EvtCountN field of the PMCR is 8 bits. */
+		mapping = event->attr.config & 0xFF;
+	} else {
+		pr_debug("event type %x not supported\n", event->attr.type);
+		return -EOPNOTSUPP;
+	}
+
+	if (mapping < 0) {
+		pr_debug("event %x:%llx not supported\n", event->attr.type,
+			 event->attr.config);
+		return mapping;
+	}
+
+	/* Check whether we need to exclude the counter from certain modes.
+	 * The ARM performance counters are on all of the time so if someone
+	 * has asked us for some excludes then we have to fail. */
+	if (event->attr.exclude_kernel || event->attr.exclude_user ||
+	    event->attr.exclude_hv || event->attr.exclude_idle) {
+		pr_debug("ARM performance counters do not support "
+			 "mode exclusion\n");
+		return -EPERM;
+	}
+
+	/* We don't assign an index until we actually place the event onto
+	 * hardware. Use -1 to signify that we haven't decided where to put it
+	 * yet. For SMP systems, each core has it's own PMU so we can't do any
+	 * clever allocation or constraints checking at this point. */
+	hwc->idx = -1;
+
+	/* Store the event encoding into the config_base field. config and
+	 * event_base are unused as the only 2 things we need to know are
+	 * the event mapping and the counter to use. The counter to use is
+	 * also the indx and the config_base is the event type. */
+	hwc->config_base	    = (unsigned long)mapping;
+	hwc->config		    = 0;
+	hwc->event_base		    = 0;
+
+	if (!hwc->sample_period) {
+		hwc->sample_period  = MAX_PERIOD;
+		hwc->last_period    = hwc->sample_period;
+		atomic64_set(&hwc->period_left, hwc->sample_period);
+	}
+
+	err = 0;
+	if (event->group_leader != event) {
+		/* Check we can get our new event in the group. */
+		err = check_group_space(event->group_leader,
+					perf_max_events - 1);
+		if (err)
+			return -EINVAL;
+	}
+
+	return err;
+}
+
+static int
+armv6pmu_reserve_hardware(void)
+{
+	int err = reserve_pmu();
+	int i;
+
+	if (err)
+		return err;
+
+	for (i = 0; i < pmu_irqs.num_irqs; ++i) {
+		err = request_irq(pmu_irqs.irqs[i], armv6_perfcounters_irq,
+				  IRQF_DISABLED, "armv6_perfctr", NULL);
+		if (err) {
+			pr_warning("unable to request IRQ%d for ARMv6 "
+				   "perf counters\n", pmu_irqs.irqs[i]);
+			break;
+		}
+	}
+
+	if (err) {
+		for (i = i - 1; i >= 0; --i) {
+			free_irq(pmu_irqs.irqs[i], NULL);
+		}
+		release_pmu();
+	}
+
+	return err;
+}
+
+static void
+armv6pmu_release_hardware(void)
+{
+	int i;
+
+	for (i = pmu_irqs.num_irqs - 1; i >= 0; --i) {
+		free_irq(pmu_irqs.irqs[i], NULL);
+	}
+
+	release_pmu();
+}
+
+static atomic_t active_events = ATOMIC_INIT(0);
+static DEFINE_MUTEX(pmu_reserve_mutex);
+
+static void
+hw_perf_event_destroy(struct perf_event *event)
+{
+	if (atomic_dec_and_mutex_lock(&active_events, &pmu_reserve_mutex)) {
+		armv6pmu_release_hardware();
+		mutex_unlock(&pmu_reserve_mutex);
+	}
+}
+
+const struct pmu *
+hw_perf_event_init(struct perf_event *event)
+{
+	int err = 0;
+
+	/* We support 3 events: one cycle counter and 2 programmable.
+	 * perf_max_events should be this + 1. */
+	perf_max_events	= ARMV6_MAX_HWEVENTS;
+	event->destroy = hw_perf_event_destroy;
+
+	if (!atomic_inc_not_zero(&active_events)) {
+		mutex_lock(&pmu_reserve_mutex);
+		if (atomic_read(&active_events) == 0) {
+			err = armv6pmu_reserve_hardware();
+		}
+
+		if (!err)
+			atomic_inc(&active_events);
+		mutex_unlock(&pmu_reserve_mutex);
+	}
+
+	if (err)
+		return ERR_PTR(err);
+
+	err = armv6_hw_perf_event_init(event);
+	if (err)
+		hw_perf_event_destroy(event);
+
+	return err ? ERR_PTR(err) : &armv6pmu;
+}
+
+/* Callchain handling code. */
+static inline void
+callchain_store(struct perf_callchain_entry *entry,
+		u64 ip)
+{
+	if (entry->nr < PERF_MAX_STACK_DEPTH)
+		entry->ip[entry->nr++] = ip;
+}
+
+/*
+ * The registers we're interested in are at the end of the variable
+ * length saved register structure. The fp points at the end of this
+ * structure so the address of this struct is:
+ * (struct frame_tail *)(xxx->fp)-1
+ *
+ * This code has been adapted from the ARM OProfile support.
+ */
+struct frame_tail {
+        struct frame_tail   *fp;
+        unsigned long	    sp;
+        unsigned long	    lr;
+} __attribute__((packed));
+
+/* Get the return address for a single stackframe and return a pointer to the
+ * next frame tail. */
+static struct frame_tail *
+user_backtrace(struct frame_tail *tail,
+	       struct perf_callchain_entry *entry)
+{
+	struct frame_tail buftail;
+
+        /* Also check accessibility of one struct frame_tail beyond */
+        if (!access_ok(VERIFY_READ, tail, sizeof(buftail)))
+                return NULL;
+        if (__copy_from_user_inatomic(&buftail, tail, sizeof(buftail)))
+                return NULL;
+
+	callchain_store(entry, buftail.lr);
+
+        /* frame pointers should strictly progress back up the stack
+         * (towards higher addresses) */
+        if (tail >= buftail.fp)
+                return NULL;
+
+        return buftail.fp - 1;
+}
+
+static void
+perf_callchain_user(struct pt_regs *regs,
+		    struct perf_callchain_entry *entry)
+{
+	struct frame_tail *tail;
+
+	callchain_store(entry, PERF_CONTEXT_USER);
+
+	if (!user_mode(regs))
+		regs = task_pt_regs(current);
+
+	tail = (struct frame_tail *)regs->ARM_fp - 1;
+
+	while (tail && !((unsigned long)tail & 0x3))
+		tail = user_backtrace(tail, entry);
+}
+
+/* Gets called by walk_stackframe() for every stackframe. This will be called
+ * whist unwinding the stackframe and is like a subroutine return so we use
+ * the PC. */
+static int
+callchain_trace(struct stackframe *fr,
+	        void *data)
+{
+	struct perf_callchain_entry *entry = data;
+	callchain_store(entry, fr->pc);
+	return 0;
+}
+
+static void
+perf_callchain_kernel(struct pt_regs *regs,
+		      struct perf_callchain_entry *entry)
+{
+	struct stackframe fr;
+
+	callchain_store(entry, PERF_CONTEXT_KERNEL);
+	fr.fp = regs->ARM_fp;
+	fr.sp = regs->ARM_sp;
+	fr.lr = regs->ARM_lr;
+	fr.pc = regs->ARM_pc;
+	walk_stackframe(&fr, callchain_trace, entry);
+}
+
+static void
+perf_do_callchain(struct pt_regs *regs,
+		  struct perf_callchain_entry *entry)
+{
+	int is_user;
+
+	if (!regs)
+		return;
+
+	is_user = user_mode(regs);
+
+	if (!current || !current->pid)
+		return;
+
+	if (is_user && current->state != TASK_RUNNING)
+		return;
+
+	if (!is_user)
+		perf_callchain_kernel(regs, entry);
+
+	if (current->mm)
+		perf_callchain_user(regs, entry);
+}
+
+static DEFINE_PER_CPU(struct perf_callchain_entry, pmc_irq_entry);
+
+struct perf_callchain_entry *
+perf_callchain(struct pt_regs *regs)
+{
+	struct perf_callchain_entry *entry = &__get_cpu_var(pmc_irq_entry);
+
+	entry->nr = 0;
+	perf_do_callchain(regs, entry);
+	return entry;
+}
-- 
1.6.5.4

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 4/4] arm/perfevents: implement perf event support for ARMv6
  2009-12-11  9:44       ` [PATCH 4/4] arm/perfevents: implement perf event support for ARMv6 Jamie Iles
@ 2009-12-11 10:19         ` Ingo Molnar
  0 siblings, 0 replies; 6+ messages in thread
From: Ingo Molnar @ 2009-12-11 10:19 UTC (permalink / raw)
  To: linux-arm-kernel


* Jamie Iles <jamie.iles@picochip.com> wrote:

> This patch implements support for ARMv6 performance counters in the 
> Linux performance events subsystem. ARMv6 architectures that have the 
> performance counters should enable ARMV6_PERF_EVENTS and define the 
> interrupts for the counters in arch/arm/kernel/perf_event.c
> 
> Signed-off-by: Jamie Iles <jamie.iles@picochip.com>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Ingo Molnar <mingo@elte.hu>
> ---
>  arch/arm/Kconfig                  |    5 +
>  arch/arm/include/asm/perf_event.h |    2 -
>  arch/arm/kernel/Makefile          |    1 +
>  arch/arm/kernel/perf_event.c      |  993 +++++++++++++++++++++++++++++++++++++
>  4 files changed, 999 insertions(+), 2 deletions(-)
>  create mode 100644 arch/arm/kernel/perf_event.c

Very nice!

The code looks sane - i've only got a few (minor) stylistic comments.

Firstly, you seem to be using several different, inconsistent multi-line 
comment styles:

> +	/*
> +	 * The interrupts are cleared by writing the overflow flags back to
> +	 * the control register. All of the other bits don't have any effect
> +	 * if they are rewritten, so write the whole value back.
> +	 */

> +/* Define the IRQs for the system. We could use something like a platform
> + * device but that seems fairly heavyweight for this. Also, the performance
> + * counters can't be removed or hotplugged. */

please use the first variant everywhere, that's what is specified in 
Documentation/CodingStyle as well:

  /*
   * Comment .....
   * ...... goes here.
   */

There's also some whitespace damage as well:

> +        /* frame pointers should strictly progress back up the stack
> +         * (towards higher addresses) */

(should be tabs, not spaces)

Thanks,

	Ingo

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2009-12-11 10:19 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2009-12-11  9:44 perf events for ARMv6 Jamie Iles
2009-12-11  9:44 ` [PATCH 1/4] arm: provide a mechanism to reserve performance counters Jamie Iles
2009-12-11  9:44   ` [PATCH 2/4] arm/oprofile: reserve the PMU when starting Jamie Iles
2009-12-11  9:44     ` [PATCH 3/4] arm: enable support for software perf events Jamie Iles
2009-12-11  9:44       ` [PATCH 4/4] arm/perfevents: implement perf event support for ARMv6 Jamie Iles
2009-12-11 10:19         ` Ingo Molnar

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).