MIPS: Loongson: Add oprofile support
Wu Zhangjin [Thu, 2 Jul 2009 15:25:46 +0000 (23:25 +0800)]
This kernel support is needed by the user-space tool:oprofile to profile
linux kernel or applications via loongson2 performance counters. you can
enable this driver via CONFIG_OPROFILE = y or m.

On Loongson2 there are two performance counters, each one can count 16
events respectively. when anyone of the performance counter overflows, an
interrupt will be generated and is routed to the IRQ MIPS_CPU_IRQ_BASE + 6.

Signed-off-by: Yanhua <yanh@lemote.com>
Signed-off-by: Wu Zhangjin <wuzj@lemote.com>
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>

arch/mips/include/asm/mach-lemote/loongson.h
arch/mips/lemote/lm2e/irq.c
arch/mips/oprofile/Makefile
arch/mips/oprofile/common.c
arch/mips/oprofile/op_model_loongson2.c [new file with mode: 0644]

index 916eace..95ee4c8 100644 (file)
@@ -50,4 +50,7 @@ extern void __init prom_init_env(void);
 #define LOONGSON_PXARB_CFG      BONITO(BONITO_REGBASE + 0x68)
 #define LOONGSON_PXARB_STATUS       BONITO(BONITO_REGBASE + 0x6c)
 
+/* loongson2-specific perf counter IRQ */
+#define LOONGSON2_PERFCNT_IRQ   (MIPS_CPU_IRQ_BASE + 6)
+
 #endif /* __ASM_MACH_LOONGSON_LOONGSON_H */
index fb7643a..9585f5a 100644 (file)
@@ -58,6 +58,8 @@ asmlinkage void plat_irq_dispatch(void)
 
        if (pending & CAUSEF_IP7)
                do_IRQ(MIPS_CPU_IRQ_BASE + 7);
+       else if (pending & CAUSEF_IP6) /* perf counter loverflow */
+               do_IRQ(LOONGSON2_PERFCNT_IRQ);
        else if (pending & CAUSEF_IP5)
                i8259_irqdispatch();
        else if (pending & CAUSEF_IP2)
index bf3be6f..02cc65e 100644 (file)
@@ -15,3 +15,4 @@ oprofile-$(CONFIG_CPU_MIPS64)         += op_model_mipsxx.o
 oprofile-$(CONFIG_CPU_R10000)          += op_model_mipsxx.o
 oprofile-$(CONFIG_CPU_SB1)             += op_model_mipsxx.o
 oprofile-$(CONFIG_CPU_RM9000)          += op_model_rm9000.o
+oprofile-$(CONFIG_CPU_LOONGSON2)       += op_model_loongson2.o
index 3bf3354..7832ad2 100644 (file)
@@ -16,6 +16,7 @@
 
 extern struct op_mips_model op_model_mipsxx_ops __attribute__((weak));
 extern struct op_mips_model op_model_rm9000_ops __attribute__((weak));
+extern struct op_mips_model op_model_loongson2_ops __attribute__((weak));
 
 static struct op_mips_model *model;
 
@@ -93,6 +94,9 @@ int __init oprofile_arch_init(struct oprofile_operations *ops)
        case CPU_RM9000:
                lmodel = &op_model_rm9000_ops;
                break;
+       case CPU_LOONGSON2:
+               lmodel = &op_model_loongson2_ops;
+               break;
        };
 
        if (!lmodel)
diff --git a/arch/mips/oprofile/op_model_loongson2.c b/arch/mips/oprofile/op_model_loongson2.c
new file mode 100644 (file)
index 0000000..655cb8d
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * Loongson2 performance counter driver for oprofile
+ *
+ * Copyright (C) 2009 Lemote Inc. & Insititute of Computing Technology
+ * Author: Yanhua <yanh@lemote.com>
+ * Author: Wu Zhangjin <wuzj@lemote.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ */
+#include <linux/init.h>
+#include <linux/oprofile.h>
+#include <linux/interrupt.h>
+
+#include <loongson.h>                  /* LOONGSON2_PERFCNT_IRQ */
+#include "op_impl.h"
+
+/*
+ * a patch should be sent to oprofile with the loongson-specific support.
+ * otherwise, the oprofile tool will not recognize this and complain about
+ * "cpu_type 'unset' is not valid".
+ */
+#define LOONGSON2_CPU_TYPE     "mips/godson2"
+
+#define LOONGSON2_COUNTER1_EVENT(event)        ((event & 0x0f) << 5)
+#define LOONGSON2_COUNTER2_EVENT(event)        ((event & 0x0f) << 9)
+
+#define LOONGSON2_PERFCNT_EXL                  (1UL    <<  0)
+#define LOONGSON2_PERFCNT_KERNEL               (1UL    <<  1)
+#define LOONGSON2_PERFCNT_SUPERVISOR   (1UL    <<  2)
+#define LOONGSON2_PERFCNT_USER                 (1UL    <<  3)
+#define LOONGSON2_PERFCNT_INT_EN               (1UL    <<  4)
+#define LOONGSON2_PERFCNT_OVERFLOW             (1ULL   << 31)
+
+/* Loongson2 performance counter register */
+#define read_c0_perfctrl() __read_64bit_c0_register($24, 0)
+#define write_c0_perfctrl(val) __write_64bit_c0_register($24, 0, val)
+#define read_c0_perfcnt() __read_64bit_c0_register($25, 0)
+#define write_c0_perfcnt(val) __write_64bit_c0_register($25, 0, val)
+
+static struct loongson2_register_config {
+       unsigned int ctrl;
+       unsigned long long reset_counter1;
+       unsigned long long reset_counter2;
+       int cnt1_enalbed, cnt2_enalbed;
+} reg;
+
+DEFINE_SPINLOCK(sample_lock);
+
+static char *oprofid = "LoongsonPerf";
+static irqreturn_t loongson2_perfcount_handler(int irq, void *dev_id);
+/* Compute all of the registers in preparation for enabling profiling.  */
+
+static void loongson2_reg_setup(struct op_counter_config *cfg)
+{
+       unsigned int ctrl = 0;
+
+       reg.reset_counter1 = 0;
+       reg.reset_counter2 = 0;
+       /* Compute the performance counter ctrl word.  */
+       /* For now count kernel and user mode */
+       if (cfg[0].enabled) {
+               ctrl |= LOONGSON2_COUNTER1_EVENT(cfg[0].event);
+               reg.reset_counter1 = 0x80000000ULL - cfg[0].count;
+       }
+
+       if (cfg[1].enabled) {
+               ctrl |= LOONGSON2_COUNTER2_EVENT(cfg[1].event);
+               reg.reset_counter2 = (0x80000000ULL - cfg[1].count);
+       }
+
+       if (cfg[0].enabled || cfg[1].enabled) {
+               ctrl |= LOONGSON2_PERFCNT_EXL | LOONGSON2_PERFCNT_INT_EN;
+               if (cfg[0].kernel || cfg[1].kernel)
+                       ctrl |= LOONGSON2_PERFCNT_KERNEL;
+               if (cfg[0].user || cfg[1].user)
+                       ctrl |= LOONGSON2_PERFCNT_USER;
+       }
+
+       reg.ctrl = ctrl;
+
+       reg.cnt1_enalbed = cfg[0].enabled;
+       reg.cnt2_enalbed = cfg[1].enabled;
+
+}
+
+/* Program all of the registers in preparation for enabling profiling.  */
+
+static void loongson2_cpu_setup(void *args)
+{
+       uint64_t perfcount;
+
+       perfcount = (reg.reset_counter2 << 32) | reg.reset_counter1;
+       write_c0_perfcnt(perfcount);
+}
+
+static void loongson2_cpu_start(void *args)
+{
+       /* Start all counters on current CPU */
+       if (reg.cnt1_enalbed || reg.cnt2_enalbed)
+               write_c0_perfctrl(reg.ctrl);
+}
+
+static void loongson2_cpu_stop(void *args)
+{
+       /* Stop all counters on current CPU */
+       write_c0_perfctrl(0);
+       memset(&reg, 0, sizeof(reg));
+}
+
+static irqreturn_t loongson2_perfcount_handler(int irq, void *dev_id)
+{
+       uint64_t counter, counter1, counter2;
+       struct pt_regs *regs = get_irq_regs();
+       int enabled;
+       unsigned long flags;
+
+       /*
+        * LOONGSON2 defines two 32-bit performance counters.
+        * To avoid a race updating the registers we need to stop the counters
+        * while we're messing with
+        * them ...
+        */
+
+       /* Check whether the irq belongs to me */
+       enabled = reg.cnt1_enalbed | reg.cnt2_enalbed;
+       if (!enabled)
+               return IRQ_NONE;
+
+       counter = read_c0_perfcnt();
+       counter1 = counter & 0xffffffff;
+       counter2 = counter >> 32;
+
+       spin_lock_irqsave(&sample_lock, flags);
+
+       if (counter1 & LOONGSON2_PERFCNT_OVERFLOW) {
+               if (reg.cnt1_enalbed)
+                       oprofile_add_sample(regs, 0);
+               counter1 = reg.reset_counter1;
+       }
+       if (counter2 & LOONGSON2_PERFCNT_OVERFLOW) {
+               if (reg.cnt2_enalbed)
+                       oprofile_add_sample(regs, 1);
+               counter2 = reg.reset_counter2;
+       }
+
+       spin_unlock_irqrestore(&sample_lock, flags);
+
+       write_c0_perfcnt((counter2 << 32) | counter1);
+
+       return IRQ_HANDLED;
+}
+
+static int __init loongson2_init(void)
+{
+       return request_irq(LOONGSON2_PERFCNT_IRQ, loongson2_perfcount_handler,
+                          IRQF_SHARED, "Perfcounter", oprofid);
+}
+
+static void loongson2_exit(void)
+{
+       write_c0_perfctrl(0);
+       free_irq(LOONGSON2_PERFCNT_IRQ, oprofid);
+}
+
+struct op_mips_model op_model_loongson2_ops = {
+       .reg_setup = loongson2_reg_setup,
+       .cpu_setup = loongson2_cpu_setup,
+       .init = loongson2_init,
+       .exit = loongson2_exit,
+       .cpu_start = loongson2_cpu_start,
+       .cpu_stop = loongson2_cpu_stop,
+       .cpu_type = LOONGSON2_CPU_TYPE,
+       .num_counters = 2
+};