2008-05-20 03:53:02 +04:00
/*
2005-04-17 02:20:36 +04:00
* unaligned . c : Unaligned load / store trap handling with special
* cases for the kernel to do them more quickly .
*
* Copyright ( C ) 1996 David S . Miller ( davem @ caip . rutgers . edu )
* Copyright ( C ) 1996 Jakub Jelinek ( jj @ sunsite . mff . cuni . cz )
*/
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/mm.h>
# include <asm/ptrace.h>
# include <asm/processor.h>
# include <asm/uaccess.h>
# include <linux/smp.h>
2009-12-11 12:07:53 +03:00
# include <linux/perf_event.h>
2005-04-17 02:20:36 +04:00
enum direction {
load , /* ld, ldd, ldh, ldsh */
store , /* st, std, sth, stsh */
both , /* Swap, ldstub, etc. */
fpload ,
fpstore ,
invalid ,
} ;
static inline enum direction decode_direction ( unsigned int insn )
{
unsigned long tmp = ( insn > > 21 ) & 1 ;
if ( ! tmp )
return load ;
else {
if ( ( ( insn > > 19 ) & 0x3f ) = = 15 )
return both ;
else
return store ;
}
}
/* 8 = double-word, 4 = word, 2 = half-word */
static inline int decode_access_size ( unsigned int insn )
{
insn = ( insn > > 19 ) & 3 ;
if ( ! insn )
return 4 ;
else if ( insn = = 3 )
return 8 ;
else if ( insn = = 2 )
return 2 ;
else {
printk ( " Impossible unaligned trap. insn=%08x \n " , insn ) ;
die_if_kernel ( " Byte sized unaligned access?!?! " , current - > thread . kregs ) ;
return 4 ; /* just to keep gcc happy. */
}
}
/* 0x400000 = signed, 0 = unsigned */
static inline int decode_signedness ( unsigned int insn )
{
return ( insn & 0x400000 ) ;
}
static inline void maybe_flush_windows ( unsigned int rs1 , unsigned int rs2 ,
unsigned int rd )
{
if ( rs2 > = 16 | | rs1 > = 16 | | rd > = 16 ) {
/* Wheee... */
__asm__ __volatile__ ( " save %sp, -0x40, %sp \n \t "
" save %sp, -0x40, %sp \n \t "
" save %sp, -0x40, %sp \n \t "
" save %sp, -0x40, %sp \n \t "
" save %sp, -0x40, %sp \n \t "
" save %sp, -0x40, %sp \n \t "
" save %sp, -0x40, %sp \n \t "
" restore; restore; restore; restore; \n \t "
" restore; restore; restore; \n \t " ) ;
}
}
static inline int sign_extend_imm13 ( int imm )
{
return imm < < 19 > > 19 ;
}
static inline unsigned long fetch_reg ( unsigned int reg , struct pt_regs * regs )
{
2009-01-03 06:32:59 +03:00
struct reg_window32 * win ;
2005-04-17 02:20:36 +04:00
if ( reg < 16 )
return ( ! reg ? 0 : regs - > u_regs [ reg ] ) ;
/* Ho hum, the slightly complicated case. */
2009-01-03 06:32:59 +03:00
win = ( struct reg_window32 * ) regs - > u_regs [ UREG_FP ] ;
2005-04-17 02:20:36 +04:00
return win - > locals [ reg - 16 ] ; /* yes, I know what this does... */
}
static inline unsigned long safe_fetch_reg ( unsigned int reg , struct pt_regs * regs )
{
2009-01-03 06:32:59 +03:00
struct reg_window32 __user * win ;
2005-04-17 02:20:36 +04:00
unsigned long ret ;
if ( reg < 16 )
return ( ! reg ? 0 : regs - > u_regs [ reg ] ) ;
/* Ho hum, the slightly complicated case. */
2009-01-03 06:32:59 +03:00
win = ( struct reg_window32 __user * ) regs - > u_regs [ UREG_FP ] ;
2005-04-17 02:20:36 +04:00
if ( ( unsigned long ) win & 3 )
return - 1 ;
if ( get_user ( ret , & win - > locals [ reg - 16 ] ) )
return - 1 ;
return ret ;
}
static inline unsigned long * fetch_reg_addr ( unsigned int reg , struct pt_regs * regs )
{
2009-01-03 06:32:59 +03:00
struct reg_window32 * win ;
2005-04-17 02:20:36 +04:00
if ( reg < 16 )
return & regs - > u_regs [ reg ] ;
2009-01-03 06:32:59 +03:00
win = ( struct reg_window32 * ) regs - > u_regs [ UREG_FP ] ;
2005-04-17 02:20:36 +04:00
return & win - > locals [ reg - 16 ] ;
}
static unsigned long compute_effective_address ( struct pt_regs * regs ,
unsigned int insn )
{
unsigned int rs1 = ( insn > > 14 ) & 0x1f ;
unsigned int rs2 = insn & 0x1f ;
unsigned int rd = ( insn > > 25 ) & 0x1f ;
if ( insn & 0x2000 ) {
maybe_flush_windows ( rs1 , 0 , rd ) ;
return ( fetch_reg ( rs1 , regs ) + sign_extend_imm13 ( insn ) ) ;
} else {
maybe_flush_windows ( rs1 , rs2 , rd ) ;
return ( fetch_reg ( rs1 , regs ) + fetch_reg ( rs2 , regs ) ) ;
}
}
unsigned long safe_compute_effective_address ( struct pt_regs * regs ,
unsigned int insn )
{
unsigned int rs1 = ( insn > > 14 ) & 0x1f ;
unsigned int rs2 = insn & 0x1f ;
unsigned int rd = ( insn > > 25 ) & 0x1f ;
if ( insn & 0x2000 ) {
maybe_flush_windows ( rs1 , 0 , rd ) ;
return ( safe_fetch_reg ( rs1 , regs ) + sign_extend_imm13 ( insn ) ) ;
} else {
maybe_flush_windows ( rs1 , rs2 , rd ) ;
return ( safe_fetch_reg ( rs1 , regs ) + safe_fetch_reg ( rs2 , regs ) ) ;
}
}
/* This is just to make gcc think panic does return... */
static void unaligned_panic ( char * str )
{
panic ( str ) ;
}
2008-03-04 02:01:05 +03:00
/* una_asm.S */
extern int do_int_load ( unsigned long * dest_reg , int size ,
unsigned long * saddr , int is_signed ) ;
extern int __do_int_store ( unsigned long * dst_addr , int size ,
unsigned long * src_val ) ;
static int do_int_store ( int reg_num , int size , unsigned long * dst_addr ,
struct pt_regs * regs )
{
unsigned long zero [ 2 ] = { 0 , 0 } ;
unsigned long * src_val ;
if ( reg_num )
src_val = fetch_reg_addr ( reg_num , regs ) ;
else {
src_val = & zero [ 0 ] ;
if ( size = = 8 )
zero [ 1 ] = fetch_reg ( 1 , regs ) ;
}
return __do_int_store ( dst_addr , size , src_val ) ;
}
2005-04-17 02:20:36 +04:00
extern void smp_capture ( void ) ;
extern void smp_release ( void ) ;
static inline void advance ( struct pt_regs * regs )
{
regs - > pc = regs - > npc ;
regs - > npc + = 4 ;
}
static inline int floating_point_load_or_store_p ( unsigned int insn )
{
return ( insn > > 24 ) & 1 ;
}
static inline int ok_for_kernel ( unsigned int insn )
{
return ! floating_point_load_or_store_p ( insn ) ;
}
2008-03-04 02:01:05 +03:00
static void kernel_mna_trap_fault ( struct pt_regs * regs , unsigned int insn )
2005-04-17 02:20:36 +04:00
{
unsigned long g2 = regs - > u_regs [ UREG_G2 ] ;
unsigned long fixup = search_extables_range ( regs - > pc , & g2 ) ;
if ( ! fixup ) {
unsigned long address = compute_effective_address ( regs , insn ) ;
if ( address < PAGE_SIZE ) {
printk ( KERN_ALERT " Unable to handle kernel NULL pointer dereference in mna handler " ) ;
} else
printk ( KERN_ALERT " Unable to handle kernel paging request in mna handler " ) ;
printk ( KERN_ALERT " at virtual address %08lx \n " , address ) ;
printk ( KERN_ALERT " current->{mm,active_mm}->context = %08lx \n " ,
( current - > mm ? current - > mm - > context :
current - > active_mm - > context ) ) ;
printk ( KERN_ALERT " current->{mm,active_mm}->pgd = %08lx \n " ,
( current - > mm ? ( unsigned long ) current - > mm - > pgd :
( unsigned long ) current - > active_mm - > pgd ) ) ;
die_if_kernel ( " Oops " , regs ) ;
/* Not reached */
}
regs - > pc = fixup ;
regs - > npc = regs - > pc + 4 ;
regs - > u_regs [ UREG_G2 ] = g2 ;
}
asmlinkage void kernel_unaligned_trap ( struct pt_regs * regs , unsigned int insn )
{
enum direction dir = decode_direction ( insn ) ;
int size = decode_access_size ( insn ) ;
if ( ! ok_for_kernel ( insn ) | | dir = = both ) {
printk ( " Unsupported unaligned load/store trap for kernel at <%08lx>. \n " ,
regs - > pc ) ;
unaligned_panic ( " Wheee. Kernel does fpu/atomic unaligned load/store. " ) ;
} else {
unsigned long addr = compute_effective_address ( regs , insn ) ;
2008-03-04 02:01:05 +03:00
int err ;
2005-04-17 02:20:36 +04:00
2011-06-27 16:41:57 +04:00
perf_sw_event ( PERF_COUNT_SW_ALIGNMENT_FAULTS , 1 , regs , addr ) ;
2008-03-04 02:01:05 +03:00
switch ( dir ) {
2005-04-17 02:20:36 +04:00
case load :
2008-03-04 02:01:05 +03:00
err = do_int_load ( fetch_reg_addr ( ( ( insn > > 25 ) & 0x1f ) ,
regs ) ,
size , ( unsigned long * ) addr ,
decode_signedness ( insn ) ) ;
2005-04-17 02:20:36 +04:00
break ;
case store :
2008-03-04 02:01:05 +03:00
err = do_int_store ( ( ( insn > > 25 ) & 0x1f ) , size ,
( unsigned long * ) addr , regs ) ;
2005-04-17 02:20:36 +04:00
break ;
default :
panic ( " Impossible kernel unaligned trap. " ) ;
/* Not reached... */
}
2008-03-04 02:01:05 +03:00
if ( err )
kernel_mna_trap_fault ( regs , insn ) ;
else
advance ( regs ) ;
2005-04-17 02:20:36 +04:00
}
}
static inline int ok_for_user ( struct pt_regs * regs , unsigned int insn ,
enum direction dir )
{
unsigned int reg ;
int check = ( dir = = load ) ? VERIFY_READ : VERIFY_WRITE ;
int size = ( ( insn > > 19 ) & 3 ) = = 3 ? 8 : 4 ;
if ( ( regs - > pc | regs - > npc ) & 3 )
return 0 ;
/* Must access_ok() in all the necessary places. */
# define WINREG_ADDR(regnum) \
( ( void __user * ) ( ( ( unsigned long * ) regs - > u_regs [ UREG_FP ] ) + ( regnum ) ) )
reg = ( insn > > 25 ) & 0x1f ;
if ( reg > = 16 ) {
if ( ! access_ok ( check , WINREG_ADDR ( reg - 16 ) , size ) )
return - EFAULT ;
}
reg = ( insn > > 14 ) & 0x1f ;
if ( reg > = 16 ) {
if ( ! access_ok ( check , WINREG_ADDR ( reg - 16 ) , size ) )
return - EFAULT ;
}
if ( ! ( insn & 0x2000 ) ) {
reg = ( insn & 0x1f ) ;
if ( reg > = 16 ) {
if ( ! access_ok ( check , WINREG_ADDR ( reg - 16 ) , size ) )
return - EFAULT ;
}
}
# undef WINREG_ADDR
return 0 ;
}
2008-03-04 02:01:05 +03:00
static void user_mna_trap_fault ( struct pt_regs * regs , unsigned int insn )
2005-04-17 02:20:36 +04:00
{
siginfo_t info ;
info . si_signo = SIGBUS ;
info . si_errno = 0 ;
info . si_code = BUS_ADRALN ;
info . si_addr = ( void __user * ) safe_compute_effective_address ( regs , insn ) ;
info . si_trapno = 0 ;
send_sig_info ( SIGBUS , & info , current ) ;
}
asmlinkage void user_unaligned_trap ( struct pt_regs * regs , unsigned int insn )
{
enum direction dir ;
if ( ! ( current - > thread . flags & SPARC_FLAG_UNALIGNED ) | |
( ( ( insn > > 30 ) & 3 ) ! = 3 ) )
goto kill_user ;
dir = decode_direction ( insn ) ;
if ( ! ok_for_user ( regs , insn , dir ) ) {
goto kill_user ;
} else {
2008-03-04 02:01:05 +03:00
int err , size = decode_access_size ( insn ) ;
2005-04-17 02:20:36 +04:00
unsigned long addr ;
if ( floating_point_load_or_store_p ( insn ) ) {
printk ( " User FPU load/store unaligned unsupported. \n " ) ;
goto kill_user ;
}
addr = compute_effective_address ( regs , insn ) ;
2011-06-27 16:41:57 +04:00
perf_sw_event ( PERF_COUNT_SW_ALIGNMENT_FAULTS , 1 , regs , addr ) ;
2005-04-17 02:20:36 +04:00
switch ( dir ) {
case load :
2008-03-04 02:01:05 +03:00
err = do_int_load ( fetch_reg_addr ( ( ( insn > > 25 ) & 0x1f ) ,
regs ) ,
size , ( unsigned long * ) addr ,
decode_signedness ( insn ) ) ;
2005-04-17 02:20:36 +04:00
break ;
case store :
2008-03-04 02:01:05 +03:00
err = do_int_store ( ( ( insn > > 25 ) & 0x1f ) , size ,
( unsigned long * ) addr , regs ) ;
2005-04-17 02:20:36 +04:00
break ;
case both :
/*
* This was supported in 2.4 . However , we question
* the value of SWAP instruction across word boundaries .
*/
printk ( " Unaligned SWAP unsupported. \n " ) ;
2008-03-04 02:01:05 +03:00
err = - EFAULT ;
2005-04-17 02:20:36 +04:00
break ;
default :
unaligned_panic ( " Impossible user unaligned trap. " ) ;
goto out ;
}
2008-03-04 02:01:05 +03:00
if ( err )
goto kill_user ;
else
advance ( regs ) ;
2005-04-17 02:20:36 +04:00
goto out ;
}
kill_user :
user_mna_trap_fault ( regs , insn ) ;
out :
2010-09-09 07:57:59 +04:00
;
2005-04-17 02:20:36 +04:00
}