diff options
author | Michal Simek <monstr@monstr.eu> | 2010-08-03 11:45:08 +0200 |
---|---|---|
committer | Michal Simek <monstr@monstr.eu> | 2010-08-04 10:45:17 +0200 |
commit | 2d5973cb5ac5d04662f86e19a06a4c52fa4c4ae3 (patch) | |
tree | 21ddc58eed5ccad856fc44dfb0ee794da41d5b16 | |
parent | 751f1605e03533a6279ccf456e938e9595c7d888 (diff) |
microblaze: Add KGDB support
Kgdb uses brki r16, 0x18 instruction to call
low level _debug_exception function which save
current state to pt_regs and call microblaze_kgdb_break
function. _debug_exception should be called only from
the kernel space. User space calling is not supported
because user application debugging uses different handling.
pt_regs_to_gdb_regs loads additional special registers
which can't be changed
* Enable KGDB in Kconfig
* Remove ancient not-tested KGDB support
* Remove ancient _debug_exception code from entry.S
Only MMU KGDB support is supported.
Signed-off-by: Michal Simek <monstr@monstr.eu>
CC: Jason Wessel <jason.wessel@windriver.com>
CC: John Williams <john.williams@petalogix.com>
CC: Edgar E. Iglesias <edgar.iglesias@petalogix.com>
CC: linux-kernel@vger.kernel.org
Acked-by: Jason Wessel <jason.wessel@windriver.com>
-rw-r--r-- | arch/microblaze/Kconfig | 1 | ||||
-rw-r--r-- | arch/microblaze/include/asm/exceptions.h | 16 | ||||
-rw-r--r-- | arch/microblaze/include/asm/kgdb.h | 28 | ||||
-rw-r--r-- | arch/microblaze/kernel/Makefile | 1 | ||||
-rw-r--r-- | arch/microblaze/kernel/entry.S | 106 | ||||
-rw-r--r-- | arch/microblaze/kernel/exceptions.c | 1 | ||||
-rw-r--r-- | arch/microblaze/kernel/kgdb.c | 147 | ||||
-rw-r--r-- | arch/microblaze/mm/fault.c | 15 |
8 files changed, 234 insertions, 81 deletions
diff --git a/arch/microblaze/Kconfig b/arch/microblaze/Kconfig index a51742190c1..be3855250db 100644 --- a/arch/microblaze/Kconfig +++ b/arch/microblaze/Kconfig @@ -14,6 +14,7 @@ config MICROBLAZE select USB_ARCH_HAS_EHCI select ARCH_WANT_OPTIONAL_GPIOLIB select HAVE_OPROFILE + select HAVE_ARCH_KGDB select HAVE_DMA_ATTRS select HAVE_DMA_API_DEBUG select TRACING_SUPPORT diff --git a/arch/microblaze/include/asm/exceptions.h b/arch/microblaze/include/asm/exceptions.h index fa0e36657fd..6479097b802 100644 --- a/arch/microblaze/include/asm/exceptions.h +++ b/arch/microblaze/include/asm/exceptions.h @@ -69,22 +69,6 @@ asmlinkage void full_exception(struct pt_regs *regs, unsigned int type, void die(const char *str, struct pt_regs *fp, long err); void _exception(int signr, struct pt_regs *regs, int code, unsigned long addr); -#if defined(CONFIG_KGDB) -void (*debugger)(struct pt_regs *regs); -int (*debugger_bpt)(struct pt_regs *regs); -int (*debugger_sstep)(struct pt_regs *regs); -int (*debugger_iabr_match)(struct pt_regs *regs); -int (*debugger_dabr_match)(struct pt_regs *regs); -void (*debugger_fault_handler)(struct pt_regs *regs); -#else -#define debugger(regs) do { } while (0) -#define debugger_bpt(regs) 0 -#define debugger_sstep(regs) 0 -#define debugger_iabr_match(regs) 0 -#define debugger_dabr_match(regs) 0 -#define debugger_fault_handler ((void (*)(struct pt_regs *))0) -#endif - #endif /*__ASSEMBLY__ */ #endif /* __KERNEL__ */ #endif /* _ASM_MICROBLAZE_EXCEPTIONS_H */ diff --git a/arch/microblaze/include/asm/kgdb.h b/arch/microblaze/include/asm/kgdb.h new file mode 100644 index 00000000000..78b17d40b23 --- /dev/null +++ b/arch/microblaze/include/asm/kgdb.h @@ -0,0 +1,28 @@ +#ifdef __KERNEL__ +#ifndef __MICROBLAZE_KGDB_H__ +#define __MICROBLAZE_KGDB_H__ + +#ifndef __ASSEMBLY__ + +#define CACHE_FLUSH_IS_SAFE 1 +#define BUFMAX 2048 + +/* + * 32 32-bit general purpose registers (r0-r31) + * 6 32-bit special registers (pc, msr, ear, esr, fsr, btr) + * 12 32-bit PVR + * 7 32-bit MMU Regs (redr, rpid, rzpr, rtlbx, rtlbsx, rtlblo, rtlbhi) + * ------ + * 57 registers + */ +#define NUMREGBYTES (57 * 4) + +#define BREAK_INSTR_SIZE 4 +static inline void arch_kgdb_breakpoint(void) +{ + __asm__ __volatile__("brki r16, 0x18;"); +} + +#endif /* __ASSEMBLY__ */ +#endif /* __MICROBLAZE_KGDB_H__ */ +#endif /* __KERNEL__ */ diff --git a/arch/microblaze/kernel/Makefile b/arch/microblaze/kernel/Makefile index d66ddef53c0..5eecc9f1fbd 100644 --- a/arch/microblaze/kernel/Makefile +++ b/arch/microblaze/kernel/Makefile @@ -28,5 +28,6 @@ obj-$(CONFIG_MODULES) += microblaze_ksyms.o module.o obj-$(CONFIG_MMU) += misc.o obj-$(CONFIG_STACKTRACE) += stacktrace.o obj-$(CONFIG_FUNCTION_TRACER) += ftrace.o mcount.o +obj-$(CONFIG_KGDB) += kgdb.o obj-y += entry$(MMU).o diff --git a/arch/microblaze/kernel/entry.S b/arch/microblaze/kernel/entry.S index 5a5cb584293..304882e5645 100644 --- a/arch/microblaze/kernel/entry.S +++ b/arch/microblaze/kernel/entry.S @@ -745,11 +745,8 @@ IRQ_return: /* MS: Make global symbol for debugging */ nop /* - * `Debug' trap - * We enter dbtrap in "BIP" (breakpoint) mode. - * So we exit the breakpoint mode with an 'rtbd' and proceed with the - * original dbtrap. - * however, wait to save state first + * Debug trap for KGDB. Enter to _debug_exception by brki r16, 0x18 + * and call handling function with saved pt_regs */ C_ENTRY(_debug_exception): /* BIP bit is set on entry, no interrupts can occur */ @@ -759,18 +756,44 @@ C_ENTRY(_debug_exception): nop andi r1, r1, MSR_UMS bnei r1, 1f - /* Kernel-mode state save. */ +/* MS: Kernel-mode state save - kgdb */ lwi r1, r0, TOPHYS(PER_CPU(ENTRY_SP)); /* Reload kernel stack-ptr*/ - tophys(r1,r1); - addik r1, r1, -STATE_SAVE_SIZE; /* Make room on the stack. */ + /* BIP bit is set on entry, no interrupts can occur */ + addik r1, r1, CONFIG_KERNEL_BASE_ADDR - CONFIG_KERNEL_START - STATE_SAVE_SIZE; SAVE_REGS; + /* save all regs to pt_reg structure */ + swi r0, r1, PTO+PT_R0; /* R0 must be saved too */ + swi r14, r1, PTO+PT_R14 /* rewrite saved R14 value */ + swi r16, r1, PTO+PT_R16 + swi r16, r1, PTO+PT_PC; /* PC and r16 are the same */ + swi r17, r1, PTO+PT_R17 + /* save special purpose registers to pt_regs */ + mfs r11, rear; + swi r11, r1, PTO+PT_EAR; + mfs r11, resr; + swi r11, r1, PTO+PT_ESR; + mfs r11, rfsr; + swi r11, r1, PTO+PT_FSR; + + /* stack pointer is in physical address at it is decrease + * by STATE_SAVE_SIZE but we need to get correct R1 value */ + addik r11, r1, CONFIG_KERNEL_START - CONFIG_KERNEL_BASE_ADDR + STATE_SAVE_SIZE; + swi r11, r1, PTO+PT_R1 + /* MS: r31 - current pointer isn't changed */ + tovirt(r1,r1) +#ifdef CONFIG_KGDB + addi r5, r1, PTO /* pass pt_reg address as the first arg */ + la r15, r0, dbtrap_call; /* return address */ + rtbd r0, microblaze_kgdb_break + nop; +#endif + /* MS: Place handler for brki from kernel space if KGDB is OFF. + * It is very unlikely that another brki instruction is called. */ + bri 0 - swi r1, r1, PTO + PT_MODE; - brid 2f; - nop; /* Fill delay slot */ -1: /* User-mode state save. */ - lwi r1, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); /* get saved current */ +/* MS: User-mode state save - gdb */ +1: lwi r1, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); /* get saved current */ tophys(r1,r1); lwi r1, r1, TS_THREAD_INFO; /* get the thread info */ addik r1, r1, THREAD_SIZE; /* calculate kernel stack pointer */ @@ -781,36 +804,32 @@ C_ENTRY(_debug_exception): swi r17, r1, PTO+PT_R17; swi r16, r1, PTO+PT_R16; swi r16, r1, PTO+PT_PC; /* Save LP */ - swi r0, r1, PTO + PT_MODE; /* Was in user-mode. */ lwi r11, r0, TOPHYS(PER_CPU(ENTRY_SP)); swi r11, r1, PTO+PT_R1; /* Store user SP. */ -2: lwi CURRENT_TASK, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); + lwi CURRENT_TASK, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); tovirt(r1,r1) - set_vms; addik r5, r1, PTO; addik r15, r0, dbtrap_call; -dbtrap_call: /* return point for kernel/user entry */ +dbtrap_call: /* Return point for kernel/user entry + 8 because of rtsd r15, 8 */ rtbd r0, sw_exception nop - set_bip; /* Ints masked for state restore*/ + /* MS: The first instruction for the second part of the gdb/kgdb */ + set_bip; /* Ints masked for state restore */ lwi r11, r1, PTO + PT_MODE; bnei r11, 2f; - +/* MS: Return to user space - gdb */ /* Get current task ptr into r11 */ lwi r11, CURRENT_TASK, TS_THREAD_INFO; /* get thread info */ lwi r11, r11, TI_FLAGS; /* get flags in thread info */ andi r11, r11, _TIF_NEED_RESCHED; beqi r11, 5f; -/* Call the scheduler before returning from a syscall/trap. */ - + /* Call the scheduler before returning from a syscall/trap. */ bralid r15, schedule; /* Call scheduler */ nop; /* delay slot */ - /* XXX Is PT_DTRACE handling needed here? */ - /* XXX m68knommu also checks TASK_STATE & TASK_COUNTER here. */ /* Maybe handle a signal */ 5: lwi r11, CURRENT_TASK, TS_THREAD_INFO; /* get thread info */ @@ -818,48 +837,37 @@ dbtrap_call: /* return point for kernel/user entry */ andi r11, r11, _TIF_SIGPENDING; beqi r11, 1f; /* Signals to handle, handle them */ -/* Handle a signal return; Pending signals should be in r18. */ - /* Not all registers are saved by the normal trap/interrupt entry - points (for instance, call-saved registers (because the normal - C-compiler calling sequence in the kernel makes sure they're - preserved), and call-clobbered registers in the case of - traps), but signal handlers may want to examine or change the - complete register state. Here we save anything not saved by - the normal entry sequence, so that it may be safely restored - (in a possibly modified form) after do_signal returns. */ - addik r5, r1, PTO; /* Arg 1: struct pt_regs *regs */ addi r7, r0, 0; /* Arg 3: int in_syscall */ bralid r15, do_signal; /* Handle any signals */ add r6, r0, r0; /* Arg 2: sigset_t *oldset */ - /* Finally, return to user state. */ -1: - swi CURRENT_TASK, r0, PER_CPU(CURRENT_SAVE); /* save current */ +1: swi CURRENT_TASK, r0, PER_CPU(CURRENT_SAVE); /* save current */ VM_OFF; tophys(r1,r1); - + /* MS: Restore all regs */ RESTORE_REGS lwi r17, r1, PTO+PT_R17; lwi r16, r1, PTO+PT_R16; - addik r1, r1, STATE_SAVE_SIZE /* Clean up stack space. */ - - - lwi r1, r1, PT_R1 - PT_SIZE; - /* Restore user stack pointer. */ - bri 6f; + addik r1, r1, STATE_SAVE_SIZE /* Clean up stack space */ + lwi r1, r1, PT_R1 - PT_SIZE; /* Restore user stack pointer */ +DBTRAP_return_user: /* MS: Make global symbol for debugging */ + rtbd r16, 0; /* MS: Instructions to return from a debug trap */ + nop; -/* Return to kernel state. */ +/* MS: Return to kernel state - kgdb */ 2: VM_OFF; tophys(r1,r1); + /* MS: Restore all regs */ RESTORE_REGS - addik r1, r1, STATE_SAVE_SIZE /* Clean up stack space. */ - + lwi r14, r1, PTO+PT_R14; + lwi r16, r1, PTO+PT_PC; + lwi r17, r1, PTO+PT_R17; + addik r1, r1, STATE_SAVE_SIZE; /* MS: Clean up stack space */ tovirt(r1,r1); -6: -DBTRAP_return: /* Make global symbol for debugging */ - rtbd r16, 0; /* Instructions to return from an IRQ */ +DBTRAP_return_kernel: /* MS: Make global symbol for debugging */ + rtbd r16, 0; /* MS: Instructions to return from a debug trap */ nop; diff --git a/arch/microblaze/kernel/exceptions.c b/arch/microblaze/kernel/exceptions.c index e0c6f8c2bd9..b98ee8d0c1c 100644 --- a/arch/microblaze/kernel/exceptions.c +++ b/arch/microblaze/kernel/exceptions.c @@ -59,7 +59,6 @@ void _exception(int signr, struct pt_regs *regs, int code, unsigned long addr) siginfo_t info; if (kernel_mode(regs)) { - debugger(regs); die("Exception in kernel mode", regs, signr); } info.si_signo = signr; diff --git a/arch/microblaze/kernel/kgdb.c b/arch/microblaze/kernel/kgdb.c new file mode 100644 index 00000000000..bfc006b7f2d --- /dev/null +++ b/arch/microblaze/kernel/kgdb.c @@ -0,0 +1,147 @@ +/* + * Microblaze KGDB support + * + * 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/kgdb.h> +#include <linux/kdebug.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <asm/cacheflush.h> +#include <asm/asm-offsets.h> +#include <asm/pvr.h> + +#define GDB_REG 0 +#define GDB_PC 32 +#define GDB_MSR 33 +#define GDB_EAR 34 +#define GDB_ESR 35 +#define GDB_FSR 36 +#define GDB_BTR 37 +#define GDB_PVR 38 +#define GDB_REDR 50 +#define GDB_RPID 51 +#define GDB_RZPR 52 +#define GDB_RTLBX 53 +#define GDB_RTLBSX 54 /* mfs can't read it */ +#define GDB_RTLBLO 55 +#define GDB_RTLBHI 56 + +/* keep pvr separately because it is unchangeble */ +struct pvr_s pvr; + +void pt_regs_to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *regs) +{ + int i; + unsigned long *pt_regb = (unsigned long *)regs; + int temp; + /* registers r0 - r31, pc, msr, ear, esr, fsr + do not save pt_mode */ + for (i = 0; i < (sizeof(struct pt_regs) / 4) - 1; i++) + gdb_regs[i] = pt_regb[i]; + + /* Branch target register can't be changed */ + __asm__ __volatile__ ("mfs %0, rbtr;" : "=r"(temp) : ); + gdb_regs[GDB_BTR] = temp; + + /* pvr part - we have 11 pvr regs */ + for (i = 0; i < sizeof(struct pvr_s)/4; i++) + gdb_regs[GDB_PVR + i] = pvr.pvr[i]; + + /* read special registers - can't be changed */ + __asm__ __volatile__ ("mfs %0, redr;" : "=r"(temp) : ); + gdb_regs[GDB_REDR] = temp; + __asm__ __volatile__ ("mfs %0, rpid;" : "=r"(temp) : ); + gdb_regs[GDB_RPID] = temp; + __asm__ __volatile__ ("mfs %0, rzpr;" : "=r"(temp) : ); + gdb_regs[GDB_RZPR] = temp; + __asm__ __volatile__ ("mfs %0, rtlbx;" : "=r"(temp) : ); + gdb_regs[GDB_RTLBX] = temp; + __asm__ __volatile__ ("mfs %0, rtlblo;" : "=r"(temp) : ); + gdb_regs[GDB_RTLBLO] = temp; + __asm__ __volatile__ ("mfs %0, rtlbhi;" : "=r"(temp) : ); + gdb_regs[GDB_RTLBHI] = temp; +} + +void gdb_regs_to_pt_regs(unsigned long *gdb_regs, struct pt_regs *regs) +{ + int i; + unsigned long *pt_regb = (unsigned long *)regs; + + /* pt_regs and gdb_regs have the same 37 values. + * The rest of gdb_regs are unused and can't be changed. + * r0 register value can't be changed too. */ + for (i = 1; i < (sizeof(struct pt_regs) / 4) - 1; i++) + pt_regb[i] = gdb_regs[i]; +} + +void microblaze_kgdb_break(struct pt_regs *regs) +{ + if (kgdb_handle_exception(1, SIGTRAP, 0, regs) != 0) + return 0; + + /* Jump over the first arch_kgdb_breakpoint which is barrier to + * get kgdb work. The same solution is used for powerpc */ + if (*(u32 *) (regs->pc) == *(u32 *) (&arch_kgdb_ops.gdb_bpt_instr)) + regs->pc += BREAK_INSTR_SIZE; +} + +/* untested */ +void sleeping_thread_to_gdb_regs(unsigned long *gdb_regs, struct task_struct *p) +{ + int i; + unsigned long *pt_regb = (unsigned long *)(p->thread.regs); + + /* registers r0 - r31, pc, msr, ear, esr, fsr + do not save pt_mode */ + for (i = 0; i < (sizeof(struct pt_regs) / 4) - 1; i++) + gdb_regs[i] = pt_regb[i]; + + /* pvr part - we have 11 pvr regs */ + for (i = 0; i < sizeof(struct pvr_s)/4; i++) + gdb_regs[GDB_PVR + i] = pvr.pvr[i]; +} + +void kgdb_arch_set_pc(struct pt_regs *regs, unsigned long ip) +{ + regs->pc = ip; +} + +int kgdb_arch_handle_exception(int vector, int signo, int err_code, + char *remcom_in_buffer, char *remcom_out_buffer, + struct pt_regs *regs) +{ + char *ptr; + unsigned long address; + int cpu = smp_processor_id(); + + switch (remcom_in_buffer[0]) { + case 'c': + /* handle the optional parameter */ + ptr = &remcom_in_buffer[1]; + if (kgdb_hex2long(&ptr, &address)) + regs->pc = address; + + return 0; + } + return -1; /* this means that we do not want to exit from the handler */ +} + +int kgdb_arch_init(void) +{ + get_pvr(&pvr); /* Fill PVR structure */ + return 0; +} + +void kgdb_arch_exit(void) +{ + /* Nothing to do */ +} + +/* + * Global data + */ +struct kgdb_arch arch_kgdb_ops = { + .gdb_bpt_instr = {0xba, 0x0c, 0x00, 0x18}, /* brki r16, 0x18 */ +}; diff --git a/arch/microblaze/mm/fault.c b/arch/microblaze/mm/fault.c index b224c650a18..57bd2a09610 100644 --- a/arch/microblaze/mm/fault.c +++ b/arch/microblaze/mm/fault.c @@ -37,10 +37,6 @@ #include <linux/uaccess.h> #include <asm/exceptions.h> -#if defined(CONFIG_KGDB) -int debugger_kernel_faults = 1; -#endif - static unsigned long pte_misses; /* updated by do_page_fault() */ static unsigned long pte_errors; /* updated by do_page_fault() */ @@ -81,10 +77,6 @@ void bad_page_fault(struct pt_regs *regs, unsigned long address, int sig) } /* kernel has accessed a bad area */ -#if defined(CONFIG_KGDB) - if (debugger_kernel_faults) - debugger(regs); -#endif die("kernel access of bad area", regs, sig); } @@ -115,13 +107,6 @@ void do_page_fault(struct pt_regs *regs, unsigned long address, if ((error_code & 0x13) == 0x13 || (error_code & 0x11) == 0x11) is_write = 0; -#if defined(CONFIG_KGDB) - if (debugger_fault_handler && regs->trap == 0x300) { - debugger_fault_handler(regs); - return; - } -#endif /* CONFIG_KGDB */ - if (unlikely(in_atomic() || !mm)) { if (kernel_mode(regs)) goto bad_area_nosemaphore; |