2013-01-18 15:12:24 +05:30
/*
* 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>
2013-03-07 16:47:23 +05:30
# include <linux/sched.h>
2017-02-08 18:51:37 +01:00
# include <linux/sched/task_stack.h>
2013-01-18 15:12:24 +05:30
# 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 ;
}
int kgdb_arch_init ( void )
{
single_step_data . armed = 0 ;
return 0 ;
}
2013-06-12 15:13:40 +05:30
void kgdb_trap ( struct pt_regs * regs )
2013-01-18 15:12:24 +05:30
{
/* 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 .
*/
2013-06-11 18:56:54 +05:30
if ( regs - > ecr_param = = 3 )
2013-01-18 15:12:24 +05:30
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 ;
}
2013-10-24 11:50:09 +08:00
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 ( ) ;
}
2013-01-18 15:12:24 +05:30
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
} ;