diff --git a/modules/pcjs/lib/bus.js b/modules/pcjs/lib/bus.js index d8f393f20..bc71bd1ad 100644 --- a/modules/pcjs/lib/bus.js +++ b/modules/pcjs/lib/bus.js @@ -39,6 +39,7 @@ if (typeof module !== 'undefined') { var Memory = require("./memory"); var Messages = require("./messages"); var State = require("./state"); + var X86 = require("./x86"); } /** @@ -67,8 +68,8 @@ if (typeof module !== 'undefined') { * @constructor * @extends Component * @param {Object} parmsBus - * @param {X86CPU|Component} cpu - * @param {Debugger|Component} dbg + * @param {X86CPU} cpu + * @param {Debugger} dbg */ function Bus(parmsBus, cpu, dbg) { @@ -689,20 +690,59 @@ Bus.prototype.enablePageBlocks = function(addrPD) }; /** - * mapPageBlock(addr) + * mapPageBlock(addr, fWrite) * - * If addr is a valid linear address, then we must obtain the following information: + * Locate the corresponding physical PDE, PTE and memory blocks for the given linear address. * - * (1) the physical Memory object corresponding to the linear address in addr - * (2) the physical Memory object containing that memory's PTE - * (3) the offset within the preceding physical Memory object of that memory's PTE - * - * Item (1) allows us to convert an "unpaged" Memory block in to a "paged" Memory block, and the combination of - * (2) and (3) allows us to efficiently update the PTE whenever the page is accesssed and/or modified. + * @this {Bus} + * @param {number} addr is a linear address + * @param {boolean} fWrite (true if called for a write, false if for a read) + * @return {Memory|null} */ -Bus.prototype.mapPageBlock = function(addr) +Bus.prototype.mapPageBlock = function(addr, fWrite) { - return null; + var offPDE = (addr & X86.LADDR.PDE.MASK) >>> X86.LADDR.PDE.SHIFT; + var addrPDE = this.cpu.regCR3 + offPDE; // TODO: adding offPDE could be eliminated, along with the busMask mask + var blockPDE = this.aPhysBlocks[(addrPDE & this.busMask) >>> this.blockShift]; + var pde = blockPDE.readLong(offPDE); + + if (!(pde & X86.PTE.PRESENT)) { + X86.fnPageFault.call(this.cpu, addr, false, fWrite); + return null; + } + + if (!(pde & X86.PTE.USER) && this.cpu.segCS.cpl == 3) { + X86.fnPageFault.call(this.cpu, addr, true, fWrite); + return null; + } + + var offPTE = (addr & X86.LADDR.PTE.MASK) >>> X86.LADDR.PTE.SHIFT; + var addrPTE = (pde & X86.PTE.FRAME) + offPTE; // TODO: adding offPTE could be eliminated, along with the busMask mask + var blockPTE = this.aPhysBlocks[(addrPTE & this.busMask) >>> this.blockShift]; + var pte = blockPTE.readLong(offPTE); + + if (!(pte & X86.PTE.PRESENT)) { + X86.fnPageFault.call(this.cpu, addr, false, fWrite); + return null; + } + + if (!(pte & X86.PTE.USER) && this.cpu.segCS.cpl == 3) { + X86.fnPageFault.call(this.cpu, addr, true, fWrite); + return null; + } + + var addrPhys = (pte & X86.PTE.FRAME) + (addr & X86.LADDR.OFFSET); // TODO: Adding OFFSET could be eliminated, along with the busMask mask + var blockPhys = this.aPhysBlocks[(addrPhys & this.busMask) >>> this.blockShift]; + + /* + * So we have the block containing the physical memory corresponding to the given linear address. + * + * Now we create a new "paged" Memory block and record the physical block info using setPhysBlock(). + */ + var addrPage = addr & ~X86.LADDR.OFFSET; + var blockPage = new Memory(addrPage, 0, this.blockSize, Memory.TYPE.PAGED); + blockPage.setPhysBlock(blockPage, blockPDE, offPDE, blockPTE, offPTE); + return blockPage; }; /** diff --git a/modules/pcjs/lib/computer.js b/modules/pcjs/lib/computer.js index 50b2a72ed..fc98c4680 100644 --- a/modules/pcjs/lib/computer.js +++ b/modules/pcjs/lib/computer.js @@ -140,13 +140,17 @@ function Computer(parmsComputer, parmsMachine, fSuspended) { /* * Find the appropriate CPU (and Debugger and Control Panel, if any) + * + * CLOSURE COMPILER TIP: To override the type of a right-hand expression (as we need to do here, + * where we know getComponentByType() will only return an X86CPU object or null), wrap the expression + * in parentheses. I never knew this until I stumbled across it in "Closure: The Definitive Guide". */ - this.cpu = Component.getComponentByType("CPU", this.id); + this.cpu = /** @type {X86CPU} */ (Component.getComponentByType("CPU", this.id)); if (!this.cpu) { Component.error("Unable to find CPU component"); return; } - this.dbg = Component.getComponentByType("Debugger", this.id); + this.dbg = /** @type {Debugger} */ (Component.getComponentByType("Debugger", this.id)); /* * Initialize the Bus component diff --git a/modules/pcjs/lib/defines.js b/modules/pcjs/lib/defines.js index 671ef0dd2..eae9ff76d 100644 --- a/modules/pcjs/lib/defines.js +++ b/modules/pcjs/lib/defines.js @@ -149,6 +149,7 @@ if (typeof module !== 'undefined') { global.BUGS_8086 = BUGS_8086; global.I386 = I386; global.COMPAQ386 = COMPAQ386; + global.PAGEBLOCKS = PAGEBLOCKS; /* * TODO: When we're "required" by Node, should we return anything via module.exports? */ diff --git a/modules/pcjs/lib/memory.js b/modules/pcjs/lib/memory.js index 22338d3dc..0233fee8c 100644 --- a/modules/pcjs/lib/memory.js +++ b/modules/pcjs/lib/memory.js @@ -36,6 +36,7 @@ if (typeof module !== 'undefined') { var str = require("../../shared/lib/strlib"); var Component = require("../../shared/lib/component"); var Messages = require("./messages"); + var X86 = require("./x86"); } /** @@ -201,14 +202,15 @@ function Memory(addr, used, size, type, controller, bus) * empty (that is, their data arrays are uninitialized) and the memory type is NONE. */ Memory.TYPE = { - NONE: 0, - RAM: 1, - ROM: 2, - VIDEO: 3, - CTRL: 4, - UNPAGED:5, - NAMES: ["NONE", "RAM", "ROM", "VIDEO", "H/W"], - COLORS: ["black", "blue", "green", "cyan"] + NONE: 0, + RAM: 1, + ROM: 2, + VIDEO: 3, + CTRL: 4, + UNPAGED: 5, + PAGED: 6, + NAMES: ["NONE", "RAM", "ROM", "VIDEO", "H/W", "UNPAGED", "PAGED"], + COLORS: ["black", "blue", "green", "cyan"] }; /* @@ -232,8 +234,8 @@ Memory.prototype = { clone: function(mem, type) { /* * Original memory block IDs are even; cloned memory block IDs are odd; - * the original ID of the current block is lost, but that's OK, since it was - * presumably produced merely to become a clone. + * the original ID of the current block is lost, but that's OK, since it was presumably + * produced merely to become a clone. */ this.id = mem.id | 0x1; this.used = mem.used; @@ -361,7 +363,17 @@ Memory.prototype = { * @param {boolean} [fDirect] */ setAccess: function(afn, fDirect) { - afn = afn || (this.type == Memory.TYPE.UNPAGED? Memory.afnUnpaged : Memory.afnNone); + if (!afn) { + if (this.type == Memory.TYPE.UNPAGED) { + afn = Memory.afnUnpaged; + } + else if (this.type == Memory.TYPE.PAGED) { + afn = Memory.afnPaged; + } else { + Component.assert(this.type == Memory.TYPE.NONE); + afn = Memory.afnNone; + } + } if (fDirect === undefined) fDirect = true; // TODO: Verify that this is desired default behavior this.setReadAccess(afn, fDirect); this.setWriteAccess(afn, fDirect); @@ -424,7 +436,7 @@ Memory.prototype = { * setDebugger(dbg, addr, size) * * @this {Memory} - * @param {Debugger|Component} dbg + * @param {Debugger} dbg * @param {number} addr of block * @param {number} size of block */ @@ -437,14 +449,48 @@ Memory.prototype = { } }, /** - * setPageBlock(addr) + * adjustEndian(dw) + * + * @this {Memory} + * @param {number} dw + * @return {number} + */ + adjustEndian: function(dw) { + if (TYPEDARRAYS && !littleEndian) { + dw = (dw << 24) | ((dw << 8) & 0x00ff0000) | ((dw >> 8) & 0x0000ff00) | (dw >>> 24); + } + return dw; + }, + /** + * getPageBlock(addr, fWrite) * * @this {Memory} * @param {number} addr + * @param {boolean} fWrite (true if called for a write, false if for a read) * @return {Memory} */ - setPageBlock: function(addr) { - return this.bus.mapPageBlock(addr); + getPageBlock: function(addr, fWrite) { + var block = this.bus.mapPageBlock(addr, fWrite); + return block || this; + }, + /** + * setPhysBlock(blockPhys, blockPDE, offPDE, blockPTE, offPTE) + * + * @this {Memory} + * @param {Memory} blockPhys + * @param {Memory} blockPDE + * @param {number} offPDE + * @param {Memory} blockPTE + * @param {number} offPTE + */ + setPhysBlock: function(blockPhys, blockPDE, offPDE, blockPTE, offPTE) { + this.blockPhys = blockPhys; + this.blockPDE = blockPDE; + this.iPDE = offPDE >> 2; // convert offPDE into an adw index (iPDE) + this.blockPTE = blockPTE; + this.iPTE = offPTE >> 2; // convert offPTE into an adw index (iPTE) + this.bitPTEDirty = this.adjustEndian(X86.PTE.ACCESSED | X86.PTE.DIRTY); + this.bitPTEAccessed = this.adjustEndian(X86.PTE.ACCESSED); }, /** * addBreakpoint(off, fWrite) @@ -679,11 +725,6 @@ Memory.prototype = { var idw = off >> 2; var nShift = (off & 0x3) << 3; if (nShift < 24) { - /* - * 0: 0xffff0000 - * 8: 0xff0000ff - * 16: 0x0000ffff - */ this.adw[idw] = (this.adw[idw] & ~(0xffff << nShift)) | (w << nShift); } else { this.adw[idw] = (this.adw[idw] & 0x00ffffff) | (w << 24); @@ -714,11 +755,6 @@ Memory.prototype = { if (!nShift) { this.adw[idw] = l; } else { - /* - * 8: 0xffffff00 - * 16: 0xffff0000 - * 24: 0xff000000 - */ var mask = (0xffffffff|0) << nShift; this.adw[idw] = (this.adw[idw] & ~mask) | (l << nShift); idw++; @@ -815,6 +851,84 @@ Memory.prototype = { } this.writeLongDirect(off, l); }, + /** + * readBytePaged(off, addr) + * + * @this {Memory} + * @param {number} off + * @param {number} addr + * @return {number} + */ + readBytePaged: function readBytePaged(off, addr) { + this.blockPDE.adw[this.iPDE] |= this.bitPTEAccessed; + this.blockPTE.adw[this.iPTE] |= this.bitPTEAccessed; + return this.blockPhys.readByte(off, addr); + }, + /** + * readShortPaged(off, addr) + * + * @this {Memory} + * @param {number} off + * @param {number} addr + * @return {number} + */ + readShortPaged: function readShortPaged(off, addr) { + this.blockPDE.adw[this.iPDE] |= this.bitPTEAccessed; + this.blockPTE.adw[this.iPTE] |= this.bitPTEAccessed; + return this.blockPhys.readShort(off, addr); + }, + /** + * readLongPaged(off, addr) + * + * @this {Memory} + * @param {number} off + * @param {number} addr + * @return {number} + */ + readLongPaged: function readLongPaged(off, addr) { + this.blockPDE.adw[this.iPDE] |= this.bitPTEAccessed; + this.blockPTE.adw[this.iPTE] |= this.bitPTEAccessed; + return this.blockPhys.readLong(off, addr); + }, + /** + * writeBytePaged(off, b, addr) + * + * @this {Memory} + * @param {number} off + * @param {number} b + * @param {number} addr + */ + writeBytePaged: function writeBytePaged(off, b, addr) { + this.blockPDE.adw[this.iPDE] |= this.bitPTEAccessed; + this.blockPTE.adw[this.iPTE] |= this.bitPTEDirty; + this.blockPhys.writeByte(off, b, addr); + }, + /** + * writeShortPaged(off, w, addr) + * + * @this {Memory} + * @param {number} off + * @param {number} w + * @param {number} addr + */ + writeShortPaged: function writeShortPaged(off, w, addr) { + this.blockPDE.adw[this.iPDE] |= this.bitPTEAccessed; + this.blockPTE.adw[this.iPTE] |= this.bitPTEDirty; + this.blockPhys.writeShort(off, w, addr); + }, + /** + * writeLongPaged(off, l, addr) + * + * @this {Memory} + * @param {number} off + * @param {number} l + * @param {number} addr + */ + writeLongPaged: function writeLongPaged(off, l, addr) { + this.blockPDE.adw[this.iPDE] |= this.bitPTEAccessed; + this.blockPTE.adw[this.iPTE] |= this.bitPTEDirty; + this.blockPhys.writeLong(off, l, addr); + }, /** * readByteUnpaged(off, addr) * @@ -824,7 +938,7 @@ Memory.prototype = { * @return {number} */ readByteUnpaged: function readByteUnpaged(off, addr) { - return this.setPageBlock(addr).readByte(off, addr); + return this.getPageBlock(addr, false).readByte(off, addr); }, /** * readShortUnpaged(off, addr) @@ -835,7 +949,7 @@ Memory.prototype = { * @return {number} */ readShortUnpaged: function readShortUnpaged(off, addr) { - return this.setPageBlock(addr).readShort(off, addr); + return this.getPageBlock(addr, false).readShort(off, addr); }, /** * readLongUnpaged(off, addr) @@ -846,7 +960,7 @@ Memory.prototype = { * @return {number} */ readLongUnpaged: function readLongUnpaged(off, addr) { - return this.setPageBlock(addr).readLong(off, addr); + return this.getPageBlock(addr, false).readLong(off, addr); }, /** * writeByteUnpaged(off, b, addr) @@ -857,7 +971,7 @@ Memory.prototype = { * @param {number} addr */ writeByteUnpaged: function writeByteUnpaged(off, b, addr) { - this.setPageBlock(addr).writeByte(off, b, addr); + this.getPageBlock(addr, true).writeByte(off, b, addr); }, /** * writeShortUnpaged(off, w, addr) @@ -868,7 +982,7 @@ Memory.prototype = { * @param {number} addr */ writeShortUnpaged: function writeShortUnpaged(off, w, addr) { - this.setPageBlock(addr).writeShort(off, w, addr); + this.getPageBlock(addr, true).writeShort(off, w, addr); }, /** * writeLongUnpaged(off, l, addr) @@ -879,7 +993,7 @@ Memory.prototype = { * @param {number} addr */ writeLongUnpaged: function writeLongUnpaged(off, l, addr) { - this.setPageBlock(addr).writeLong(off, l, addr); + this.getPageBlock(addr, true).writeLong(off, l, addr); }, /** * readByteBigEndian(off, addr) @@ -1132,8 +1246,8 @@ Memory.prototype = { }; /* - * This is the effective definition of afnNone, but we need not fully define it, because setAccess() already - * uses these defaults when any of the 6 handlers (ie, 3 read handlers followed by 3 write handlers) are undefined. + * This is the effective definition of afnNone, but we need not fully define it, because setAccess() + * uses these defaults when any of the 6 handlers (ie, 3 read handlers and 3 write handlers) are undefined. * Memory.afnNone = [Memory.prototype.readNone, Memory.prototype.readShortDefault, Memory.prototype.readLongDefault, Memory.prototype.writeNone, Memory.prototype.writeShortDefault, Memory.prototype.writeLongDefault]; */ @@ -1143,7 +1257,7 @@ Memory.afnMemory = [Memory.prototype.readByteMemory, Memory.prototyp Memory.afnChecked = [Memory.prototype.readByteChecked, Memory.prototype.readShortChecked, Memory.prototype.readLongChecked, Memory.prototype.writeByteChecked, Memory.prototype.writeShortChecked, Memory.prototype.writeLongChecked]; if (PAGEBLOCKS) { -// Memory.afnPaged = [Memory.prototype.readBytePaged, Memory.prototype.readShortPaged, Memory.prototype.readLongPaged, Memory.prototype.writeBytePaged, Memory.prototype.writeShortPaged, Memory.prototype.writeLongPaged]; + Memory.afnPaged = [Memory.prototype.readBytePaged, Memory.prototype.readShortPaged, Memory.prototype.readLongPaged, Memory.prototype.writeBytePaged, Memory.prototype.writeShortPaged, Memory.prototype.writeLongPaged]; Memory.afnUnpaged = [Memory.prototype.readByteUnpaged, Memory.prototype.readShortUnpaged, Memory.prototype.readLongUnpaged, Memory.prototype.writeByteUnpaged, Memory.prototype.writeShortUnpaged, Memory.prototype.writeLongUnpaged]; } diff --git a/modules/pcjs/lib/x86.js b/modules/pcjs/lib/x86.js index 24889d1fc..9b6171866 100644 --- a/modules/pcjs/lib/x86.js +++ b/modules/pcjs/lib/x86.js @@ -171,6 +171,25 @@ var X86 = { }, INVALID: 0 // use X86.DESC.INVALID for invalid DESC values }, + LADDR: { // linear address + PDE: { // index of page directory entry + MASK: 0xffc00000, + SHIFT: 20 // (addr & DIR.MASK) >>> DIR.SHIFT yields a page directory offset (ie, index * 4) + }, + PTE: { // index of page table entry + MASK: 0x003ff000, + SHIFT: 10 // (addr & PAGE.MASK) >>> PAGE.SHIFT yields a page table offset (ie, index * 4) + }, + OFFSET: 0x00000fff + }, + PTE: { + FRAME: 0xfffff000, + DIRTY: 0x00000040, // page has been modified + ACCESSED: 0x00000020, // page has been accessed + USER: 0x00000004, // set for user level (CPL 3), clear for supervisor level (CPL 0-2) + READWRITE: 0x00000002, // set for read/write, clear for read-only (affects CPL 3 only) + PRESENT: 0x00000001 // set for present page, clear for not-present page + }, TSS: { PREV_TSS: 0x00, CPL0_SP: 0x02, // start of values altered by task switches @@ -234,6 +253,7 @@ var X86 = { NP_FAULT: 0x0B, // Not Present Fault (protected-mode only) SS_FAULT: 0x0C, // Stack Fault (protected-mode only) GP_FAULT: 0x0D, // General Protection Fault + PG_FAULT: 0x0E, // Page Fault MF_FAULT: 0x10 // Math Fault (see ESC or WAIT) }, ERRCODE: { diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js index b4a46dc64..4e148e6bf 100644 --- a/modules/pcjs/lib/x86func.js +++ b/modules/pcjs/lib/x86func.js @@ -3461,6 +3461,26 @@ X86.fnFault = function(nFault, nError, fHalt) this.opFlags &= ~(X86.OPFLAG.NOREAD | X86.OPFLAG.NOWRITE); }; +/** + * fnPageFault(addr, fPresent, fWrite) + * + * Helper to dispatch page faults. + * + * @this {X86CPU} + * @param {number} addr + * @param {boolean} fPresent + * @param {boolean} fWrite + */ +X86.fnPageFault = function(addr, fPresent, fWrite) +{ + this.regCR2 = addr; + var nError = 0; + if (fPresent) nError |= X86.PTE.PRESENT; + if (fWrite) nError |= X86.PTE.READWRITE; + if (this.segCS.cpl == 3) nError |= X86.PTE.USER; + X86.fnFault.call(this, X86.EXCEPTION.PG_FAULT, nError); +}; + /** * fnFaultMessage() * diff --git a/modules/pcjs/lib/x86op0f.js b/modules/pcjs/lib/x86op0f.js index 41d2122d8..9b8462c05 100644 --- a/modules/pcjs/lib/x86op0f.js +++ b/modules/pcjs/lib/x86op0f.js @@ -307,6 +307,11 @@ X86.opMOVcr = function MOVcr() break; case 0x3: this.regCR3 = this.regEBX; + /* + * Normal use of regCR3 involves adding a 0-4K (12-bit) offset to obtain a page directory entry, so + * let's ensure that the low 12 bits of regCR3 are always zero. + */ + this.assert(!(this.regCR3 & X86.LADDR.OFFSET)); this.regEBX = temp; break; }