206 lines
		
	
	
	
		
			5 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			206 lines
		
	
	
	
		
			5 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |  * 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 <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
 | ||
|  | }; |