ARC: kgdb support
authorMischa Jonker <mjonker@synopsys.com>
Fri, 18 Jan 2013 09:42:24 +0000 (15:12 +0530)
committerVineet Gupta <vgupta@synopsys.com>
Fri, 15 Feb 2013 17:46:07 +0000 (23:16 +0530)
Signed-off-by: Mischa Jonker <mjonker@synopsys.com>
Signed-off-by: Vineet Gupta <vgupta@synopsys.com>
Cc: Jason Wessel <jason.wessel@windriver.com>
Acked-by: Jason Wessel <jason.wessel@windriver.com>
arch/arc/Kconfig
arch/arc/include/asm/kgdb.h [new file with mode: 0644]
arch/arc/kernel/Makefile
arch/arc/kernel/kgdb.c [new file with mode: 0644]
arch/arc/kernel/traps.c

index f8042835e7464b31d05a4a821ee82ae944d58d01..69a939af72c60de56c62f517fd863ee6fc35796b 100644 (file)
@@ -22,6 +22,7 @@ config ARC
        select GENERIC_PENDING_IRQ if SMP
        select GENERIC_SIGALTSTACK
        select GENERIC_SMP_IDLE_THREAD
+       select HAVE_ARCH_KGDB
        select HAVE_ARCH_TRACEHOOK
        select HAVE_GENERIC_HARDIRQS
        select HAVE_KPROBES
@@ -378,7 +379,7 @@ config ARC_DW2_UNWIND
 
 config ARC_DBG_TLB_PARANOIA
        bool "Paranoia Checks in Low Level TLB Handlers"
-       depends on ARC_DBG && !SMP
+       depends on ARC_DBG
        default n
 
 config ARC_DBG_TLB_MISS_COUNT
diff --git a/arch/arc/include/asm/kgdb.h b/arch/arc/include/asm/kgdb.h
new file mode 100644 (file)
index 0000000..f3c4934
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * kgdb support for ARC
+ *
+ * Copyright (C) 2012 Synopsys, Inc. (www.synopsys.com)
+ *
+ * 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 __ARC_KGDB_H__
+#define __ARC_KGDB_H__
+
+#ifdef CONFIG_KGDB
+
+#include <asm/user.h>
+
+/* to ensure compatibility with Linux 2.6.35, we don't implement the get/set
+ * register API yet */
+#undef DBG_MAX_REG_NUM
+
+#define GDB_MAX_REGS           39
+
+#define BREAK_INSTR_SIZE       2
+#define CACHE_FLUSH_IS_SAFE    1
+#define NUMREGBYTES            (GDB_MAX_REGS * 4)
+#define BUFMAX                 2048
+
+static inline void arch_kgdb_breakpoint(void)
+{
+       __asm__ __volatile__ ("trap_s   0x4\n");
+}
+
+extern void kgdb_trap(struct pt_regs *regs, int param);
+
+enum arc700_linux_regnums {
+       _R0             = 0,
+       _R1, _R2, _R3, _R4, _R5, _R6, _R7, _R8, _R9, _R10, _R11, _R12, _R13,
+       _R14, _R15, _R16, _R17, _R18, _R19, _R20, _R21, _R22, _R23, _R24,
+       _R25, _R26,
+       _BTA            = 27,
+       _LP_START       = 28,
+       _LP_END         = 29,
+       _LP_COUNT       = 30,
+       _STATUS32       = 31,
+       _BLINK          = 32,
+       _FP             = 33,
+       __SP            = 34,
+       _EFA            = 35,
+       _RET            = 36,
+       _ORIG_R8        = 37,
+       _STOP_PC        = 38
+};
+
+#else
+static inline void kgdb_trap(struct pt_regs *regs, int param)
+{
+}
+#endif
+
+#endif /* __ARC_KGDB_H__ */
index 507a33d4a255731613220862572b26691ca117d6..6da2b12cf7e3b8ec24bf87541beacc225228fc8d 100644 (file)
@@ -17,6 +17,7 @@ obj-$(CONFIG_SMP)                     += smp.o
 obj-$(CONFIG_ARC_DW2_UNWIND)           += unwind.o
 obj-$(CONFIG_KPROBES)                  += kprobes.o
 obj-$(CONFIG_ARC_MISALIGN_ACCESS)      += unaligned.o
+obj-$(CONFIG_KGDB)                     += kgdb.o
 
 obj-$(CONFIG_ARC_FPU_SAVE_RESTORE)     += fpu.o
 CFLAGS_fpu.o   += -mdpfp
diff --git a/arch/arc/kernel/kgdb.c b/arch/arc/kernel/kgdb.c
new file mode 100644 (file)
index 0000000..2888ba5
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * kgdb support for ARC
+ *
+ * Copyright (C) 2012 Synopsys, Inc. (www.synopsys.com)
+ *
+ * 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/kgdb.h>
+#include <asm/disasm.h>
+#include <asm/cacheflush.h>
+
+static void to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *kernel_regs,
+                       struct callee_regs *cregs)
+{
+       int regno;
+
+       for (regno = 0; regno <= 26; regno++)
+               gdb_regs[_R0 + regno] = get_reg(regno, kernel_regs, cregs);
+
+       for (regno = 27; regno < GDB_MAX_REGS; regno++)
+               gdb_regs[regno] = 0;
+
+       gdb_regs[_FP]           = kernel_regs->fp;
+       gdb_regs[__SP]          = kernel_regs->sp;
+       gdb_regs[_BLINK]        = kernel_regs->blink;
+       gdb_regs[_RET]          = kernel_regs->ret;
+       gdb_regs[_STATUS32]     = kernel_regs->status32;
+       gdb_regs[_LP_COUNT]     = kernel_regs->lp_count;
+       gdb_regs[_LP_END]       = kernel_regs->lp_end;
+       gdb_regs[_LP_START]     = kernel_regs->lp_start;
+       gdb_regs[_BTA]          = kernel_regs->bta;
+       gdb_regs[_STOP_PC]      = kernel_regs->ret;
+}
+
+static void from_gdb_regs(unsigned long *gdb_regs, struct pt_regs *kernel_regs,
+                       struct callee_regs *cregs)
+{
+       int regno;
+
+       for (regno = 0; regno <= 26; regno++)
+               set_reg(regno, gdb_regs[regno + _R0], kernel_regs, cregs);
+
+       kernel_regs->fp         = gdb_regs[_FP];
+       kernel_regs->sp         = gdb_regs[__SP];
+       kernel_regs->blink      = gdb_regs[_BLINK];
+       kernel_regs->ret        = gdb_regs[_RET];
+       kernel_regs->status32   = gdb_regs[_STATUS32];
+       kernel_regs->lp_count   = gdb_regs[_LP_COUNT];
+       kernel_regs->lp_end     = gdb_regs[_LP_END];
+       kernel_regs->lp_start   = gdb_regs[_LP_START];
+       kernel_regs->bta        = gdb_regs[_BTA];
+}
+
+
+void pt_regs_to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *kernel_regs)
+{
+       to_gdb_regs(gdb_regs, kernel_regs, (struct callee_regs *)
+               current->thread.callee_reg);
+}
+
+void gdb_regs_to_pt_regs(unsigned long *gdb_regs, struct pt_regs *kernel_regs)
+{
+       from_gdb_regs(gdb_regs, kernel_regs, (struct callee_regs *)
+               current->thread.callee_reg);
+}
+
+void sleeping_thread_to_gdb_regs(unsigned long *gdb_regs,
+                                struct task_struct *task)
+{
+       if (task)
+               to_gdb_regs(gdb_regs, task_pt_regs(task),
+                       (struct callee_regs *) task->thread.callee_reg);
+}
+
+struct single_step_data_t {
+       uint16_t opcode[2];
+       unsigned long address[2];
+       int is_branch;
+       int armed;
+} single_step_data;
+
+static void undo_single_step(struct pt_regs *regs)
+{
+       if (single_step_data.armed) {
+               int i;
+
+               for (i = 0; i < (single_step_data.is_branch ? 2 : 1); i++) {
+                       memcpy((void *) single_step_data.address[i],
+                               &single_step_data.opcode[i],
+                               BREAK_INSTR_SIZE);
+
+                       flush_icache_range(single_step_data.address[i],
+                               single_step_data.address[i] +
+                               BREAK_INSTR_SIZE);
+               }
+               single_step_data.armed = 0;
+       }
+}
+
+static void place_trap(unsigned long address, void *save)
+{
+       memcpy(save, (void *) address, BREAK_INSTR_SIZE);
+       memcpy((void *) address, &arch_kgdb_ops.gdb_bpt_instr,
+               BREAK_INSTR_SIZE);
+       flush_icache_range(address, address + BREAK_INSTR_SIZE);
+}
+
+static void do_single_step(struct pt_regs *regs)
+{
+       single_step_data.is_branch = disasm_next_pc((unsigned long)
+               regs->ret, regs, (struct callee_regs *)
+               current->thread.callee_reg,
+               &single_step_data.address[0],
+               &single_step_data.address[1]);
+
+       place_trap(single_step_data.address[0], &single_step_data.opcode[0]);
+
+       if (single_step_data.is_branch) {
+               place_trap(single_step_data.address[1],
+                       &single_step_data.opcode[1]);
+       }
+
+       single_step_data.armed++;
+}
+
+int kgdb_arch_handle_exception(int e_vector, int signo, int err_code,
+                              char *remcomInBuffer, char *remcomOutBuffer,
+                              struct pt_regs *regs)
+{
+       unsigned long addr;
+       char *ptr;
+
+       undo_single_step(regs);
+
+       switch (remcomInBuffer[0]) {
+       case 's':
+       case 'c':
+               ptr = &remcomInBuffer[1];
+               if (kgdb_hex2long(&ptr, &addr))
+                       regs->ret = addr;
+
+       case 'D':
+       case 'k':
+               atomic_set(&kgdb_cpu_doing_single_step, -1);
+
+               if (remcomInBuffer[0] == 's') {
+                       do_single_step(regs);
+                       atomic_set(&kgdb_cpu_doing_single_step,
+                                  smp_processor_id());
+               }
+
+               return 0;
+       }
+       return -1;
+}
+
+unsigned long kgdb_arch_pc(int exception, struct pt_regs *regs)
+{
+       return instruction_pointer(regs);
+}
+
+int kgdb_arch_init(void)
+{
+       single_step_data.armed = 0;
+       return 0;
+}
+
+void kgdb_trap(struct pt_regs *regs, int param)
+{
+       /* trap_s 3 is used for breakpoints that overwrite existing
+        * instructions, while trap_s 4 is used for compiled breakpoints.
+        *
+        * with trap_s 3 breakpoints the original instruction needs to be
+        * restored and continuation needs to start at the location of the
+        * breakpoint.
+        *
+        * with trap_s 4 (compiled) breakpoints, continuation needs to
+        * start after the breakpoint.
+        */
+       if (param == 3)
+               instruction_pointer(regs) -= BREAK_INSTR_SIZE;
+
+       kgdb_handle_exception(1, SIGTRAP, 0, regs);
+}
+
+void kgdb_arch_exit(void)
+{
+}
+
+void kgdb_arch_set_pc(struct pt_regs *regs, unsigned long ip)
+{
+       instruction_pointer(regs) = ip;
+}
+
+struct kgdb_arch arch_kgdb_ops = {
+       /* breakpoint instruction: TRAP_S 0x3 */
+#ifdef CONFIG_CPU_BIG_ENDIAN
+       .gdb_bpt_instr          = {0x78, 0x7e},
+#else
+       .gdb_bpt_instr          = {0x7e, 0x78},
+#endif
+};
index ec802c52a1cad5a688e59d6728fd078a3a5d1643..7496995371e813738725bac8bb68f40c843d476f 100644 (file)
@@ -20,6 +20,7 @@
 #include <asm/setup.h>
 #include <asm/kprobes.h>
 #include <asm/unaligned.h>
+#include <asm/kgdb.h>
 
 void __init trap_init(void)
 {
@@ -141,6 +142,11 @@ void do_non_swi_trap(unsigned long cause, unsigned long address,
                trap_is_kprobe(param, address, regs);
                break;
 
+       case 3:
+       case 4:
+               kgdb_trap(regs, param);
+               break;
+
        default:
                break;
        }