2005-04-16 15:20:36 -07:00
/*---------------------------------------------------------------------------+
| get_address . c |
| |
| Get the effective address from an FPU instruction . |
| |
| Copyright ( C ) 1992 , 1993 , 1994 , 1997 |
| W . Metzenthen , 22 Parker St , Ormond , Vic 3163 , |
| Australia . E - mail billm @ suburbia . net |
| |
| |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*---------------------------------------------------------------------------+
| Note : |
| The file contains code which accesses user memory . |
| Emulator static data may change when user memory is accessed , due to |
| other processes using the emulator while swapping is in progress . |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
# include <linux/stddef.h>
# include <asm/uaccess.h>
# include <asm/desc.h>
# include "fpu_system.h"
# include "exception.h"
# include "fpu_emu.h"
# define FPU_WRITE_BIT 0x10
static int reg_offset [ ] = {
offsetof ( struct info , ___eax ) ,
offsetof ( struct info , ___ecx ) ,
offsetof ( struct info , ___edx ) ,
offsetof ( struct info , ___ebx ) ,
offsetof ( struct info , ___esp ) ,
offsetof ( struct info , ___ebp ) ,
offsetof ( struct info , ___esi ) ,
offsetof ( struct info , ___edi )
} ;
# define REG_(x) (*(long *)(reg_offset[(x)]+(u_char *) FPU_info))
static int reg_offset_vm86 [ ] = {
offsetof ( struct info , ___cs ) ,
offsetof ( struct info , ___vm86_ds ) ,
offsetof ( struct info , ___vm86_es ) ,
offsetof ( struct info , ___vm86_fs ) ,
offsetof ( struct info , ___vm86_gs ) ,
offsetof ( struct info , ___ss ) ,
offsetof ( struct info , ___vm86_ds )
} ;
# define VM86_REG_(x) (*(unsigned short *) \
( reg_offset_vm86 [ ( ( unsigned ) x ) ] + ( u_char * ) FPU_info ) )
/* These are dummy, fs and gs are not saved on the stack. */
# define ___FS ___ds
# define ___GS ___ds
static int reg_offset_pm [ ] = {
offsetof ( struct info , ___cs ) ,
offsetof ( struct info , ___ds ) ,
offsetof ( struct info , ___es ) ,
offsetof ( struct info , ___FS ) ,
offsetof ( struct info , ___GS ) ,
offsetof ( struct info , ___ss ) ,
offsetof ( struct info , ___ds )
} ;
# define PM_REG_(x) (*(unsigned short *) \
( reg_offset_pm [ ( ( unsigned ) x ) ] + ( u_char * ) FPU_info ) )
/* Decode the SIB byte. This function assumes mod != 0 */
static int sib ( int mod , unsigned long * fpu_eip )
{
u_char ss , index , base ;
long offset ;
RE_ENTRANT_CHECK_OFF ;
FPU_code_access_ok ( 1 ) ;
FPU_get_user ( base , ( u_char __user * ) ( * fpu_eip ) ) ; /* The SIB byte */
RE_ENTRANT_CHECK_ON ;
( * fpu_eip ) + + ;
ss = base > > 6 ;
index = ( base > > 3 ) & 7 ;
base & = 7 ;
if ( ( mod = = 0 ) & & ( base = = 5 ) )
offset = 0 ; /* No base register */
else
offset = REG_ ( base ) ;
if ( index = = 4 )
{
/* No index register */
/* A non-zero ss is illegal */
if ( ss )
EXCEPTION ( EX_Invalid ) ;
}
else
{
offset + = ( REG_ ( index ) ) < < ss ;
}
if ( mod = = 1 )
{
/* 8 bit signed displacement */
long displacement ;
RE_ENTRANT_CHECK_OFF ;
FPU_code_access_ok ( 1 ) ;
FPU_get_user ( displacement , ( signed char __user * ) ( * fpu_eip ) ) ;
offset + = displacement ;
RE_ENTRANT_CHECK_ON ;
( * fpu_eip ) + + ;
}
else if ( mod = = 2 | | base = = 5 ) /* The second condition also has mod==0 */
{
/* 32 bit displacement */
long displacement ;
RE_ENTRANT_CHECK_OFF ;
FPU_code_access_ok ( 4 ) ;
FPU_get_user ( displacement , ( long __user * ) ( * fpu_eip ) ) ;
offset + = displacement ;
RE_ENTRANT_CHECK_ON ;
( * fpu_eip ) + = 4 ;
}
return offset ;
}
static unsigned long vm86_segment ( u_char segment ,
struct address * addr )
{
segment - - ;
# ifdef PARANOID
if ( segment > PREFIX_SS_ )
{
EXCEPTION ( EX_INTERNAL | 0x130 ) ;
math_abort ( FPU_info , SIGSEGV ) ;
}
# endif /* PARANOID */
addr - > selector = VM86_REG_ ( segment ) ;
return ( unsigned long ) VM86_REG_ ( segment ) < < 4 ;
}
/* This should work for 16 and 32 bit protected mode. */
static long pm_address ( u_char FPU_modrm , u_char segment ,
struct address * addr , long offset )
{
struct desc_struct descriptor ;
unsigned long base_address , limit , address , seg_top ;
segment - - ;
# ifdef PARANOID
/* segment is unsigned, so this also detects if segment was 0: */
if ( segment > PREFIX_SS_ )
{
EXCEPTION ( EX_INTERNAL | 0x132 ) ;
math_abort ( FPU_info , SIGSEGV ) ;
}
# endif /* PARANOID */
switch ( segment )
{
/* fs and gs aren't used by the kernel, so they still have their
user - space values . */
case PREFIX_FS_ - 1 :
2005-09-03 15:56:38 -07:00
/* N.B. - movl %seg, mem is a 2 byte write regardless of prefix */
savesegment ( fs , addr - > selector ) ;
2005-04-16 15:20:36 -07:00
break ;
case PREFIX_GS_ - 1 :
2005-09-03 15:56:38 -07:00
savesegment ( gs , addr - > selector ) ;
2005-04-16 15:20:36 -07:00
break ;
default :
addr - > selector = PM_REG_ ( segment ) ;
}
descriptor = LDT_DESCRIPTOR ( PM_REG_ ( segment ) ) ;
base_address = SEG_BASE_ADDR ( descriptor ) ;
address = base_address + offset ;
limit = base_address
+ ( SEG_LIMIT ( descriptor ) + 1 ) * SEG_GRANULARITY ( descriptor ) - 1 ;
if ( limit < base_address ) limit = 0xffffffff ;
if ( SEG_EXPAND_DOWN ( descriptor ) )
{
if ( SEG_G_BIT ( descriptor ) )
seg_top = 0xffffffff ;
else
{
seg_top = base_address + ( 1 < < 20 ) ;
if ( seg_top < base_address ) seg_top = 0xffffffff ;
}
access_limit =
( address < = limit ) | | ( address > = seg_top ) ? 0 :
( ( seg_top - address ) > = 255 ? 255 : seg_top - address ) ;
}
else
{
access_limit =
( address > limit ) | | ( address < base_address ) ? 0 :
( ( limit - address ) > = 254 ? 255 : limit - address + 1 ) ;
}
if ( SEG_EXECUTE_ONLY ( descriptor ) | |
( ! SEG_WRITE_PERM ( descriptor ) & & ( FPU_modrm & FPU_WRITE_BIT ) ) )
{
access_limit = 0 ;
}
return address ;
}
/*
MOD R / M byte : MOD = = 3 has a special use for the FPU
SIB byte used iff R / M = 100 b
7 6 5 4 3 2 1 0
. . . . . . . . . . . . . . . . . . . . . . .
MOD OPCODE ( 2 ) R / M
SIB byte
7 6 5 4 3 2 1 0
. . . . . . . . . . . . . . . . . . . . . . .
SS INDEX BASE
*/
void __user * FPU_get_address ( u_char FPU_modrm , unsigned long * fpu_eip ,
struct address * addr ,
fpu_addr_modes addr_modes )
{
u_char mod ;
unsigned rm = FPU_modrm & 7 ;
long * cpu_reg_ptr ;
int address = 0 ; /* Initialized just to stop compiler warnings. */
/* Memory accessed via the cs selector is write protected
in ` non - segmented ' 32 bit protected mode . */
if ( ! addr_modes . default_mode & & ( FPU_modrm & FPU_WRITE_BIT )
& & ( addr_modes . override . segment = = PREFIX_CS_ ) )
{
math_abort ( FPU_info , SIGSEGV ) ;
}
addr - > selector = FPU_DS ; /* Default, for 32 bit non-segmented mode. */
mod = ( FPU_modrm > > 6 ) & 3 ;
if ( rm = = 4 & & mod ! = 3 )
{
address = sib ( mod , fpu_eip ) ;
}
else
{
cpu_reg_ptr = & REG_ ( rm ) ;
switch ( mod )
{
case 0 :
if ( rm = = 5 )
{
/* Special case: disp32 */
RE_ENTRANT_CHECK_OFF ;
FPU_code_access_ok ( 4 ) ;
FPU_get_user ( address , ( unsigned long __user * ) ( * fpu_eip ) ) ;
( * fpu_eip ) + = 4 ;
RE_ENTRANT_CHECK_ON ;
addr - > offset = address ;
return ( void __user * ) address ;
}
else
{
address = * cpu_reg_ptr ; /* Just return the contents
of the cpu register */
addr - > offset = address ;
return ( void __user * ) address ;
}
case 1 :
/* 8 bit signed displacement */
RE_ENTRANT_CHECK_OFF ;
FPU_code_access_ok ( 1 ) ;
FPU_get_user ( address , ( signed char __user * ) ( * fpu_eip ) ) ;
RE_ENTRANT_CHECK_ON ;
( * fpu_eip ) + + ;
break ;
case 2 :
/* 32 bit displacement */
RE_ENTRANT_CHECK_OFF ;
FPU_code_access_ok ( 4 ) ;
FPU_get_user ( address , ( long __user * ) ( * fpu_eip ) ) ;
( * fpu_eip ) + = 4 ;
RE_ENTRANT_CHECK_ON ;
break ;
case 3 :
/* Not legal for the FPU */
EXCEPTION ( EX_Invalid ) ;
}
address + = * cpu_reg_ptr ;
}
addr - > offset = address ;
switch ( addr_modes . default_mode )
{
case 0 :
break ;
case VM86 :
address + = vm86_segment ( addr_modes . override . segment , addr ) ;
break ;
case PM16 :
case SEG32 :
address = pm_address ( FPU_modrm , addr_modes . override . segment ,
addr , address ) ;
break ;
default :
EXCEPTION ( EX_INTERNAL | 0x133 ) ;
}
return ( void __user * ) address ;
}
void __user * FPU_get_address_16 ( u_char FPU_modrm , unsigned long * fpu_eip ,
struct address * addr ,
fpu_addr_modes addr_modes )
{
u_char mod ;
unsigned rm = FPU_modrm & 7 ;
int address = 0 ; /* Default used for mod == 0 */
/* Memory accessed via the cs selector is write protected
in ` non - segmented ' 32 bit protected mode . */
if ( ! addr_modes . default_mode & & ( FPU_modrm & FPU_WRITE_BIT )
& & ( addr_modes . override . segment = = PREFIX_CS_ ) )
{
math_abort ( FPU_info , SIGSEGV ) ;
}
addr - > selector = FPU_DS ; /* Default, for 32 bit non-segmented mode. */
mod = ( FPU_modrm > > 6 ) & 3 ;
switch ( mod )
{
case 0 :
if ( rm = = 6 )
{
/* Special case: disp16 */
RE_ENTRANT_CHECK_OFF ;
FPU_code_access_ok ( 2 ) ;
FPU_get_user ( address , ( unsigned short __user * ) ( * fpu_eip ) ) ;
( * fpu_eip ) + = 2 ;
RE_ENTRANT_CHECK_ON ;
goto add_segment ;
}
break ;
case 1 :
/* 8 bit signed displacement */
RE_ENTRANT_CHECK_OFF ;
FPU_code_access_ok ( 1 ) ;
FPU_get_user ( address , ( signed char __user * ) ( * fpu_eip ) ) ;
RE_ENTRANT_CHECK_ON ;
( * fpu_eip ) + + ;
break ;
case 2 :
/* 16 bit displacement */
RE_ENTRANT_CHECK_OFF ;
FPU_code_access_ok ( 2 ) ;
FPU_get_user ( address , ( unsigned short __user * ) ( * fpu_eip ) ) ;
( * fpu_eip ) + = 2 ;
RE_ENTRANT_CHECK_ON ;
break ;
case 3 :
/* Not legal for the FPU */
EXCEPTION ( EX_Invalid ) ;
break ;
}
switch ( rm )
{
case 0 :
address + = FPU_info - > ___ebx + FPU_info - > ___esi ;
break ;
case 1 :
address + = FPU_info - > ___ebx + FPU_info - > ___edi ;
break ;
case 2 :
address + = FPU_info - > ___ebp + FPU_info - > ___esi ;
if ( addr_modes . override . segment = = PREFIX_DEFAULT )
addr_modes . override . segment = PREFIX_SS_ ;
break ;
case 3 :
address + = FPU_info - > ___ebp + FPU_info - > ___edi ;
if ( addr_modes . override . segment = = PREFIX_DEFAULT )
addr_modes . override . segment = PREFIX_SS_ ;
break ;
case 4 :
address + = FPU_info - > ___esi ;
break ;
case 5 :
address + = FPU_info - > ___edi ;
break ;
case 6 :
address + = FPU_info - > ___ebp ;
if ( addr_modes . override . segment = = PREFIX_DEFAULT )
addr_modes . override . segment = PREFIX_SS_ ;
break ;
case 7 :
address + = FPU_info - > ___ebx ;
break ;
}
add_segment :
address & = 0xffff ;
addr - > offset = address ;
switch ( addr_modes . default_mode )
{
case 0 :
break ;
case VM86 :
address + = vm86_segment ( addr_modes . override . segment , addr ) ;
break ;
case PM16 :
case SEG32 :
address = pm_address ( FPU_modrm , addr_modes . override . segment ,
addr , address ) ;
break ;
default :
EXCEPTION ( EX_INTERNAL | 0x131 ) ;
}
return ( void __user * ) address ;
}