| /* |
| * Copyright 2013 Tilera Corporation. All Rights Reserved. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation, version 2. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or |
| * NON INFRINGEMENT. See the GNU General Public License for |
| * more details. |
| * |
| * TILE-Gx KGDB support. |
| */ |
| |
| #include <linux/ptrace.h> |
| #include <linux/kgdb.h> |
| #include <linux/kdebug.h> |
| #include <linux/uaccess.h> |
| #include <linux/module.h> |
| #include <asm/cacheflush.h> |
| |
| static tile_bundle_bits singlestep_insn = TILEGX_BPT_BUNDLE | DIE_SSTEPBP; |
| static unsigned long stepped_addr; |
| static tile_bundle_bits stepped_instr; |
| |
| struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] = { |
| { "r0", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[0])}, |
| { "r1", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[1])}, |
| { "r2", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[2])}, |
| { "r3", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[3])}, |
| { "r4", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[4])}, |
| { "r5", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[5])}, |
| { "r6", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[6])}, |
| { "r7", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[7])}, |
| { "r8", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[8])}, |
| { "r9", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[9])}, |
| { "r10", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[10])}, |
| { "r11", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[11])}, |
| { "r12", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[12])}, |
| { "r13", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[13])}, |
| { "r14", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[14])}, |
| { "r15", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[15])}, |
| { "r16", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[16])}, |
| { "r17", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[17])}, |
| { "r18", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[18])}, |
| { "r19", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[19])}, |
| { "r20", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[20])}, |
| { "r21", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[21])}, |
| { "r22", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[22])}, |
| { "r23", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[23])}, |
| { "r24", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[24])}, |
| { "r25", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[25])}, |
| { "r26", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[26])}, |
| { "r27", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[27])}, |
| { "r28", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[28])}, |
| { "r29", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[29])}, |
| { "r30", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[30])}, |
| { "r31", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[31])}, |
| { "r32", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[32])}, |
| { "r33", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[33])}, |
| { "r34", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[34])}, |
| { "r35", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[35])}, |
| { "r36", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[36])}, |
| { "r37", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[37])}, |
| { "r38", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[38])}, |
| { "r39", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[39])}, |
| { "r40", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[40])}, |
| { "r41", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[41])}, |
| { "r42", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[42])}, |
| { "r43", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[43])}, |
| { "r44", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[44])}, |
| { "r45", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[45])}, |
| { "r46", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[46])}, |
| { "r47", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[47])}, |
| { "r48", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[48])}, |
| { "r49", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[49])}, |
| { "r50", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[50])}, |
| { "r51", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[51])}, |
| { "r52", GDB_SIZEOF_REG, offsetof(struct pt_regs, regs[52])}, |
| { "tp", GDB_SIZEOF_REG, offsetof(struct pt_regs, tp)}, |
| { "sp", GDB_SIZEOF_REG, offsetof(struct pt_regs, sp)}, |
| { "lr", GDB_SIZEOF_REG, offsetof(struct pt_regs, lr)}, |
| { "sn", GDB_SIZEOF_REG, -1}, |
| { "idn0", GDB_SIZEOF_REG, -1}, |
| { "idn1", GDB_SIZEOF_REG, -1}, |
| { "udn0", GDB_SIZEOF_REG, -1}, |
| { "udn1", GDB_SIZEOF_REG, -1}, |
| { "udn2", GDB_SIZEOF_REG, -1}, |
| { "udn3", GDB_SIZEOF_REG, -1}, |
| { "zero", GDB_SIZEOF_REG, -1}, |
| { "pc", GDB_SIZEOF_REG, offsetof(struct pt_regs, pc)}, |
| { "faultnum", GDB_SIZEOF_REG, offsetof(struct pt_regs, faultnum)}, |
| }; |
| |
| char *dbg_get_reg(int regno, void *mem, struct pt_regs *regs) |
| { |
| if (regno >= DBG_MAX_REG_NUM || regno < 0) |
| return NULL; |
| |
| if (dbg_reg_def[regno].offset != -1) |
| memcpy(mem, (void *)regs + dbg_reg_def[regno].offset, |
| dbg_reg_def[regno].size); |
| else |
| memset(mem, 0, dbg_reg_def[regno].size); |
| return dbg_reg_def[regno].name; |
| } |
| |
| int dbg_set_reg(int regno, void *mem, struct pt_regs *regs) |
| { |
| if (regno >= DBG_MAX_REG_NUM || regno < 0) |
| return -EINVAL; |
| |
| if (dbg_reg_def[regno].offset != -1) |
| memcpy((void *)regs + dbg_reg_def[regno].offset, mem, |
| dbg_reg_def[regno].size); |
| return 0; |
| } |
| |
| /* |
| * Similar to pt_regs_to_gdb_regs() except that process is sleeping and so |
| * we may not be able to get all the info. |
| */ |
| void |
| sleeping_thread_to_gdb_regs(unsigned long *gdb_regs, struct task_struct *task) |
| { |
| int reg; |
| struct pt_regs *thread_regs; |
| unsigned long *ptr = gdb_regs; |
| |
| if (task == NULL) |
| return; |
| |
| /* Initialize to zero. */ |
| memset(gdb_regs, 0, NUMREGBYTES); |
| |
| thread_regs = task_pt_regs(task); |
| for (reg = 0; reg <= TREG_LAST_GPR; reg++) |
| *(ptr++) = thread_regs->regs[reg]; |
| |
| gdb_regs[TILEGX_PC_REGNUM] = thread_regs->pc; |
| gdb_regs[TILEGX_FAULTNUM_REGNUM] = thread_regs->faultnum; |
| } |
| |
| void kgdb_arch_set_pc(struct pt_regs *regs, unsigned long pc) |
| { |
| regs->pc = pc; |
| } |
| |
| static void kgdb_call_nmi_hook(void *ignored) |
| { |
| kgdb_nmicallback(raw_smp_processor_id(), NULL); |
| } |
| |
| void kgdb_roundup_cpus(unsigned long flags) |
| { |
| local_irq_enable(); |
| smp_call_function(kgdb_call_nmi_hook, NULL, 0); |
| local_irq_disable(); |
| } |
| |
| /* |
| * Convert a kernel address to the writable kernel text mapping. |
| */ |
| static unsigned long writable_address(unsigned long addr) |
| { |
| unsigned long ret = 0; |
| |
| if (core_kernel_text(addr)) |
| ret = addr - MEM_SV_START + PAGE_OFFSET; |
| else if (is_module_text_address(addr)) |
| ret = addr; |
| else |
| pr_err("Unknown virtual address 0x%lx\n", addr); |
| |
| return ret; |
| } |
| |
| /* |
| * Calculate the new address for after a step. |
| */ |
| static unsigned long get_step_address(struct pt_regs *regs) |
| { |
| int src_reg; |
| int jump_off; |
| int br_off; |
| unsigned long addr; |
| unsigned int opcode; |
| tile_bundle_bits bundle; |
| |
| /* Move to the next instruction by default. */ |
| addr = regs->pc + TILEGX_BUNDLE_SIZE_IN_BYTES; |
| bundle = *(unsigned long *)instruction_pointer(regs); |
| |
| /* 0: X mode, Otherwise: Y mode. */ |
| if (bundle & TILEGX_BUNDLE_MODE_MASK) { |
| if (get_Opcode_Y1(bundle) == RRR_1_OPCODE_Y1 && |
| get_RRROpcodeExtension_Y1(bundle) == |
| UNARY_RRR_1_OPCODE_Y1) { |
| opcode = get_UnaryOpcodeExtension_Y1(bundle); |
| |
| switch (opcode) { |
| case JALR_UNARY_OPCODE_Y1: |
| case JALRP_UNARY_OPCODE_Y1: |
| case JR_UNARY_OPCODE_Y1: |
| case JRP_UNARY_OPCODE_Y1: |
| src_reg = get_SrcA_Y1(bundle); |
| dbg_get_reg(src_reg, &addr, regs); |
| break; |
| } |
| } |
| } else if (get_Opcode_X1(bundle) == RRR_0_OPCODE_X1) { |
| if (get_RRROpcodeExtension_X1(bundle) == |
| UNARY_RRR_0_OPCODE_X1) { |
| opcode = get_UnaryOpcodeExtension_X1(bundle); |
| |
| switch (opcode) { |
| case JALR_UNARY_OPCODE_X1: |
| case JALRP_UNARY_OPCODE_X1: |
| case JR_UNARY_OPCODE_X1: |
| case JRP_UNARY_OPCODE_X1: |
| src_reg = get_SrcA_X1(bundle); |
| dbg_get_reg(src_reg, &addr, regs); |
| break; |
| } |
| } |
| } else if (get_Opcode_X1(bundle) == JUMP_OPCODE_X1) { |
| opcode = get_JumpOpcodeExtension_X1(bundle); |
| |
| switch (opcode) { |
| case JAL_JUMP_OPCODE_X1: |
| case J_JUMP_OPCODE_X1: |
| jump_off = sign_extend(get_JumpOff_X1(bundle), 27); |
| addr = regs->pc + |
| (jump_off << TILEGX_LOG2_BUNDLE_SIZE_IN_BYTES); |
| break; |
| } |
| } else if (get_Opcode_X1(bundle) == BRANCH_OPCODE_X1) { |
| br_off = 0; |
| opcode = get_BrType_X1(bundle); |
| |
| switch (opcode) { |
| case BEQZT_BRANCH_OPCODE_X1: |
| case BEQZ_BRANCH_OPCODE_X1: |
| if (get_SrcA_X1(bundle) == 0) |
| br_off = get_BrOff_X1(bundle); |
| break; |
| case BGEZT_BRANCH_OPCODE_X1: |
| case BGEZ_BRANCH_OPCODE_X1: |
| if (get_SrcA_X1(bundle) >= 0) |
| br_off = get_BrOff_X1(bundle); |
| break; |
| case BGTZT_BRANCH_OPCODE_X1: |
| case BGTZ_BRANCH_OPCODE_X1: |
| if (get_SrcA_X1(bundle) > 0) |
| br_off = get_BrOff_X1(bundle); |
| break; |
| case BLBCT_BRANCH_OPCODE_X1: |
| case BLBC_BRANCH_OPCODE_X1: |
| if (!(get_SrcA_X1(bundle) & 1)) |
| br_off = get_BrOff_X1(bundle); |
| break; |
| case BLBST_BRANCH_OPCODE_X1: |
| case BLBS_BRANCH_OPCODE_X1: |
| if (get_SrcA_X1(bundle) & 1) |
| br_off = get_BrOff_X1(bundle); |
| break; |
| case BLEZT_BRANCH_OPCODE_X1: |
| case BLEZ_BRANCH_OPCODE_X1: |
| if (get_SrcA_X1(bundle) <= 0) |
| br_off = get_BrOff_X1(bundle); |
| break; |
| case BLTZT_BRANCH_OPCODE_X1: |
| case BLTZ_BRANCH_OPCODE_X1: |
| if (get_SrcA_X1(bundle) < 0) |
| br_off = get_BrOff_X1(bundle); |
| break; |
| case BNEZT_BRANCH_OPCODE_X1: |
| case BNEZ_BRANCH_OPCODE_X1: |
| if (get_SrcA_X1(bundle) != 0) |
| br_off = get_BrOff_X1(bundle); |
| break; |
| } |
| |
| if (br_off != 0) { |
| br_off = sign_extend(br_off, 17); |
| addr = regs->pc + |
| (br_off << TILEGX_LOG2_BUNDLE_SIZE_IN_BYTES); |
| } |
| } |
| |
| return addr; |
| } |
| |
| /* |
| * Replace the next instruction after the current instruction with a |
| * breakpoint instruction. |
| */ |
| static void do_single_step(struct pt_regs *regs) |
| { |
| unsigned long addr_wr; |
| |
| /* Determine where the target instruction will send us to. */ |
| stepped_addr = get_step_address(regs); |
| probe_kernel_read((char *)&stepped_instr, (char *)stepped_addr, |
| BREAK_INSTR_SIZE); |
| |
| addr_wr = writable_address(stepped_addr); |
| probe_kernel_write((char *)addr_wr, (char *)&singlestep_insn, |
| BREAK_INSTR_SIZE); |
| smp_wmb(); |
| flush_icache_range(stepped_addr, stepped_addr + BREAK_INSTR_SIZE); |
| } |
| |
| static void undo_single_step(struct pt_regs *regs) |
| { |
| unsigned long addr_wr; |
| |
| if (stepped_instr == 0) |
| return; |
| |
| addr_wr = writable_address(stepped_addr); |
| probe_kernel_write((char *)addr_wr, (char *)&stepped_instr, |
| BREAK_INSTR_SIZE); |
| stepped_instr = 0; |
| smp_wmb(); |
| flush_icache_range(stepped_addr, stepped_addr + BREAK_INSTR_SIZE); |
| } |
| |
| /* |
| * Calls linux_debug_hook before the kernel dies. If KGDB is enabled, |
| * then try to fall into the debugger. |
| */ |
| static int |
| kgdb_notify(struct notifier_block *self, unsigned long cmd, void *ptr) |
| { |
| int ret; |
| unsigned long flags; |
| struct die_args *args = (struct die_args *)ptr; |
| struct pt_regs *regs = args->regs; |
| |
| #ifdef CONFIG_KPROBES |
| /* |
| * Return immediately if the kprobes fault notifier has set |
| * DIE_PAGE_FAULT. |
| */ |
| if (cmd == DIE_PAGE_FAULT) |
| return NOTIFY_DONE; |
| #endif /* CONFIG_KPROBES */ |
| |
| switch (cmd) { |
| case DIE_BREAK: |
| case DIE_COMPILED_BPT: |
| break; |
| case DIE_SSTEPBP: |
| local_irq_save(flags); |
| kgdb_handle_exception(0, SIGTRAP, 0, regs); |
| local_irq_restore(flags); |
| return NOTIFY_STOP; |
| default: |
| /* Userspace events, ignore. */ |
| if (user_mode(regs)) |
| return NOTIFY_DONE; |
| } |
| |
| local_irq_save(flags); |
| ret = kgdb_handle_exception(args->trapnr, args->signr, args->err, regs); |
| local_irq_restore(flags); |
| if (ret) |
| return NOTIFY_DONE; |
| |
| return NOTIFY_STOP; |
| } |
| |
| static struct notifier_block kgdb_notifier = { |
| .notifier_call = kgdb_notify, |
| }; |
| |
| /* |
| * kgdb_arch_handle_exception - Handle architecture specific GDB packets. |
| * @vector: The error vector of the exception that happened. |
| * @signo: The signal number of the exception that happened. |
| * @err_code: The error code of the exception that happened. |
| * @remcom_in_buffer: The buffer of the packet we have read. |
| * @remcom_out_buffer: The buffer of %BUFMAX bytes to write a packet into. |
| * @regs: The &struct pt_regs of the current process. |
| * |
| * This function MUST handle the 'c' and 's' command packets, |
| * as well packets to set / remove a hardware breakpoint, if used. |
| * If there are additional packets which the hardware needs to handle, |
| * they are handled here. The code should return -1 if it wants to |
| * process more packets, and a %0 or %1 if it wants to exit from the |
| * kgdb callback. |
| */ |
| 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; |
| |
| /* Undo any stepping we may have done. */ |
| undo_single_step(regs); |
| |
| switch (remcom_in_buffer[0]) { |
| case 'c': |
| case 's': |
| case 'D': |
| case 'k': |
| /* |
| * Try to read optional parameter, pc unchanged if no parm. |
| * If this was a compiled-in breakpoint, we need to move |
| * to the next instruction or we will just breakpoint |
| * over and over again. |
| */ |
| ptr = &remcom_in_buffer[1]; |
| if (kgdb_hex2long(&ptr, &address)) |
| regs->pc = address; |
| else if (*(unsigned long *)regs->pc == compiled_bpt) |
| regs->pc += BREAK_INSTR_SIZE; |
| |
| if (remcom_in_buffer[0] == 's') { |
| do_single_step(regs); |
| kgdb_single_step = 1; |
| atomic_set(&kgdb_cpu_doing_single_step, |
| raw_smp_processor_id()); |
| } else |
| atomic_set(&kgdb_cpu_doing_single_step, -1); |
| |
| return 0; |
| } |
| |
| return -1; /* this means that we do not want to exit from the handler */ |
| } |
| |
| struct kgdb_arch arch_kgdb_ops; |
| |
| /* |
| * kgdb_arch_init - Perform any architecture specific initalization. |
| * |
| * This function will handle the initalization of any architecture |
| * specific callbacks. |
| */ |
| int kgdb_arch_init(void) |
| { |
| tile_bundle_bits bundle = TILEGX_BPT_BUNDLE; |
| |
| memcpy(arch_kgdb_ops.gdb_bpt_instr, &bundle, BREAK_INSTR_SIZE); |
| return register_die_notifier(&kgdb_notifier); |
| } |
| |
| /* |
| * kgdb_arch_exit - Perform any architecture specific uninitalization. |
| * |
| * This function will handle the uninitalization of any architecture |
| * specific callbacks, for dynamic registration and unregistration. |
| */ |
| void kgdb_arch_exit(void) |
| { |
| unregister_die_notifier(&kgdb_notifier); |
| } |
| |
| int kgdb_arch_set_breakpoint(struct kgdb_bkpt *bpt) |
| { |
| int err; |
| unsigned long addr_wr = writable_address(bpt->bpt_addr); |
| |
| if (addr_wr == 0) |
| return -1; |
| |
| err = probe_kernel_read(bpt->saved_instr, (char *)bpt->bpt_addr, |
| BREAK_INSTR_SIZE); |
| if (err) |
| return err; |
| |
| err = probe_kernel_write((char *)addr_wr, arch_kgdb_ops.gdb_bpt_instr, |
| BREAK_INSTR_SIZE); |
| smp_wmb(); |
| flush_icache_range((unsigned long)bpt->bpt_addr, |
| (unsigned long)bpt->bpt_addr + BREAK_INSTR_SIZE); |
| return err; |
| } |
| |
| int kgdb_arch_remove_breakpoint(struct kgdb_bkpt *bpt) |
| { |
| int err; |
| unsigned long addr_wr = writable_address(bpt->bpt_addr); |
| |
| if (addr_wr == 0) |
| return -1; |
| |
| err = probe_kernel_write((char *)addr_wr, (char *)bpt->saved_instr, |
| BREAK_INSTR_SIZE); |
| smp_wmb(); |
| flush_icache_range((unsigned long)bpt->bpt_addr, |
| (unsigned long)bpt->bpt_addr + BREAK_INSTR_SIZE); |
| return err; |
| } |