tilegx: support KGDB
Enter kernel debugger at boot with: --hvd UART_1=1 --hvx kgdbwait --hvx kgdboc=ttyS1,115200 or at runtime with: echo ttyS1,115200 > /sys/module/kgdboc/parameters/kgdboc echo g > /proc/sysrq-trigger Signed-off-by: Chris Metcalf <cmetcalf@tilera.com>
This commit is contained in:
parent
b5c6c1a72a
commit
8157107b13
@ -127,6 +127,7 @@ config TILEGX
|
||||
select HAVE_FTRACE_MCOUNT_RECORD
|
||||
select HAVE_KPROBES
|
||||
select HAVE_KRETPROBES
|
||||
select HAVE_ARCH_KGDB
|
||||
|
||||
config TILEPRO
|
||||
def_bool !TILEGX
|
||||
|
71
arch/tile/include/asm/kgdb.h
Normal file
71
arch/tile/include/asm/kgdb.h
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __TILE_KGDB_H__
|
||||
#define __TILE_KGDB_H__
|
||||
|
||||
#include <linux/kdebug.h>
|
||||
#include <arch/opcode.h>
|
||||
|
||||
#define GDB_SIZEOF_REG sizeof(unsigned long)
|
||||
|
||||
/*
|
||||
* TILE-Gx gdb is expecting the following register layout:
|
||||
* 56 GPRs(R0 - R52, TP, SP, LR), 8 special GPRs(networks and ZERO),
|
||||
* plus the PC and the faultnum.
|
||||
*
|
||||
* Even though kernel not use the 8 special GPRs, they need to be present
|
||||
* in the registers sent for correct processing in the host-side gdb.
|
||||
*
|
||||
*/
|
||||
#define DBG_MAX_REG_NUM (56+8+2)
|
||||
#define NUMREGBYTES (DBG_MAX_REG_NUM * GDB_SIZEOF_REG)
|
||||
|
||||
/*
|
||||
* BUFMAX defines the maximum number of characters in inbound/outbound
|
||||
* buffers at least NUMREGBYTES*2 are needed for register packets,
|
||||
* Longer buffer is needed to list all threads.
|
||||
*/
|
||||
#define BUFMAX 2048
|
||||
|
||||
#define BREAK_INSTR_SIZE TILEGX_BUNDLE_SIZE_IN_BYTES
|
||||
|
||||
/*
|
||||
* Require cache flush for set/clear a software breakpoint or write memory.
|
||||
*/
|
||||
#define CACHE_FLUSH_IS_SAFE 1
|
||||
|
||||
/*
|
||||
* The compiled-in breakpoint instruction can be used to "break" into
|
||||
* the debugger via magic system request key (sysrq-G).
|
||||
*/
|
||||
static tile_bundle_bits compiled_bpt = TILEGX_BPT_BUNDLE | DIE_COMPILED_BPT;
|
||||
|
||||
enum tilegx_regnum {
|
||||
TILEGX_PC_REGNUM = TREG_LAST_GPR + 9,
|
||||
TILEGX_FAULTNUM_REGNUM,
|
||||
};
|
||||
|
||||
/*
|
||||
* Generate a breakpoint exception to "break" into the debugger.
|
||||
*/
|
||||
static inline void arch_kgdb_breakpoint(void)
|
||||
{
|
||||
asm volatile (".quad %0\n\t"
|
||||
::""(compiled_bpt));
|
||||
}
|
||||
|
||||
#endif /* __TILE_KGDB_H__ */
|
@ -29,5 +29,6 @@ obj-$(CONFIG_TILE_USB) += usb.o
|
||||
obj-$(CONFIG_TILE_HVGLUE_TRACE) += hvglue_trace.o
|
||||
obj-$(CONFIG_FUNCTION_TRACER) += ftrace.o mcount_64.o
|
||||
obj-$(CONFIG_KPROBES) += kprobes.o
|
||||
obj-$(CONFIG_KGDB) += kgdb.o
|
||||
|
||||
obj-y += vdso/
|
||||
|
499
arch/tile/kernel/kgdb.c
Normal file
499
arch/tile/kernel/kgdb.c
Normal file
@ -0,0 +1,499 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
@ -240,6 +240,11 @@ static int do_bpt(struct pt_regs *regs)
|
||||
notify_die(DIE_BREAK, "debug", regs, bundle,
|
||||
INT_ILL, SIGTRAP);
|
||||
break;
|
||||
/* compiled_bpt */
|
||||
case DIE_COMPILED_BPT:
|
||||
notify_die(DIE_COMPILED_BPT, "debug", regs, bundle,
|
||||
INT_ILL, SIGTRAP);
|
||||
break;
|
||||
/* breakpoint2_insn */
|
||||
case DIE_SSTEPBP:
|
||||
notify_die(DIE_SSTEPBP, "single_step", regs, bundle,
|
||||
|
Loading…
Reference in New Issue
Block a user