Added BACKTRACK support to I/O instructions, so that ATC Disk I/O can be tracked, too
This commit is contained in:
parent
ad015c1ce6
commit
14a55eb1fa
6 changed files with 111 additions and 21 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -866,7 +866,8 @@ X86CPU.prototype.resetRegs = function()
|
|||
btiDILo: 0,
|
||||
btiDIHi: 0,
|
||||
btiMemLo: 0,
|
||||
btiMemHi: 0
|
||||
btiMemHi: 0,
|
||||
btiIO: 0
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "pcjs",
|
||||
"version": "1.16.4",
|
||||
"version": "1.16.5",
|
||||
"description": "Node-enabled version of PCjs",
|
||||
"main": "server.js",
|
||||
"directories": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue