2005-04-16 15:20:36 -07:00
/*
2007-11-28 20:19:38 +09:00
* arch / sh / kernel / traps_64 . c
2005-04-16 15:20:36 -07:00
*
* Copyright ( C ) 2000 , 2001 Paolo Alberelli
* Copyright ( C ) 2003 , 2004 Paul Mundt
* Copyright ( C ) 2003 , 2004 Richard Curnow
*
2007-11-28 20:19:38 +09:00
* 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 .
2005-04-16 15:20:36 -07:00
*/
# include <linux/sched.h>
# include <linux/kernel.h>
# include <linux/string.h>
# include <linux/errno.h>
# include <linux/ptrace.h>
# include <linux/timer.h>
# include <linux/mm.h>
# include <linux/smp.h>
# include <linux/init.h>
# include <linux/delay.h>
# include <linux/spinlock.h>
# include <linux/kallsyms.h>
# include <linux/interrupt.h>
# include <linux/sysctl.h>
# include <linux/module.h>
2010-10-13 06:55:26 +09:00
# include <linux/perf_event.h>
2005-04-16 15:20:36 -07:00
# include <asm/uaccess.h>
# include <asm/io.h>
2012-06-14 14:05:24 +09:00
# include <asm/alignment.h>
2005-04-16 15:20:36 -07:00
# include <asm/processor.h>
# include <asm/pgtable.h>
2008-04-13 21:15:38 +03:00
# include <asm/fpu.h>
2005-04-16 15:20:36 -07:00
2012-06-14 14:05:24 +09:00
static int read_opcode ( reg_size_t pc , insn_size_t * result_opcode , int from_user_mode )
2005-04-16 15:20:36 -07:00
{
int get_user_error ;
unsigned long aligned_pc ;
2012-06-14 14:05:24 +09:00
insn_size_t opcode ;
2005-04-16 15:20:36 -07:00
if ( ( pc & 3 ) = = 1 ) {
/* SHmedia */
aligned_pc = pc & ~ 3 ;
if ( from_user_mode ) {
2012-06-14 14:05:24 +09:00
if ( ! access_ok ( VERIFY_READ , aligned_pc , sizeof ( insn_size_t ) ) ) {
2005-04-16 15:20:36 -07:00
get_user_error = - EFAULT ;
} else {
2012-06-14 14:05:24 +09:00
get_user_error = __get_user ( opcode , ( insn_size_t * ) aligned_pc ) ;
2005-04-16 15:20:36 -07:00
* result_opcode = opcode ;
}
return get_user_error ;
} else {
/* If the fault was in the kernel, we can either read
* this directly , or if not , we fault .
*/
2012-06-14 14:05:24 +09:00
* result_opcode = * ( insn_size_t * ) aligned_pc ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
} else if ( ( pc & 1 ) = = 0 ) {
/* SHcompact */
/* TODO : provide handling for this. We don't really support
user - mode SHcompact yet , and for a kernel fault , this would
have to come from a module built for SHcompact . */
return - EFAULT ;
} else {
/* misaligned */
return - EFAULT ;
}
}
static int address_is_sign_extended ( __u64 a )
{
__u64 b ;
# if (NEFF == 32)
b = ( __u64 ) ( __s64 ) ( __s32 ) ( a & 0xffffffffUL ) ;
return ( b = = a ) ? 1 : 0 ;
# else
# error "Sign extend check only works for NEFF==32"
# endif
}
2012-06-14 14:05:24 +09:00
/* return -1 for fault, 0 for OK */
2005-04-16 15:20:36 -07:00
static int generate_and_check_address ( struct pt_regs * regs ,
2012-06-14 14:05:24 +09:00
insn_size_t opcode ,
2005-04-16 15:20:36 -07:00
int displacement_not_indexed ,
int width_shift ,
__u64 * address )
{
__u64 base_address , addr ;
int basereg ;
2012-06-14 14:05:24 +09:00
switch ( 1 < < width_shift ) {
case 1 : inc_unaligned_byte_access ( ) ; break ;
case 2 : inc_unaligned_word_access ( ) ; break ;
case 4 : inc_unaligned_dword_access ( ) ; break ;
case 8 : inc_unaligned_multi_access ( ) ; break ;
}
2005-04-16 15:20:36 -07:00
basereg = ( opcode > > 20 ) & 0x3f ;
base_address = regs - > regs [ basereg ] ;
if ( displacement_not_indexed ) {
__s64 displacement ;
displacement = ( opcode > > 10 ) & 0x3ff ;
2015-11-06 16:31:05 -08:00
displacement = sign_extend64 ( displacement , 9 ) ;
2005-04-16 15:20:36 -07:00
addr = ( __u64 ) ( ( __s64 ) base_address + ( displacement < < width_shift ) ) ;
} else {
__u64 offset ;
int offsetreg ;
offsetreg = ( opcode > > 10 ) & 0x3f ;
offset = regs - > regs [ offsetreg ] ;
addr = base_address + offset ;
}
/* Check sign extended */
2012-06-14 14:05:24 +09:00
if ( ! address_is_sign_extended ( addr ) )
2005-04-16 15:20:36 -07:00
return - 1 ;
/* Check accessible. For misaligned access in the kernel, assume the
address is always accessible ( and if not , just fault when the
load / store gets done . ) */
if ( user_mode ( regs ) ) {
2012-06-14 14:05:24 +09:00
inc_unaligned_user_access ( ) ;
if ( addr > = TASK_SIZE )
2005-04-16 15:20:36 -07:00
return - 1 ;
2012-06-14 14:05:24 +09:00
} else
inc_unaligned_kernel_access ( ) ;
2005-04-16 15:20:36 -07:00
* address = addr ;
2012-06-14 14:05:24 +09:00
perf_sw_event ( PERF_COUNT_SW_EMULATION_FAULTS , 1 , regs , addr ) ;
unaligned_fixups_notify ( current , opcode , regs ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
static void misaligned_kernel_word_load ( __u64 address , int do_sign_extend , __u64 * result )
{
unsigned short x ;
unsigned char * p , * q ;
p = ( unsigned char * ) ( int ) address ;
q = ( unsigned char * ) & x ;
q [ 0 ] = p [ 0 ] ;
q [ 1 ] = p [ 1 ] ;
if ( do_sign_extend ) {
* result = ( __u64 ) ( __s64 ) * ( short * ) & x ;
} else {
* result = ( __u64 ) x ;
}
}
static void misaligned_kernel_word_store ( __u64 address , __u64 value )
{
unsigned short x ;
unsigned char * p , * q ;
p = ( unsigned char * ) ( int ) address ;
q = ( unsigned char * ) & x ;
x = ( __u16 ) value ;
p [ 0 ] = q [ 0 ] ;
p [ 1 ] = q [ 1 ] ;
}
static int misaligned_load ( struct pt_regs * regs ,
2012-06-14 14:05:24 +09:00
insn_size_t opcode ,
2005-04-16 15:20:36 -07:00
int displacement_not_indexed ,
int width_shift ,
int do_sign_extend )
{
/* Return -1 for a fault, 0 for OK */
int error ;
int destreg ;
__u64 address ;
error = generate_and_check_address ( regs , opcode ,
displacement_not_indexed , width_shift , & address ) ;
2012-06-14 14:05:24 +09:00
if ( error < 0 )
2005-04-16 15:20:36 -07:00
return error ;
2010-10-13 06:55:26 +09:00
2005-04-16 15:20:36 -07:00
destreg = ( opcode > > 4 ) & 0x3f ;
if ( user_mode ( regs ) ) {
__u64 buffer ;
if ( ! access_ok ( VERIFY_READ , ( unsigned long ) address , 1UL < < width_shift ) ) {
return - 1 ;
}
if ( __copy_user ( & buffer , ( const void * ) ( int ) address , ( 1 < < width_shift ) ) > 0 ) {
return - 1 ; /* fault */
}
switch ( width_shift ) {
case 1 :
if ( do_sign_extend ) {
regs - > regs [ destreg ] = ( __u64 ) ( __s64 ) * ( __s16 * ) & buffer ;
} else {
regs - > regs [ destreg ] = ( __u64 ) * ( __u16 * ) & buffer ;
}
break ;
case 2 :
regs - > regs [ destreg ] = ( __u64 ) ( __s64 ) * ( __s32 * ) & buffer ;
break ;
case 3 :
regs - > regs [ destreg ] = buffer ;
break ;
default :
printk ( " Unexpected width_shift %d in misaligned_load, PC=%08lx \n " ,
width_shift , ( unsigned long ) regs - > pc ) ;
break ;
}
2009-05-08 20:32:56 +09:00
} else {
2005-04-16 15:20:36 -07:00
/* kernel mode - we can take short cuts since if we fault, it's a genuine bug */
__u64 lo , hi ;
switch ( width_shift ) {
case 1 :
misaligned_kernel_word_load ( address , do_sign_extend , & regs - > regs [ destreg ] ) ;
break ;
case 2 :
asm ( " ldlo.l %1, 0, %0 " : " =r " ( lo ) : " r " ( address ) ) ;
asm ( " ldhi.l %1, 3, %0 " : " =r " ( hi ) : " r " ( address ) ) ;
regs - > regs [ destreg ] = lo | hi ;
break ;
case 3 :
asm ( " ldlo.q %1, 0, %0 " : " =r " ( lo ) : " r " ( address ) ) ;
asm ( " ldhi.q %1, 7, %0 " : " =r " ( hi ) : " r " ( address ) ) ;
regs - > regs [ destreg ] = lo | hi ;
break ;
default :
printk ( " Unexpected width_shift %d in misaligned_load, PC=%08lx \n " ,
width_shift , ( unsigned long ) regs - > pc ) ;
break ;
}
}
return 0 ;
}
static int misaligned_store ( struct pt_regs * regs ,
2012-06-14 14:05:24 +09:00
insn_size_t opcode ,
2005-04-16 15:20:36 -07:00
int displacement_not_indexed ,
int width_shift )
{
/* Return -1 for a fault, 0 for OK */
int error ;
int srcreg ;
__u64 address ;
error = generate_and_check_address ( regs , opcode ,
displacement_not_indexed , width_shift , & address ) ;
2012-06-14 14:05:24 +09:00
if ( error < 0 )
2005-04-16 15:20:36 -07:00
return error ;
2010-10-13 06:55:26 +09:00
2005-04-16 15:20:36 -07:00
srcreg = ( opcode > > 4 ) & 0x3f ;
if ( user_mode ( regs ) ) {
__u64 buffer ;
if ( ! access_ok ( VERIFY_WRITE , ( unsigned long ) address , 1UL < < width_shift ) ) {
return - 1 ;
}
switch ( width_shift ) {
case 1 :
* ( __u16 * ) & buffer = ( __u16 ) regs - > regs [ srcreg ] ;
break ;
case 2 :
* ( __u32 * ) & buffer = ( __u32 ) regs - > regs [ srcreg ] ;
break ;
case 3 :
buffer = regs - > regs [ srcreg ] ;
break ;
default :
printk ( " Unexpected width_shift %d in misaligned_store, PC=%08lx \n " ,
width_shift , ( unsigned long ) regs - > pc ) ;
break ;
}
if ( __copy_user ( ( void * ) ( int ) address , & buffer , ( 1 < < width_shift ) ) > 0 ) {
return - 1 ; /* fault */
}
2009-05-08 20:32:56 +09:00
} else {
2005-04-16 15:20:36 -07:00
/* kernel mode - we can take short cuts since if we fault, it's a genuine bug */
__u64 val = regs - > regs [ srcreg ] ;
switch ( width_shift ) {
case 1 :
misaligned_kernel_word_store ( address , val ) ;
break ;
case 2 :
asm ( " stlo.l %1, 0, %0 " : : " r " ( val ) , " r " ( address ) ) ;
asm ( " sthi.l %1, 3, %0 " : : " r " ( val ) , " r " ( address ) ) ;
break ;
case 3 :
asm ( " stlo.q %1, 0, %0 " : : " r " ( val ) , " r " ( address ) ) ;
asm ( " sthi.q %1, 7, %0 " : : " r " ( val ) , " r " ( address ) ) ;
break ;
default :
printk ( " Unexpected width_shift %d in misaligned_store, PC=%08lx \n " ,
width_shift , ( unsigned long ) regs - > pc ) ;
break ;
}
}
return 0 ;
}
/* Never need to fix up misaligned FPU accesses within the kernel since that's a real
error . */
static int misaligned_fpu_load ( struct pt_regs * regs ,
2012-06-14 14:05:24 +09:00
insn_size_t opcode ,
2005-04-16 15:20:36 -07:00
int displacement_not_indexed ,
int width_shift ,
int do_paired_load )
{
/* Return -1 for a fault, 0 for OK */
int error ;
int destreg ;
__u64 address ;
error = generate_and_check_address ( regs , opcode ,
displacement_not_indexed , width_shift , & address ) ;
2012-06-14 14:05:24 +09:00
if ( error < 0 )
2005-04-16 15:20:36 -07:00
return error ;
2010-10-13 06:55:26 +09:00
2005-04-16 15:20:36 -07:00
destreg = ( opcode > > 4 ) & 0x3f ;
if ( user_mode ( regs ) ) {
__u64 buffer ;
__u32 buflo , bufhi ;
if ( ! access_ok ( VERIFY_READ , ( unsigned long ) address , 1UL < < width_shift ) ) {
return - 1 ;
}
if ( __copy_user ( & buffer , ( const void * ) ( int ) address , ( 1 < < width_shift ) ) > 0 ) {
return - 1 ; /* fault */
}
/* 'current' may be the current owner of the FPU state, so
context switch the registers into memory so they can be
indexed by register number . */
if ( last_task_used_math = = current ) {
2007-11-10 20:27:03 +09:00
enable_fpu ( ) ;
2009-12-14 20:12:04 +00:00
save_fpu ( current ) ;
2007-11-10 20:27:03 +09:00
disable_fpu ( ) ;
2005-04-16 15:20:36 -07:00
last_task_used_math = NULL ;
regs - > sr | = SR_FD ;
}
buflo = * ( __u32 * ) & buffer ;
bufhi = * ( 1 + ( __u32 * ) & buffer ) ;
switch ( width_shift ) {
case 2 :
2010-01-19 15:40:03 +09:00
current - > thread . xstate - > hardfpu . fp_regs [ destreg ] = buflo ;
2005-04-16 15:20:36 -07:00
break ;
case 3 :
if ( do_paired_load ) {
2010-01-19 15:40:03 +09:00
current - > thread . xstate - > hardfpu . fp_regs [ destreg ] = buflo ;
current - > thread . xstate - > hardfpu . fp_regs [ destreg + 1 ] = bufhi ;
2005-04-16 15:20:36 -07:00
} else {
2008-02-13 20:28:12 +09:00
# if defined(CONFIG_CPU_LITTLE_ENDIAN)
2010-01-19 15:40:03 +09:00
current - > thread . xstate - > hardfpu . fp_regs [ destreg ] = bufhi ;
current - > thread . xstate - > hardfpu . fp_regs [ destreg + 1 ] = buflo ;
2005-04-16 15:20:36 -07:00
# else
2010-01-19 15:40:03 +09:00
current - > thread . xstate - > hardfpu . fp_regs [ destreg ] = buflo ;
current - > thread . xstate - > hardfpu . fp_regs [ destreg + 1 ] = bufhi ;
2005-04-16 15:20:36 -07:00
# endif
}
break ;
default :
printk ( " Unexpected width_shift %d in misaligned_fpu_load, PC=%08lx \n " ,
width_shift , ( unsigned long ) regs - > pc ) ;
break ;
}
return 0 ;
} else {
die ( " Misaligned FPU load inside kernel " , regs , 0 ) ;
return - 1 ;
}
}
static int misaligned_fpu_store ( struct pt_regs * regs ,
2012-06-14 14:05:24 +09:00
insn_size_t opcode ,
2005-04-16 15:20:36 -07:00
int displacement_not_indexed ,
int width_shift ,
int do_paired_load )
{
/* Return -1 for a fault, 0 for OK */
int error ;
int srcreg ;
__u64 address ;
error = generate_and_check_address ( regs , opcode ,
displacement_not_indexed , width_shift , & address ) ;
2012-06-14 14:05:24 +09:00
if ( error < 0 )
2005-04-16 15:20:36 -07:00
return error ;
2010-10-13 06:55:26 +09:00
2005-04-16 15:20:36 -07:00
srcreg = ( opcode > > 4 ) & 0x3f ;
if ( user_mode ( regs ) ) {
__u64 buffer ;
/* Initialise these to NaNs. */
__u32 buflo = 0xffffffffUL , bufhi = 0xffffffffUL ;
if ( ! access_ok ( VERIFY_WRITE , ( unsigned long ) address , 1UL < < width_shift ) ) {
return - 1 ;
}
/* 'current' may be the current owner of the FPU state, so
context switch the registers into memory so they can be
indexed by register number . */
if ( last_task_used_math = = current ) {
2007-11-10 20:27:03 +09:00
enable_fpu ( ) ;
2009-12-14 20:12:04 +00:00
save_fpu ( current ) ;
2007-11-10 20:27:03 +09:00
disable_fpu ( ) ;
2005-04-16 15:20:36 -07:00
last_task_used_math = NULL ;
regs - > sr | = SR_FD ;
}
switch ( width_shift ) {
case 2 :
2010-01-19 15:40:03 +09:00
buflo = current - > thread . xstate - > hardfpu . fp_regs [ srcreg ] ;
2005-04-16 15:20:36 -07:00
break ;
case 3 :
if ( do_paired_load ) {
2010-01-19 15:40:03 +09:00
buflo = current - > thread . xstate - > hardfpu . fp_regs [ srcreg ] ;
bufhi = current - > thread . xstate - > hardfpu . fp_regs [ srcreg + 1 ] ;
2005-04-16 15:20:36 -07:00
} else {
2008-02-13 20:28:12 +09:00
# if defined(CONFIG_CPU_LITTLE_ENDIAN)
2010-01-19 15:40:03 +09:00
bufhi = current - > thread . xstate - > hardfpu . fp_regs [ srcreg ] ;
buflo = current - > thread . xstate - > hardfpu . fp_regs [ srcreg + 1 ] ;
2005-04-16 15:20:36 -07:00
# else
2010-01-19 15:40:03 +09:00
buflo = current - > thread . xstate - > hardfpu . fp_regs [ srcreg ] ;
bufhi = current - > thread . xstate - > hardfpu . fp_regs [ srcreg + 1 ] ;
2005-04-16 15:20:36 -07:00
# endif
}
break ;
default :
printk ( " Unexpected width_shift %d in misaligned_fpu_store, PC=%08lx \n " ,
width_shift , ( unsigned long ) regs - > pc ) ;
break ;
}
* ( __u32 * ) & buffer = buflo ;
* ( 1 + ( __u32 * ) & buffer ) = bufhi ;
if ( __copy_user ( ( void * ) ( int ) address , & buffer , ( 1 < < width_shift ) ) > 0 ) {
return - 1 ; /* fault */
}
return 0 ;
} else {
die ( " Misaligned FPU load inside kernel " , regs , 0 ) ;
return - 1 ;
}
}
static int misaligned_fixup ( struct pt_regs * regs )
{
2012-06-14 14:05:24 +09:00
insn_size_t opcode ;
2005-04-16 15:20:36 -07:00
int error ;
int major , minor ;
2012-06-14 14:05:24 +09:00
unsigned int user_action ;
2005-04-16 15:20:36 -07:00
2012-06-14 14:05:24 +09:00
user_action = unaligned_user_action ( ) ;
if ( ! ( user_action & UM_FIXUP ) )
2009-05-08 20:32:56 +09:00
return - 1 ;
2005-04-16 15:20:36 -07:00
error = read_opcode ( regs - > pc , & opcode , user_mode ( regs ) ) ;
if ( error < 0 ) {
return error ;
}
major = ( opcode > > 26 ) & 0x3f ;
minor = ( opcode > > 16 ) & 0xf ;
switch ( major ) {
case ( 0x84 > > 2 ) : /* LD.W */
error = misaligned_load ( regs , opcode , 1 , 1 , 1 ) ;
break ;
case ( 0xb0 > > 2 ) : /* LD.UW */
error = misaligned_load ( regs , opcode , 1 , 1 , 0 ) ;
break ;
case ( 0x88 > > 2 ) : /* LD.L */
error = misaligned_load ( regs , opcode , 1 , 2 , 1 ) ;
break ;
case ( 0x8c > > 2 ) : /* LD.Q */
error = misaligned_load ( regs , opcode , 1 , 3 , 0 ) ;
break ;
case ( 0xa4 > > 2 ) : /* ST.W */
error = misaligned_store ( regs , opcode , 1 , 1 ) ;
break ;
case ( 0xa8 > > 2 ) : /* ST.L */
error = misaligned_store ( regs , opcode , 1 , 2 ) ;
break ;
case ( 0xac > > 2 ) : /* ST.Q */
error = misaligned_store ( regs , opcode , 1 , 3 ) ;
break ;
case ( 0x40 > > 2 ) : /* indexed loads */
switch ( minor ) {
case 0x1 : /* LDX.W */
error = misaligned_load ( regs , opcode , 0 , 1 , 1 ) ;
break ;
case 0x5 : /* LDX.UW */
error = misaligned_load ( regs , opcode , 0 , 1 , 0 ) ;
break ;
case 0x2 : /* LDX.L */
error = misaligned_load ( regs , opcode , 0 , 2 , 1 ) ;
break ;
case 0x3 : /* LDX.Q */
error = misaligned_load ( regs , opcode , 0 , 3 , 0 ) ;
break ;
default :
error = - 1 ;
break ;
}
break ;
case ( 0x60 > > 2 ) : /* indexed stores */
switch ( minor ) {
case 0x1 : /* STX.W */
error = misaligned_store ( regs , opcode , 0 , 1 ) ;
break ;
case 0x2 : /* STX.L */
error = misaligned_store ( regs , opcode , 0 , 2 ) ;
break ;
case 0x3 : /* STX.Q */
error = misaligned_store ( regs , opcode , 0 , 3 ) ;
break ;
default :
error = - 1 ;
break ;
}
break ;
case ( 0x94 > > 2 ) : /* FLD.S */
error = misaligned_fpu_load ( regs , opcode , 1 , 2 , 0 ) ;
break ;
case ( 0x98 > > 2 ) : /* FLD.P */
error = misaligned_fpu_load ( regs , opcode , 1 , 3 , 1 ) ;
break ;
case ( 0x9c > > 2 ) : /* FLD.D */
error = misaligned_fpu_load ( regs , opcode , 1 , 3 , 0 ) ;
break ;
case ( 0x1c > > 2 ) : /* floating indexed loads */
switch ( minor ) {
case 0x8 : /* FLDX.S */
error = misaligned_fpu_load ( regs , opcode , 0 , 2 , 0 ) ;
break ;
case 0xd : /* FLDX.P */
error = misaligned_fpu_load ( regs , opcode , 0 , 3 , 1 ) ;
break ;
case 0x9 : /* FLDX.D */
error = misaligned_fpu_load ( regs , opcode , 0 , 3 , 0 ) ;
break ;
default :
error = - 1 ;
break ;
}
break ;
case ( 0xb4 > > 2 ) : /* FLD.S */
error = misaligned_fpu_store ( regs , opcode , 1 , 2 , 0 ) ;
break ;
case ( 0xb8 > > 2 ) : /* FLD.P */
error = misaligned_fpu_store ( regs , opcode , 1 , 3 , 1 ) ;
break ;
case ( 0xbc > > 2 ) : /* FLD.D */
error = misaligned_fpu_store ( regs , opcode , 1 , 3 , 0 ) ;
break ;
case ( 0x3c > > 2 ) : /* floating indexed stores */
switch ( minor ) {
case 0x8 : /* FSTX.S */
error = misaligned_fpu_store ( regs , opcode , 0 , 2 , 0 ) ;
break ;
case 0xd : /* FSTX.P */
error = misaligned_fpu_store ( regs , opcode , 0 , 3 , 1 ) ;
break ;
case 0x9 : /* FSTX.D */
error = misaligned_fpu_store ( regs , opcode , 0 , 3 , 0 ) ;
break ;
default :
error = - 1 ;
break ;
}
break ;
default :
/* Fault */
error = - 1 ;
break ;
}
if ( error < 0 ) {
return error ;
} else {
regs - > pc + = 4 ; /* Skip the instruction that's just been emulated */
return 0 ;
}
2012-06-14 14:46:36 +09:00
}
static void do_unhandled_exception ( int signr , char * str , unsigned long error ,
struct pt_regs * regs )
{
if ( user_mode ( regs ) )
force_sig ( signr , current ) ;
die_if_no_fixup ( str , regs , error ) ;
}
# define DO_ERROR(signr, str, name) \
asmlinkage void do_ # # name ( unsigned long error_code , struct pt_regs * regs ) \
{ \
do_unhandled_exception ( signr , str , error_code , regs ) ; \
}
DO_ERROR ( SIGILL , " illegal slot instruction " , illegal_slot_inst )
DO_ERROR ( SIGSEGV , " address error (exec) " , address_error_exec )
# if defined(CONFIG_SH64_ID2815_WORKAROUND)
# define OPCODE_INVALID 0
# define OPCODE_USER_VALID 1
# define OPCODE_PRIV_VALID 2
/* getcon/putcon - requires checking which control register is referenced. */
# define OPCODE_CTRL_REG 3
/* Table of valid opcodes for SHmedia mode.
Form a 10 - bit value by concatenating the major / minor opcodes i . e .
opcode [ 31 : 26 , 20 : 16 ] . The 6 MSBs of this value index into the following
array . The 4 LSBs select the bit - pair in the entry ( bits 1 : 0 correspond to
LSBs = = 4 ' b0000 etc ) . */
static unsigned long shmedia_opcode_table [ 64 ] = {
0x55554044 , 0x54445055 , 0x15141514 , 0x14541414 , 0x00000000 , 0x10001000 , 0x01110055 , 0x04050015 ,
0x00000444 , 0xc0000000 , 0x44545515 , 0x40405555 , 0x55550015 , 0x10005555 , 0x55555505 , 0x04050000 ,
0x00000555 , 0x00000404 , 0x00040445 , 0x15151414 , 0x00000000 , 0x00000000 , 0x00000000 , 0x00000000 ,
0x00000055 , 0x40404444 , 0x00000404 , 0xc0009495 , 0x00000000 , 0x00000000 , 0x00000000 , 0x00000000 ,
0x55555555 , 0x55555555 , 0x55555555 , 0x55555555 , 0x55555555 , 0x55555555 , 0x55555555 , 0x55555555 ,
0x55555555 , 0x55555555 , 0x55555555 , 0x55555555 , 0x55555555 , 0x55555555 , 0x55555555 , 0x55555555 ,
0x80005050 , 0x04005055 , 0x55555555 , 0x55555555 , 0x55555555 , 0x55555555 , 0x55555555 , 0x55555555 ,
0x81055554 , 0x00000404 , 0x55555555 , 0x55555555 , 0x00000000 , 0x00000000 , 0x00000000 , 0x00000000
} ;
/* Workaround SH5-101 cut2 silicon defect #2815 :
in some situations , inter - mode branches from SHcompact - > SHmedia
which should take ITLBMISS or EXECPROT exceptions at the target
falsely take RESINST at the target instead . */
void do_reserved_inst ( unsigned long error_code , struct pt_regs * regs )
{
insn_size_t opcode = 0x6ff4fff0 ; /* guaranteed reserved opcode */
unsigned long pc , aligned_pc ;
unsigned long index , shift ;
unsigned long major , minor , combined ;
unsigned long reserved_field ;
int opcode_state ;
int get_user_error ;
int signr = SIGILL ;
char * exception_name = " reserved_instruction " ;
pc = regs - > pc ;
/* SHcompact is not handled */
if ( unlikely ( ( pc & 3 ) = = 0 ) )
goto out ;
/* SHmedia : check for defect. This requires executable vmas
to be readable too . */
aligned_pc = pc & ~ 3 ;
if ( ! access_ok ( VERIFY_READ , aligned_pc , sizeof ( insn_size_t ) ) )
get_user_error = - EFAULT ;
else
get_user_error = __get_user ( opcode , ( insn_size_t * ) aligned_pc ) ;
if ( get_user_error < 0 ) {
/*
* Error trying to read opcode . This typically means a
* real fault , not a RESINST any more . So change the
* codes .
*/
exception_name = " address error (exec) " ;
signr = SIGSEGV ;
goto out ;
}
/* These bits are currently reserved as zero in all valid opcodes */
reserved_field = opcode & 0xf ;
if ( unlikely ( reserved_field ) )
goto out ; /* invalid opcode */
major = ( opcode > > 26 ) & 0x3f ;
minor = ( opcode > > 16 ) & 0xf ;
combined = ( major < < 4 ) | minor ;
index = major ;
shift = minor < < 1 ;
opcode_state = ( shmedia_opcode_table [ index ] > > shift ) & 0x3 ;
switch ( opcode_state ) {
case OPCODE_INVALID :
/* Trap. */
break ;
case OPCODE_USER_VALID :
/*
* Restart the instruction : the branch to the instruction
* will now be from an RTE not from SHcompact so the
* silicon defect won ' t be triggered .
*/
return ;
case OPCODE_PRIV_VALID :
if ( ! user_mode ( regs ) ) {
/*
* Should only ever get here if a module has
* SHcompact code inside it . If so , the same fix
* up is needed .
*/
return ; /* same reason */
}
/*
* Otherwise , user mode trying to execute a privileged
* instruction - fall through to trap .
*/
break ;
case OPCODE_CTRL_REG :
/* If in privileged mode, return as above. */
if ( ! user_mode ( regs ) )
return ;
/* In user mode ... */
if ( combined = = 0x9f ) { /* GETCON */
unsigned long regno = ( opcode > > 20 ) & 0x3f ;
if ( regno > = 62 )
return ;
/* reserved/privileged control register => trap */
} else if ( combined = = 0x1bf ) { /* PUTCON */
unsigned long regno = ( opcode > > 4 ) & 0x3f ;
if ( regno > = 62 )
return ;
/* reserved/privileged control register => trap */
}
break ;
default :
/* Fall through to trap. */
break ;
}
2005-04-16 15:20:36 -07:00
2012-06-14 14:46:36 +09:00
out :
do_unhandled_exception ( signr , exception_name , error_code , regs ) ;
}
# else /* CONFIG_SH64_ID2815_WORKAROUND */
/* If the workaround isn't needed, this is just a straightforward reserved
instruction */
DO_ERROR ( SIGILL , " reserved instruction " , reserved_inst )
# endif /* CONFIG_SH64_ID2815_WORKAROUND */
/* Called with interrupts disabled */
asmlinkage void do_exception_error ( unsigned long ex , struct pt_regs * regs )
{
die_if_kernel ( " exception " , regs , ex ) ;
}
asmlinkage int do_unknown_trapa ( unsigned long scId , struct pt_regs * regs )
{
/* Syscall debug */
printk ( " System call ID error: [0x1#args:8 #syscall:16 0x%lx] \n " , scId ) ;
die_if_kernel ( " unknown trapa " , regs , scId ) ;
return - ENOSYS ;
}
/* Implement misaligned load/store handling for kernel (and optionally for user
mode too ) . Limitation : only SHmedia mode code is handled - there is no
handling at all for misaligned accesses occurring in SHcompact code yet . */
asmlinkage void do_address_error_load ( unsigned long error_code , struct pt_regs * regs )
{
if ( misaligned_fixup ( regs ) < 0 )
do_unhandled_exception ( SIGSEGV , " address error(load) " ,
error_code , regs ) ;
}
asmlinkage void do_address_error_store ( unsigned long error_code , struct pt_regs * regs )
{
if ( misaligned_fixup ( regs ) < 0 )
do_unhandled_exception ( SIGSEGV , " address error(store) " ,
error_code , regs ) ;
2005-04-16 15:20:36 -07:00
}
asmlinkage void do_debug_interrupt ( unsigned long code , struct pt_regs * regs )
{
u64 peek_real_address_q ( u64 addr ) ;
u64 poke_real_address_q ( u64 addr , u64 val ) ;
unsigned long long DM_EXP_CAUSE_PHY = 0x0c100010 ;
unsigned long long exp_cause ;
/* It's not worth ioremapping the debug module registers for the amount
of access we make to them - just go direct to their physical
addresses . */
exp_cause = peek_real_address_q ( DM_EXP_CAUSE_PHY ) ;
2012-06-14 14:46:36 +09:00
if ( exp_cause & ~ 4 )
2005-04-16 15:20:36 -07:00
printk ( " DM.EXP_CAUSE had unexpected bits set (=%08lx) \n " ,
( unsigned long ) ( exp_cause & 0xffffffff ) ) ;
show_state ( ) ;
/* Clear all DEBUGINT causes */
poke_real_address_q ( DM_EXP_CAUSE_PHY , 0x0 ) ;
}
2010-05-18 15:23:48 +09:00
2013-06-18 17:10:12 -04:00
void per_cpu_trap_init ( void )
2010-05-18 15:23:48 +09:00
{
/* Nothing to do for now, VBR initialization later. */
}