/*************************************************************************** * * All modifications in this file to the original code are * (C) Copyright 1992, ..., 2004 the "DOSEMU-Development-Team". * * for details see file COPYING in the DOSEMU distribution * * * SIMX86 a Intel 80x86 cpu emulator * Copyright (C) 1997,2001 Alberto Vignani, FIAT Research Center * a.vignani@crf.it * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * Additional copyright notes: * * 1. The kernel-level vm86 handling was taken out of the Linux kernel * (linux/arch/i386/kernel/vm86.c). This code originaly was written by * Linus Torvalds with later enhancements by Lutz Molgedey and Hans Lermen. * * linux/kernel/ldt.c * Copyright (C) 1992 Krishna Balasubramanian and Linus Torvalds * ***************************************************************************/ #include "config.h" /* ======================================================================= */ /* * Some protected-mode stuff, not much changed from the 1999 version. * Interfaces with DPMI and its LDT format. * Will be heavily reworked later. */ #include #include #include #include "emu.h" #include "timers.h" #include "pic.h" #include "emu86.h" #include "cpu-emu.h" #include "codegen-arch.h" #include "protmode.h" Descriptor *GDT = NULL; Descriptor *LDT = NULL; Gatedesc *IDT = NULL; static unsigned short sysxfer[] = { DT_NO_XFER, DT_XFER_TSS16, DT_XFER_LDT, DT_XFER_TSS16, DT_XFER_CG16, DT_XFER_TSKG, DT_XFER_IG16, DT_XFER_TRP16, DT_NO_XFER, DT_XFER_TSS32, DT_NO_XFER, DT_XFER_TSS32, DT_XFER_CG32, DT_NO_XFER, DT_XFER_IG32, DT_XFER_TRP32 }; static char ofsnam[] = "??? GS: FS: ES: DS: ??? ??? ??? " "??? ??? ??? ??? ??? ??? ??? ??? " "CS: ??? ??? SS: ??? ??? ??? ??? "; static SDTR *ofsseg[] = { NULL, &GS_DTR,&FS_DTR,&ES_DTR,&DS_DTR,NULL,NULL,NULL, NULL, NULL, NULL, NULL, NULL, NULL,NULL,NULL, &CS_DTR,NULL, NULL, &SS_DTR,NULL, NULL,NULL,NULL }; #define MKOFSNAM(o,b) ((*((long *)(b))=*((long *)(ofsnam+(o)))), \ ((b)[3]=0), (b)) int SetSegReal(unsigned short sel, int ofs) { static char buf[4]; SDTR *sd; sd = ofsseg[(ofs>>2)]; CPUWORD(ofs) = sel; if ((sd->Oldsel==sel)&&(sd->Attrib==2)) { e_printf("SetSeg REAL %s%04x cached\n",MKOFSNAM(ofs,buf),sel); } else { sd->BoundL = (sel<<4); sd->BoundH = sd->BoundL + 0xffff; sd->Oldsel = sel; sd->Attrib = 2; e_printf("SetSeg REAL %s%04x\n",MKOFSNAM(ofs,buf),sel); } return 0; } int SetSegProt(int a16, int ofs, unsigned char *big, unsigned long sel) { static char buf[4]; unsigned short wFlags, sys; unsigned char lbig; Descriptor *dt; SDTR *sd; sd = ofsseg[(ofs>>2)]; if ((sd->Oldsel==sel)&&((sd->Attrib&3)==1)) { e_printf("SetSeg PROT %s%04lx cached\n",MKOFSNAM(ofs,buf),sel); if (big) *big = (sd->Attrib&4? 0xff:0); return 0; } sd->Oldsel = sel; sd->Attrib = 0; TheCPU.scp_err = sel & 0xfffc; if (sel < 4) { if ((ofs==Ofs_CS)||(ofs==Ofs_SS)) return EXCP0D_GPF; sd->BoundL = 0xc0000000; return 0; /* DS..GS can be 0 for some while */ } /* DT checks */ if (sel & 4) { dt = LDT; if ((dt == NULL) || (dt[sel>>3].S==0) || ((sel & 0xfff8) > TheCPU.LDTR.Limit)) { e_printf("Invalid LDT selector %#lx\n", sel); return EXCP0D_GPF; } } else { dt = GDT; /* GDT is not yet there */ if ((dt == NULL) || ((sel & 0xfff8) > TheCPU.GDTR.Limit)) { if (dt) e_printf("Invalid GDT selector %#lx\n", sel); return EXCP0D_GPF; } } /* should set CPL here if seg==CS ??? */ wFlags = GetSelectorFlags(sel); sys = (wFlags & DF_USER); if (!(wFlags & DF_PRESENT)) { e_printf("DT: selector %lx not present\n",sel); if (ofs==Ofs_SS) return EXCP0C_STACK; else return EXCP0B_NOSEG; } if (!sys) { /* must be GDT now */ unsigned short sx = sysxfer[wFlags&15]; if (debug_level('e')>3) e_printf("GDT system segment %#lx type %d\n",sel,sx); if (sx==DT_NO_XFER) return EXCP0D_GPF; sd->BoundH = 0; /* try to trap if not checked */ return 0; /* will check sys segment again later */ } lbig = (wFlags & DF_32)? 0xff : 0; /* check data/code */ if (ofs==Ofs_CS) { /* data can't be executed... really? */ if (!(wFlags & DF_CODE)) { dbug_printf("Attempt to execute into data segment %lx\n",sel); //return EXCP0D_GPF; } a16 = (lbig? 0:ADDR16); } else { /* we CAN move a code sel into [DEFG]S provided that it * can be read - but how can we trap writes? */ /* Error summary (Intel): * SS zero GP * RPL != CPL GP * DPL != CPL GP * data not writable GP * not present SS * [DEFG]S not data or readable code GP * data or nonconf code AND * RPL>DPL AND CPL>DPL GP * not present NP */ if ((wFlags & DF_CODE)&&(!(wFlags & DF_CREADABLE))) return EXCP0D_GPF; } if (lbig && a16) { if (debug_level('e')>3) e_printf("Large segment %#lx in 16-bit mode\n",sel); } else if (!lbig && !a16) { if (debug_level('e')>3) e_printf("Small segment %#lx in 32-bit mode\n",sel); } SetFlagAccessed(sel); sd->BoundL = (long)GetPhysicalAddress(sel); sd->BoundH = sd->BoundL + (long)GetSelectorByteLimit(sel); sd->Attrib = (lbig&4) | 1; e_printf("SetSeg PROT %s%04lx\n",MKOFSNAM(ofs,buf),sel); if (big) *big = lbig; if (debug_level('e')>2) { e_printf("PMSEL %#04lx bounds=%08lx:%08lx flg=%04x big=%d\n", sel, sd->BoundL, sd->BoundH, wFlags, lbig&1); } return 0; } #if 0 /* not (yet) used */ void ValidateAddr(unsigned char *addr, unsigned short sel) { unsigned char *base; unsigned short wFlags; if (!PROTMODE()) return; /* always ok */ wFlags = GetSelectorFlags(sel); if (wFlags & DF_PRESENT) { base = GetSelectorAddress(sel); if (addr >= base) { base += GetSelectorByteLimit(sel); if (addr <= base) return; dbug_printf("addr %#x > limit for sel %#x\n",(int)addr,sel); } else dbug_printf("addr %#x < base for sel %#x\n",(int)addr,sel); } else dbug_printf("selector %#x not present\n",sel); leavedos(0); } #endif /* flags with 0x409e mask: * 0090 16 RO data * 0092 16 RW data * 0094 16 RO data * 0096 16 RW data * 0098 16 FO NC code * 009a 16 RF NC code * 009c 16 FO C code * 009e 16 RF C code * 4090 32 RO data * 4092 32 RW data * 4094 32 RO data * 4096 32 RW data * 4098 32 FO NC code * 409a 32 RF NC code * 409c 32 FO C code * 409e 32 RF C code * flags with 0x009f mask: * x081 16 TSS * x082 LDT * x083 16 TSS * x084 16 call gate * x085 task gate * x086 16 int gate * x087 16 trap gate * x089 32 TSS * x08b 32 TSS * x08c 32 call gate * x08e 32 int gate * x08f 32 trap gate */ unsigned short GetSelectorXfer(unsigned short w) { unsigned short flags; if (!PROTMODE()) return DT_RM_XFER; flags=DTgetFlags(w); if (!(flags&DF_PRESENT)) return DT_NO_XFER; if (flags&DF_USER) { if (flags&DF_CODE) return (flags&DF_32? DT_XFER_CODE32:DT_XFER_CODE16); else return (flags&DF_32? DT_XFER_DATA32:DT_XFER_DATA16); } return sysxfer[flags&15]; } /* ======================================================================= */ int hsw_verr(unsigned short sel) { unsigned short wFlags; if (V86MODE()) return -1; /* maybe error */ /* test for present && CPL>=DPL && readable */ wFlags = GetSelectorFlags(sel); if (wFlags & DF_PRESENT) { if (!(wFlags & DF_CODE) || (wFlags & DF_CREADABLE)) return 1; } return 0; /* not ok, ZF->0 */ } int hsw_verw(unsigned short sel) { unsigned short wFlags; if (V86MODE()) return -1; /* maybe error */ /* test for present && CPL>=DPL && writeable */ wFlags = GetSelectorFlags(sel); if (wFlags & DF_PRESENT) { if (!(wFlags & DF_CODE) || (wFlags & DF_DWRITEABLE)) return 1; } return 0; /* not ok, ZF->0 */ } /* ======================================================================= */ static int emu_read_ldt(char *ptr, unsigned long bytecount) { uint32_t *lp = (uint32_t *)LDT; int i, size=0; for (i = 0; (i < LGDT_ENTRIES) && (size < bytecount); i++) { if (*lp) { D_printf("EMU86: read LDT entry %04x: %08x%08x\n",i, lp[1], lp[0]); } if (ptr) { __memcpy(ptr, lp, LGDT_ENTRY_SIZE); ptr += LGDT_ENTRY_SIZE; } size += LGDT_ENTRY_SIZE; lp+=2; } e_printf("EMU86: %d LDT entries read\n", i); return size; } static int emu_update_LDT (struct modify_ldt_ldt_s *ldt_info, int oldmode) { static char *xftab[] = { "D16","D32","C16","C32" }; Descriptor *lp; int bSelType; /* Install the new entry ... */ lp = &LDT[ldt_info->entry_number]; MKBASE(lp,(long)ldt_info->base_addr); MKLIMIT(lp,(long)ldt_info->limit); /* * LDT bits 40-43 (5th byte): * 3 2 1 0 * C/D E W A * 0 0 0 R/O data * 0 0 1 R/W data, expand-up stack * 0 1 0 R/O data * 0 1 1 R/W data, expand-down stack * C/D C R A * 1 0 0 F/O code, non-conforming * 1 0 1 F/R code, non-conforming * 1 1 0 F/O code, conforming * 1 1 1 F/R code, conforming * */ lp->type = ((ldt_info->read_exec_only ^ 1) << 1) | (ldt_info->contents << 2); lp->S = 1; /* not SYS */ lp->DPL = 3; lp->present = (ldt_info->seg_not_present ^ 1); lp->AVL = (oldmode? 0:ldt_info->useable); lp->DB = ldt_info->seg_32bit; lp->gran = ldt_info->limit_in_pages; bSelType = ((lp->type>>2)&2) | lp->DB; D_printf("EMU86: LDT entry %#x type %s: b=%08x l=%x%s fl=%04x\n", ldt_info->entry_number, xftab[bSelType], DT_BASE(lp), DT_LIMIT(lp), (lp->gran? "fff":""), DT_FLAGS(lp)); return 0; } static int emu_write_ldt(void *ptr, unsigned long bytecount, int oldmode) { int error; struct modify_ldt_ldt_s ldt_info; error = -EINVAL; if (bytecount != sizeof(ldt_info)) { dbug_printf("EMU86: write_ldt: bytecount=%ld\n",bytecount); goto out; } __memcpy(&ldt_info, ptr, sizeof(ldt_info)); error = -EINVAL; if (ldt_info.entry_number >= LGDT_ENTRIES) { dbug_printf("EMU86: write_ldt: entry=%d\n",ldt_info.entry_number); goto out; } if (ldt_info.contents == 3) { if (oldmode) { dbug_printf("EMU86: write_ldt: oldmode\n"); goto out; } if (ldt_info.seg_not_present == 0) { dbug_printf("EMU86: write_ldt: seg_not_present\n"); goto out; } } error = emu_update_LDT(&ldt_info, oldmode); out: return error; } int emu_modify_ldt(int func, void *ptr, unsigned long bytecount) { int ret = -ENOSYS; #if 1 { long *lptr = (long *)ptr; D_printf("EMU86: modify_ldt %02x %ld [%08lx %08lx %08lx %08lx]\n", func, bytecount, lptr[0], lptr[1], lptr[2], lptr[3] ); } #endif switch (func) { case 0: ret = emu_read_ldt((char *)ptr, bytecount); break; case 1: ret = emu_write_ldt(ptr, bytecount, 1); break; case 0x11: ret = emu_write_ldt(ptr, bytecount, 0); break; } return ret; } /* ======================================================================= */ int e_larlsl(int mode, unsigned short sel) { Descriptor *dt; DTR *dtr; int pl; dt = (sel & 4? LDT : GDT); dtr = (sel & 4? &(TheCPU.LDTR) : &(TheCPU.GDTR)); /* check DT and limits */ if (dt==NULL || ((sel&0xfff8) > dtr->Limit)) { return 0; } /* check system segments if in GDT */ if (dt==GDT && (GDT[sel>>3].S==0)) { /* "is a valid descriptor type" */ int styp=GDT[sel>>3].type; if (styp==0 || styp==6 || styp==7 || styp==8 || styp==10 || styp==13 || styp==14 || styp==15) return 0; } /* "if the selector is visible at the current privilege level * (modified by the selector's RPL)" */ pl = CPL; if ((sel&3)>pl) pl=(sel&3); if (pl > dt->DPL) { return 0; } /* Intel manual says nothing about present bit */ /* is all ok now? */ return 1; } /* ======================================================================= */ /* called from dpmi.c */ unsigned short emu_do_LAR (unsigned short selector) { unsigned short flg=0; if (LDT) { flg = DT_FLAGS(&LDT[selector>>3]) & 0xff; if (flg) LDT[selector>>3].type |= 1; } return flg; } /* called from dpmi.c */ void emu_mhp_SetTypebyte (unsigned short selector, int typebyte) { if (LDT) { #if 0 /* clean version */ Descriptor *lp = &LDT[selector>>3]; lp->type = typebyte & 15; lp->S = typebyte>>4; lp->DPL = (typebyte>>5)&3; lp->present = typebyte>>7; #else /* quick & dirty & little-endian too */ char *lp = (char *)(&LDT[selector>>3]); lp[5] = (char)typebyte; #endif } } /* ======================================================================= */