|  | /*---------------------------------------------------------------------------+ | 
|  | |  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 <linux/uaccess.h> | 
|  | #include <asm/vm86.h> | 
|  |  | 
|  | #include "fpu_system.h" | 
|  | #include "exception.h" | 
|  | #include "fpu_emu.h" | 
|  |  | 
|  | #define FPU_WRITE_BIT 0x10 | 
|  |  | 
|  | static int reg_offset[] = { | 
|  | offsetof(struct pt_regs, ax), | 
|  | offsetof(struct pt_regs, cx), | 
|  | offsetof(struct pt_regs, dx), | 
|  | offsetof(struct pt_regs, bx), | 
|  | offsetof(struct pt_regs, sp), | 
|  | offsetof(struct pt_regs, bp), | 
|  | offsetof(struct pt_regs, si), | 
|  | offsetof(struct pt_regs, di) | 
|  | }; | 
|  |  | 
|  | #define REG_(x) (*(long *)(reg_offset[(x)] + (u_char *)FPU_info->regs)) | 
|  |  | 
|  | static int reg_offset_vm86[] = { | 
|  | offsetof(struct pt_regs, cs), | 
|  | offsetof(struct kernel_vm86_regs, ds), | 
|  | offsetof(struct kernel_vm86_regs, es), | 
|  | offsetof(struct kernel_vm86_regs, fs), | 
|  | offsetof(struct kernel_vm86_regs, gs), | 
|  | offsetof(struct pt_regs, ss), | 
|  | offsetof(struct kernel_vm86_regs, ds) | 
|  | }; | 
|  |  | 
|  | #define VM86_REG_(x) (*(unsigned short *) \ | 
|  | (reg_offset_vm86[((unsigned)x)] + (u_char *)FPU_info->regs)) | 
|  |  | 
|  | static int reg_offset_pm[] = { | 
|  | offsetof(struct pt_regs, cs), | 
|  | offsetof(struct pt_regs, ds), | 
|  | offsetof(struct pt_regs, es), | 
|  | offsetof(struct pt_regs, fs), | 
|  | offsetof(struct pt_regs, ds),	/* dummy, not saved on stack */ | 
|  | offsetof(struct pt_regs, ss), | 
|  | offsetof(struct pt_regs, ds) | 
|  | }; | 
|  |  | 
|  | #define PM_REG_(x) (*(unsigned short *) \ | 
|  | (reg_offset_pm[((unsigned)x)] + (u_char *)FPU_info->regs)) | 
|  |  | 
|  | /* 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) { | 
|  | case PREFIX_GS_ - 1: | 
|  | /* user gs handling can be lazy, use special accessors */ | 
|  | addr->selector = get_user_gs(FPU_info->regs); | 
|  | break; | 
|  | default: | 
|  | addr->selector = PM_REG_(segment); | 
|  | } | 
|  |  | 
|  | descriptor = FPU_get_ldt_descriptor(addr->selector); | 
|  | 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 = ®_(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->regs->bx + FPU_info->regs->si; | 
|  | break; | 
|  | case 1: | 
|  | address += FPU_info->regs->bx + FPU_info->regs->di; | 
|  | break; | 
|  | case 2: | 
|  | address += FPU_info->regs->bp + FPU_info->regs->si; | 
|  | if (addr_modes.override.segment == PREFIX_DEFAULT) | 
|  | addr_modes.override.segment = PREFIX_SS_; | 
|  | break; | 
|  | case 3: | 
|  | address += FPU_info->regs->bp + FPU_info->regs->di; | 
|  | if (addr_modes.override.segment == PREFIX_DEFAULT) | 
|  | addr_modes.override.segment = PREFIX_SS_; | 
|  | break; | 
|  | case 4: | 
|  | address += FPU_info->regs->si; | 
|  | break; | 
|  | case 5: | 
|  | address += FPU_info->regs->di; | 
|  | break; | 
|  | case 6: | 
|  | address += FPU_info->regs->bp; | 
|  | if (addr_modes.override.segment == PREFIX_DEFAULT) | 
|  | addr_modes.override.segment = PREFIX_SS_; | 
|  | break; | 
|  | case 7: | 
|  | address += FPU_info->regs->bx; | 
|  | 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; | 
|  | } |