]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - arch/x86/kernel/kgdb.c
kgdb, x86: Avoid invoking kgdb_nmicallback twice per NMI
[linux-2.6-omap-h63xx.git] / arch / x86 / kernel / kgdb.c
index 5d7a21119bf83af40387da7dfdc386e3f9cc3e7d..10435a120d2227bffd79a3df193f4238049ce2b0 100644 (file)
 #include <asm/apicdef.h>
 #include <asm/system.h>
 
-#ifdef CONFIG_X86_32
-# include <mach_ipi.h>
-#else
-# include <asm/mach_apic.h>
-#endif
+#include <mach_ipi.h>
 
 /*
  * Put the error code here just in case the user cares:
@@ -73,6 +69,9 @@ static int gdb_x86vector = -1;
  */
 void pt_regs_to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *regs)
 {
+#ifndef CONFIG_X86_32
+       u32 *gdb_regs32 = (u32 *)gdb_regs;
+#endif
        gdb_regs[GDB_AX]        = regs->ax;
        gdb_regs[GDB_BX]        = regs->bx;
        gdb_regs[GDB_CX]        = regs->cx;
@@ -80,9 +79,9 @@ void pt_regs_to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *regs)
        gdb_regs[GDB_SI]        = regs->si;
        gdb_regs[GDB_DI]        = regs->di;
        gdb_regs[GDB_BP]        = regs->bp;
-       gdb_regs[GDB_PS]        = regs->flags;
        gdb_regs[GDB_PC]        = regs->ip;
 #ifdef CONFIG_X86_32
+       gdb_regs[GDB_PS]        = regs->flags;
        gdb_regs[GDB_DS]        = regs->ds;
        gdb_regs[GDB_ES]        = regs->es;
        gdb_regs[GDB_CS]        = regs->cs;
@@ -98,6 +97,9 @@ void pt_regs_to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *regs)
        gdb_regs[GDB_R13]       = regs->r13;
        gdb_regs[GDB_R14]       = regs->r14;
        gdb_regs[GDB_R15]       = regs->r15;
+       gdb_regs32[GDB_PS]      = regs->flags;
+       gdb_regs32[GDB_CS]      = regs->cs;
+       gdb_regs32[GDB_SS]      = regs->ss;
 #endif
        gdb_regs[GDB_SP]        = regs->sp;
 }
@@ -116,6 +118,9 @@ void pt_regs_to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *regs)
  */
 void sleeping_thread_to_gdb_regs(unsigned long *gdb_regs, struct task_struct *p)
 {
+#ifndef CONFIG_X86_32
+       u32 *gdb_regs32 = (u32 *)gdb_regs;
+#endif
        gdb_regs[GDB_AX]        = 0;
        gdb_regs[GDB_BX]        = 0;
        gdb_regs[GDB_CX]        = 0;
@@ -133,8 +138,10 @@ void sleeping_thread_to_gdb_regs(unsigned long *gdb_regs, struct task_struct *p)
        gdb_regs[GDB_FS]        = 0xFFFF;
        gdb_regs[GDB_GS]        = 0xFFFF;
 #else
-       gdb_regs[GDB_PS]        = *(unsigned long *)(p->thread.sp + 8);
-       gdb_regs[GDB_PC]        = 0;
+       gdb_regs32[GDB_PS]      = *(unsigned long *)(p->thread.sp + 8);
+       gdb_regs32[GDB_CS]      = __KERNEL_CS;
+       gdb_regs32[GDB_SS]      = __KERNEL_DS;
+       gdb_regs[GDB_PC]        = p->thread.ip;
        gdb_regs[GDB_R8]        = 0;
        gdb_regs[GDB_R9]        = 0;
        gdb_regs[GDB_R10]       = 0;
@@ -157,6 +164,9 @@ void sleeping_thread_to_gdb_regs(unsigned long *gdb_regs, struct task_struct *p)
  */
 void gdb_regs_to_pt_regs(unsigned long *gdb_regs, struct pt_regs *regs)
 {
+#ifndef CONFIG_X86_32
+       u32 *gdb_regs32 = (u32 *)gdb_regs;
+#endif
        regs->ax                = gdb_regs[GDB_AX];
        regs->bx                = gdb_regs[GDB_BX];
        regs->cx                = gdb_regs[GDB_CX];
@@ -164,9 +174,9 @@ void gdb_regs_to_pt_regs(unsigned long *gdb_regs, struct pt_regs *regs)
        regs->si                = gdb_regs[GDB_SI];
        regs->di                = gdb_regs[GDB_DI];
        regs->bp                = gdb_regs[GDB_BP];
-       regs->flags             = gdb_regs[GDB_PS];
        regs->ip                = gdb_regs[GDB_PC];
 #ifdef CONFIG_X86_32
+       regs->flags             = gdb_regs[GDB_PS];
        regs->ds                = gdb_regs[GDB_DS];
        regs->es                = gdb_regs[GDB_ES];
        regs->cs                = gdb_regs[GDB_CS];
@@ -179,9 +189,128 @@ void gdb_regs_to_pt_regs(unsigned long *gdb_regs, struct pt_regs *regs)
        regs->r13               = gdb_regs[GDB_R13];
        regs->r14               = gdb_regs[GDB_R14];
        regs->r15               = gdb_regs[GDB_R15];
+       regs->flags             = gdb_regs32[GDB_PS];
+       regs->cs                = gdb_regs32[GDB_CS];
+       regs->ss                = gdb_regs32[GDB_SS];
 #endif
 }
 
+static struct hw_breakpoint {
+       unsigned                enabled;
+       unsigned                type;
+       unsigned                len;
+       unsigned long           addr;
+} breakinfo[4];
+
+static void kgdb_correct_hw_break(void)
+{
+       unsigned long dr7;
+       int correctit = 0;
+       int breakbit;
+       int breakno;
+
+       get_debugreg(dr7, 7);
+       for (breakno = 0; breakno < 4; breakno++) {
+               breakbit = 2 << (breakno << 1);
+               if (!(dr7 & breakbit) && breakinfo[breakno].enabled) {
+                       correctit = 1;
+                       dr7 |= breakbit;
+                       dr7 &= ~(0xf0000 << (breakno << 2));
+                       dr7 |= ((breakinfo[breakno].len << 2) |
+                                breakinfo[breakno].type) <<
+                              ((breakno << 2) + 16);
+                       if (breakno >= 0 && breakno <= 3)
+                               set_debugreg(breakinfo[breakno].addr, breakno);
+
+               } else {
+                       if ((dr7 & breakbit) && !breakinfo[breakno].enabled) {
+                               correctit = 1;
+                               dr7 &= ~breakbit;
+                               dr7 &= ~(0xf0000 << (breakno << 2));
+                       }
+               }
+       }
+       if (correctit)
+               set_debugreg(dr7, 7);
+}
+
+static int
+kgdb_remove_hw_break(unsigned long addr, int len, enum kgdb_bptype bptype)
+{
+       int i;
+
+       for (i = 0; i < 4; i++)
+               if (breakinfo[i].addr == addr && breakinfo[i].enabled)
+                       break;
+       if (i == 4)
+               return -1;
+
+       breakinfo[i].enabled = 0;
+
+       return 0;
+}
+
+static void kgdb_remove_all_hw_break(void)
+{
+       int i;
+
+       for (i = 0; i < 4; i++)
+               memset(&breakinfo[i], 0, sizeof(struct hw_breakpoint));
+}
+
+static int
+kgdb_set_hw_break(unsigned long addr, int len, enum kgdb_bptype bptype)
+{
+       unsigned type;
+       int i;
+
+       for (i = 0; i < 4; i++)
+               if (!breakinfo[i].enabled)
+                       break;
+       if (i == 4)
+               return -1;
+
+       switch (bptype) {
+       case BP_HARDWARE_BREAKPOINT:
+               type = 0;
+               len  = 1;
+               break;
+       case BP_WRITE_WATCHPOINT:
+               type = 1;
+               break;
+       case BP_ACCESS_WATCHPOINT:
+               type = 3;
+               break;
+       default:
+               return -1;
+       }
+
+       if (len == 1 || len == 2 || len == 4)
+               breakinfo[i].len  = len - 1;
+       else
+               return -1;
+
+       breakinfo[i].enabled = 1;
+       breakinfo[i].addr = addr;
+       breakinfo[i].type = type;
+
+       return 0;
+}
+
+/**
+ *     kgdb_disable_hw_debug - Disable hardware debugging while we in kgdb.
+ *     @regs: Current &struct pt_regs.
+ *
+ *     This function will be called if the particular architecture must
+ *     disable hardware debugging while it is processing gdb packets or
+ *     handling exception.
+ */
+void kgdb_disable_hw_debug(struct pt_regs *regs)
+{
+       /* Disable hardware debugging while we are in kgdb: */
+       set_debugreg(0UL, 7);
+}
+
 /**
  *     kgdb_post_primary_code - Save error vector/code numbers.
  *     @regs: Original pt_regs.
@@ -243,6 +372,7 @@ int kgdb_arch_handle_exception(int e_vector, int signo, int err_code,
                               struct pt_regs *linux_regs)
 {
        unsigned long addr;
+       unsigned long dr6;
        char *ptr;
        int newPC;
 
@@ -253,21 +383,37 @@ int kgdb_arch_handle_exception(int e_vector, int signo, int err_code,
                ptr = &remcomInBuffer[1];
                if (kgdb_hex2long(&ptr, &addr))
                        linux_regs->ip = addr;
+       case 'D':
+       case 'k':
                newPC = linux_regs->ip;
 
                /* clear the trace bit */
-               linux_regs->flags &= ~TF_MASK;
+               linux_regs->flags &= ~X86_EFLAGS_TF;
                atomic_set(&kgdb_cpu_doing_single_step, -1);
 
                /* set the trace bit if we're stepping */
                if (remcomInBuffer[0] == 's') {
-                       linux_regs->flags |= TF_MASK;
+                       linux_regs->flags |= X86_EFLAGS_TF;
                        kgdb_single_step = 1;
-                       if (kgdb_contthread) {
-                               atomic_set(&kgdb_cpu_doing_single_step,
-                                          raw_smp_processor_id());
+                       atomic_set(&kgdb_cpu_doing_single_step,
+                                  raw_smp_processor_id());
+               }
+
+               get_debugreg(dr6, 6);
+               if (!(dr6 & 0x4000)) {
+                       int breakno;
+
+                       for (breakno = 0; breakno < 4; breakno++) {
+                               if (dr6 & (1 << breakno) &&
+                                   breakinfo[breakno].type == 0) {
+                                       /* Set restore flag: */
+                                       linux_regs->flags |= X86_EFLAGS_RF;
+                                       break;
+                               }
                        }
                }
+               set_debugreg(0UL, 6);
+               kgdb_correct_hw_break();
 
                return 0;
        }
@@ -309,12 +455,7 @@ static int __kgdb_notify(struct die_args *args, unsigned long cmd)
                return NOTIFY_DONE;
 
        case DIE_NMI_IPI:
-               if (atomic_read(&kgdb_active) != -1) {
-                       /* KGDB CPU roundup */
-                       kgdb_nmicallback(raw_smp_processor_id(), regs);
-                       was_in_debug_nmi[raw_smp_processor_id()] = 1;
-                       touch_nmi_watchdog();
-               }
+               /* Just ignore, we will handle the roundup on DIE_NMI. */
                return NOTIFY_DONE;
 
        case DIE_NMIUNKNOWN:
@@ -335,9 +476,15 @@ static int __kgdb_notify(struct die_args *args, unsigned long cmd)
 
        case DIE_DEBUG:
                if (atomic_read(&kgdb_cpu_doing_single_step) ==
-                       raw_smp_processor_id() &&
-                       user_mode(regs))
-                       return single_step_cont(regs, args);
+                   raw_smp_processor_id()) {
+                       if (user_mode(regs))
+                               return single_step_cont(regs, args);
+                       break;
+               } else if (test_thread_flag(TIF_SINGLESTEP))
+                       /* This means a user thread is single stepping
+                        * a system call which should be ignored
+                        */
+                       return NOTIFY_DONE;
                /* fall through */
        default:
                if (user_mode(regs))
@@ -347,6 +494,8 @@ static int __kgdb_notify(struct die_args *args, unsigned long cmd)
        if (kgdb_handle_exception(args->trapnr, args->signr, args->err, regs))
                return NOTIFY_DONE;
 
+       /* Must touch watchdog before return to normal operation */
+       touch_nmi_watchdog();
        return NOTIFY_STOP;
 }
 
@@ -426,4 +575,9 @@ unsigned long kgdb_arch_pc(int exception, struct pt_regs *regs)
 struct kgdb_arch arch_kgdb_ops = {
        /* Breakpoint instruction: */
        .gdb_bpt_instr          = { 0xcc },
+       .flags                  = KGDB_HW_BREAKPOINT,
+       .set_hw_breakpoint      = kgdb_set_hw_break,
+       .remove_hw_breakpoint   = kgdb_remove_hw_break,
+       .remove_all_hw_break    = kgdb_remove_all_hw_break,
+       .correct_hw_break       = kgdb_correct_hw_break,
 };