MIPS: Add HARDWARE_WATCHPOINTS definitions and support code.
authorDavid Daney <ddaney@avtrex.com>
Tue, 23 Sep 2008 07:05:54 +0000 (00:05 -0700)
committerRalf Baechle <ralf@linux-mips.org>
Sat, 11 Oct 2008 15:18:56 +0000 (16:18 +0100)
This is the main support code for the patch.  Here we just add the
code, the following patches hook it up.

Signed-off-by: David Daney <ddaney@avtrex.com>
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
 create mode 100644 arch/mips/include/asm/watch.h
 create mode 100644 arch/mips/kernel/watch.c

arch/mips/include/asm/cpu-info.h
arch/mips/include/asm/processor.h
arch/mips/include/asm/thread_info.h
arch/mips/include/asm/watch.h [new file with mode: 0644]
arch/mips/kernel/Makefile
arch/mips/kernel/watch.c [new file with mode: 0644]

index 2de73dbb2e9e3cac04f2ad7c7abf0654acbbf0bb..744cd8fb107f8777d824312f42d9abb1cef57d68 100644 (file)
@@ -12,6 +12,8 @@
 #ifndef __ASM_CPU_INFO_H
 #define __ASM_CPU_INFO_H
 
+#include <linux/types.h>
+
 #include <asm/cache.h>
 
 /*
@@ -69,6 +71,10 @@ struct cpuinfo_mips {
        int                     tc_id;   /* Thread Context number */
 #endif
        void                    *data;  /* Additional data */
+       unsigned int            watch_reg_count;   /* Number that exist */
+       unsigned int            watch_reg_use_cnt; /* Usable by ptrace */
+#define NUM_WATCH_REGS 4
+       u16                     watch_reg_masks[NUM_WATCH_REGS];
 } __attribute__((aligned(SMP_CACHE_BYTES)));
 
 extern struct cpuinfo_mips cpu_data[];
index a1e4453469f9af2e09ed52cd3f2b428b46b0c5da..18ee58e3944545bc82a77d6eb6781c033bf60eca 100644 (file)
@@ -105,6 +105,19 @@ struct mips_dsp_state {
        {0,} \
 }
 
+struct mips3264_watch_reg_state {
+       /* The width of watchlo is 32 in a 32 bit kernel and 64 in a
+          64 bit kernel.  We use unsigned long as it has the same
+          property. */
+       unsigned long watchlo[NUM_WATCH_REGS];
+       /* Only the mask and IRW bits from watchhi. */
+       u16 watchhi[NUM_WATCH_REGS];
+};
+
+union mips_watch_reg_state {
+       struct mips3264_watch_reg_state mips3264;
+};
+
 typedef struct {
        unsigned long seg;
 } mm_segment_t;
@@ -137,6 +150,9 @@ struct thread_struct {
        /* Saved state of the DSP ASE, if available. */
        struct mips_dsp_state dsp;
 
+       /* Saved watch register state, if available. */
+       union mips_watch_reg_state watch;
+
        /* Other stuff associated with the thread. */
        unsigned long cp0_badvaddr;     /* Last user fault */
        unsigned long cp0_baduaddr;     /* Last kernel fault accessing USEG */
@@ -192,6 +208,10 @@ struct thread_struct {
                .dspr           = {0, },                        \
                .dspcontrol     = 0,                            \
        },                                                      \
+       /*                                                      \
+        * saved watch register stuff                           \
+        */                                                     \
+       .watch = {{{0,},},},                                    \
        /*                                                      \
         * Other stuff associated with the process              \
         */                                                     \
index bb3060699df29f437facbb5631fd44985ca9e69e..3f76de73c943e825be4464ff4cfc245fdd421aa1 100644 (file)
@@ -124,6 +124,7 @@ register struct thread_info *__current_thread_info __asm__("$28");
 #define TIF_32BIT_REGS         22      /* also implies 16/32 fprs */
 #define TIF_32BIT_ADDR         23      /* 32-bit address space (o32/n32) */
 #define TIF_FPUBOUND           24      /* thread bound to FPU-full CPU set */
+#define TIF_LOAD_WATCH         25      /* If set, load watch registers */
 #define TIF_SYSCALL_TRACE      31      /* syscall trace active */
 
 #define _TIF_SYSCALL_TRACE     (1<<TIF_SYSCALL_TRACE)
@@ -140,6 +141,7 @@ register struct thread_info *__current_thread_info __asm__("$28");
 #define _TIF_32BIT_REGS                (1<<TIF_32BIT_REGS)
 #define _TIF_32BIT_ADDR                (1<<TIF_32BIT_ADDR)
 #define _TIF_FPUBOUND          (1<<TIF_FPUBOUND)
+#define _TIF_LOAD_WATCH                (1<<TIF_LOAD_WATCH)
 
 /* work to do on interrupt/exception return */
 #define _TIF_WORK_MASK         (0x0000ffef & ~_TIF_SECCOMP)
diff --git a/arch/mips/include/asm/watch.h b/arch/mips/include/asm/watch.h
new file mode 100644 (file)
index 0000000..20126ec
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ *
+ * Copyright (C) 2008 David Daney
+ */
+#ifndef _ASM_WATCH_H
+#define _ASM_WATCH_H
+
+#include <linux/bitops.h>
+
+#include <asm/mipsregs.h>
+
+void mips_install_watch_registers(void);
+void mips_read_watch_registers(void);
+void mips_clear_watch_registers(void);
+void mips_probe_watch_registers(struct cpuinfo_mips *c);
+
+#ifdef CONFIG_HARDWARE_WATCHPOINTS
+#define __restore_watch() do {                                         \
+       if (unlikely(test_bit(TIF_LOAD_WATCH,                           \
+                             &current_thread_info()->flags))) {        \
+               mips_install_watch_registers();                         \
+       }                                                               \
+} while (0)
+
+#else
+#define __restore_watch() do {} while (0)
+#endif
+
+#endif /* _ASM_WATCH_H */
index 25775cb54000dd3871673c512e43cf59398c6461..d9da7112aaf8188b90eb88bde35f556cdd933887 100644 (file)
@@ -6,7 +6,7 @@ extra-y         := head.o init_task.o vmlinux.lds
 
 obj-y          += cpu-probe.o branch.o entry.o genex.o irq.o process.o \
                   ptrace.o reset.o setup.o signal.o syscall.o \
-                  time.o topology.o traps.o unaligned.o
+                  time.o topology.o traps.o unaligned.o watch.o
 
 obj-$(CONFIG_CEVT_BCM1480)     += cevt-bcm1480.o
 obj-$(CONFIG_CEVT_R4K)         += cevt-r4k.o
diff --git a/arch/mips/kernel/watch.c b/arch/mips/kernel/watch.c
new file mode 100644 (file)
index 0000000..c154069
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * 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.
+ *
+ * Copyright (C) 2008 David Daney
+ */
+
+#include <linux/sched.h>
+
+#include <asm/processor.h>
+#include <asm/watch.h>
+
+/*
+ * Install the watch registers for the current thread.  A maximum of
+ * four registers are installed although the machine may have more.
+ */
+void mips_install_watch_registers(void)
+{
+       struct mips3264_watch_reg_state *watches =
+               &current->thread.watch.mips3264;
+       switch (current_cpu_data.watch_reg_use_cnt) {
+       default:
+               BUG();
+       case 4:
+               write_c0_watchlo3(watches->watchlo[3]);
+               /* Write 1 to the I, R, and W bits to clear them, and
+                  1 to G so all ASIDs are trapped. */
+               write_c0_watchhi3(0x40000007 | watches->watchhi[3]);
+       case 3:
+               write_c0_watchlo2(watches->watchlo[2]);
+               write_c0_watchhi2(0x40000007 | watches->watchhi[2]);
+       case 2:
+               write_c0_watchlo1(watches->watchlo[1]);
+               write_c0_watchhi1(0x40000007 | watches->watchhi[1]);
+       case 1:
+               write_c0_watchlo0(watches->watchlo[0]);
+               write_c0_watchhi0(0x40000007 | watches->watchhi[0]);
+       }
+}
+
+/*
+ * Read back the watchhi registers so the user space debugger has
+ * access to the I, R, and W bits.  A maximum of four registers are
+ * read although the machine may have more.
+ */
+void mips_read_watch_registers(void)
+{
+       struct mips3264_watch_reg_state *watches =
+               &current->thread.watch.mips3264;
+       switch (current_cpu_data.watch_reg_use_cnt) {
+       default:
+               BUG();
+       case 4:
+               watches->watchhi[3] = (read_c0_watchhi3() & 0x0fff);
+       case 3:
+               watches->watchhi[2] = (read_c0_watchhi2() & 0x0fff);
+       case 2:
+               watches->watchhi[1] = (read_c0_watchhi1() & 0x0fff);
+       case 1:
+               watches->watchhi[0] = (read_c0_watchhi0() & 0x0fff);
+       }
+       if (current_cpu_data.watch_reg_use_cnt == 1 &&
+           (watches->watchhi[0] & 7) == 0) {
+               /* Pathological case of release 1 architecture that
+                * doesn't set the condition bits.  We assume that
+                * since we got here, the watch condition was met and
+                * signal that the conditions requested in watchlo
+                * were met.  */
+               watches->watchhi[0] |= (watches->watchlo[0] & 7);
+       }
+ }
+
+/*
+ * Disable all watch registers.  Although only four registers are
+ * installed, all are cleared to eliminate the possibility of endless
+ * looping in the watch handler.
+ */
+void mips_clear_watch_registers(void)
+{
+       switch (current_cpu_data.watch_reg_count) {
+       default:
+               BUG();
+       case 8:
+               write_c0_watchlo7(0);
+       case 7:
+               write_c0_watchlo6(0);
+       case 6:
+               write_c0_watchlo5(0);
+       case 5:
+               write_c0_watchlo4(0);
+       case 4:
+               write_c0_watchlo3(0);
+       case 3:
+               write_c0_watchlo2(0);
+       case 2:
+               write_c0_watchlo1(0);
+       case 1:
+               write_c0_watchlo0(0);
+       }
+}
+
+__cpuinit void mips_probe_watch_registers(struct cpuinfo_mips *c)
+{
+       unsigned int t;
+
+       if ((c->options & MIPS_CPU_WATCH) == 0)
+               return;
+       /*
+        * Check which of the I,R and W bits are supported, then
+        * disable the register.
+        */
+       write_c0_watchlo0(7);
+       t = read_c0_watchlo0();
+       write_c0_watchlo0(0);
+       c->watch_reg_masks[0] = t & 7;
+
+       /* Write the mask bits and read them back to determine which
+        * can be used. */
+       c->watch_reg_count = 1;
+       c->watch_reg_use_cnt = 1;
+       t = read_c0_watchhi0();
+       write_c0_watchhi0(t | 0xff8);
+       t = read_c0_watchhi0();
+       c->watch_reg_masks[0] |= (t & 0xff8);
+       if ((t & 0x80000000) == 0)
+               return;
+
+       write_c0_watchlo1(7);
+       t = read_c0_watchlo1();
+       write_c0_watchlo1(0);
+       c->watch_reg_masks[1] = t & 7;
+
+       c->watch_reg_count = 2;
+       c->watch_reg_use_cnt = 2;
+       t = read_c0_watchhi1();
+       write_c0_watchhi1(t | 0xff8);
+       t = read_c0_watchhi1();
+       c->watch_reg_masks[1] |= (t & 0xff8);
+       if ((t & 0x80000000) == 0)
+               return;
+
+       write_c0_watchlo2(7);
+       t = read_c0_watchlo2();
+       write_c0_watchlo2(0);
+       c->watch_reg_masks[2] = t & 7;
+
+       c->watch_reg_count = 3;
+       c->watch_reg_use_cnt = 3;
+       t = read_c0_watchhi2();
+       write_c0_watchhi2(t | 0xff8);
+       t = read_c0_watchhi2();
+       c->watch_reg_masks[2] |= (t & 0xff8);
+       if ((t & 0x80000000) == 0)
+               return;
+
+       write_c0_watchlo3(7);
+       t = read_c0_watchlo3();
+       write_c0_watchlo3(0);
+       c->watch_reg_masks[3] = t & 7;
+
+       c->watch_reg_count = 4;
+       c->watch_reg_use_cnt = 4;
+       t = read_c0_watchhi3();
+       write_c0_watchhi3(t | 0xff8);
+       t = read_c0_watchhi3();
+       c->watch_reg_masks[3] |= (t & 0xff8);
+       if ((t & 0x80000000) == 0)
+               return;
+
+       /* We use at most 4, but probe and report up to 8. */
+       c->watch_reg_count = 5;
+       t = read_c0_watchhi4();
+       if ((t & 0x80000000) == 0)
+               return;
+
+       c->watch_reg_count = 6;
+       t = read_c0_watchhi5();
+       if ((t & 0x80000000) == 0)
+               return;
+
+       c->watch_reg_count = 7;
+       t = read_c0_watchhi6();
+       if ((t & 0x80000000) == 0)
+               return;
+
+       c->watch_reg_count = 8;
+}