x86_64-hw_breakpoints.patch From: Jason Wessel CC: ak@suse.de Subject: [PATCH] This adds hardware breakpoint support for x86_64. This is the minimal set of routines support hw breakpoints on the x86_64 arch. It has the known limitation that the system boot will wipe out the hw breakpoints at cpu_init time and any user space hw breakpoints will wipe out the kernel breakpoints. In the future this implementation could change such that the the kernel space may have its own context to swap in for hw breakpoints. For now this minimal implementation works well given the limitations. Signed-off-by: Milind Dumbare Signed-off-by: Jason Wessel Signed-off-by: Sergei Shtylyov arch/x86/kernel/kgdb_64.c | 145 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 128 insertions(+), 17 deletions(-) --- --- a/arch/x86/kernel/kgdb_64.c +++ b/arch/x86/kernel/kgdb_64.c @@ -17,6 +17,8 @@ * Copyright (C) 2000-2001 VERITAS Software Corporation. * Copyright (C) 2002 Andi Kleen, SuSE Labs * Copyright (C) 2004 LinSysSoft Technologies Pvt. Ltd. + * Copyright (C) 2007 Jason Wessel, Wind River Systems, Inc. + * Copyright (C) 2007 MontaVista Software, Inc. */ /**************************************************************************** * Contributor: Lake Stevens Instrument Division$ @@ -116,22 +118,127 @@ void gdb_regs_to_regs(unsigned long *gdb regs->r15 = gdb_regs[_R15]; } /* gdb_regs_to_regs */ -struct hw_breakpoint { +static struct hw_breakpoint { unsigned enabled; unsigned type; unsigned len; unsigned long addr; } breakinfo[4] = { - {enabled:0}, - {enabled:0}, - {enabled:0}, - {enabled:0} + { .enabled = 0 }, + { .enabled = 0 }, + { .enabled = 0 }, + { .enabled = 0 }, }; +static void kgdb_correct_hw_break(void) +{ + int breakno; + int breakbit; + int correctit = 0; + unsigned long dr7; + + 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); + switch (breakno) { + case 0: + set_debugreg(breakinfo[0].addr, 0); + break; + + case 1: + set_debugreg(breakinfo[1].addr, 1); + break; + + case 2: + set_debugreg(breakinfo[2].addr, 2); + break; + + case 3: + set_debugreg(breakinfo[3].addr, 3); + break; + } + } 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) +{ + int i; + unsigned type; + + 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; +} + void kgdb_disable_hw_debug(struct pt_regs *regs) { /* Disable hardware debugging while we are in kgdb */ - asm volatile ("movq %0,%%db7": /* no output */ :"r" (0UL)); + set_debugreg(0UL, 7); } void kgdb_post_master_code(struct pt_regs *regs, int e_vector, int err_code) @@ -151,7 +258,6 @@ int kgdb_arch_handle_exception(int e_vec struct pt_regs *linux_regs) { unsigned long addr; - unsigned long breakno; char *ptr; int newPC; unsigned long dr6; @@ -167,8 +273,8 @@ int kgdb_arch_handle_exception(int e_vec /* clear the trace bit */ linux_regs->eflags &= ~TF_MASK; - atomic_set(&cpu_doing_single_step, -1); + /* set the trace bit if we're stepping */ if (remcomInBuffer[0] == 's') { linux_regs->eflags |= TF_MASK; @@ -179,20 +285,21 @@ int kgdb_arch_handle_exception(int e_vec } - asm volatile ("movq %%db6, %0\n":"=r" (dr6)); + get_debugreg(dr6, 6); if (!(dr6 & 0x4000)) { + int breakno; + for (breakno = 0; breakno < 4; ++breakno) { - if (dr6 & (1 << breakno)) { - if (breakinfo[breakno].type == 0) { - /* Set restore flag */ - linux_regs->eflags |= - X86_EFLAGS_RF; - break; - } + if (dr6 & (1 << breakno) && + breakinfo[breakno].type == 0) { + /* Set restore flag */ + linux_regs->eflags |= X86_EFLAGS_RF; + break; } } } - asm volatile ("movq %0, %%db6\n"::"r" (0UL)); + set_debugreg(0UL, 6); + kgdb_correct_hw_break(); return 0; } @@ -381,4 +488,8 @@ struct kgdb_arch arch_kgdb_ops = { .gdb_bpt_instr = {0xcc}, .flags = KGDB_HW_BREAKPOINT, .shadowth = 1, + .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, };