diff options
Diffstat (limited to 'arch/arc/kernel/kgdb.c')
| -rw-r--r-- | arch/arc/kernel/kgdb.c | 206 | 
1 files changed, 206 insertions, 0 deletions
diff --git a/arch/arc/kernel/kgdb.c b/arch/arc/kernel/kgdb.c new file mode 100644 index 00000000000..52bdc83c149 --- /dev/null +++ b/arch/arc/kernel/kgdb.c @@ -0,0 +1,206 @@ +/* + * kgdb support for ARC + * + * Copyright (C) 2012 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kgdb.h> +#include <linux/sched.h> +#include <asm/disasm.h> +#include <asm/cacheflush.h> + +static void to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *kernel_regs, +			struct callee_regs *cregs) +{ +	int regno; + +	for (regno = 0; regno <= 26; regno++) +		gdb_regs[_R0 + regno] = get_reg(regno, kernel_regs, cregs); + +	for (regno = 27; regno < GDB_MAX_REGS; regno++) +		gdb_regs[regno] = 0; + +	gdb_regs[_FP]		= kernel_regs->fp; +	gdb_regs[__SP]		= kernel_regs->sp; +	gdb_regs[_BLINK]	= kernel_regs->blink; +	gdb_regs[_RET]		= kernel_regs->ret; +	gdb_regs[_STATUS32]	= kernel_regs->status32; +	gdb_regs[_LP_COUNT]	= kernel_regs->lp_count; +	gdb_regs[_LP_END]	= kernel_regs->lp_end; +	gdb_regs[_LP_START]	= kernel_regs->lp_start; +	gdb_regs[_BTA]		= kernel_regs->bta; +	gdb_regs[_STOP_PC]	= kernel_regs->ret; +} + +static void from_gdb_regs(unsigned long *gdb_regs, struct pt_regs *kernel_regs, +			struct callee_regs *cregs) +{ +	int regno; + +	for (regno = 0; regno <= 26; regno++) +		set_reg(regno, gdb_regs[regno + _R0], kernel_regs, cregs); + +	kernel_regs->fp		= gdb_regs[_FP]; +	kernel_regs->sp		= gdb_regs[__SP]; +	kernel_regs->blink	= gdb_regs[_BLINK]; +	kernel_regs->ret	= gdb_regs[_RET]; +	kernel_regs->status32	= gdb_regs[_STATUS32]; +	kernel_regs->lp_count	= gdb_regs[_LP_COUNT]; +	kernel_regs->lp_end	= gdb_regs[_LP_END]; +	kernel_regs->lp_start	= gdb_regs[_LP_START]; +	kernel_regs->bta	= gdb_regs[_BTA]; +} + + +void pt_regs_to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *kernel_regs) +{ +	to_gdb_regs(gdb_regs, kernel_regs, (struct callee_regs *) +		current->thread.callee_reg); +} + +void gdb_regs_to_pt_regs(unsigned long *gdb_regs, struct pt_regs *kernel_regs) +{ +	from_gdb_regs(gdb_regs, kernel_regs, (struct callee_regs *) +		current->thread.callee_reg); +} + +void sleeping_thread_to_gdb_regs(unsigned long *gdb_regs, +				 struct task_struct *task) +{ +	if (task) +		to_gdb_regs(gdb_regs, task_pt_regs(task), +			(struct callee_regs *) task->thread.callee_reg); +} + +struct single_step_data_t { +	uint16_t opcode[2]; +	unsigned long address[2]; +	int is_branch; +	int armed; +} single_step_data; + +static void undo_single_step(struct pt_regs *regs) +{ +	if (single_step_data.armed) { +		int i; + +		for (i = 0; i < (single_step_data.is_branch ? 2 : 1); i++) { +			memcpy((void *) single_step_data.address[i], +				&single_step_data.opcode[i], +				BREAK_INSTR_SIZE); + +			flush_icache_range(single_step_data.address[i], +				single_step_data.address[i] + +				BREAK_INSTR_SIZE); +		} +		single_step_data.armed = 0; +	} +} + +static void place_trap(unsigned long address, void *save) +{ +	memcpy(save, (void *) address, BREAK_INSTR_SIZE); +	memcpy((void *) address, &arch_kgdb_ops.gdb_bpt_instr, +		BREAK_INSTR_SIZE); +	flush_icache_range(address, address + BREAK_INSTR_SIZE); +} + +static void do_single_step(struct pt_regs *regs) +{ +	single_step_data.is_branch = disasm_next_pc((unsigned long) +		regs->ret, regs, (struct callee_regs *) +		current->thread.callee_reg, +		&single_step_data.address[0], +		&single_step_data.address[1]); + +	place_trap(single_step_data.address[0], &single_step_data.opcode[0]); + +	if (single_step_data.is_branch) { +		place_trap(single_step_data.address[1], +			&single_step_data.opcode[1]); +	} + +	single_step_data.armed++; +} + +int kgdb_arch_handle_exception(int e_vector, int signo, int err_code, +			       char *remcomInBuffer, char *remcomOutBuffer, +			       struct pt_regs *regs) +{ +	unsigned long addr; +	char *ptr; + +	undo_single_step(regs); + +	switch (remcomInBuffer[0]) { +	case 's': +	case 'c': +		ptr = &remcomInBuffer[1]; +		if (kgdb_hex2long(&ptr, &addr)) +			regs->ret = addr; + +	case 'D': +	case 'k': +		atomic_set(&kgdb_cpu_doing_single_step, -1); + +		if (remcomInBuffer[0] == 's') { +			do_single_step(regs); +			atomic_set(&kgdb_cpu_doing_single_step, +				   smp_processor_id()); +		} + +		return 0; +	} +	return -1; +} + +unsigned long kgdb_arch_pc(int exception, struct pt_regs *regs) +{ +	return instruction_pointer(regs); +} + +int kgdb_arch_init(void) +{ +	single_step_data.armed = 0; +	return 0; +} + +void kgdb_trap(struct pt_regs *regs, int param) +{ +	/* trap_s 3 is used for breakpoints that overwrite existing +	 * instructions, while trap_s 4 is used for compiled breakpoints. +	 * +	 * with trap_s 3 breakpoints the original instruction needs to be +	 * restored and continuation needs to start at the location of the +	 * breakpoint. +	 * +	 * with trap_s 4 (compiled) breakpoints, continuation needs to +	 * start after the breakpoint. +	 */ +	if (param == 3) +		instruction_pointer(regs) -= BREAK_INSTR_SIZE; + +	kgdb_handle_exception(1, SIGTRAP, 0, regs); +} + +void kgdb_arch_exit(void) +{ +} + +void kgdb_arch_set_pc(struct pt_regs *regs, unsigned long ip) +{ +	instruction_pointer(regs) = ip; +} + +struct kgdb_arch arch_kgdb_ops = { +	/* breakpoint instruction: TRAP_S 0x3 */ +#ifdef CONFIG_CPU_BIG_ENDIAN +	.gdb_bpt_instr		= {0x78, 0x7e}, +#else +	.gdb_bpt_instr		= {0x7e, 0x78}, +#endif +};  |