tile: Add support for handling PMC hardware
authorZhigang Lu <zlu@tilera.com>
Mon, 27 Jan 2014 07:11:07 +0000 (15:11 +0800)
committerChris Metcalf <cmetcalf@tilera.com>
Fri, 7 Mar 2014 16:19:47 +0000 (11:19 -0500)
The PMC module is used by perf_events, oprofile and watchdogs.

Signed-off-by: Zhigang Lu <zlu@tilera.com>
Signed-off-by: Chris Metcalf <cmetcalf@tilera.com>
arch/tile/Kconfig
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/pmc.c [new file with mode: 0644]

index b3692ce..3067b15 100644 (file)
@@ -66,6 +66,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/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 27a2bf3..71d8353 100644 (file)
@@ -25,6 +25,7 @@ obj-$(CONFIG_PCI)             += pci_gx.o
 else
 obj-$(CONFIG_PCI)              += pci.o
 endif
+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 2cbe6d5..605ffbd 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 */
@@ -1835,8 +1835,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 +1878,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 +1903,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 b8fc497..8f892a5 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
@@ -1491,8 +1491,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 +1541,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
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());
+}