From 5e6604b760840d2404e688029b4781dac49423c5 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Fri, 31 Oct 2014 13:40:19 -0700 Subject: [PATCH] Implemented (but have not yet tested) 286 LOADALL --- my_modules/pcjs-client/lib/debugger.js | 223 ++++++++++-------- my_modules/pcjs-client/lib/x86.js | 18 +- my_modules/pcjs-client/lib/x86cpu.js | 51 +++- my_modules/pcjs-client/lib/x86help.js | 25 ++ my_modules/pcjs-client/lib/x86op0f.js | 98 ++++++-- my_modules/pcjs-client/lib/x86opxx.js | 6 +- my_modules/pcjs-client/lib/x86seg.js | 77 +++--- my_modules/shared/lib/externs.js | 6 +- .../reference/intel/80286/loadall/README.md | 4 +- 9 files changed, 335 insertions(+), 173 deletions(-) diff --git a/my_modules/pcjs-client/lib/debugger.js b/my_modules/pcjs-client/lib/debugger.js index 334629625..aae4c2ed8 100644 --- a/my_modules/pcjs-client/lib/debugger.js +++ b/my_modules/pcjs-client/lib/debugger.js @@ -2828,7 +2828,7 @@ if (DEBUGGER) { this.getFlagStr("S") + this.getFlagStr("Z") + this.getFlagStr("A") + this.getFlagStr("P") + this.getFlagStr("C"); if (fProt) { s += " MS=" + str.toHexWord(this.cpu.regMSW) + '\n' + - this.getDTRStr("LD", this.cpu.segLDT.sel, this.cpu.segLDT.base, this.cpu.segLDT.limit) + ' ' + + this.getDTRStr("LD", this.cpu.segLDT.sel, this.cpu.segLDT.base, this.cpu.segLDT.base + this.cpu.segLDT.limit) + ' ' + this.getDTRStr("GD", null, this.cpu.addrGDT, this.cpu.addrGDTLimit) + ' ' + this.getDTRStr("ID", null, this.cpu.addrIDT, this.cpu.addrIDTLimit) + " TR=" + str.toHexWord(this.cpu.segTSS.sel) + " A20=" + (this.bus.getA20()? "ON" : "OFF"); @@ -2948,7 +2948,7 @@ if (DEBUGGER) { break; /* * I used to alias "PC" to "IP", until I discovered that early (perhaps even ALL) versions of DEBUG.COM - * treat "PC" as an alias for the 16-bit flags register. TODO: Add support for "PC" as the flags register. + * treat "PC" as an alias for the 16-bit flags register. So for purposes of parseValue(), "PC" has been removed. */ case "IP": value = this.cpu.regIP; @@ -4082,105 +4082,130 @@ if (DEBUGGER) { } var w = parseInt(sValue, 16); if (!isNaN(w)) { - switch (sReg.toUpperCase()) { - case "AL": - this.cpu.regAX = (this.cpu.regAX & 0xff00) | (w & 0xff); - break; - case "AH": - this.cpu.regAX = (this.cpu.regAX & 0x00ff) | ((w << 8) & 0xff); - break; - case "AX": - this.cpu.regAX = (w & 0xffff); - break; - case "BL": - this.cpu.regBX = (this.cpu.regBX & 0xff00) | (w & 0xff); - break; - case "BH": - this.cpu.regBX = (this.cpu.regBX & 0x00ff) | ((w << 8) & 0xff); - break; - case "BX": - this.cpu.regBX = (w & 0xffff); - break; - case "CL": - this.cpu.regCX = (this.cpu.regCX & 0xff00) | (w & 0xff); - break; - case "CH": - this.cpu.regCX = (this.cpu.regCX & 0x00ff) | ((w << 8) & 0xff); - break; - case "CX": - this.cpu.regCX = (w & 0xffff); - break; - case "DL": - this.cpu.regDX = (this.cpu.regDX & 0xff00) | (w & 0xff); - break; - case "DH": - this.cpu.regDX = (this.cpu.regDX & 0x00ff) | ((w << 8) & 0xff); - break; - case "DX": - this.cpu.regDX = (w & 0xffff); - break; - case "SP": - this.cpu.regSP = (w & 0xffff); - break; - case "BP": - this.cpu.regBP = (w & 0xffff); - break; - case "SI": - this.cpu.regSI = (w & 0xffff); - break; - case "DI": - this.cpu.regDI = (w & 0xffff); - break; - case "DS": - this.cpu.setDS(w); - break; - case "ES": - this.cpu.setES(w); - break; - case "SS": - this.cpu.setSS(w); - break; - case "CS": - fIns = true; - this.cpu.setCS(w); - this.aAddrNextCode = this.newAddr(this.cpu.regIP, this.cpu.segCS.sel); - break; - /* - * I used to alias "PC" to "IP", until I discovered that early (perhaps ALL) versions of - * DEBUG.COM treat "PC" as an alias for the 16-bit flags register. TODO: Add support for "PC". - */ - case "IP": - fIns = true; - this.cpu.setIP(w); - this.aAddrNextCode = this.newAddr(this.cpu.regIP, this.cpu.segCS.sel); - break; - case "C": - if (w) this.cpu.setCF(); else this.cpu.clearCF(); - break; - case "P": - if (w) this.cpu.setPF(); else this.cpu.clearPF(); - break; - case "A": - if (w) this.cpu.setAF(); else this.cpu.clearAF(); - break; - case "Z": - if (w) this.cpu.setZF(); else this.cpu.clearZF(); - break; - case "S": - if (w) this.cpu.setSF(); else this.cpu.clearSF(); - break; - case "I": - if (w) this.cpu.setIF(); else this.cpu.clearIF(); - break; - case "D": - if (w) this.cpu.setDF(); else this.cpu.clearDF(); - break; - case "V": - if (w) this.cpu.setOF(); else this.cpu.clearOF(); - break; - default: + sReg = sReg.toUpperCase(); + switch (sReg) { + case "AL": + this.cpu.regAX = (this.cpu.regAX & 0xff00) | (w & 0xff); + break; + case "AH": + this.cpu.regAX = (this.cpu.regAX & 0x00ff) | ((w << 8) & 0xff); + break; + case "AX": + this.cpu.regAX = (w & 0xffff); + break; + case "BL": + this.cpu.regBX = (this.cpu.regBX & 0xff00) | (w & 0xff); + break; + case "BH": + this.cpu.regBX = (this.cpu.regBX & 0x00ff) | ((w << 8) & 0xff); + break; + case "BX": + this.cpu.regBX = (w & 0xffff); + break; + case "CL": + this.cpu.regCX = (this.cpu.regCX & 0xff00) | (w & 0xff); + break; + case "CH": + this.cpu.regCX = (this.cpu.regCX & 0x00ff) | ((w << 8) & 0xff); + break; + case "CX": + this.cpu.regCX = (w & 0xffff); + break; + case "DL": + this.cpu.regDX = (this.cpu.regDX & 0xff00) | (w & 0xff); + break; + case "DH": + this.cpu.regDX = (this.cpu.regDX & 0x00ff) | ((w << 8) & 0xff); + break; + case "DX": + this.cpu.regDX = (w & 0xffff); + break; + case "SP": + this.cpu.regSP = (w & 0xffff); + break; + case "BP": + this.cpu.regBP = (w & 0xffff); + break; + case "SI": + this.cpu.regSI = (w & 0xffff); + break; + case "DI": + this.cpu.regDI = (w & 0xffff); + break; + case "DS": + this.cpu.setDS(w); + break; + case "ES": + this.cpu.setES(w); + break; + case "SS": + this.cpu.setSS(w); + break; + case "CS": + fIns = true; + this.cpu.setCS(w); + this.aAddrNextCode = this.newAddr(this.cpu.regIP, this.cpu.segCS.sel); + break; + case "IP": + fIns = true; + this.cpu.setIP(w); + this.aAddrNextCode = this.newAddr(this.cpu.regIP, this.cpu.segCS.sel); + break; + /* + * I used to alias "PC" to "IP", until I discovered that early (perhaps ALL) versions of + * DEBUG.COM treat "PC" as an alias for the 16-bit flags register. + */ + case "PC": + this.cpu.setPS(w); + break; + case "C": + if (w) this.cpu.setCF(); else this.cpu.clearCF(); + break; + case "P": + if (w) this.cpu.setPF(); else this.cpu.clearPF(); + break; + case "A": + if (w) this.cpu.setAF(); else this.cpu.clearAF(); + break; + case "Z": + if (w) this.cpu.setZF(); else this.cpu.clearZF(); + break; + case "S": + if (w) this.cpu.setSF(); else this.cpu.clearSF(); + break; + case "I": + if (w) this.cpu.setIF(); else this.cpu.clearIF(); + break; + case "D": + if (w) this.cpu.setDF(); else this.cpu.clearDF(); + break; + case "V": + if (w) this.cpu.setOF(); else this.cpu.clearOF(); + break; + default: + var fUnknown = true; + if (this.cpu.model >= X86.MODEL_80286) { + fUnknown = false; + switch(sReg){ + case "MS": + X86Help.opHelpLMSW(w); + break; + case "TR": + this.cpu.segTSS.load(w); + break; + /* + * TODO: Add support for GDTR (addr and limit), IDTR (addr and limit), and perhaps + * even the ability to edit descriptor information associated with each segment register. + */ + default: + fUnknown = true; + break; + } + } + if (fUnknown) { this.println("unknown register: " + sReg); return; + } } } else { diff --git a/my_modules/pcjs-client/lib/x86.js b/my_modules/pcjs-client/lib/x86.js index aec459c95..0c7c5dbd5 100644 --- a/my_modules/pcjs-client/lib/x86.js +++ b/my_modules/pcjs-client/lib/x86.js @@ -57,7 +57,10 @@ var X86 = { IF: 0x0200, // bit 9: Interrupt flag DF: 0x0400, // bit 10: Direction flag OF: 0x0800, // bit 11: Overflow flag - IOPL: 0x3000, // 12-13: I/O Privilege Level, always set on 8086/80186, clear on 80286 + IOPL: { + MASK: 0x3000, // 12-13: I/O Privilege Level (always set on 8086/80186, clear on 80286) + SHIFT: 12 + }, NT: 0x4000, // bit 14: Nested Task flag, always set on 8086/80186, clear on 80286 BIT15: 0x8000 // bit 15: reserved, always set on 8086/80186, clear otherwise }, @@ -200,7 +203,7 @@ var X86 = { NOINTR: 0x0004, // indicates a segreg has been set, or a prefix, or an STI (delay INTR acknowledgement) SEG: 0x0010, LOCK: 0x0020, - REPZ: 0x0040, // repeat while Z (NOTE: this value MUST match PS_ZF; see opCMPSb/opCMPSw/opSCASb/opSCASw) + REPZ: 0x0040, // repeat while Z (NOTE: this value MUST match PS.ZF; see opCMPSb/opCMPSw/opSCASb/opSCASw) REPNZ: 0x0080, // repeat while NZ REPEAT: 0x0100, // this indicates that an instruction is being repeated (ie, some iteration AFTER the first) PUSHSP: 0x0200 // the SP register is potentially being referenced by a PUSH SP opcode, adjustment may be required @@ -279,10 +282,13 @@ X86.PS.DIRECT = (X86.PS.TF | X86.PS.IF | X86.PS.DF); X86.PS.INDIRECT = (X86.PS.CF | X86.PS.PF | X86.PS.AF | X86.PS.ZF | X86.PS.SF | X86.PS.OF); /* - * NOTE: This is the default for 8086/8088; other processors must tweak these bits before - * calling setPS(). TODO: Verify that PS_1 was always set on reset, even on the 8086/8088. + * NOTE: These are the default "always set" PS bits for the 8086/8088; other processors must + * adjust these bits accordingly. The final adjusted value is then stored in the X86CPU object + * as "this.PS_SET"; setPS() must use that value, NOT this one. + * + * TODO: Verify that PS.BIT1 was always set on reset, even on the 8086/8088. */ -X86.PS.SET = (X86.PS.BIT1 | X86.PS.IOPL | X86.PS.NT | X86.PS.BIT15); +X86.PS.SET = (X86.PS.BIT1 | X86.PS.IOPL.MASK | X86.PS.NT | X86.PS.BIT15); /* * getPS() brings all the direct and indirect flags together, and setPS() performs the @@ -295,7 +301,7 @@ X86.PS.SET = (X86.PS.BIT1 | X86.PS.IOPL | X86.PS.NT | X86.PS.BIT15); * this.resultParitySign * this.resultAuxOverflow * - * PS_SAHF is a subset of the arithmetic flags, and refers only to those flags that the + * PS.SAHF is a subset of the arithmetic flags, and refers only to those flags that the * SAHF and LAHF "8080 legacy" opcodes affect. */ X86.PS.SAHF = (X86.PS.CF | X86.PS.PF | X86.PS.AF | X86.PS.ZF | X86.PS.SF); diff --git a/my_modules/pcjs-client/lib/x86cpu.js b/my_modules/pcjs-client/lib/x86cpu.js index fe6f783c7..d345eb10f 100644 --- a/my_modules/pcjs-client/lib/x86cpu.js +++ b/my_modules/pcjs-client/lib/x86cpu.js @@ -671,7 +671,7 @@ X86CPU.prototype.initProcessor = function() { this.PS_SET = X86.PS.SET; this.OPFLAG_NOINTR8086 = X86.OPFLAG.NOINTR; - this.nShiftCountMask = 0xff; // on an 8086/8088, there effectively is NO mask + this.nShiftCountMask = 0xff; // on an 8086/8088, all shift counts are used as-is this.CYCLES = (this.model >= X86.MODEL_80286? X86CPU.CYCLES_80286 : X86CPU.CYCLES_8088); @@ -715,7 +715,7 @@ X86CPU.prototype.initProcessor = function() this.OPFLAG_NOINTR8086 = 0; // used with instructions that should *not* set NOINTR on an 80286 (eg, non-SS segment loads) this.aOps[0x0F] = X86OpXX.op0F; this.aOps[X86.OPCODE.ARPL] = X86OpXX.opARPL; - this.aOps[X86.OPCODE.PUSHSP]= X86OpXX.op286PUSHSP; + this.aOps[X86.OPCODE.PUSHSP]= X86OpXX.opPUSHSP; } } }; @@ -760,6 +760,9 @@ X86CPU.prototype.reset = function() * which takes both an offset and a segment, or setIP(), whichever is appropriate; in unusual cases where only * segCS is changing (eg, undocumented 8086 opcodes), use setCS(). * + * On the 80286, another "register" that mirrors segCS is nCPL: whenever CS is updated, nCPL is updated + * with the CS selector's access level. + * * The other segment registers (DS, SS and ES) have similar setters (for segDS, segSS and segES), but those * functions do not mirror any special segment:offset values in the same way that regEIP mirrors CS:IP. * @@ -784,6 +787,7 @@ X86CPU.prototype.resetRegs = function() this.regMSW = X86.MSW.SET; this.addrIDT = 0; this.addrIDTLimit = 0x03FF; this.descIDT = {off: 0, sel: 0, acc: 0, maskPS: -1}; + this.nIOPL = 0; // this should be set before the first setPS() call /* * Segment registers used to be defined as separate variables (eg, regCS and regCS0 stored the @@ -795,7 +799,7 @@ X86CPU.prototype.resetRegs = function() this.segSS = new X86Seg(this, "SS"); this.segES = new X86Seg(this, "ES"); this.segZERO = new X86Seg(this, "ZERO"); - this.setCSIP(0, 0xFFFF); + this.setCSIP(0, 0xFFFF); // this should be called before the first setPS() call /* * Assorted 80286-specific registers. The GDTR and IDTR registers are stored as the following pieces: @@ -822,7 +826,8 @@ X86CPU.prototype.resetRegs = function() } /* - * This resets the Processor Status flags (regPS), along with all the internal "result registers". + * This resets the Processor Status flags (regPS), along with all the internal "result registers"; + * we've taken care to ensure that both nCPL and nIOPL are initialized before this first setPS() call. */ this.setPS(0); @@ -1019,7 +1024,7 @@ X86CPU.prototype.setProtMode = function(fProt) X86CPU.prototype.saveProtMode = function() { if (this.addrGDT != null) { - return [this.regMSW, this.addrGDT, this.addrGDTLimit, this.addrIDT, this.addrIDTLimit, this.segLDT.save(), this.segTSS.save()]; + return [this.regMSW, this.addrGDT, this.addrGDTLimit, this.addrIDT, this.addrIDTLimit, this.segLDT.save(), this.segTSS.save(), this.nIOPL]; } return null; }; @@ -1042,6 +1047,8 @@ X86CPU.prototype.restoreProtMode = function(a) this.addrIDTLimit = a[4]; this.segLDT.restore(a[5]); this.segTSS.restore(a[6]); + this.nIOPL = a[7]; + this.nCPL = this.segCS.level; this.setProtMode(); } }; @@ -1059,8 +1066,8 @@ X86CPU.prototype.restoreProtMode = function(a) X86CPU.prototype.save = function() { var state = new State(this); - state.set(0, [this.regAX, this.regBX, this.regCX, this.regDX, this.regSP, this.regBP, this.regSI, this.regDI]); - state.set(1, [this.regIP, this.segCS.save(), this.segDS.save(), this.segSS.save(), this.segES.save(), this.getPS(), this.saveProtMode()]); + state.set(0, [this.regAX, this.regBX, this.regCX, this.regDX, this.regSP, this.regBP, this.regSI, this.regDI, this.nIOPL]); + state.set(1, [this.regIP, this.segCS.save(), this.segDS.save(), this.segSS.save(), this.segES.save(), this.saveProtMode(), this.getPS()]); state.set(2, [this.segData.sName, this.segStack.sName, this.opFlags, this.opPrefixes, this.intFlags, this.regEA, this.regEAWrite]); state.set(3, [this.nBurstDivisor, this.nTotalCycles, this.getSpeed()]); state.set(4, this.bus.saveMemory()); @@ -1088,13 +1095,14 @@ X86CPU.prototype.restore = function(data) this.regBP = a[5]; this.regSI = a[6]; this.regDI = a[7]; + this.nIOPL = a[8] || 0; a = data[1]; this.segCS.restore(a[1]); this.segDS.restore(a[2]); this.segSS.restore(a[3]); this.segES.restore(a[4]); - this.setPS(a[5]); - this.restoreProtMode(a[6]); + this.restoreProtMode(a[5]); + this.setPS(a[6]); this.setIP(a[0]); a = data[2]; this.segData = this.getSeg(a[0]); @@ -1240,6 +1248,7 @@ X86CPU.prototype.loadIDTEntry = function(nIDT) X86CPU.prototype.setCS = function(sel) { this.regEIP = this.segCS.load(sel) + this.regIP; + this.nCPL = this.segCS.level; this.opFlags |= this.OPFLAG_NOINTR8086; if (PREFETCH) this.flushPrefetch(this.regEIP); }; @@ -1308,9 +1317,8 @@ X86CPU.prototype.setIP = function(off) * NOTE: Unlike setIP(), which is often passed a computation, the offsets passed to setCSIP() are strictly * 16-bit values, so there's never any need to mask them with 0xffff (although it doesn't hurt to assert that). * - * As an aside, this function is called setCSIP() instead of setCSIP() to reflect the order of the parameters - * (IP value first, CS value second), which matches the order that CS:IP values are normally stored in memory, - * allowing us to make calls like this: + * And even though this function is called setCSIP(), please note the order of the parameters is IP,CS, + * which matches the order that CS:IP values are normally stored in memory, allowing us to make calls like: * * this.setCSIP(this.popWord(), this.popWord()); * @@ -1322,6 +1330,7 @@ X86CPU.prototype.setCSIP = function(off, sel) { Component.assert((off & 0xffff) == off); this.regEIP = this.segCS.load(sel) + (this.regIP = off); + this.nCPL = this.segCS.level; if (PREFETCH) this.flushPrefetch(this.regEIP); }; @@ -1623,14 +1632,30 @@ X86CPU.prototype.getPS = function() */ X86CPU.prototype.setPS = function(regPS) { - this.resultSize = X86.RESULT.SIZE_BYTE; // NOTE: We could have chosen SIZE_WORD, too; the choice here seems irrelevant + this.resultSize = X86.RESULT.SIZE_BYTE; // NOTE: We could have chosen SIZE_WORD, too; it's irrelevant this.resultValue = this.resultParitySign = this.resultAuxOverflow = 0; + if (regPS & X86.PS.CF) this.setCF(); if (!(regPS & X86.PS.PF)) this.resultParitySign |= 0x1; if (regPS & X86.PS.AF) this.resultAuxOverflow |= X86.RESULT.AUXOVF_AF; if (!(regPS & X86.PS.ZF)) this.clearZF(); if (regPS & X86.PS.SF) this.setSF(); if (regPS & X86.PS.OF) this.setOF(); + + /* + * Since PS.IOPL and PS.IF are part of PS.DIRECT, we need to take care of any 80286-specific checks before + * setting the PS.DIRECT bits. Specifically, PS.IOPL is unchanged if CPL > 0, and PS.IF is unchanged if CPL > IOPL. + */ + if (!this.nCPL) { + this.nIOPL = (regPS & X86.PS.IOPL.MASK) >> X86.PS.IOPL.SHIFT; // IOPL allowed to change + if (this.nIOPL && !(this.regMSW & X86.MSW.PE)) this.nIOPL = 0; // effective IOPL remains 0 + } else { + regPS = (regPS & ~X86.PS.IOPL.MASK) | (this.regPS & X86.PS.IOPL.MASK); // IOPL not allowed to change + } + if (this.nCPL > this.nIOPL) { + regPS = (regPS & ~X86.PS.IF) | (this.regPS & X86.PS.IF); // IF not allowed to change + } + this.regPS = (this.regPS & ~X86.PS.DIRECT) | (regPS & X86.PS.DIRECT) | this.PS_SET; /* diff --git a/my_modules/pcjs-client/lib/x86help.js b/my_modules/pcjs-client/lib/x86help.js index fffa3853b..541a49b0f 100644 --- a/my_modules/pcjs-client/lib/x86help.js +++ b/my_modules/pcjs-client/lib/x86help.js @@ -426,6 +426,31 @@ var X86Help = { * TODO: Now what? */ }, + /** + * opHelpLMSW(w) + * + * Factored out of x86op0f.js, since both opLMSW and opLOADALL are capable of loading a new MSW. + * The caller is responsible for assessing the appropriate cycle cost. + * + * @this {X86CPU} + * @param {number} w + */ + opHelpLMSW: function(w) { + /* + * This instruction is always allowed to set MSW.PE, but it cannot clear MSW.PE once set; + * therefore, we always OR the previous value of MSW.PE into the new value before loading. + */ + w |= (this.regMSW & X86.MSW.PE); + this.regMSW = (this.regMSW & X86.MSW.SET) | (w & ~X86.MSW.SET); + /* + * Since the 80286 cannot return to real-mode via this instruction, the only transition we + * must worry about is to protected-mode. And don't worry, there's no harm calling setProtMode() + * if the CPU is already in protected-mode (we could certainly optimize the call out in that + * case, but this instruction isn't used frequently enough to warrant it). + */ + if (this.regMSW & X86.MSW.PE) this.setProtMode(true); + + }, /** * @this {X86CPU} */ diff --git a/my_modules/pcjs-client/lib/x86op0f.js b/my_modules/pcjs-client/lib/x86op0f.js index dcf41ea8c..380d6b2d5 100644 --- a/my_modules/pcjs-client/lib/x86op0f.js +++ b/my_modules/pcjs-client/lib/x86op0f.js @@ -82,6 +82,79 @@ var X86Op0F = { opLSL: function() { X86Mods.aOpModsRegWord[this.getIPByte()].call(this, X86Help.opHelpLSL); }, + /** + * opLOADALL() + * + * From the "Undocumented iAPX 286 Test Instruction" document at http://www.pcjs.org/pubs/pc/reference/intel/80286/loadall/: + * + * Physical Address (Hex) Associated CPU Register + * 800-805 None + * 806-807 MSW + * 808-815 None + * 816-817 TR + * 818-819 Flag word + * 81A-81B IP + * 81C-81D LDT + * 81E-81F DS + * 820-821 SS + * 822-823 CS + * 824-825 ES + * 826-827 DI + * 828-829 SI + * 82A-82B BP + * 82C-82D SP + * 82E-82F BX + * 830-831 DX + * 832-833 CX + * 834-835 AX + * 836-83B ES descriptor cache + * 83C-841 CS descriptor cache + * 842-847 SS descriptor cache + * 848-84D DS descriptor cache + * 84E-853 GDTR + * 854-859 LDT descriptor cache + * 85A-85F IDTR + * 860-865 TSS descriptor cache + * + * Oddly, the above document gives two contradictory cycle counts for LOADALL: 190 and 195. I'll go with 195, for + * no particular reason. + * + * @this {X86CPU} + * + * op=0x0F,0x05 (loadall) + */ + opLOADALL: function() { + X86Help.opHelpLMSW.call(this, this.getWord(0x806)); + this.setPS(this.getWord(0x818)); + this.regDI = this.getWord(0x826); + this.regSI = this.getWord(0x828); + this.regBP = this.getWord(0x82A); + this.regSP = this.getWord(0x82C); + this.regBX = this.getWord(0x82E); + this.regDX = this.getWord(0x830); + this.regCX = this.getWord(0x832); + this.regAX = this.getWord(0x834); + /* + * loadDesc() is an X86Seg class method that we must use to force the specified descriptor to be loaded; + * since the processor might still be in real-mode, we can't use normal segment register instance methods. + */ + X86Seg.loadDesc.call(this.segES, this.getWord(0x824), 0x836); + X86Seg.loadDesc.call(this.segCS, this.getWord(0x822), 0x83C); + X86Seg.loadDesc.call(this.segSS, this.getWord(0x820), 0x842); + X86Seg.loadDesc.call(this.segDS, this.getWord(0x81E), 0x848); + this.nCPL = this.segCS.level; + this.setIP(this.getWord(0x81A)); + /* + * TODO: The bytes at 0x851 and 0x85D "should be zeroes", but do we rely on that, or should we load zeroes ourselves? + */ + this.addrGDT = this.getWord(0x84E) | (this.getWord(0x850) << 16); + this.addrGDTLimit = this.addrGDT + this.getWord(0x852); + this.segLDT.loadDesc(this.getWord(0x81C), 0x854); + this.addrIDT = this.getWord(0x85A) | (this.getWord(0x85C) << 16); + this.addrIDTLimit = this.addrIDT + this.getWord(0x85E); + this.segTSS.loadDesc(this.getWord(0x816), 0x860); + this.nStepCycles -= 195; + }, /** * @this {X86CPU} * @param {number} dst @@ -244,8 +317,9 @@ var X86Op0F = { * 145E:4BC3 CB RETF * * This code is expecting SGDT on an 80286 to set the 6th "undefined" byte to 0xFF. So we use setWord() - * instead of setByte() and force the upper byte to 0xFF. TODO: Remove the 0xFF00 below on post-80286 - * processors; also, this behavior may be unique to real-mode. + * instead of setByte() and force the upper byte to 0xFF. + * + * TODO: Remove the 0xFF00 below on post-80286 processors; also, determine whether this behavior is unique to real-mode. */ this.setWord(this.regEA + 4, 0xFF00 | (this.addrGDT >> 16)); this.nStepCycles -= 11; @@ -270,8 +344,9 @@ var X86Op0F = { this.setWord(this.regEA + 2, this.addrIDT); /* * As with SGDT, the 6th byte is technically "undefined" on an 80286, but we now set it to 0xFF, for the - * same reasons discussed in SGDT (above). TODO: Remove the 0xFF00 below on post-80286 processors; also, - * this behavior may be unique to real-mode. + * same reasons discussed in SGDT (above). + * + * TODO: Remove the 0xFF00 below on post-80286 processors; also, determine whether this behavior is unique to real-mode. */ this.setWord(this.regEA + 4, 0xFF00 | (this.addrIDT >> 16)); this.nStepCycles -= 12; @@ -341,17 +416,8 @@ var X86Op0F = { * @return {number} */ opLMSW: function(dst, src) { - this.regMSW = (this.regMSW & X86.MSW.SET) | (dst & ~X86.MSW.SET); - this.nStepCycles -= (3 + (this.regEA < 0? 0 : 3)); - /* - * Since the 80286 did not allow you to disable protected-mode (ie, return to real-mode) by - * CLEARING the X86.MSW.PE bit, we need only check for the bit being SET. And the only functions - * that call setProtMode() are resetRegs() and this function, so there's no danger of the mode - * getting out of sync with the X86.MSW.PE bit. - */ - if (this.regMSW & X86.MSW.PE) { - this.setProtMode(true); - } + X86Help.opHelpLMSW.call(this, dst); + this.nStepCycles -= (this.regEA < 0? 3 : 6); if (FASTDISABLE) this.setEAWord = this.setEAWordDisabled; else this.opFlags |= X86.OPFLAG.NOWRITE; return dst; } @@ -359,7 +425,7 @@ var X86Op0F = { X86Op0F.aOps0F = [ X86Op0F.opGRP6, X86Op0F.opGRP7, X86Op0F.opLAR, X86Op0F.opLSL, // 0x00-0x03 - X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, X86Help.opUndefined, // 0x04-0x07 + X86Help.opUndefined, X86Op0F.opLOADALL, X86Help.opUndefined, X86Help.opUndefined, // 0x04-0x07 /* * On all processors (except the 8086/8088, of course), 0x0F,0x0B is also referred to as "UD2": an * instruction guaranteed to raise a #UD (Invalid Opcode) exception (INT 0x06) on all future x86 processors. diff --git a/my_modules/pcjs-client/lib/x86opxx.js b/my_modules/pcjs-client/lib/x86opxx.js index 7dcdae85d..95c65436d 100644 --- a/my_modules/pcjs-client/lib/x86opxx.js +++ b/my_modules/pcjs-client/lib/x86opxx.js @@ -985,7 +985,7 @@ var X86OpXX = { * * op=0x54 (push SP) */ - opPUSHSP: function() { + opPUSHSP8086: function() { var w = (this.regSP - 2) & 0xffff; this.pushWord(w); this.nStepCycles -= this.CYCLES.nOpCyclesPushReg; @@ -995,7 +995,7 @@ var X86OpXX = { * * op=0x54 (push SP) */ - op286PUSHSP: function() { + opPUSHSP: function() { this.pushWord(this.regSP); this.nStepCycles -= this.CYCLES.nOpCyclesPushReg; }, @@ -3385,7 +3385,7 @@ X86OpXX.aOps = [ X86OpXX.opDECAX, X86OpXX.opDECCX, X86OpXX.opDECDX, X86OpXX.opDECBX, // 0x48-0x4B X86OpXX.opDECSP, X86OpXX.opDECBP, X86OpXX.opDECSI, X86OpXX.opDECDI, // 0x4C-0x4F X86OpXX.opPUSHAX, X86OpXX.opPUSHCX, X86OpXX.opPUSHDX, X86OpXX.opPUSHBX, // 0x50-0x53 - X86OpXX.opPUSHSP, X86OpXX.opPUSHBP, X86OpXX.opPUSHSI, X86OpXX.opPUSHDI, // 0x54-0x57 + X86OpXX.opPUSHSP8086, X86OpXX.opPUSHBP, X86OpXX.opPUSHSI, X86OpXX.opPUSHDI, // 0x54-0x57 X86OpXX.opPOPAX, X86OpXX.opPOPCX, X86OpXX.opPOPDX, X86OpXX.opPOPBX, // 0x58-0x5B X86OpXX.opPOPSP, X86OpXX.opPOPBP, X86OpXX.opPOPSI, X86OpXX.opPOPDI, // 0x5C-0x5F /* diff --git a/my_modules/pcjs-client/lib/x86seg.js b/my_modules/pcjs-client/lib/x86seg.js index 42a1744ba..188acd824 100644 --- a/my_modules/pcjs-client/lib/x86seg.js +++ b/my_modules/pcjs-client/lib/x86seg.js @@ -75,6 +75,7 @@ X86Seg.loadReal = function loadReal(sel, fSuppress) { this.sel = sel; this.limit = 0xffff; // TODO: Consider NOT setting the limit field in real-mode (unless it's required for, say, LOADALL support?) + this.level = 0; return this.base = sel << 4; }; @@ -110,51 +111,64 @@ X86Seg.loadProt = function loadProt(sel, fSuppress) addrDT = this.cpu.segLDT.base; addrDTLimit = this.cpu.segLDT.limit; } - var offDT = addrDT + (sel & X86.SEL.MASK); - if (offDT + 7 <= addrDTLimit) { - this.checkRead = X86Seg.checkReadProtEnabled; - this.checkWrite = X86Seg.checkWriteProtEnabled; - + var offDesc = addrDT + (sel & X86.SEL.MASK); + if (offDesc + 7 <= addrDTLimit) { /* * TODO: This is only the first of many steps toward accurately counting cycles in protected mode; * I simply noted that "POP segreg" takes 5 cycles in real mode and 20 in protected mode, so I'm * starting with a 15-cycle difference. Obviously the difference will be much greater when the load fails. */ this.cpu.nStepCycles -= 15; + return X86Seg.loadDesc.call(this, sel, offDesc, true); + } + return -1; +}; - /* - * TODO: Use (direct) Bus memory interfaces instead of (indirect) CPU memory interfaces here? - */ - var limit = this.cpu.getWord(offDT + X86.DESC.LIMIT.OFFSET); - var acc = this.cpu.getWord(offDT + X86.DESC.ACC.OFFSET); - var base = this.cpu.getWord(offDT + X86.DESC.BASE.OFFSET) | ((acc & X86.DESC.ACC.BASE1623) << 16); +/** + * loadDesc(sel, offDesc, fProt) + * + * @this {X86Seg} + * @param {number} sel containing a selector + * @param {number} offDesc is the physical address of a descriptor + * @param {boolean} [fProt] is true if protected-mode, false if real-mode, undefined if TBD + * @return {number} base address of selected segment, or -1 if error + */ +X86Seg.loadDesc = function(sel, offDesc, fProt) +{ + var limit = this.cpu.getWord(offDesc + X86.DESC.LIMIT.OFFSET); + var acc = this.cpu.getWord(offDesc + X86.DESC.ACC.OFFSET); + var base = this.cpu.getWord(offDesc + X86.DESC.BASE.OFFSET) | ((acc & X86.DESC.ACC.BASE1623) << 16); - Component.assert(this.cpu.getWord(offDT + 0x06) == 0); + Component.assert(this.cpu.getWord(offDesc + 0x06) == 0); - /* - * For LSL (which uses fSuppress), we must support X86.DESC.ACC.TYPE.SEG as well as TSS and LDT. - */ - var accType = (acc & X86.DESC.ACC.TYPE.MASK); - if (accType & X86.DESC.ACC.TYPE.SEG) { + fProt = this.setProt(fProt); + + /* + * For LSL (which uses fSuppress), we must support X86.DESC.ACC.TYPE.SEG as well as TSS and LDT. + */ + var accType = (acc & X86.DESC.ACC.TYPE.MASK); + + if (accType & X86.DESC.ACC.TYPE.SEG) { + if (fProt) { if ((accType & X86.DESC.ACC.TYPE.CODE_READABLE) == X86.DESC.ACC.TYPE.CODE) { this.checkWrite = X86Seg.checkReadProtDisabled; } if ((accType & X86.DESC.ACC.TYPE.CODE) || !(accType & X86.DESC.ACC.TYPE.WRITEABLE)) { this.checkWrite = X86Seg.checkWriteProtDisabled; } - this.sel = sel; - this.limit = limit; - this.acc = acc; - this.level = (acc & X86.DESC.ACC.LEVEL.MASK) >> X86.DESC.ACC.LEVEL.SHIFT; - return this.base = base; - } - else if (accType && accType <= X86.DESC.ACC.TYPE.TSS_BUSY) { - this.sel = sel; - this.limit = limit; - this.acc = acc; - this.level = (acc & X86.DESC.ACC.LEVEL.MASK) >> X86.DESC.ACC.LEVEL.SHIFT; - return this.base = base; } + this.sel = sel; + this.limit = limit; + this.acc = acc; + this.level = (acc & X86.DESC.ACC.LEVEL.MASK) >> X86.DESC.ACC.LEVEL.SHIFT; + return this.base = base; + } + else if (accType && accType <= X86.DESC.ACC.TYPE.TSS_BUSY) { + this.sel = sel; + this.limit = limit; + this.acc = acc; + this.level = (acc & X86.DESC.ACC.LEVEL.MASK) >> X86.DESC.ACC.LEVEL.SHIFT; + return this.base = base; } return -1; }; @@ -321,8 +335,12 @@ X86Seg.prototype.setBase = function(addr) /** * setProt(fProt) * + * This must be used to ensure that the segment register's state (ie, load and check methods) matches + * the current operating mode (real or protected). + * * @this {X86Seg} * @param {boolean} [fProt] + * @return {boolean} */ X86Seg.prototype.setProt = function(fProt) { @@ -338,6 +356,7 @@ X86Seg.prototype.setProt = function(fProt) this.checkRead = X86Seg.checkReadReal; this.checkWrite = X86Seg.checkWriteReal; } + return fProt; }; if (typeof module !== 'undefined') module.exports = X86Seg; diff --git a/my_modules/shared/lib/externs.js b/my_modules/shared/lib/externs.js index cf4dadc0f..536f0ad29 100644 --- a/my_modules/shared/lib/externs.js +++ b/my_modules/shared/lib/externs.js @@ -32,8 +32,4 @@ "use strict"; -/* - * Unless we declare "module", even code like "if (typeof module === 'undefined') ..." is disallowed by the compiler. - */ -var module; -var webkitAudioContext; \ No newline at end of file +var webkitAudioContext; diff --git a/pubs/pc/reference/intel/80286/loadall/README.md b/pubs/pc/reference/intel/80286/loadall/README.md index a0d942894..1314d663f 100644 --- a/pubs/pc/reference/intel/80286/loadall/README.md +++ b/pubs/pc/reference/intel/80286/loadall/README.md @@ -19,7 +19,7 @@ iAPX 86 real mode programs. All CPU registers (including LDTR, TR, GDTR, IDTR, and MSW) are loaded from memory by this instruction. The normally hidden descriptor cache registers for the ES, DS, SS, CS, TR, and LDT registers are also loaded. LOADALL may be executed in either real address mode or protected mode (CPL must be 0). Any attempt to execute -LOADALL at any privilege level other than 0 in protected rode causes exception 13 with an error code of 0. +LOADALL at any privilege level other than 0 in protected mode causes exception 13 with an error code of 0. LOADALL allows direct control over the base, limit, and access rights associated with each segment register. These values are kept in the descriptor cache registers which are normally hidden from programs. In protected mode, @@ -147,4 +147,4 @@ For proper protected mode operation, the following is required: 4. The DPL fields of the ES and DS descriptors should be 3 to prevent their being zeroed by RET or IRET instructions. -[This information is from an undated 15-page Intel document titled "Undocumented iAPX 286 Test Instruction"] \ No newline at end of file +[This information is from an undated 15-page Intel document titled "Undocumented iAPX 286 Test Instruction"]