]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
Merge git://git.kernel.org/pub/scm/linux/kernel/git/cmetcalf/linux-tile
authorLinus Torvalds <torvalds@linux-foundation.org>
Sun, 6 Apr 2014 15:11:57 +0000 (08:11 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sun, 6 Apr 2014 15:11:57 +0000 (08:11 -0700)
Pull arch/tile updates from Chris Metcalf:
 "These fix a few stray build issues seen in linux-next, and also add
  the minimal required support for perf to tilegx"

* git://git.kernel.org/pub/scm/linux/kernel/git/cmetcalf/linux-tile:
  arch/tile: remove unused variable 'devcap'
  tile: Fix vDSO compilation issue with allyesconfig
  perf tools: Allow building for tile
  tile/perf: Support perf_events on tilegx and tilepro
  tile: Enable NMIs on return from handle_nmi() without errors
  tile: Add support for handling PMC hardware
  tile: don't use __get_cpu_var() with structure-typed arguments
  tile: avoid overflow in ns2cycles

15 files changed:
arch/tile/Kconfig
arch/tile/include/asm/perf_event.h [new file with mode: 0644]
arch/tile/include/asm/pmc.h [new file with mode: 0644]
arch/tile/kernel/Makefile
arch/tile/kernel/intvec_32.S
arch/tile/kernel/intvec_64.S
arch/tile/kernel/irq.c
arch/tile/kernel/messaging.c
arch/tile/kernel/pci.c
arch/tile/kernel/perf_event.c [new file with mode: 0644]
arch/tile/kernel/pmc.c [new file with mode: 0644]
arch/tile/kernel/time.c
arch/tile/kernel/vdso/Makefile
tools/perf/config/Makefile.arch
tools/perf/perf.h

index b3692ce78f9034773415503017c2a4cb24fa1d2c..31c8c62239956a59e44bd650ef8f6c2eebdd7285 100644 (file)
@@ -3,6 +3,8 @@
 
 config TILE
        def_bool y
+       select HAVE_PERF_EVENTS
+       select USE_PMC if PERF_EVENTS
        select HAVE_DMA_ATTRS
        select HAVE_DMA_API_DEBUG
        select HAVE_KVM if !TILEGX
@@ -66,6 +68,10 @@ config HUGETLB_SUPER_PAGES
 config GENERIC_TIME_VSYSCALL
        def_bool y
 
+# Enable PMC if PERF_EVENTS, OPROFILE, or WATCHPOINTS are enabled.
+config USE_PMC
+       bool
+
 # FIXME: tilegx can implement a more efficient rwsem.
 config RWSEM_GENERIC_SPINLOCK
        def_bool y
diff --git a/arch/tile/include/asm/perf_event.h b/arch/tile/include/asm/perf_event.h
new file mode 100644 (file)
index 0000000..59c5b16
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014 Tilera Corporation. All Rights Reserved.
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU General Public License
+ *   as published by the Free Software Foundation, version 2.
+ *
+ *   This program is distributed in the hope that it will be useful, but
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ *   NON INFRINGEMENT.  See the GNU General Public License for
+ *   more details.
+ */
+
+#ifndef _ASM_TILE_PERF_EVENT_H
+#define _ASM_TILE_PERF_EVENT_H
+
+#include <linux/percpu.h>
+DECLARE_PER_CPU(u64, perf_irqs);
+
+unsigned long handle_syscall_link_address(void);
+#endif /* _ASM_TILE_PERF_EVENT_H */
diff --git a/arch/tile/include/asm/pmc.h b/arch/tile/include/asm/pmc.h
new file mode 100644 (file)
index 0000000..7ae3956
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014 Tilera Corporation. All Rights Reserved.
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU General Public License
+ *   as published by the Free Software Foundation, version 2.
+ *
+ *   This program is distributed in the hope that it will be useful, but
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ *   NON INFRINGEMENT.  See the GNU General Public License for
+ *   more details.
+ */
+
+#ifndef _ASM_TILE_PMC_H
+#define _ASM_TILE_PMC_H
+
+#include <linux/ptrace.h>
+
+#define TILE_BASE_COUNTERS     2
+
+/* Bitfields below are derived from SPR PERF_COUNT_CTL*/
+#ifndef __tilegx__
+/* PERF_COUNT_CTL on TILEPro */
+#define TILE_CTL_EXCL_USER     (1 << 7) /* exclude user level */
+#define TILE_CTL_EXCL_KERNEL   (1 << 8) /* exclude kernel level */
+#define TILE_CTL_EXCL_HV       (1 << 9) /* exclude hypervisor level */
+
+#define TILE_SEL_MASK          0x7f    /* 7 bits for event SEL,
+                                       COUNT_0_SEL */
+#define TILE_PLM_MASK          0x780   /* 4 bits priv level msks,
+                                       COUNT_0_MASK*/
+#define TILE_EVENT_MASK        (TILE_SEL_MASK | TILE_PLM_MASK)
+
+#else /* __tilegx__*/
+/* PERF_COUNT_CTL on TILEGx*/
+#define TILE_CTL_EXCL_USER     (1 << 10) /* exclude user level */
+#define TILE_CTL_EXCL_KERNEL   (1 << 11) /* exclude kernel level */
+#define TILE_CTL_EXCL_HV       (1 << 12) /* exclude hypervisor level */
+
+#define TILE_SEL_MASK          0x3f    /* 6 bits for event SEL,
+                                       COUNT_0_SEL*/
+#define TILE_BOX_MASK          0x1c0   /* 3 bits box msks,
+                                       COUNT_0_BOX */
+#define TILE_PLM_MASK          0x3c00  /* 4 bits priv level msks,
+                                       COUNT_0_MASK */
+#define TILE_EVENT_MASK        (TILE_SEL_MASK | TILE_BOX_MASK | TILE_PLM_MASK)
+#endif /* __tilegx__*/
+
+/* Takes register and fault number.  Returns error to disable the interrupt. */
+typedef int (*perf_irq_t)(struct pt_regs *, int);
+
+int userspace_perf_handler(struct pt_regs *regs, int fault);
+
+perf_irq_t reserve_pmc_hardware(perf_irq_t new_perf_irq);
+void release_pmc_hardware(void);
+
+unsigned long pmc_get_overflow(void);
+void pmc_ack_overflow(unsigned long status);
+
+void unmask_pmc_interrupts(void);
+void mask_pmc_interrupts(void);
+
+#endif /* _ASM_TILE_PMC_H */
index 27a2bf39dae887c9056cfb8aea4e97e72770fde1..21f77bf68c690a438c8ca9540e0f9e7574872c17 100644 (file)
@@ -25,6 +25,8 @@ obj-$(CONFIG_PCI)             += pci_gx.o
 else
 obj-$(CONFIG_PCI)              += pci.o
 endif
+obj-$(CONFIG_PERF_EVENTS)      += perf_event.o
+obj-$(CONFIG_USE_PMC)          += pmc.o
 obj-$(CONFIG_TILE_USB)         += usb.o
 obj-$(CONFIG_TILE_HVGLUE_TRACE)        += hvglue_trace.o
 obj-$(CONFIG_FUNCTION_TRACER)  += ftrace.o mcount_64.o
index 2cbe6d5dd6b04db3ea071fb12c3dbb1866dc818e..cdbda45a4e4bba22c5397338b34f3310426fcd77 100644 (file)
@@ -313,13 +313,13 @@ intvec_\vecname:
         movei  r3, 0
        }
        .else
-       .ifc \c_routine, op_handle_perf_interrupt
+       .ifc \c_routine, handle_perf_interrupt
        {
         mfspr  r2, PERF_COUNT_STS
         movei  r3, -1   /* not used, but set for consistency */
        }
        .else
-       .ifc \c_routine, op_handle_aux_perf_interrupt
+       .ifc \c_routine, handle_perf_interrupt
        {
         mfspr  r2, AUX_PERF_COUNT_STS
         movei  r3, -1   /* not used, but set for consistency */
@@ -946,6 +946,13 @@ STD_ENTRY(interrupt_return)
        bzt     r30, .Lrestore_regs
 3:
 
+       /* We are relying on INT_PERF_COUNT at 33, and AUX_PERF_COUNT at 48 */
+       {
+        moveli r0, lo16(1 << (INT_PERF_COUNT - 32))
+        bz     r31, .Lrestore_regs
+       }
+       auli    r0, r0, ha16(1 << (INT_AUX_PERF_COUNT - 32))
+       mtspr   SPR_INTERRUPT_MASK_RESET_K_1, r0
 
        /*
         * We now commit to returning from this interrupt, since we will be
@@ -1171,6 +1178,10 @@ handle_nmi:
         PTREGS_PTR(r0, PTREGS_OFFSET_BASE)
        }
        FEEDBACK_REENTER(handle_nmi)
+       {
+        movei  r30, 1
+        seq    r31, r0, zero
+       }
        j       interrupt_return
        STD_ENDPROC(handle_nmi)
 
@@ -1835,8 +1846,9 @@ int_unalign:
 /* Include .intrpt array of interrupt vectors */
        .section ".intrpt", "ax"
 
-#define op_handle_perf_interrupt bad_intr
-#define op_handle_aux_perf_interrupt bad_intr
+#ifndef CONFIG_USE_PMC
+#define handle_perf_interrupt bad_intr
+#endif
 
 #ifndef CONFIG_HARDWALL
 #define do_hardwall_trap bad_intr
@@ -1877,7 +1889,7 @@ int_unalign:
        int_hand     INT_IDN_AVAIL, IDN_AVAIL, bad_intr
        int_hand     INT_UDN_AVAIL, UDN_AVAIL, bad_intr
        int_hand     INT_PERF_COUNT, PERF_COUNT, \
-                    op_handle_perf_interrupt, handle_nmi
+                    handle_perf_interrupt, handle_nmi
        int_hand     INT_INTCTRL_3, INTCTRL_3, bad_intr
 #if CONFIG_KERNEL_PL == 2
        dc_dispatch  INT_INTCTRL_2, INTCTRL_2
@@ -1902,7 +1914,7 @@ int_unalign:
        int_hand     INT_SN_CPL, SN_CPL, bad_intr
        int_hand     INT_DOUBLE_FAULT, DOUBLE_FAULT, do_trap
        int_hand     INT_AUX_PERF_COUNT, AUX_PERF_COUNT, \
-                    op_handle_aux_perf_interrupt, handle_nmi
+                    handle_perf_interrupt, handle_nmi
 
        /* Synthetic interrupt delivered only by the simulator */
        int_hand     INT_BREAKPOINT, BREAKPOINT, do_breakpoint
index b8fc497f24370c0d7c17536664cb19c40923e5bf..5b67efcecabd17603deb47cd622d94e9b710d470 100644 (file)
@@ -509,10 +509,10 @@ intvec_\vecname:
        .ifc \c_routine, do_trap
        mfspr   r2, GPV_REASON
        .else
-       .ifc \c_routine, op_handle_perf_interrupt
+       .ifc \c_routine, handle_perf_interrupt
        mfspr   r2, PERF_COUNT_STS
        .else
-       .ifc \c_routine, op_handle_aux_perf_interrupt
+       .ifc \c_routine, handle_perf_interrupt
        mfspr   r2, AUX_PERF_COUNT_STS
        .endif
        .endif
@@ -971,6 +971,15 @@ STD_ENTRY(interrupt_return)
        beqzt   r30, .Lrestore_regs
 3:
 
+#if INT_PERF_COUNT + 1 != INT_AUX_PERF_COUNT
+# error Bad interrupt assumption
+#endif
+       {
+        movei  r0, 3   /* two adjacent bits for the PERF_COUNT mask */
+        beqz   r31, .Lrestore_regs
+       }
+       shli    r0, r0, INT_PERF_COUNT
+       mtspr   SPR_INTERRUPT_MASK_RESET_K, r0
 
        /*
         * We now commit to returning from this interrupt, since we will be
@@ -1187,7 +1196,7 @@ handle_nmi:
        FEEDBACK_REENTER(handle_nmi)
        {
         movei  r30, 1
-        move   r31, r0
+        cmpeq  r31, r0, zero
        }
        j       interrupt_return
        STD_ENDPROC(handle_nmi)
@@ -1491,8 +1500,9 @@ STD_ENTRY(fill_ra_stack)
        .global intrpt_start
 intrpt_start:
 
-#define op_handle_perf_interrupt bad_intr
-#define op_handle_aux_perf_interrupt bad_intr
+#ifndef CONFIG_USE_PMC
+#define handle_perf_interrupt bad_intr
+#endif
 
 #ifndef CONFIG_HARDWALL
 #define do_hardwall_trap bad_intr
@@ -1540,9 +1550,9 @@ intrpt_start:
 #endif
        int_hand     INT_IPI_0, IPI_0, bad_intr
        int_hand     INT_PERF_COUNT, PERF_COUNT, \
-                    op_handle_perf_interrupt, handle_nmi
+                    handle_perf_interrupt, handle_nmi
        int_hand     INT_AUX_PERF_COUNT, AUX_PERF_COUNT, \
-                    op_handle_perf_interrupt, handle_nmi
+                    handle_perf_interrupt, handle_nmi
        int_hand     INT_INTCTRL_3, INTCTRL_3, bad_intr
 #if CONFIG_KERNEL_PL == 2
        dc_dispatch  INT_INTCTRL_2, INTCTRL_2
index 0586fdb9352d2b2ca88cf48af4125216f135b3ae..906a76bdb31d488e6ffa7fd4416e091812d611fc 100644 (file)
@@ -21,6 +21,7 @@
 #include <hv/drv_pcie_rc_intf.h>
 #include <arch/spr_def.h>
 #include <asm/traps.h>
+#include <linux/perf_event.h>
 
 /* Bit-flag stored in irq_desc->chip_data to indicate HW-cleared irqs. */
 #define IS_HW_CLEARED 1
@@ -260,6 +261,23 @@ void ack_bad_irq(unsigned int irq)
        pr_err("unexpected IRQ trap at vector %02x\n", irq);
 }
 
+/*
+ * /proc/interrupts printing:
+ */
+int arch_show_interrupts(struct seq_file *p, int prec)
+{
+#ifdef CONFIG_PERF_EVENTS
+       int i;
+
+       seq_printf(p, "%*s: ", prec, "PMI");
+
+       for_each_online_cpu(i)
+               seq_printf(p, "%10llu ", per_cpu(perf_irqs, i));
+       seq_puts(p, "  perf_events\n");
+#endif
+       return 0;
+}
+
 /*
  * Generic, controller-independent functions:
  */
index 00331af9525dcf52c990f3ee30abb13d367d7889..7867266f97169a5dd87fb09508e6833a94ef5b74 100644 (file)
@@ -68,8 +68,8 @@ void hv_message_intr(struct pt_regs *regs, int intnum)
 #endif
 
        while (1) {
-               rmi = hv_receive_message(__get_cpu_var(msg_state),
-                                        (HV_VirtAddr) message,
+               HV_MsgState *state = this_cpu_ptr(&msg_state);
+               rmi = hv_receive_message(*state, (HV_VirtAddr) message,
                                         sizeof(message));
                if (rmi.msglen == 0)
                        break;
index c45593db7718e8f1a589fe081054389d97ee4e6d..1f80a88c75a6ace1369e1888cbf0b369f7dc3cb6 100644 (file)
@@ -250,8 +250,6 @@ static void fixup_read_and_payload_sizes(void)
 
        /* Scan for the smallest maximum payload size. */
        for_each_pci_dev(dev) {
-               u32 devcap;
-
                if (!pci_is_pcie(dev))
                        continue;
 
diff --git a/arch/tile/kernel/perf_event.c b/arch/tile/kernel/perf_event.c
new file mode 100644 (file)
index 0000000..2bf6c9c
--- /dev/null
@@ -0,0 +1,1005 @@
+/*
+ * Copyright 2014 Tilera Corporation. All Rights Reserved.
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU General Public License
+ *   as published by the Free Software Foundation, version 2.
+ *
+ *   This program is distributed in the hope that it will be useful, but
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ *   NON INFRINGEMENT.  See the GNU General Public License for
+ *   more details.
+ *
+ *
+ * Perf_events support for Tile processor.
+ *
+ * This code is based upon the x86 perf event
+ * code, which is:
+ *
+ *  Copyright (C) 2008 Thomas Gleixner <tglx@linutronix.de>
+ *  Copyright (C) 2008-2009 Red Hat, Inc., Ingo Molnar
+ *  Copyright (C) 2009 Jaswinder Singh Rajput
+ *  Copyright (C) 2009 Advanced Micro Devices, Inc., Robert Richter
+ *  Copyright (C) 2008-2009 Red Hat, Inc., Peter Zijlstra <pzijlstr@redhat.com>
+ *  Copyright (C) 2009 Intel Corporation, <markus.t.metzger@intel.com>
+ *  Copyright (C) 2009 Google, Inc., Stephane Eranian
+ */
+
+#include <linux/kprobes.h>
+#include <linux/kernel.h>
+#include <linux/kdebug.h>
+#include <linux/mutex.h>
+#include <linux/bitmap.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/perf_event.h>
+#include <linux/atomic.h>
+#include <asm/traps.h>
+#include <asm/stack.h>
+#include <asm/pmc.h>
+#include <hv/hypervisor.h>
+
+#define TILE_MAX_COUNTERS      4
+
+#define PERF_COUNT_0_IDX       0
+#define PERF_COUNT_1_IDX       1
+#define AUX_PERF_COUNT_0_IDX   2
+#define AUX_PERF_COUNT_1_IDX   3
+
+struct cpu_hw_events {
+       int                     n_events;
+       struct perf_event       *events[TILE_MAX_COUNTERS]; /* counter order */
+       struct perf_event       *event_list[TILE_MAX_COUNTERS]; /* enabled
+                                                               order */
+       int                     assign[TILE_MAX_COUNTERS];
+       unsigned long           active_mask[BITS_TO_LONGS(TILE_MAX_COUNTERS)];
+       unsigned long           used_mask;
+};
+
+/* TILE arch specific performance monitor unit */
+struct tile_pmu {
+       const char      *name;
+       int             version;
+       const int       *hw_events;     /* generic hw events table */
+       /* generic hw cache events table */
+       const int       (*cache_events)[PERF_COUNT_HW_CACHE_MAX]
+                                      [PERF_COUNT_HW_CACHE_OP_MAX]
+                                      [PERF_COUNT_HW_CACHE_RESULT_MAX];
+       int             (*map_hw_event)(u64);    /*method used to map
+                                                 hw events */
+       int             (*map_cache_event)(u64); /*method used to map
+                                                 cache events */
+
+       u64             max_period;             /* max sampling period */
+       u64             cntval_mask;            /* counter width mask */
+       int             cntval_bits;            /* counter width */
+       int             max_events;             /* max generic hw events
+                                               in map */
+       int             num_counters;           /* number base + aux counters */
+       int             num_base_counters;      /* number base counters */
+};
+
+DEFINE_PER_CPU(u64, perf_irqs);
+static DEFINE_PER_CPU(struct cpu_hw_events, cpu_hw_events);
+
+#define TILE_OP_UNSUPP         (-1)
+
+#ifndef __tilegx__
+/* TILEPro hardware events map */
+static const int tile_hw_event_map[] = {
+       [PERF_COUNT_HW_CPU_CYCLES]              = 0x01, /* ONE */
+       [PERF_COUNT_HW_INSTRUCTIONS]            = 0x06, /* MP_BUNDLE_RETIRED */
+       [PERF_COUNT_HW_CACHE_REFERENCES]        = TILE_OP_UNSUPP,
+       [PERF_COUNT_HW_CACHE_MISSES]            = TILE_OP_UNSUPP,
+       [PERF_COUNT_HW_BRANCH_INSTRUCTIONS]     = 0x16, /*
+                                         MP_CONDITIONAL_BRANCH_ISSUED */
+       [PERF_COUNT_HW_BRANCH_MISSES]           = 0x14, /*
+                                         MP_CONDITIONAL_BRANCH_MISSPREDICT */
+       [PERF_COUNT_HW_BUS_CYCLES]              = TILE_OP_UNSUPP,
+};
+#else
+/* TILEGx hardware events map */
+static const int tile_hw_event_map[] = {
+       [PERF_COUNT_HW_CPU_CYCLES]              = 0x181, /* ONE */
+       [PERF_COUNT_HW_INSTRUCTIONS]            = 0xdb, /* INSTRUCTION_BUNDLE */
+       [PERF_COUNT_HW_CACHE_REFERENCES]        = TILE_OP_UNSUPP,
+       [PERF_COUNT_HW_CACHE_MISSES]            = TILE_OP_UNSUPP,
+       [PERF_COUNT_HW_BRANCH_INSTRUCTIONS]     = 0xd9, /*
+                                               COND_BRANCH_PRED_CORRECT */
+       [PERF_COUNT_HW_BRANCH_MISSES]           = 0xda, /*
+                                               COND_BRANCH_PRED_INCORRECT */
+       [PERF_COUNT_HW_BUS_CYCLES]              = TILE_OP_UNSUPP,
+};
+#endif
+
+#define C(x) PERF_COUNT_HW_CACHE_##x
+
+/*
+ * Generalized hw caching related hw_event table, filled
+ * in on a per model basis. A value of -1 means
+ * 'not supported', any other value means the
+ * raw hw_event ID.
+ */
+#ifndef __tilegx__
+/* TILEPro hardware cache event map */
+static const int tile_cache_event_map[PERF_COUNT_HW_CACHE_MAX]
+                                    [PERF_COUNT_HW_CACHE_OP_MAX]
+                                    [PERF_COUNT_HW_CACHE_RESULT_MAX] = {
+[C(L1D)] = {
+       [C(OP_READ)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = 0x21, /* RD_MISS */
+       },
+       [C(OP_WRITE)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = 0x22, /* WR_MISS */
+       },
+       [C(OP_PREFETCH)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+},
+[C(L1I)] = {
+       [C(OP_READ)] = {
+               [C(RESULT_ACCESS)] = 0x12, /* MP_ICACHE_HIT_ISSUED */
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_WRITE)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_PREFETCH)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+},
+[C(LL)] = {
+       [C(OP_READ)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_WRITE)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_PREFETCH)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+},
+[C(DTLB)] = {
+       [C(OP_READ)] = {
+               [C(RESULT_ACCESS)] = 0x1d, /* TLB_CNT */
+               [C(RESULT_MISS)] = 0x20, /* TLB_EXCEPTION */
+       },
+       [C(OP_WRITE)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_PREFETCH)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+},
+[C(ITLB)] = {
+       [C(OP_READ)] = {
+               [C(RESULT_ACCESS)] = 0x13, /* MP_ITLB_HIT_ISSUED */
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_WRITE)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_PREFETCH)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+},
+[C(BPU)] = {
+       [C(OP_READ)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_WRITE)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_PREFETCH)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+},
+};
+#else
+/* TILEGx hardware events map */
+static const int tile_cache_event_map[PERF_COUNT_HW_CACHE_MAX]
+                                    [PERF_COUNT_HW_CACHE_OP_MAX]
+                                    [PERF_COUNT_HW_CACHE_RESULT_MAX] = {
+[C(L1D)] = {
+       /*
+        * Like some other architectures (e.g. ARM), 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)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = 0x44, /* RD_MISS */
+       },
+       [C(OP_WRITE)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = 0x45, /* WR_MISS */
+       },
+       [C(OP_PREFETCH)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+},
+[C(L1I)] = {
+       [C(OP_READ)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_WRITE)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_PREFETCH)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+},
+[C(LL)] = {
+       [C(OP_READ)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_WRITE)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_PREFETCH)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+},
+[C(DTLB)] = {
+       [C(OP_READ)] = {
+               [C(RESULT_ACCESS)] = 0x40, /* TLB_CNT */
+               [C(RESULT_MISS)] = 0x43, /* TLB_EXCEPTION */
+       },
+       [C(OP_WRITE)] = {
+               [C(RESULT_ACCESS)] = 0x40, /* TLB_CNT */
+               [C(RESULT_MISS)] = 0x43, /* TLB_EXCEPTION */
+       },
+       [C(OP_PREFETCH)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+},
+[C(ITLB)] = {
+       [C(OP_READ)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = 0xd4, /* ITLB_MISS_INT */
+       },
+       [C(OP_WRITE)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = 0xd4, /* ITLB_MISS_INT */
+       },
+       [C(OP_PREFETCH)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+},
+[C(BPU)] = {
+       [C(OP_READ)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_WRITE)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+       [C(OP_PREFETCH)] = {
+               [C(RESULT_ACCESS)] = TILE_OP_UNSUPP,
+               [C(RESULT_MISS)] = TILE_OP_UNSUPP,
+       },
+},
+};
+#endif
+
+static atomic_t tile_active_events;
+static DEFINE_MUTEX(perf_intr_reserve_mutex);
+
+static int tile_map_hw_event(u64 config);
+static int tile_map_cache_event(u64 config);
+
+static int tile_pmu_handle_irq(struct pt_regs *regs, int fault);
+
+/*
+ * To avoid new_raw_count getting larger then pre_raw_count
+ * in tile_perf_event_update(), we limit the value of max_period to 2^31 - 1.
+ */
+static const struct tile_pmu tilepmu = {
+#ifndef __tilegx__
+       .name = "tilepro",
+#else
+       .name = "tilegx",
+#endif
+       .max_events = ARRAY_SIZE(tile_hw_event_map),
+       .map_hw_event = tile_map_hw_event,
+       .hw_events = tile_hw_event_map,
+       .map_cache_event = tile_map_cache_event,
+       .cache_events = &tile_cache_event_map,
+       .cntval_bits = 32,
+       .cntval_mask = (1ULL << 32) - 1,
+       .max_period = (1ULL << 31) - 1,
+       .num_counters = TILE_MAX_COUNTERS,
+       .num_base_counters = TILE_BASE_COUNTERS,
+};
+
+static const struct tile_pmu *tile_pmu __read_mostly;
+
+/*
+ * Check whether perf event is enabled.
+ */
+int tile_perf_enabled(void)
+{
+       return atomic_read(&tile_active_events) != 0;
+}
+
+/*
+ * Read Performance Counters.
+ */
+static inline u64 read_counter(int idx)
+{
+       u64 val = 0;
+
+       /* __insn_mfspr() only takes an immediate argument */
+       switch (idx) {
+       case PERF_COUNT_0_IDX:
+               val = __insn_mfspr(SPR_PERF_COUNT_0);
+               break;
+       case PERF_COUNT_1_IDX:
+               val = __insn_mfspr(SPR_PERF_COUNT_1);
+               break;
+       case AUX_PERF_COUNT_0_IDX:
+               val = __insn_mfspr(SPR_AUX_PERF_COUNT_0);
+               break;
+       case AUX_PERF_COUNT_1_IDX:
+               val = __insn_mfspr(SPR_AUX_PERF_COUNT_1);
+               break;
+       default:
+               WARN_ON_ONCE(idx > AUX_PERF_COUNT_1_IDX ||
+                               idx < PERF_COUNT_0_IDX);
+       }
+
+       return val;
+}
+
+/*
+ * Write Performance Counters.
+ */
+static inline void write_counter(int idx, u64 value)
+{
+       /* __insn_mtspr() only takes an immediate argument */
+       switch (idx) {
+       case PERF_COUNT_0_IDX:
+               __insn_mtspr(SPR_PERF_COUNT_0, value);
+               break;
+       case PERF_COUNT_1_IDX:
+               __insn_mtspr(SPR_PERF_COUNT_1, value);
+               break;
+       case AUX_PERF_COUNT_0_IDX:
+               __insn_mtspr(SPR_AUX_PERF_COUNT_0, value);
+               break;
+       case AUX_PERF_COUNT_1_IDX:
+               __insn_mtspr(SPR_AUX_PERF_COUNT_1, value);
+               break;
+       default:
+               WARN_ON_ONCE(idx > AUX_PERF_COUNT_1_IDX ||
+                               idx < PERF_COUNT_0_IDX);
+       }
+}
+
+/*
+ * Enable performance event by setting
+ * Performance Counter Control registers.
+ */
+static inline void tile_pmu_enable_event(struct perf_event *event)
+{
+       struct hw_perf_event *hwc = &event->hw;
+       unsigned long cfg, mask;
+       int shift, idx = hwc->idx;
+
+       /*
+        * prevent early activation from tile_pmu_start() in hw_perf_enable
+        */
+
+       if (WARN_ON_ONCE(idx == -1))
+               return;
+
+       if (idx < tile_pmu->num_base_counters)
+               cfg = __insn_mfspr(SPR_PERF_COUNT_CTL);
+       else
+               cfg = __insn_mfspr(SPR_AUX_PERF_COUNT_CTL);
+
+       switch (idx) {
+       case PERF_COUNT_0_IDX:
+       case AUX_PERF_COUNT_0_IDX:
+               mask = TILE_EVENT_MASK;
+               shift = 0;
+               break;
+       case PERF_COUNT_1_IDX:
+       case AUX_PERF_COUNT_1_IDX:
+               mask = TILE_EVENT_MASK << 16;
+               shift = 16;
+               break;
+       default:
+               WARN_ON_ONCE(idx < PERF_COUNT_0_IDX ||
+                       idx > AUX_PERF_COUNT_1_IDX);
+               return;
+       }
+
+       /* Clear mask bits to enable the event. */
+       cfg &= ~mask;
+       cfg |= hwc->config << shift;
+
+       if (idx < tile_pmu->num_base_counters)
+               __insn_mtspr(SPR_PERF_COUNT_CTL, cfg);
+       else
+               __insn_mtspr(SPR_AUX_PERF_COUNT_CTL, cfg);
+}
+
+/*
+ * Disable performance event by clearing
+ * Performance Counter Control registers.
+ */
+static inline void tile_pmu_disable_event(struct perf_event *event)
+{
+       struct hw_perf_event *hwc = &event->hw;
+       unsigned long cfg, mask;
+       int idx = hwc->idx;
+
+       if (idx == -1)
+               return;
+
+       if (idx < tile_pmu->num_base_counters)
+               cfg = __insn_mfspr(SPR_PERF_COUNT_CTL);
+       else
+               cfg = __insn_mfspr(SPR_AUX_PERF_COUNT_CTL);
+
+       switch (idx) {
+       case PERF_COUNT_0_IDX:
+       case AUX_PERF_COUNT_0_IDX:
+               mask = TILE_PLM_MASK;
+               break;
+       case PERF_COUNT_1_IDX:
+       case AUX_PERF_COUNT_1_IDX:
+               mask = TILE_PLM_MASK << 16;
+               break;
+       default:
+               WARN_ON_ONCE(idx < PERF_COUNT_0_IDX ||
+                       idx > AUX_PERF_COUNT_1_IDX);
+               return;
+       }
+
+       /* Set mask bits to disable the event. */
+       cfg |= mask;
+
+       if (idx < tile_pmu->num_base_counters)
+               __insn_mtspr(SPR_PERF_COUNT_CTL, cfg);
+       else
+               __insn_mtspr(SPR_AUX_PERF_COUNT_CTL, cfg);
+}
+
+/*
+ * Propagate event elapsed time into the generic event.
+ * Can only be executed on the CPU where the event is active.
+ * Returns the delta events processed.
+ */
+static u64 tile_perf_event_update(struct perf_event *event)
+{
+       struct hw_perf_event *hwc = &event->hw;
+       int shift = 64 - tile_pmu->cntval_bits;
+       u64 prev_raw_count, new_raw_count;
+       u64 oldval;
+       int idx = hwc->idx;
+       u64 delta;
+
+       /*
+        * Careful: an NMI might modify the previous event value.
+        *
+        * Our tactic to handle this is to first atomically read and
+        * exchange a new raw count - then add that new-prev delta
+        * count to the generic event atomically:
+        */
+again:
+       prev_raw_count = local64_read(&hwc->prev_count);
+       new_raw_count = read_counter(idx);
+
+       oldval = local64_cmpxchg(&hwc->prev_count, prev_raw_count,
+                                new_raw_count);
+       if (oldval != prev_raw_count)
+               goto again;
+
+       /*
+        * Now we have the new raw value and have updated the prev
+        * timestamp already. We can now calculate the elapsed delta
+        * (event-)time and add that to the generic event.
+        *
+        * Careful, not all hw sign-extends above the physical width
+        * of the count.
+        */
+       delta = (new_raw_count << shift) - (prev_raw_count << shift);
+       delta >>= shift;
+
+       local64_add(delta, &event->count);
+       local64_sub(delta, &hwc->period_left);
+
+       return new_raw_count;
+}
+
+/*
+ * Set the next IRQ period, based on the hwc->period_left value.
+ * To be called with the event disabled in hw:
+ */
+static int tile_event_set_period(struct perf_event *event)
+{
+       struct hw_perf_event *hwc = &event->hw;
+       int idx = hwc->idx;
+       s64 left = local64_read(&hwc->period_left);
+       s64 period = hwc->sample_period;
+       int ret = 0;
+
+       /*
+        * If we are way outside a reasonable range then just skip forward:
+        */
+       if (unlikely(left <= -period)) {
+               left = period;
+               local64_set(&hwc->period_left, left);
+               hwc->last_period = period;
+               ret = 1;
+       }
+
+       if (unlikely(left <= 0)) {
+               left += period;
+               local64_set(&hwc->period_left, left);
+               hwc->last_period = period;
+               ret = 1;
+       }
+       if (left > tile_pmu->max_period)
+               left = tile_pmu->max_period;
+
+       /*
+        * The hw event starts counting from this event offset,
+        * mark it to be able to extra future deltas:
+        */
+       local64_set(&hwc->prev_count, (u64)-left);
+
+       write_counter(idx, (u64)(-left) & tile_pmu->cntval_mask);
+
+       perf_event_update_userpage(event);
+
+       return ret;
+}
+
+/*
+ * Stop the event but do not release the PMU counter
+ */
+static void tile_pmu_stop(struct perf_event *event, int flags)
+{
+       struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
+       struct hw_perf_event *hwc = &event->hw;
+       int idx = hwc->idx;
+
+       if (__test_and_clear_bit(idx, cpuc->active_mask)) {
+               tile_pmu_disable_event(event);
+               cpuc->events[hwc->idx] = NULL;
+               WARN_ON_ONCE(hwc->state & PERF_HES_STOPPED);
+               hwc->state |= PERF_HES_STOPPED;
+       }
+
+       if ((flags & PERF_EF_UPDATE) && !(hwc->state & PERF_HES_UPTODATE)) {
+               /*
+                * Drain the remaining delta count out of a event
+                * that we are disabling:
+                */
+               tile_perf_event_update(event);
+               hwc->state |= PERF_HES_UPTODATE;
+       }
+}
+
+/*
+ * Start an event (without re-assigning counter)
+ */
+static void tile_pmu_start(struct perf_event *event, int flags)
+{
+       struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
+       int idx = event->hw.idx;
+
+       if (WARN_ON_ONCE(!(event->hw.state & PERF_HES_STOPPED)))
+               return;
+
+       if (WARN_ON_ONCE(idx == -1))
+               return;
+
+       if (flags & PERF_EF_RELOAD) {
+               WARN_ON_ONCE(!(event->hw.state & PERF_HES_UPTODATE));
+               tile_event_set_period(event);
+       }
+
+       event->hw.state = 0;
+
+       cpuc->events[idx] = event;
+       __set_bit(idx, cpuc->active_mask);
+
+       unmask_pmc_interrupts();
+
+       tile_pmu_enable_event(event);
+
+       perf_event_update_userpage(event);
+}
+
+/*
+ * Add a single event to the PMU.
+ *
+ * The event is added to the group of enabled events
+ * but only if it can be scehduled with existing events.
+ */
+static int tile_pmu_add(struct perf_event *event, int flags)
+{
+       struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
+       struct hw_perf_event *hwc;
+       unsigned long mask;
+       int b, max_cnt;
+
+       hwc = &event->hw;
+
+       /*
+        * We are full.
+        */
+       if (cpuc->n_events == tile_pmu->num_counters)
+               return -ENOSPC;
+
+       cpuc->event_list[cpuc->n_events] = event;
+       cpuc->n_events++;
+
+       hwc->state = PERF_HES_UPTODATE | PERF_HES_STOPPED;
+       if (!(flags & PERF_EF_START))
+               hwc->state |= PERF_HES_ARCH;
+
+       /*
+        * Find first empty counter.
+        */
+       max_cnt = tile_pmu->num_counters;
+       mask = ~cpuc->used_mask;
+
+       /* Find next free counter. */
+       b = find_next_bit(&mask, max_cnt, 0);
+
+       /* Should not happen. */
+       if (WARN_ON_ONCE(b == max_cnt))
+               return -ENOSPC;
+
+       /*
+        * Assign counter to event.
+        */
+       event->hw.idx = b;
+       __set_bit(b, &cpuc->used_mask);
+
+       /*
+        * Start if requested.
+        */
+       if (flags & PERF_EF_START)
+               tile_pmu_start(event, PERF_EF_RELOAD);
+
+       return 0;
+}
+
+/*
+ * Delete a single event from the PMU.
+ *
+ * The event is deleted from the group of enabled events.
+ * If it is the last event, disable PMU interrupt.
+ */
+static void tile_pmu_del(struct perf_event *event, int flags)
+{
+       struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
+       int i;
+
+       /*
+        * Remove event from list, compact list if necessary.
+        */
+       for (i = 0; i < cpuc->n_events; i++) {
+               if (cpuc->event_list[i] == event) {
+                       while (++i < cpuc->n_events)
+                               cpuc->event_list[i-1] = cpuc->event_list[i];
+                       --cpuc->n_events;
+                       cpuc->events[event->hw.idx] = NULL;
+                       __clear_bit(event->hw.idx, &cpuc->used_mask);
+                       tile_pmu_stop(event, PERF_EF_UPDATE);
+                       break;
+               }
+       }
+       /*
+        * If there are no events left, then mask PMU interrupt.
+        */
+       if (cpuc->n_events == 0)
+               mask_pmc_interrupts();
+       perf_event_update_userpage(event);
+}
+
+/*
+ * Propagate event elapsed time into the event.
+ */
+static inline void tile_pmu_read(struct perf_event *event)
+{
+       tile_perf_event_update(event);
+}
+
+/*
+ * Map generic events to Tile PMU.
+ */
+static int tile_map_hw_event(u64 config)
+{
+       if (config >= tile_pmu->max_events)
+               return -EINVAL;
+       return tile_pmu->hw_events[config];
+}
+
+/*
+ * Map generic hardware cache events to Tile PMU.
+ */
+static int tile_map_cache_event(u64 config)
+{
+       unsigned int cache_type, cache_op, cache_result;
+       int code;
+
+       if (!tile_pmu->cache_events)
+               return -ENOENT;
+
+       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;
+
+       code = (*tile_pmu->cache_events)[cache_type][cache_op][cache_result];
+       if (code == TILE_OP_UNSUPP)
+               return -EINVAL;
+
+       return code;
+}
+
+static void tile_event_destroy(struct perf_event *event)
+{
+       if (atomic_dec_return(&tile_active_events) == 0)
+               release_pmc_hardware();
+}
+
+static int __tile_event_init(struct perf_event *event)
+{
+       struct perf_event_attr *attr = &event->attr;
+       struct hw_perf_event *hwc = &event->hw;
+       int code;
+
+       switch (attr->type) {
+       case PERF_TYPE_HARDWARE:
+               code = tile_pmu->map_hw_event(attr->config);
+               break;
+       case PERF_TYPE_HW_CACHE:
+               code = tile_pmu->map_cache_event(attr->config);
+               break;
+       case PERF_TYPE_RAW:
+               code = attr->config & TILE_EVENT_MASK;
+               break;
+       default:
+               /* Should not happen. */
+               return -EOPNOTSUPP;
+       }
+
+       if (code < 0)
+               return code;
+
+       hwc->config = code;
+       hwc->idx = -1;
+
+       if (attr->exclude_user)
+               hwc->config |= TILE_CTL_EXCL_USER;
+
+       if (attr->exclude_kernel)
+               hwc->config |= TILE_CTL_EXCL_KERNEL;
+
+       if (attr->exclude_hv)
+               hwc->config |= TILE_CTL_EXCL_HV;
+
+       if (!hwc->sample_period) {
+               hwc->sample_period = tile_pmu->max_period;
+               hwc->last_period = hwc->sample_period;
+               local64_set(&hwc->period_left, hwc->sample_period);
+       }
+       event->destroy = tile_event_destroy;
+       return 0;
+}
+
+static int tile_event_init(struct perf_event *event)
+{
+       int err = 0;
+       perf_irq_t old_irq_handler = NULL;
+
+       if (atomic_inc_return(&tile_active_events) == 1)
+               old_irq_handler = reserve_pmc_hardware(tile_pmu_handle_irq);
+
+       if (old_irq_handler) {
+               pr_warn("PMC hardware busy (reserved by oprofile)\n");
+
+               atomic_dec(&tile_active_events);
+               return -EBUSY;
+       }
+
+       switch (event->attr.type) {
+       case PERF_TYPE_RAW:
+       case PERF_TYPE_HARDWARE:
+       case PERF_TYPE_HW_CACHE:
+               break;
+
+       default:
+               return -ENOENT;
+       }
+
+       err = __tile_event_init(event);
+       if (err) {
+               if (event->destroy)
+                       event->destroy(event);
+       }
+       return err;
+}
+
+static struct pmu tilera_pmu = {
+       .event_init     = tile_event_init,
+       .add            = tile_pmu_add,
+       .del            = tile_pmu_del,
+
+       .start          = tile_pmu_start,
+       .stop           = tile_pmu_stop,
+
+       .read           = tile_pmu_read,
+};
+
+/*
+ * PMU's IRQ handler, PMU has 2 interrupts, they share the same handler.
+ */
+int tile_pmu_handle_irq(struct pt_regs *regs, int fault)
+{
+       struct perf_sample_data data;
+       struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events);
+       struct perf_event *event;
+       struct hw_perf_event *hwc;
+       u64 val;
+       unsigned long status;
+       int bit;
+
+       __get_cpu_var(perf_irqs)++;
+
+       if (!atomic_read(&tile_active_events))
+               return 0;
+
+       status = pmc_get_overflow();
+       pmc_ack_overflow(status);
+
+       for_each_set_bit(bit, &status, tile_pmu->num_counters) {
+
+               event = cpuc->events[bit];
+
+               if (!event)
+                       continue;
+
+               if (!test_bit(bit, cpuc->active_mask))
+                       continue;
+
+               hwc = &event->hw;
+
+               val = tile_perf_event_update(event);
+               if (val & (1ULL << (tile_pmu->cntval_bits - 1)))
+                       continue;
+
+               perf_sample_data_init(&data, 0, event->hw.last_period);
+               if (!tile_event_set_period(event))
+                       continue;
+
+               if (perf_event_overflow(event, &data, regs))
+                       tile_pmu_stop(event, 0);
+       }
+
+       return 0;
+}
+
+static bool __init supported_pmu(void)
+{
+       tile_pmu = &tilepmu;
+       return true;
+}
+
+int __init init_hw_perf_events(void)
+{
+       supported_pmu();
+       perf_pmu_register(&tilera_pmu, "cpu", PERF_TYPE_RAW);
+       return 0;
+}
+arch_initcall(init_hw_perf_events);
+
+/* Callchain handling code. */
+
+/*
+ * Tile specific backtracing code for perf_events.
+ */
+static inline void perf_callchain(struct perf_callchain_entry *entry,
+                   struct pt_regs *regs)
+{
+       struct KBacktraceIterator kbt;
+       unsigned int i;
+
+       /*
+        * Get the address just after the "jalr" instruction that
+        * jumps to the handler for a syscall.  When we find this
+        * address in a backtrace, we silently ignore it, which gives
+        * us a one-step backtrace connection from the sys_xxx()
+        * function in the kernel to the xxx() function in libc.
+        * Otherwise, we lose the ability to properly attribute time
+        * from the libc calls to the kernel implementations, since
+        * oprofile only considers PCs from backtraces a pair at a time.
+        */
+       unsigned long handle_syscall_pc = handle_syscall_link_address();
+
+       KBacktraceIterator_init(&kbt, NULL, regs);
+       kbt.profile = 1;
+
+       /*
+        * The sample for the pc is already recorded.  Now we are adding the
+        * address of the callsites on the stack.  Our iterator starts
+        * with the frame of the (already sampled) call site.  If our
+        * iterator contained a "return address" field, we could have just
+        * used it and wouldn't have needed to skip the first
+        * frame.  That's in effect what the arm and x86 versions do.
+        * Instead we peel off the first iteration to get the equivalent
+        * behavior.
+        */
+
+       if (KBacktraceIterator_end(&kbt))
+               return;
+       KBacktraceIterator_next(&kbt);
+
+       /*
+        * Set stack depth to 16 for user and kernel space respectively, that
+        * is, total 32 stack frames.
+        */
+       for (i = 0; i < 16; ++i) {
+               unsigned long pc;
+               if (KBacktraceIterator_end(&kbt))
+                       break;
+               pc = kbt.it.pc;
+               if (pc != handle_syscall_pc)
+                       perf_callchain_store(entry, pc);
+               KBacktraceIterator_next(&kbt);
+       }
+}
+
+void perf_callchain_user(struct perf_callchain_entry *entry,
+                   struct pt_regs *regs)
+{
+       perf_callchain(entry, regs);
+}
+
+void perf_callchain_kernel(struct perf_callchain_entry *entry,
+                     struct pt_regs *regs)
+{
+       perf_callchain(entry, regs);
+}
diff --git a/arch/tile/kernel/pmc.c b/arch/tile/kernel/pmc.c
new file mode 100644 (file)
index 0000000..db62cc3
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2014 Tilera Corporation. All Rights Reserved.
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU General Public License
+ *   as published by the Free Software Foundation, version 2.
+ *
+ *   This program is distributed in the hope that it will be useful, but
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ *   NON INFRINGEMENT.  See the GNU General Public License for
+ *   more details.
+ */
+
+#include <linux/errno.h>
+#include <linux/spinlock.h>
+#include <linux/module.h>
+#include <linux/atomic.h>
+#include <linux/interrupt.h>
+
+#include <asm/processor.h>
+#include <asm/pmc.h>
+
+perf_irq_t perf_irq = NULL;
+int handle_perf_interrupt(struct pt_regs *regs, int fault)
+{
+       int retval;
+
+       if (!perf_irq)
+               panic("Unexpected PERF_COUNT interrupt %d\n", fault);
+
+       nmi_enter();
+       retval = perf_irq(regs, fault);
+       nmi_exit();
+       return retval;
+}
+
+/* Reserve PMC hardware if it is available. */
+perf_irq_t reserve_pmc_hardware(perf_irq_t new_perf_irq)
+{
+       return cmpxchg(&perf_irq, NULL, new_perf_irq);
+}
+EXPORT_SYMBOL(reserve_pmc_hardware);
+
+/* Release PMC hardware. */
+void release_pmc_hardware(void)
+{
+       perf_irq = NULL;
+}
+EXPORT_SYMBOL(release_pmc_hardware);
+
+
+/*
+ * Get current overflow status of each performance counter,
+ * and auxiliary performance counter.
+ */
+unsigned long
+pmc_get_overflow(void)
+{
+       unsigned long status;
+
+       /*
+        * merge base+aux into a single vector
+        */
+       status = __insn_mfspr(SPR_PERF_COUNT_STS);
+       status |= __insn_mfspr(SPR_AUX_PERF_COUNT_STS) << TILE_BASE_COUNTERS;
+       return status;
+}
+
+/*
+ * Clear the status bit for the corresponding counter, if written
+ * with a one.
+ */
+void
+pmc_ack_overflow(unsigned long status)
+{
+       /*
+        * clear overflow status by writing ones
+        */
+       __insn_mtspr(SPR_PERF_COUNT_STS, status);
+       __insn_mtspr(SPR_AUX_PERF_COUNT_STS, status >> TILE_BASE_COUNTERS);
+}
+
+/*
+ * The perf count interrupts are masked and unmasked explicitly,
+ * and only here.  The normal irq_enable() does not enable them,
+ * and irq_disable() does not disable them.  That lets these
+ * routines drive the perf count interrupts orthogonally.
+ *
+ * We also mask the perf count interrupts on entry to the perf count
+ * interrupt handler in assembly code, and by default unmask them
+ * again (with interrupt critical section protection) just before
+ * returning from the interrupt.  If the perf count handler returns
+ * a non-zero error code, then we don't re-enable them before returning.
+ *
+ * For Pro, we rely on both interrupts being in the same word to update
+ * them atomically so we never have one enabled and one disabled.
+ */
+
+#if CHIP_HAS_SPLIT_INTR_MASK()
+# if INT_PERF_COUNT < 32 || INT_AUX_PERF_COUNT < 32
+#  error Fix assumptions about which word PERF_COUNT interrupts are in
+# endif
+#endif
+
+static inline unsigned long long pmc_mask(void)
+{
+       unsigned long long mask = 1ULL << INT_PERF_COUNT;
+       mask |= 1ULL << INT_AUX_PERF_COUNT;
+       return mask;
+}
+
+void unmask_pmc_interrupts(void)
+{
+       interrupt_mask_reset_mask(pmc_mask());
+}
+
+void mask_pmc_interrupts(void)
+{
+       interrupt_mask_set_mask(pmc_mask());
+}
index 5d10642db63e8e9f98934e36200128a9dfd26073..462dcd0c1700221adda93563b5f7904511a9c656 100644 (file)
@@ -236,7 +236,15 @@ cycles_t ns2cycles(unsigned long nsecs)
         * clock frequency.
         */
        struct clock_event_device *dev = &__raw_get_cpu_var(tile_timer);
-       return ((u64)nsecs * dev->mult) >> dev->shift;
+
+       /*
+        * as in clocksource.h and x86's timer.h, we split the calculation
+        * into 2 parts to avoid unecessary overflow of the intermediate
+        * value. This will not lead to any loss of precision.
+        */
+       u64 quot = (u64)nsecs >> dev->shift;
+       u64 rem  = (u64)nsecs & ((1ULL << dev->shift) - 1);
+       return quot * dev->mult + ((rem * dev->mult) >> dev->shift);
 }
 
 void update_vsyscall_tz(void)
index e2b7a2f4ee4103eebd8b551339c3c3ecddb6ed2e..a025f63d54cd9609820c634da186a5e71a026375 100644 (file)
@@ -104,7 +104,7 @@ $(obj-vdso32:%=%): KBUILD_AFLAGS = $(KBUILD_AFLAGS_32)
 $(obj-vdso32:%=%): KBUILD_CFLAGS = $(KBUILD_CFLAGS_32)
 
 $(obj)/vgettimeofday32.o: $(obj)/vgettimeofday.c
-       $(call if_changed,cc_o_c)
+       $(call if_changed_rule,cc_o_c)
 
 $(obj)/vrt_sigreturn32.o: $(obj)/vrt_sigreturn.S
        $(call if_changed,as_o_S)
index fef8ae922800ddd1131205deabc4f15b0940e90a..4b06719ee984618c6154fa0bfbadd78977a4861e 100644 (file)
@@ -5,7 +5,8 @@ ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \
                                   -e s/arm.*/arm/ -e s/sa110/arm/ \
                                   -e s/s390x/s390/ -e s/parisc64/parisc/ \
                                   -e s/ppc.*/powerpc/ -e s/mips.*/mips/ \
-                                  -e s/sh[234].*/sh/ -e s/aarch64.*/arm64/ )
+                                  -e s/sh[234].*/sh/ -e s/aarch64.*/arm64/ \
+                                  -e s/tile.*/tile/ )
 
 # Additional ARCH settings for x86
 ifeq ($(ARCH),i386)
index e18a8b5e69531cca4e1fbf889098d779ff14cf4d..5c11ecad02a9623c4324ca4aeefdb6fa1135c9f4 100644 (file)
 #define CPUINFO_PROC   "core ID"
 #endif
 
+#ifdef __tile__
+#define mb()           asm volatile ("mf" ::: "memory")
+#define wmb()          asm volatile ("mf" ::: "memory")
+#define rmb()          asm volatile ("mf" ::: "memory")
+#define cpu_relax()    asm volatile ("mfspr zero, PASS" ::: "memory")
+#define CPUINFO_PROC    "model name"
+#endif
+
 #define barrier() asm volatile ("" ::: "memory")
 
 #ifndef cpu_relax