2011-10-31 18:44:34 -05:00
/*
* Kernel traps / events for Hexagon processor
*
2014-12-08 15:36:20 -06:00
* Copyright ( c ) 2010 - 2014 , The Linux Foundation . All rights reserved .
2011-10-31 18:44:34 -05:00
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation .
*
* 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 . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA
* 02110 - 1301 , USA .
*/
# include <linux/init.h>
2017-02-08 18:51:30 +01:00
# include <linux/sched/signal.h>
2017-02-08 18:51:35 +01:00
# include <linux/sched/debug.h>
2017-02-08 18:51:37 +01:00
# include <linux/sched/task_stack.h>
2011-10-31 18:44:34 -05:00
# include <linux/module.h>
# include <linux/kallsyms.h>
# include <linux/kdebug.h>
# include <linux/syscalls.h>
# include <linux/signal.h>
# include <linux/tracehook.h>
# include <asm/traps.h>
# include <asm/vm_fault.h>
# include <asm/syscall.h>
# include <asm/registers.h>
# include <asm/unistd.h>
# include <asm/sections.h>
# ifdef CONFIG_KGDB
# include <linux / kgdb.h>
# endif
# define TRAP_SYSCALL 1
# define TRAP_DEBUG 0xdb
void __init trap_init ( void )
{
}
# ifdef CONFIG_GENERIC_BUG
/* Maybe should resemble arch/sh/kernel/traps.c ?? */
int is_valid_bugaddr ( unsigned long addr )
{
return 1 ;
}
# endif /* CONFIG_GENERIC_BUG */
static const char * ex_name ( int ex )
{
switch ( ex ) {
case HVM_GE_C_XPROT :
case HVM_GE_C_XUSER :
return " Execute protection fault " ;
case HVM_GE_C_RPROT :
case HVM_GE_C_RUSER :
return " Read protection fault " ;
case HVM_GE_C_WPROT :
case HVM_GE_C_WUSER :
return " Write protection fault " ;
case HVM_GE_C_XMAL :
return " Misaligned instruction " ;
2012-10-28 19:54:37 -05:00
case HVM_GE_C_WREG :
return " Multiple writes to same register in packet " ;
case HVM_GE_C_PCAL :
return " Program counter values that are not properly aligned " ;
2011-10-31 18:44:34 -05:00
case HVM_GE_C_RMAL :
return " Misaligned data load " ;
case HVM_GE_C_WMAL :
return " Misaligned data store " ;
case HVM_GE_C_INVI :
case HVM_GE_C_PRIVI :
return " Illegal instruction " ;
case HVM_GE_C_BUS :
return " Precise bus error " ;
case HVM_GE_C_CACHE :
return " Cache error " ;
case 0xdb :
return " Debugger trap " ;
default :
return " Unrecognized exception " ;
}
}
static void do_show_stack ( struct task_struct * task , unsigned long * fp ,
unsigned long ip )
{
int kstack_depth_to_print = 24 ;
unsigned long offset , size ;
const char * name = NULL ;
unsigned long * newfp ;
unsigned long low , high ;
char tmpstr [ 128 ] ;
char * modname ;
int i ;
if ( task = = NULL )
task = current ;
printk ( KERN_INFO " CPU#%d, %s/%d, Call Trace: \n " ,
raw_smp_processor_id ( ) , task - > comm ,
task_pid_nr ( task ) ) ;
if ( fp = = NULL ) {
if ( task = = current ) {
asm ( " %0 = r30 " : " =r " ( fp ) ) ;
} else {
fp = ( unsigned long * )
( ( struct hexagon_switch_stack * )
task - > thread . switch_sp ) - > fp ;
}
}
if ( ( ( ( unsigned long ) fp ) & 0x3 ) | | ( ( unsigned long ) fp < 0x1000 ) ) {
printk ( KERN_INFO " -- Corrupt frame pointer %p \n " , fp ) ;
return ;
}
/* Saved link reg is one word above FP */
if ( ! ip )
ip = * ( fp + 1 ) ;
/* Expect kernel stack to be in-bounds */
low = ( unsigned long ) task_stack_page ( task ) ;
high = low + THREAD_SIZE - 8 ;
low + = sizeof ( struct thread_info ) ;
for ( i = 0 ; i < kstack_depth_to_print ; i + + ) {
name = kallsyms_lookup ( ip , & size , & offset , & modname , tmpstr ) ;
printk ( KERN_INFO " [%p] 0x%lx: %s + 0x%lx " , fp , ip , name ,
offset ) ;
if ( ( ( unsigned long ) fp < low ) | | ( high < ( unsigned long ) fp ) )
printk ( KERN_CONT " (FP out of bounds!) " ) ;
if ( modname )
printk ( KERN_CONT " [%s] " , modname ) ;
printk ( KERN_CONT " \n " ) ;
newfp = ( unsigned long * ) * fp ;
if ( ( ( unsigned long ) newfp ) & 0x3 ) {
printk ( KERN_INFO " -- Corrupt frame pointer %p \n " ,
newfp ) ;
break ;
}
/* Attempt to continue past exception. */
if ( 0 = = newfp ) {
struct pt_regs * regs = ( struct pt_regs * ) ( ( ( void * ) fp )
+ 8 ) ;
if ( regs - > syscall_nr ! = - 1 ) {
printk ( KERN_INFO " -- trap0 -- syscall_nr: %ld " ,
regs - > syscall_nr ) ;
printk ( KERN_CONT " psp: %lx elr: %lx \n " ,
pt_psp ( regs ) , pt_elr ( regs ) ) ;
break ;
} else {
/* really want to see more ... */
kstack_depth_to_print + = 6 ;
printk ( KERN_INFO " -- %s (0x%lx) badva: %lx \n " ,
ex_name ( pt_cause ( regs ) ) , pt_cause ( regs ) ,
pt_badva ( regs ) ) ;
}
newfp = ( unsigned long * ) regs - > r30 ;
ip = pt_elr ( regs ) ;
} else {
ip = * ( newfp + 1 ) ;
}
/* If link reg is null, we are done. */
if ( ip = = 0x0 )
break ;
/* If newfp isn't larger, we're tracing garbage. */
if ( newfp > fp )
fp = newfp ;
else
break ;
}
}
void show_stack ( struct task_struct * task , unsigned long * fp )
{
/* Saved link reg is one word above FP */
do_show_stack ( task , fp , 0 ) ;
}
int die ( const char * str , struct pt_regs * regs , long err )
{
static struct {
spinlock_t lock ;
int counter ;
} die = {
. lock = __SPIN_LOCK_UNLOCKED ( die . lock ) ,
. counter = 0
} ;
console_verbose ( ) ;
oops_enter ( ) ;
spin_lock_irq ( & die . lock ) ;
bust_spinlocks ( 1 ) ;
printk ( KERN_EMERG " Oops: %s[#%d]: \n " , str , + + die . counter ) ;
if ( notify_die ( DIE_OOPS , str , regs , err , pt_cause ( regs ) , SIGSEGV ) = =
NOTIFY_STOP )
return 1 ;
print_modules ( ) ;
show_regs ( regs ) ;
do_show_stack ( current , & regs - > r30 , pt_elr ( regs ) ) ;
bust_spinlocks ( 0 ) ;
2013-01-21 17:17:39 +10:30
add_taint ( TAINT_DIE , LOCKDEP_NOW_UNRELIABLE ) ;
2011-10-31 18:44:34 -05:00
spin_unlock_irq ( & die . lock ) ;
if ( in_interrupt ( ) )
panic ( " Fatal exception in interrupt " ) ;
if ( panic_on_oops )
panic ( " Fatal exception " ) ;
oops_exit ( ) ;
do_exit ( err ) ;
return 0 ;
}
int die_if_kernel ( char * str , struct pt_regs * regs , long err )
{
if ( ! user_mode ( regs ) )
return die ( str , regs , err ) ;
else
return 0 ;
}
/*
* It ' s not clear that misaligned fetches are ever recoverable .
*/
static void misaligned_instruction ( struct pt_regs * regs )
{
die_if_kernel ( " Misaligned Instruction " , regs , 0 ) ;
force_sig ( SIGBUS , current ) ;
}
/*
* Misaligned loads and stores , on the other hand , can be
* emulated , and probably should be , some day . But for now
* they will be considered fatal .
*/
static void misaligned_data_load ( struct pt_regs * regs )
{
die_if_kernel ( " Misaligned Data Load " , regs , 0 ) ;
force_sig ( SIGBUS , current ) ;
}
static void misaligned_data_store ( struct pt_regs * regs )
{
die_if_kernel ( " Misaligned Data Store " , regs , 0 ) ;
force_sig ( SIGBUS , current ) ;
}
static void illegal_instruction ( struct pt_regs * regs )
{
die_if_kernel ( " Illegal Instruction " , regs , 0 ) ;
force_sig ( SIGILL , current ) ;
}
/*
* Precise bus errors may be recoverable with a a retry ,
* but for now , treat them as irrecoverable .
*/
static void precise_bus_error ( struct pt_regs * regs )
{
die_if_kernel ( " Precise Bus Error " , regs , 0 ) ;
force_sig ( SIGBUS , current ) ;
}
/*
* If anything is to be done here other than panic ,
* it will probably be complex and migrate to another
* source module . For now , just die .
*/
static void cache_error ( struct pt_regs * regs )
{
die ( " Cache Error " , regs , 0 ) ;
}
/*
* General exception handler
*/
void do_genex ( struct pt_regs * regs )
{
/*
* Decode Cause and Dispatch
*/
switch ( pt_cause ( regs ) ) {
case HVM_GE_C_XPROT :
case HVM_GE_C_XUSER :
execute_protection_fault ( regs ) ;
break ;
case HVM_GE_C_RPROT :
case HVM_GE_C_RUSER :
read_protection_fault ( regs ) ;
break ;
case HVM_GE_C_WPROT :
case HVM_GE_C_WUSER :
write_protection_fault ( regs ) ;
break ;
case HVM_GE_C_XMAL :
misaligned_instruction ( regs ) ;
break ;
2012-10-28 19:54:37 -05:00
case HVM_GE_C_WREG :
illegal_instruction ( regs ) ;
break ;
case HVM_GE_C_PCAL :
misaligned_instruction ( regs ) ;
break ;
2011-10-31 18:44:34 -05:00
case HVM_GE_C_RMAL :
misaligned_data_load ( regs ) ;
break ;
case HVM_GE_C_WMAL :
misaligned_data_store ( regs ) ;
break ;
case HVM_GE_C_INVI :
case HVM_GE_C_PRIVI :
illegal_instruction ( regs ) ;
break ;
case HVM_GE_C_BUS :
precise_bus_error ( regs ) ;
break ;
case HVM_GE_C_CACHE :
cache_error ( regs ) ;
break ;
default :
/* Halt and catch fire */
panic ( " Unrecognized exception 0x%lx \n " , pt_cause ( regs ) ) ;
break ;
}
}
/* Indirect system call dispatch */
long sys_syscall ( void )
{
printk ( KERN_ERR " sys_syscall invoked! \n " ) ;
return - ENOSYS ;
}
void do_trap0 ( struct pt_regs * regs )
{
syscall_fn syscall ;
switch ( pt_cause ( regs ) ) {
case TRAP_SYSCALL :
/* System call is trap0 #1 */
/* allow strace to catch syscall args */
if ( unlikely ( test_thread_flag ( TIF_SYSCALL_TRACE ) & &
tracehook_report_syscall_entry ( regs ) ) )
return ; /* return -ENOSYS somewhere? */
/* Interrupts should be re-enabled for syscall processing */
__vmsetie ( VM_INT_ENABLE ) ;
/*
* System call number is in r6 , arguments in r0 . . r5 .
* Fortunately , no Linux syscall has more than 6 arguments ,
* and Hexagon ABI passes first 6 arguments in registers .
* 64 - bit arguments are passed in odd / even register pairs .
* Fortunately , we have no system calls that take more
* than three arguments with more than one 64 - bit value .
* Should that change , we ' d need to redesign to copy
* between user and kernel stacks .
*/
regs - > syscall_nr = regs - > r06 ;
/*
* GPR R0 carries the first parameter , and is also used
* to report the return value . We need a backup of
* the user ' s value in case we need to do a late restart
* of the system call .
*/
regs - > restart_r0 = regs - > r00 ;
if ( ( unsigned long ) regs - > syscall_nr > = __NR_syscalls ) {
regs - > r00 = - 1 ;
} else {
syscall = ( syscall_fn )
( sys_call_table [ regs - > syscall_nr ] ) ;
2012-05-29 17:23:14 -05:00
regs - > r00 = syscall ( regs - > r00 , regs - > r01 ,
2011-10-31 18:44:34 -05:00
regs - > r02 , regs - > r03 ,
regs - > r04 , regs - > r05 ) ;
}
/* allow strace to get the syscall return state */
if ( unlikely ( test_thread_flag ( TIF_SYSCALL_TRACE ) ) )
tracehook_report_syscall_exit ( regs , 0 ) ;
break ;
case TRAP_DEBUG :
/* Trap0 0xdb is debug breakpoint */
if ( user_mode ( regs ) ) {
struct siginfo info ;
info . si_signo = SIGTRAP ;
info . si_errno = 0 ;
/*
* Some architecures add some per - thread state
* to distinguish between breakpoint traps and
* trace traps . We may want to do that , and
* set the si_code value appropriately , or we
* may want to use a different trap0 flavor .
*/
info . si_code = TRAP_BRKPT ;
info . si_addr = ( void __user * ) pt_elr ( regs ) ;
2014-12-08 15:36:20 -06:00
force_sig_info ( SIGTRAP , & info , current ) ;
2011-10-31 18:44:34 -05:00
} else {
# ifdef CONFIG_KGDB
kgdb_handle_exception ( pt_cause ( regs ) , SIGTRAP ,
TRAP_BRKPT , regs ) ;
# endif
}
break ;
}
/* Ignore other trap0 codes for now, especially 0 (Angel calls) */
}
/*
* Machine check exception handler
*/
void do_machcheck ( struct pt_regs * regs )
{
/* Halt and catch fire */
__vmstop ( ) ;
}
2013-03-07 12:03:10 -06:00
/*
* Treat this like the old 0xdb trap .
*/
void do_debug_exception ( struct pt_regs * regs )
{
regs - > hvmer . vmest & = ~ HVM_VMEST_CAUSE_MSK ;
regs - > hvmer . vmest | = ( TRAP_DEBUG < < HVM_VMEST_CAUSE_SFT ) ;
do_trap0 ( regs ) ;
}