linux/arch/i386/math-emu/get_address.c

439 lines
11 KiB
C
Raw Normal View History

/*---------------------------------------------------------------------------+
| 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))
/* This dummy, gs is not saved on the stack. */
#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 )
{
/* gs isn't used by the kernel, so it still has its
user-space value. */
case PREFIX_GS_-1:
/* N.B. - movl %seg, mem is a 2 byte write regardless of prefix */
savesegment(gs, addr->selector);
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 = 100b
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 ;
}