2008-12-11 12:46:46 +03:00
/*
* SuperH KGDB support
*
* Copyright ( C ) 2008 Paul Mundt
*
* Single stepping taken from the old stub by Henry Bell and Jeremy Siegel .
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*/
# include <linux/kgdb.h>
# include <linux/kdebug.h>
# include <linux/irq.h>
# include <linux/io.h>
# include <asm/cacheflush.h>
/* Macros for single step instruction identification */
# define OPCODE_BT(op) (((op) & 0xff00) == 0x8900)
# define OPCODE_BF(op) (((op) & 0xff00) == 0x8b00)
# define OPCODE_BTF_DISP(op) (((op) & 0x80) ? (((op) | 0xffffff80) << 1) : \
( ( ( op ) & 0x7f ) < < 1 ) )
# define OPCODE_BFS(op) (((op) & 0xff00) == 0x8f00)
# define OPCODE_BTS(op) (((op) & 0xff00) == 0x8d00)
# define OPCODE_BRA(op) (((op) & 0xf000) == 0xa000)
# define OPCODE_BRA_DISP(op) (((op) & 0x800) ? (((op) | 0xfffff800) << 1) : \
( ( ( op ) & 0x7ff ) < < 1 ) )
# define OPCODE_BRAF(op) (((op) & 0xf0ff) == 0x0023)
# define OPCODE_BRAF_REG(op) (((op) & 0x0f00) >> 8)
# define OPCODE_BSR(op) (((op) & 0xf000) == 0xb000)
# define OPCODE_BSR_DISP(op) (((op) & 0x800) ? (((op) | 0xfffff800) << 1) : \
( ( ( op ) & 0x7ff ) < < 1 ) )
# define OPCODE_BSRF(op) (((op) & 0xf0ff) == 0x0003)
# define OPCODE_BSRF_REG(op) (((op) >> 8) & 0xf)
# define OPCODE_JMP(op) (((op) & 0xf0ff) == 0x402b)
# define OPCODE_JMP_REG(op) (((op) >> 8) & 0xf)
# define OPCODE_JSR(op) (((op) & 0xf0ff) == 0x400b)
# define OPCODE_JSR_REG(op) (((op) >> 8) & 0xf)
# define OPCODE_RTS(op) ((op) == 0xb)
# define OPCODE_RTE(op) ((op) == 0x2b)
# define SR_T_BIT_MASK 0x1
# define STEP_OPCODE 0xc33d
/* Calculate the new address for after a step */
static short * get_step_address ( struct pt_regs * linux_regs )
{
2009-05-09 11:02:08 +04:00
insn_size_t op = __raw_readw ( linux_regs - > pc ) ;
2008-12-11 12:46:46 +03:00
long addr ;
/* BT */
if ( OPCODE_BT ( op ) ) {
if ( linux_regs - > sr & SR_T_BIT_MASK )
addr = linux_regs - > pc + 4 + OPCODE_BTF_DISP ( op ) ;
else
addr = linux_regs - > pc + 2 ;
}
/* BTS */
else if ( OPCODE_BTS ( op ) ) {
if ( linux_regs - > sr & SR_T_BIT_MASK )
addr = linux_regs - > pc + 4 + OPCODE_BTF_DISP ( op ) ;
else
addr = linux_regs - > pc + 4 ; /* Not in delay slot */
}
/* BF */
else if ( OPCODE_BF ( op ) ) {
if ( ! ( linux_regs - > sr & SR_T_BIT_MASK ) )
addr = linux_regs - > pc + 4 + OPCODE_BTF_DISP ( op ) ;
else
addr = linux_regs - > pc + 2 ;
}
/* BFS */
else if ( OPCODE_BFS ( op ) ) {
if ( ! ( linux_regs - > sr & SR_T_BIT_MASK ) )
addr = linux_regs - > pc + 4 + OPCODE_BTF_DISP ( op ) ;
else
addr = linux_regs - > pc + 4 ; /* Not in delay slot */
}
/* BRA */
else if ( OPCODE_BRA ( op ) )
addr = linux_regs - > pc + 4 + OPCODE_BRA_DISP ( op ) ;
/* BRAF */
else if ( OPCODE_BRAF ( op ) )
addr = linux_regs - > pc + 4
+ linux_regs - > regs [ OPCODE_BRAF_REG ( op ) ] ;
/* BSR */
else if ( OPCODE_BSR ( op ) )
addr = linux_regs - > pc + 4 + OPCODE_BSR_DISP ( op ) ;
/* BSRF */
else if ( OPCODE_BSRF ( op ) )
addr = linux_regs - > pc + 4
+ linux_regs - > regs [ OPCODE_BSRF_REG ( op ) ] ;
/* JMP */
else if ( OPCODE_JMP ( op ) )
addr = linux_regs - > regs [ OPCODE_JMP_REG ( op ) ] ;
/* JSR */
else if ( OPCODE_JSR ( op ) )
addr = linux_regs - > regs [ OPCODE_JSR_REG ( op ) ] ;
/* RTS */
else if ( OPCODE_RTS ( op ) )
addr = linux_regs - > pr ;
/* RTE */
else if ( OPCODE_RTE ( op ) )
addr = linux_regs - > regs [ 15 ] ;
/* Other */
else
addr = linux_regs - > pc + instruction_size ( op ) ;
flush_icache_range ( addr , addr + instruction_size ( op ) ) ;
return ( short * ) addr ;
}
/*
* Replace the instruction immediately after the current instruction
* ( i . e . next in the expected flow of control ) with a trap instruction ,
* so that returning will cause only a single instruction to be executed .
* Note that this model is slightly broken for instructions with delay
* slots ( e . g . B [ TF ] S , BSR , BRA etc ) , where both the branch and the
* instruction in the delay slot will be executed .
*/
static unsigned long stepped_address ;
2009-05-09 11:02:08 +04:00
static insn_size_t stepped_opcode ;
2008-12-11 12:46:46 +03:00
static void do_single_step ( struct pt_regs * linux_regs )
{
/* Determine where the target instruction will send us to */
unsigned short * addr = get_step_address ( linux_regs ) ;
stepped_address = ( int ) addr ;
/* Replace it */
stepped_opcode = __raw_readw ( ( long ) addr ) ;
* addr = STEP_OPCODE ;
/* Flush and return */
flush_icache_range ( ( long ) addr , ( long ) addr +
instruction_size ( stepped_opcode ) ) ;
}
/* Undo a single step */
static void undo_single_step ( struct pt_regs * linux_regs )
{
/* If we have stepped, put back the old instruction */
/* Use stepped_address in case we stopped elsewhere */
if ( stepped_opcode ! = 0 ) {
__raw_writew ( stepped_opcode , stepped_address ) ;
flush_icache_range ( stepped_address , stepped_address + 2 ) ;
}
stepped_opcode = 0 ;
}
void pt_regs_to_gdb_regs ( unsigned long * gdb_regs , struct pt_regs * regs )
{
int i ;
for ( i = 0 ; i < 16 ; i + + )
gdb_regs [ GDB_R0 + i ] = regs - > regs [ i ] ;
gdb_regs [ GDB_PC ] = regs - > pc ;
gdb_regs [ GDB_PR ] = regs - > pr ;
gdb_regs [ GDB_SR ] = regs - > sr ;
gdb_regs [ GDB_GBR ] = regs - > gbr ;
gdb_regs [ GDB_MACH ] = regs - > mach ;
gdb_regs [ GDB_MACL ] = regs - > macl ;
__asm__ __volatile__ ( " stc vbr, %0 " : " =r " ( gdb_regs [ GDB_VBR ] ) ) ;
}
void gdb_regs_to_pt_regs ( unsigned long * gdb_regs , struct pt_regs * regs )
{
int i ;
for ( i = 0 ; i < 16 ; i + + )
regs - > regs [ GDB_R0 + i ] = gdb_regs [ GDB_R0 + i ] ;
regs - > pc = gdb_regs [ GDB_PC ] ;
regs - > pr = gdb_regs [ GDB_PR ] ;
regs - > sr = gdb_regs [ GDB_SR ] ;
regs - > gbr = gdb_regs [ GDB_GBR ] ;
regs - > mach = gdb_regs [ GDB_MACH ] ;
regs - > macl = gdb_regs [ GDB_MACL ] ;
}
void sleeping_thread_to_gdb_regs ( unsigned long * gdb_regs , struct task_struct * p )
{
gdb_regs [ GDB_R15 ] = p - > thread . sp ;
gdb_regs [ GDB_PC ] = p - > thread . pc ;
}
int kgdb_arch_handle_exception ( int e_vector , int signo , int err_code ,
char * remcomInBuffer , char * remcomOutBuffer ,
struct pt_regs * linux_regs )
{
unsigned long addr ;
char * ptr ;
/* Undo any stepping we may have done */
undo_single_step ( linux_regs ) ;
switch ( remcomInBuffer [ 0 ] ) {
case ' c ' :
case ' s ' :
/* try to read optional parameter, pc unchanged if no parm */
ptr = & remcomInBuffer [ 1 ] ;
if ( kgdb_hex2long ( & ptr , & addr ) )
linux_regs - > pc = addr ;
case ' D ' :
case ' k ' :
atomic_set ( & kgdb_cpu_doing_single_step , - 1 ) ;
if ( remcomInBuffer [ 0 ] = = ' s ' ) {
do_single_step ( linux_regs ) ;
kgdb_single_step = 1 ;
atomic_set ( & kgdb_cpu_doing_single_step ,
raw_smp_processor_id ( ) ) ;
}
return 0 ;
}
/* this means that we do not want to exit from the handler: */
return - 1 ;
}
/*
* The primary entry points for the kgdb debug trap table entries .
*/
BUILD_TRAP_HANDLER ( singlestep )
{
unsigned long flags ;
TRAP_HANDLER_DECL ;
local_irq_save ( flags ) ;
regs - > pc - = instruction_size ( __raw_readw ( regs - > pc - 4 ) ) ;
kgdb_handle_exception ( vec > > 2 , SIGTRAP , 0 , regs ) ;
local_irq_restore ( flags ) ;
}
BUILD_TRAP_HANDLER ( breakpoint )
{
unsigned long flags ;
TRAP_HANDLER_DECL ;
local_irq_save ( flags ) ;
kgdb_handle_exception ( vec > > 2 , SIGTRAP , 0 , regs ) ;
local_irq_restore ( flags ) ;
}
int kgdb_arch_init ( void )
{
return 0 ;
}
void kgdb_arch_exit ( void )
{
}
struct kgdb_arch arch_kgdb_ops = {
/* Breakpoint instruction: trapa #0x3c */
# ifdef CONFIG_CPU_LITTLE_ENDIAN
. gdb_bpt_instr = { 0x3c , 0xc3 } ,
# else
. gdb_bpt_instr = { 0xc3 , 0x3c } ,
# endif
} ;