| /* kgdb support for MN10300 | |
| * | |
| * Copyright (C) 2010 Red Hat, Inc. All Rights Reserved. | |
| * Written by David Howells (dhowells@redhat.com) | |
| * | |
| * This program is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU General Public Licence | |
| * as published by the Free Software Foundation; either version | |
| * 2 of the Licence, or (at your option) any later version. | |
| */ | |
| #include <linux/slab.h> | |
| #include <linux/ptrace.h> | |
| #include <linux/kgdb.h> | |
| #include <linux/uaccess.h> | |
| #include <unit/leds.h> | |
| #include <unit/serial.h> | |
| #include <asm/debugger.h> | |
| #include <asm/serial-regs.h> | |
| #include "internal.h" | |
| /* | |
| * Software single-stepping breakpoint save (used by __switch_to()) | |
| */ | |
| static struct thread_info *kgdb_sstep_thread; | |
| u8 *kgdb_sstep_bp_addr[2]; | |
| u8 kgdb_sstep_bp[2]; | |
| /* | |
| * Copy kernel exception frame registers to the GDB register file | |
| */ | |
| void pt_regs_to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *regs) | |
| { | |
| unsigned long ssp = (unsigned long) (regs + 1); | |
| gdb_regs[GDB_FR_D0] = regs->d0; | |
| gdb_regs[GDB_FR_D1] = regs->d1; | |
| gdb_regs[GDB_FR_D2] = regs->d2; | |
| gdb_regs[GDB_FR_D3] = regs->d3; | |
| gdb_regs[GDB_FR_A0] = regs->a0; | |
| gdb_regs[GDB_FR_A1] = regs->a1; | |
| gdb_regs[GDB_FR_A2] = regs->a2; | |
| gdb_regs[GDB_FR_A3] = regs->a3; | |
| gdb_regs[GDB_FR_SP] = (regs->epsw & EPSW_nSL) ? regs->sp : ssp; | |
| gdb_regs[GDB_FR_PC] = regs->pc; | |
| gdb_regs[GDB_FR_MDR] = regs->mdr; | |
| gdb_regs[GDB_FR_EPSW] = regs->epsw; | |
| gdb_regs[GDB_FR_LIR] = regs->lir; | |
| gdb_regs[GDB_FR_LAR] = regs->lar; | |
| gdb_regs[GDB_FR_MDRQ] = regs->mdrq; | |
| gdb_regs[GDB_FR_E0] = regs->e0; | |
| gdb_regs[GDB_FR_E1] = regs->e1; | |
| gdb_regs[GDB_FR_E2] = regs->e2; | |
| gdb_regs[GDB_FR_E3] = regs->e3; | |
| gdb_regs[GDB_FR_E4] = regs->e4; | |
| gdb_regs[GDB_FR_E5] = regs->e5; | |
| gdb_regs[GDB_FR_E6] = regs->e6; | |
| gdb_regs[GDB_FR_E7] = regs->e7; | |
| gdb_regs[GDB_FR_SSP] = ssp; | |
| gdb_regs[GDB_FR_MSP] = 0; | |
| gdb_regs[GDB_FR_USP] = regs->sp; | |
| gdb_regs[GDB_FR_MCRH] = regs->mcrh; | |
| gdb_regs[GDB_FR_MCRL] = regs->mcrl; | |
| gdb_regs[GDB_FR_MCVF] = regs->mcvf; | |
| gdb_regs[GDB_FR_DUMMY0] = 0; | |
| gdb_regs[GDB_FR_DUMMY1] = 0; | |
| gdb_regs[GDB_FR_FS0] = 0; | |
| } | |
| /* | |
| * Extracts kernel SP/PC values understandable by gdb from the values | |
| * saved by switch_to(). | |
| */ | |
| void sleeping_thread_to_gdb_regs(unsigned long *gdb_regs, struct task_struct *p) | |
| { | |
| gdb_regs[GDB_FR_SSP] = p->thread.sp; | |
| gdb_regs[GDB_FR_PC] = p->thread.pc; | |
| gdb_regs[GDB_FR_A3] = p->thread.a3; | |
| gdb_regs[GDB_FR_USP] = p->thread.usp; | |
| gdb_regs[GDB_FR_FPCR] = p->thread.fpu_state.fpcr; | |
| } | |
| /* | |
| * Fill kernel exception frame registers from the GDB register file | |
| */ | |
| void gdb_regs_to_pt_regs(unsigned long *gdb_regs, struct pt_regs *regs) | |
| { | |
| regs->d0 = gdb_regs[GDB_FR_D0]; | |
| regs->d1 = gdb_regs[GDB_FR_D1]; | |
| regs->d2 = gdb_regs[GDB_FR_D2]; | |
| regs->d3 = gdb_regs[GDB_FR_D3]; | |
| regs->a0 = gdb_regs[GDB_FR_A0]; | |
| regs->a1 = gdb_regs[GDB_FR_A1]; | |
| regs->a2 = gdb_regs[GDB_FR_A2]; | |
| regs->a3 = gdb_regs[GDB_FR_A3]; | |
| regs->sp = gdb_regs[GDB_FR_SP]; | |
| regs->pc = gdb_regs[GDB_FR_PC]; | |
| regs->mdr = gdb_regs[GDB_FR_MDR]; | |
| regs->epsw = gdb_regs[GDB_FR_EPSW]; | |
| regs->lir = gdb_regs[GDB_FR_LIR]; | |
| regs->lar = gdb_regs[GDB_FR_LAR]; | |
| regs->mdrq = gdb_regs[GDB_FR_MDRQ]; | |
| regs->e0 = gdb_regs[GDB_FR_E0]; | |
| regs->e1 = gdb_regs[GDB_FR_E1]; | |
| regs->e2 = gdb_regs[GDB_FR_E2]; | |
| regs->e3 = gdb_regs[GDB_FR_E3]; | |
| regs->e4 = gdb_regs[GDB_FR_E4]; | |
| regs->e5 = gdb_regs[GDB_FR_E5]; | |
| regs->e6 = gdb_regs[GDB_FR_E6]; | |
| regs->e7 = gdb_regs[GDB_FR_E7]; | |
| regs->sp = gdb_regs[GDB_FR_SSP]; | |
| /* gdb_regs[GDB_FR_MSP]; */ | |
| // regs->usp = gdb_regs[GDB_FR_USP]; | |
| regs->mcrh = gdb_regs[GDB_FR_MCRH]; | |
| regs->mcrl = gdb_regs[GDB_FR_MCRL]; | |
| regs->mcvf = gdb_regs[GDB_FR_MCVF]; | |
| /* gdb_regs[GDB_FR_DUMMY0]; */ | |
| /* gdb_regs[GDB_FR_DUMMY1]; */ | |
| // regs->fpcr = gdb_regs[GDB_FR_FPCR]; | |
| // regs->fs0 = gdb_regs[GDB_FR_FS0]; | |
| } | |
| struct kgdb_arch arch_kgdb_ops = { | |
| .gdb_bpt_instr = { 0xff }, | |
| .flags = KGDB_HW_BREAKPOINT, | |
| }; | |
| static const unsigned char mn10300_kgdb_insn_sizes[256] = | |
| { | |
| /* 1 2 3 4 5 6 7 8 9 a b c d e f */ | |
| 1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, /* 0 */ | |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 1 */ | |
| 2, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, /* 2 */ | |
| 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, /* 3 */ | |
| 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, /* 4 */ | |
| 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, /* 5 */ | |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */ | |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */ | |
| 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* 8 */ | |
| 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* 9 */ | |
| 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* a */ | |
| 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* b */ | |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 2, /* c */ | |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */ | |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */ | |
| 0, 2, 2, 2, 2, 2, 2, 4, 0, 3, 0, 4, 0, 6, 7, 1 /* f */ | |
| }; | |
| /* | |
| * Attempt to emulate single stepping by means of breakpoint instructions. | |
| * Although there is a single-step trace flag in EPSW, its use is not | |
| * sufficiently documented and is only intended for use with the JTAG debugger. | |
| */ | |
| static int kgdb_arch_do_singlestep(struct pt_regs *regs) | |
| { | |
| unsigned long arg; | |
| unsigned size; | |
| u8 *pc = (u8 *)regs->pc, *sp = (u8 *)(regs + 1), cur; | |
| u8 *x = NULL, *y = NULL; | |
| int ret; | |
| ret = probe_kernel_read(&cur, pc, 1); | |
| if (ret < 0) | |
| return ret; | |
| size = mn10300_kgdb_insn_sizes[cur]; | |
| if (size > 0) { | |
| x = pc + size; | |
| goto set_x; | |
| } | |
| switch (cur) { | |
| /* Bxx (d8,PC) */ | |
| case 0xc0 ... 0xca: | |
| ret = probe_kernel_read(&arg, pc + 1, 1); | |
| if (ret < 0) | |
| return ret; | |
| x = pc + 2; | |
| if (arg >= 0 && arg <= 2) | |
| goto set_x; | |
| y = pc + (s8)arg; | |
| goto set_x_and_y; | |
| /* LXX (d8,PC) */ | |
| case 0xd0 ... 0xda: | |
| x = pc + 1; | |
| if (regs->pc == regs->lar) | |
| goto set_x; | |
| y = (u8 *)regs->lar; | |
| goto set_x_and_y; | |
| /* SETLB - loads the next four bytes into the LIR register | |
| * (which mustn't include a breakpoint instruction) */ | |
| case 0xdb: | |
| x = pc + 5; | |
| goto set_x; | |
| /* JMP (d16,PC) or CALL (d16,PC) */ | |
| case 0xcc: | |
| case 0xcd: | |
| ret = probe_kernel_read(&arg, pc + 1, 2); | |
| if (ret < 0) | |
| return ret; | |
| x = pc + (s16)arg; | |
| goto set_x; | |
| /* JMP (d32,PC) or CALL (d32,PC) */ | |
| case 0xdc: | |
| case 0xdd: | |
| ret = probe_kernel_read(&arg, pc + 1, 4); | |
| if (ret < 0) | |
| return ret; | |
| x = pc + (s32)arg; | |
| goto set_x; | |
| /* RETF */ | |
| case 0xde: | |
| x = (u8 *)regs->mdr; | |
| goto set_x; | |
| /* RET */ | |
| case 0xdf: | |
| ret = probe_kernel_read(&arg, pc + 2, 1); | |
| if (ret < 0) | |
| return ret; | |
| ret = probe_kernel_read(&x, sp + (s8)arg, 4); | |
| if (ret < 0) | |
| return ret; | |
| goto set_x; | |
| case 0xf0: | |
| ret = probe_kernel_read(&cur, pc + 1, 1); | |
| if (ret < 0) | |
| return ret; | |
| if (cur >= 0xf0 && cur <= 0xf7) { | |
| /* JMP (An) / CALLS (An) */ | |
| switch (cur & 3) { | |
| case 0: x = (u8 *)regs->a0; break; | |
| case 1: x = (u8 *)regs->a1; break; | |
| case 2: x = (u8 *)regs->a2; break; | |
| case 3: x = (u8 *)regs->a3; break; | |
| } | |
| goto set_x; | |
| } else if (cur == 0xfc) { | |
| /* RETS */ | |
| ret = probe_kernel_read(&x, sp, 4); | |
| if (ret < 0) | |
| return ret; | |
| goto set_x; | |
| } else if (cur == 0xfd) { | |
| /* RTI */ | |
| ret = probe_kernel_read(&x, sp + 4, 4); | |
| if (ret < 0) | |
| return ret; | |
| goto set_x; | |
| } else { | |
| x = pc + 2; | |
| goto set_x; | |
| } | |
| break; | |
| /* potential 3-byte conditional branches */ | |
| case 0xf8: | |
| ret = probe_kernel_read(&cur, pc + 1, 1); | |
| if (ret < 0) | |
| return ret; | |
| x = pc + 3; | |
| if (cur >= 0xe8 && cur <= 0xeb) { | |
| ret = probe_kernel_read(&arg, pc + 2, 1); | |
| if (ret < 0) | |
| return ret; | |
| if (arg >= 0 && arg <= 3) | |
| goto set_x; | |
| y = pc + (s8)arg; | |
| goto set_x_and_y; | |
| } | |
| goto set_x; | |
| case 0xfa: | |
| ret = probe_kernel_read(&cur, pc + 1, 1); | |
| if (ret < 0) | |
| return ret; | |
| if (cur == 0xff) { | |
| /* CALLS (d16,PC) */ | |
| ret = probe_kernel_read(&arg, pc + 2, 2); | |
| if (ret < 0) | |
| return ret; | |
| x = pc + (s16)arg; | |
| goto set_x; | |
| } | |
| x = pc + 4; | |
| goto set_x; | |
| case 0xfc: | |
| ret = probe_kernel_read(&cur, pc + 1, 1); | |
| if (ret < 0) | |
| return ret; | |
| if (cur == 0xff) { | |
| /* CALLS (d32,PC) */ | |
| ret = probe_kernel_read(&arg, pc + 2, 4); | |
| if (ret < 0) | |
| return ret; | |
| x = pc + (s32)arg; | |
| goto set_x; | |
| } | |
| x = pc + 6; | |
| goto set_x; | |
| } | |
| return 0; | |
| set_x: | |
| kgdb_sstep_bp_addr[0] = x; | |
| kgdb_sstep_bp_addr[1] = NULL; | |
| ret = probe_kernel_read(&kgdb_sstep_bp[0], x, 1); | |
| if (ret < 0) | |
| return ret; | |
| ret = probe_kernel_write(x, &arch_kgdb_ops.gdb_bpt_instr, 1); | |
| if (ret < 0) | |
| return ret; | |
| kgdb_sstep_thread = current_thread_info(); | |
| debugger_local_cache_flushinv_one(x); | |
| return ret; | |
| set_x_and_y: | |
| kgdb_sstep_bp_addr[0] = x; | |
| kgdb_sstep_bp_addr[1] = y; | |
| ret = probe_kernel_read(&kgdb_sstep_bp[0], x, 1); | |
| if (ret < 0) | |
| return ret; | |
| ret = probe_kernel_read(&kgdb_sstep_bp[1], y, 1); | |
| if (ret < 0) | |
| return ret; | |
| ret = probe_kernel_write(x, &arch_kgdb_ops.gdb_bpt_instr, 1); | |
| if (ret < 0) | |
| return ret; | |
| ret = probe_kernel_write(y, &arch_kgdb_ops.gdb_bpt_instr, 1); | |
| if (ret < 0) { | |
| probe_kernel_write(kgdb_sstep_bp_addr[0], | |
| &kgdb_sstep_bp[0], 1); | |
| } else { | |
| kgdb_sstep_thread = current_thread_info(); | |
| } | |
| debugger_local_cache_flushinv_one(x); | |
| debugger_local_cache_flushinv_one(y); | |
| return ret; | |
| } | |
| /* | |
| * Remove emplaced single-step breakpoints, returning true if we hit one of | |
| * them. | |
| */ | |
| static bool kgdb_arch_undo_singlestep(struct pt_regs *regs) | |
| { | |
| bool hit = false; | |
| u8 *x = kgdb_sstep_bp_addr[0], *y = kgdb_sstep_bp_addr[1]; | |
| u8 opcode; | |
| if (kgdb_sstep_thread == current_thread_info()) { | |
| if (x) { | |
| if (x == (u8 *)regs->pc) | |
| hit = true; | |
| if (probe_kernel_read(&opcode, x, | |
| 1) < 0 || | |
| opcode != 0xff) | |
| BUG(); | |
| probe_kernel_write(x, &kgdb_sstep_bp[0], 1); | |
| debugger_local_cache_flushinv_one(x); | |
| } | |
| if (y) { | |
| if (y == (u8 *)regs->pc) | |
| hit = true; | |
| if (probe_kernel_read(&opcode, y, | |
| 1) < 0 || | |
| opcode != 0xff) | |
| BUG(); | |
| probe_kernel_write(y, &kgdb_sstep_bp[1], 1); | |
| debugger_local_cache_flushinv_one(y); | |
| } | |
| } | |
| kgdb_sstep_bp_addr[0] = NULL; | |
| kgdb_sstep_bp_addr[1] = NULL; | |
| kgdb_sstep_thread = NULL; | |
| return hit; | |
| } | |
| /* | |
| * Catch a single-step-pending thread being deleted and make sure the global | |
| * single-step state is cleared. At this point the breakpoints should have | |
| * been removed by __switch_to(). | |
| */ | |
| void arch_release_thread_stack(unsigned long *stack) | |
| { | |
| struct thread_info *ti = (void *)stack; | |
| if (kgdb_sstep_thread == ti) { | |
| kgdb_sstep_thread = NULL; | |
| /* However, we may now be running in degraded mode, with most | |
| * of the CPUs disabled until such a time as KGDB is reentered, | |
| * so force immediate reentry */ | |
| kgdb_breakpoint(); | |
| } | |
| } | |
| /* | |
| * Handle unknown packets and [CcsDk] packets | |
| * - at this point breakpoints have been installed | |
| */ | |
| int kgdb_arch_handle_exception(int vector, int signo, int err_code, | |
| char *remcom_in_buffer, char *remcom_out_buffer, | |
| struct pt_regs *regs) | |
| { | |
| long addr; | |
| char *ptr; | |
| switch (remcom_in_buffer[0]) { | |
| case 'c': | |
| case 's': | |
| /* try to read optional parameter, pc unchanged if no parm */ | |
| ptr = &remcom_in_buffer[1]; | |
| if (kgdb_hex2long(&ptr, &addr)) | |
| regs->pc = addr; | |
| case 'D': | |
| case 'k': | |
| atomic_set(&kgdb_cpu_doing_single_step, -1); | |
| if (remcom_in_buffer[0] == 's') { | |
| kgdb_arch_do_singlestep(regs); | |
| kgdb_single_step = 1; | |
| atomic_set(&kgdb_cpu_doing_single_step, | |
| raw_smp_processor_id()); | |
| } | |
| return 0; | |
| } | |
| return -1; /* this means that we do not want to exit from the handler */ | |
| } | |
| /* | |
| * Handle event interception | |
| * - returns 0 if the exception should be skipped, -ERROR otherwise. | |
| */ | |
| int debugger_intercept(enum exception_code excep, int signo, int si_code, | |
| struct pt_regs *regs) | |
| { | |
| int ret; | |
| if (kgdb_arch_undo_singlestep(regs)) { | |
| excep = EXCEP_TRAP; | |
| signo = SIGTRAP; | |
| si_code = TRAP_TRACE; | |
| } | |
| ret = kgdb_handle_exception(excep, signo, si_code, regs); | |
| debugger_local_cache_flushinv(); | |
| return ret; | |
| } | |
| /* | |
| * Determine if we've hit a debugger special breakpoint | |
| */ | |
| int at_debugger_breakpoint(struct pt_regs *regs) | |
| { | |
| return regs->pc == (unsigned long)&__arch_kgdb_breakpoint; | |
| } | |
| /* | |
| * Initialise kgdb | |
| */ | |
| int kgdb_arch_init(void) | |
| { | |
| return 0; | |
| } | |
| /* | |
| * Do something, perhaps, but don't know what. | |
| */ | |
| void kgdb_arch_exit(void) | |
| { | |
| } | |
| #ifdef CONFIG_SMP | |
| void debugger_nmi_interrupt(struct pt_regs *regs, enum exception_code code) | |
| { | |
| kgdb_nmicallback(arch_smp_processor_id(), regs); | |
| debugger_local_cache_flushinv(); | |
| } | |
| void kgdb_roundup_cpus(unsigned long flags) | |
| { | |
| smp_jump_to_debugger(); | |
| } | |
| #endif |