diff --git a/modules/pcjs/lib/bus.js b/modules/pcjs/lib/bus.js index 66845b31f8..357b6e8611 100644 --- a/modules/pcjs/lib/bus.js +++ b/modules/pcjs/lib/bus.js @@ -98,7 +98,7 @@ function Bus(parmsBus, cpu, dbg) * * iBlock & this.blockMask * - * While we *could* say that we mask addresses with this.addrLimit to simulate "A20 wrap", the simple + * While we *could* say that we mask addresses with this.addrMask to simulate "A20 wrap", the simple * fact is it relieves us from bounds-checking every aMemBlocks index. Address wrapping at the 1Mb * boundary (ie, the A20 address line) is something we'll have to deal with more carefully on the 80286. * @@ -407,6 +407,11 @@ Bus.prototype.removeMemory = function(addr, size) /** * getByte(addr) * + * Why does the CPU use its own getByte() (which is substantially similar to ours) instead of calling us? + * + * It certainly could, but the CPU also needs to update some BACKTRACK state. There may also be a slight + * performance advantage calling its own method vs. calling through another object (ie, the Bus object). + * * @this {Bus} * @param {number} addr is a physical (non-segmented) address * @return {number} byte (8-bit) value at that address @@ -419,6 +424,8 @@ Bus.prototype.getByte = function(addr) /** * getByteDirect(addr) * + * This is useful for the Debugger and other components that want to bypass getByte() breakpoint detection. + * * @this {Bus} * @param {number} addr is a physical (non-segmented) address * @return {number} byte (8-bit) value at that address @@ -431,6 +438,12 @@ Bus.prototype.getByteDirect = function(addr) /** * getWord(addr) * + * Why does the CPU use its own getWord() (which is substantially similar to ours) instead of calling us? + * + * It certainly could, but the CPU also needs to update its cycle count, along with some BACKTRACK state. + * There may also be a slight performance advantage calling its own method vs. calling through another object + * (ie, the Bus object). + * * @this {Bus} * @param {number} addr is a physical (non-segmented) address * @return {number} word (16-bit) value at that address @@ -448,6 +461,8 @@ Bus.prototype.getWord = function(addr) /** * getWordDirect(addr) * + * This is useful for the Debugger and other components that want to bypass getWord() breakpoint detection. + * * @this {Bus} * @param {number} addr is a physical (non-segmented) address * @return {number} word (16-bit) value at that address @@ -465,6 +480,11 @@ Bus.prototype.getWordDirect = function(addr) /** * setByte(addr, b) * + * Why does the CPU use its own setByte() (which is substantially similar to ours) instead of calling us? + * + * It certainly could, but the CPU also needs to update some BACKTRACK state. There may also be a slight + * performance advantage calling its own method vs. calling through another object (ie, the Bus object). + * * @this {Bus} * @param {number} addr is a physical (non-segmented) address * @param {number} b is the byte (8-bit) value to write (we truncate it to 8 bits to be safe) @@ -477,6 +497,9 @@ Bus.prototype.setByte = function(addr, b) /** * setByteDirect(addr, b) * + * This is useful for the Debugger and other components that want to bypass breakpoint detection AND read-only + * memory protection (for example, this is an interface the ROM component could use to initialize ROM contents). + * * @this {Bus} * @param {number} addr is a physical (non-segmented) address * @param {number} b is the byte (8-bit) value to write (we truncate it to 8 bits to be safe) @@ -489,6 +512,12 @@ Bus.prototype.setByteDirect = function(addr, b) /** * setWord(addr, w) * + * Why does the CPU use its own setWord() (which is substantially similar to ours) instead of calling us? + * + * It certainly could, but the CPU also needs to update its cycle count, along with some BACKTRACK state. + * There may also be a slight performance advantage calling its own method vs. calling through another object + * (ie, the Bus object). + * * @this {Bus} * @param {number} addr is a physical (non-segmented) address * @param {number} w is the word (16-bit) value to write (we truncate it to 16 bits to be safe) @@ -508,6 +537,9 @@ Bus.prototype.setWord = function(addr, w) /** * setWordDirect(addr, w) * + * This is useful for the Debugger and other components that want to bypass breakpoint detection AND read-only + * memory protection (for example, this is an interface the ROM component could use to initialize ROM contents). + * * @this {Bus} * @param {number} addr is a physical (non-segmented) address * @param {number} w is the word (16-bit) value to write (we truncate it to 16 bits to be safe) @@ -541,9 +573,15 @@ Bus.prototype.setWordDirect = function(addr, w) Bus.prototype.addBackTrackObject = function(obj, bto, off) { if (BACKTRACK && obj) { + var cbtObjects = this.abtObjects.length; + if (!bto) { + /* + * Try the most recently created bto, on the off-chance it's what the caller needs + */ + if (cbtObjects) bto = this.abtObjects[cbtObjects-1]; + } if (!bto || bto.obj != obj || off < bto.off || off >= bto.off + Bus.BACKTRACK.OFF_MAX) { var slot; - var cbtObjects = this.abtObjects.length; bto = {obj: obj, off: off, slot: 0, refs: 0}; if (!this.cbtDeletions) { slot = cbtObjects; @@ -569,6 +607,23 @@ Bus.prototype.addBackTrackObject = function(obj, bto, off) return null; }; +/** + * getBackTrackIndex(bto, off) + * + * @this {Bus} + * @param {Object|null} bto + * @param {number} off + * @return {number} + */ +Bus.prototype.getBackTrackIndex = function(bto, off) +{ + var bti = 0; + if (BACKTRACK && bto) { + bti = (bto.slot << Bus.BACKTRACK.SLOT_SHIFT) | (Bus.BACKTRACK.GEN_START << Bus.BACKTRACK.GEN_SHIFT) | (off - bto.off); + } + return bti; +}; + /** * writeBackTrackObject(addr, bto, off) * @@ -843,6 +898,10 @@ Bus.prototype.checkPortInputNotify = function(port, addrFrom) { var bIn = 0xff; var aNotify = this.aPortInputNotify[port]; + + if (BACKTRACK) { + this.cpu.backTrack.btiIO = 0; + } if (aNotify !== undefined) { if (aNotify[1]) { bIn = aNotify[1].call(aNotify[0], port, addrFrom); diff --git a/modules/pcjs/lib/fdc.js b/modules/pcjs/lib/fdc.js index baba68ee68..1fb3889964 100644 --- a/modules/pcjs/lib/fdc.js +++ b/modules/pcjs/lib/fdc.js @@ -2309,7 +2309,7 @@ FDC.prototype.doFormat = function(drive) FDC.prototype.readByte = function(drive, done) { var b = -1; - var obj, off; // these variables are purely for BACKTRACK purposes + var obj = null, off = 0; // these variables are purely for BACKTRACK purposes if (!drive.resCode && drive.disk) { do { diff --git a/modules/pcjs/lib/hdc.js b/modules/pcjs/lib/hdc.js index 2dde2ddc89..65f44d21d5 100644 --- a/modules/pcjs/lib/hdc.js +++ b/modules/pcjs/lib/hdc.js @@ -1394,13 +1394,25 @@ HDC.prototype.inATCData = function(port, addrFrom) * the pump; all we can do is assert that the pump has something in it. If bIn is inexplicably negative, * well, then the caller will get 0xff. */ - bIn = this.readByte(this.drive); + var hdc = this; + bIn = this.readByte(this.drive, function(b, fAsync, obj, off) { + hdc.assert(!fAsync); + if (BACKTRACK) { + if (!off && obj.file) { + hdc.println("loading " + obj.file.sPath + '[' + obj.offFile + "] via port 0x" + str.toHexWord(port)); + } + /* + * TODO: We could define a cached BTO that's reset prior to a new ATC command, and then pass that + * to addBackTrackObject() here instead of null; but for now, we're going to rely on that function's + * simplistic MRU cache. If that fails, the worst that will (or should) happen is we'll burn through + * more BackTrack wrapper objects than necessary, and run the risk of running out. + */ + var bto = hdc.bus.addBackTrackObject(obj, null, off); + hdc.cpu.backTrack.btiIO = hdc.bus.getBackTrackIndex(bto, off); + } + }); this.assert(bIn >= 0); - /* - * Now that we've supplied a full sector of data, see if the caller's expecting additional sectors; - * if so, prime the pump again. The caller should not poll us again until another interrupt's been delivered. - */ if (this.drive.ibSector == 1) { /* * messagePort() calls, if enabled, can be overwhelming for this port, so limit them to the first byte @@ -1411,7 +1423,10 @@ HDC.prototype.inATCData = function(port, addrFrom) } } else if (this.drive.ibSector == this.drive.cbSector) { - + /* + * Now that we've supplied a full sector of data, see if the caller's expecting additional sectors; + * if so, prime the pump again. The caller should not poll us again until another interrupt's been delivered. + */ if (this.messageEnabled(Messages.DATA | Messages.HDC)) { var sDump = this.drive.disk.dumpSector(this.drive.sector); if (sDump) this.dbg.message(sDump); @@ -1424,7 +1439,6 @@ HDC.prototype.inATCData = function(port, addrFrom) * additional bytes into the inATCData() stream. And we must first set DATA_REQ in the STATUS register. */ if (this.drive.nBytes >= this.drive.cbSector) { - var hdc = this; hdc.regStatus = HDC.ATC.STATUS.BUSY | HDC.ATC.STATUS.DATA_REQ; this.readByte(this.drive, function(b, fAsync) { if (b >= 0) { @@ -2478,26 +2492,29 @@ HDC.prototype.doDMAFormat = function(drive, done) * * @this {HDC} * @param {Object} drive - * @param {function(number,boolean)} [done] (number is next available byte from drive, or -1 if no more bytes available) + * @param {function(number,boolean,Object,number)} [done] (number is next available byte from drive, or -1 if no more bytes available) * @param {boolean} [fAutoInc] (default is true to auto-increment) * @return {number} the requested byte, or -1 if unavailable */ HDC.prototype.readByte = function(drive, done, fAutoInc) { var b = -1; + var obj = null, off = 0; // these variables are purely for BACKTRACK purposes if (drive.errorCode) { - if (done) done(b, false); + if (done) done(b, false, obj, off); return b; } var inc = (fAutoInc !== false? 1 : 0); if (drive.sector) { + off = drive.ibSector; b = drive.disk.read(drive.sector, drive.ibSector); drive.ibSector += inc; if (b >= 0) { - if (done) done(b, false); + obj = drive.sector; + if (done) done(b, false, obj, off); return b; } } @@ -2514,7 +2531,8 @@ HDC.prototype.readByte = function(drive, done, fAutoInc) if (drive.disk) { drive.disk.seek(drive.wCylinder, drive.bHead, drive.bSector + drive.bSectorBias, false, function(sector, fAsync) { if ((drive.sector = sector)) { - drive.ibSector = 0; + obj = sector; + off = drive.ibSector = 0; /* * We "pre-advance" bSector et al now, instead of waiting to advance it right before the seek(). * This allows the initial call to readByte() to perform a seek without triggering an unwanted advance. @@ -2525,12 +2543,12 @@ HDC.prototype.readByte = function(drive, done, fAutoInc) } else { drive.errorCode = HDC.XTC.DATA.ERR.NO_SECTOR; } - done(b, fAsync); + done(b, fAsync, obj, off); }); return b; } drive.errorCode = HDC.XTC.DATA.ERR.NO_SECTOR; - done(b, false); + done(b, false, obj, off); } return b; }; diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 3c64c56553..b275ac7b03 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -866,7 +866,8 @@ X86CPU.prototype.resetRegs = function() btiDILo: 0, btiDIHi: 0, btiMemLo: 0, - btiMemHi: 0 + btiMemHi: 0, + btiIO: 0 }; } diff --git a/modules/pcjs/lib/x86opxx.js b/modules/pcjs/lib/x86opxx.js index 98e4bc8caf..f50a6773c4 100644 --- a/modules/pcjs/lib/x86opxx.js +++ b/modules/pcjs/lib/x86opxx.js @@ -1213,6 +1213,7 @@ var X86OpXX = { if (nReps--) { var b = this.bus.checkPortInputNotify(this.regDX, this.regEIP - nDelta - 1); + if (BACKTRACK) this.backTrack.btiMemLo = this.backTrack.btiIO; this.setSOByte(this.segES, this.regDI, b); this.regDI = (this.regDI + ((this.regPS & X86.PS.DF)? -1 : 1)) & 0xffff; this.nStepCycles -= nCycles; @@ -1258,7 +1259,10 @@ var X86OpXX = { } if (nReps--) { var addrFrom = this.regEIP - nDelta - 1; - var w = this.bus.checkPortInputNotify(this.regDX, addrFrom) | (this.bus.checkPortInputNotify(this.regDX, addrFrom) << 8); + var w = this.bus.checkPortInputNotify(this.regDX, addrFrom); + if (BACKTRACK) this.backTrack.btiMemLo = this.backTrack.btiIO; + w |= (this.bus.checkPortInputNotify(this.regDX, addrFrom) << 8); + if (BACKTRACK) this.backTrack.btiMemHi = this.backTrack.btiIO; this.setSOWord(this.segES, this.regDI, w); this.regDI = (this.regDI + ((this.regPS & X86.PS.DF)? -2 : 2)) & 0xffff; this.nStepCycles -= nCycles; @@ -3014,6 +3018,7 @@ var X86OpXX = { opINb: function() { var port = this.getIPByte(); this.regAX = (this.regAX & ~0xff) | this.bus.checkPortInputNotify(port, this.regEIP - 2); + if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiIO; this.nStepCycles -= this.CYCLES.nOpCyclesInP; }, /** @@ -3023,7 +3028,10 @@ var X86OpXX = { */ opINw: function() { var port = this.getIPByte(); - this.regAX = this.bus.checkPortInputNotify(port, this.regEIP - 1) | (this.bus.checkPortInputNotify((port + 1) & 0xffff, this.regEIP - 2) << 8); + this.regAX = this.bus.checkPortInputNotify(port, this.regEIP - 2); + if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiIO; + this.regAX |= (this.bus.checkPortInputNotify((port + 1) & 0xffff, this.regEIP - 2) << 8); + if (BACKTRACK) this.backTrack.btiAH = this.backTrack.btiIO; this.nStepCycles -= this.CYCLES.nOpCyclesInP; }, /** @@ -3094,6 +3102,7 @@ var X86OpXX = { */ opINDXb: function() { this.regAX = (this.regAX & ~0xff) | this.bus.checkPortInputNotify(this.regDX, this.regEIP - 1); + if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiIO; this.nStepCycles -= this.CYCLES.nOpCyclesInDX; }, /** @@ -3102,7 +3111,10 @@ var X86OpXX = { * op=0xED (in AX,dx) */ opINDXw: function() { - this.regAX = this.bus.checkPortInputNotify(this.regDX, this.regEIP - 1) | (this.bus.checkPortInputNotify((this.regDX + 1) & 0xffff, this.regEIP - 1) << 8); + this.regAX = this.bus.checkPortInputNotify(this.regDX, this.regEIP - 1); + if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiIO; + this.regAX |= (this.bus.checkPortInputNotify((this.regDX + 1) & 0xffff, this.regEIP - 1) << 8); + if (BACKTRACK) this.backTrack.btiAH = this.backTrack.btiIO; this.nStepCycles -= this.CYCLES.nOpCyclesInDX; }, /** diff --git a/package.json b/package.json index c7d13867d1..8eaf0d0344 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pcjs", - "version": "1.16.4", + "version": "1.16.5", "description": "Node-enabled version of PCjs", "main": "server.js", "directories": {