Fixed save/restore (hopefully) when A20 is off, as well as when HDC I/O is in progress (although the latter needs more testing)
This commit is contained in:
parent
b8ddedd9cf
commit
63121a1768
6 changed files with 99 additions and 57 deletions
|
|
@ -1185,7 +1185,7 @@ Bus.prototype.getSymbol = function(addr, fNearest)
|
|||
/**
|
||||
* saveMemory()
|
||||
*
|
||||
* The only memory blocks we save are those marked as dirty; most likely all of RAM will have been marked dirty,
|
||||
* The only memory blocks we save are those marked as dirty, but most likely all of RAM will have been marked dirty,
|
||||
* and even if our dirty-memory flags were as smart as our dirty-sector flags (ie, were set only when a write changed
|
||||
* what was already there), it's unlikely that would reduce the number of RAM blocks we must save/restore. At least
|
||||
* all the ROM blocks should be clean (except in the unlikely event that the Debugger was used to modify them).
|
||||
|
|
@ -1214,6 +1214,15 @@ Bus.prototype.saveMemory = function()
|
|||
{
|
||||
var i = 0;
|
||||
var a = [];
|
||||
|
||||
/*
|
||||
* A quick-and-dirty work-around for 32-bit bus machines, to ensure that all blocks in the 2nd Mb are
|
||||
* mapped in before we save. We do this by forcing A20 on, and then turning it back off again before we
|
||||
* leave.
|
||||
*/
|
||||
var fA20 = this.getA20();
|
||||
if (!fA20) this.setA20(true);
|
||||
|
||||
for (var iBlock = 0; iBlock < this.nBlockTotal; iBlock++) {
|
||||
var block = this.aMemBlocks[iBlock];
|
||||
/*
|
||||
|
|
@ -1226,7 +1235,10 @@ Bus.prototype.saveMemory = function()
|
|||
a[i++] = State.compress(block.save());
|
||||
}
|
||||
}
|
||||
a[i] = this.getA20();
|
||||
|
||||
if (!fA20) this.setA20(false);
|
||||
a[i] = fA20;
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4299,6 +4299,7 @@ ChipSet.prototype.out8042InBuffData = function(port, bOut, addrFrom)
|
|||
this.printMessageIO(port, bOut, addrFrom, "8042_INBUF.DATA", null, Messages.C8042);
|
||||
|
||||
if (this.b8042Status & ChipSet.KBC.STATUS.CMD_FLAG) {
|
||||
|
||||
switch (this.b8042InBuff) {
|
||||
|
||||
case ChipSet.KBC.CMD.WRITE_CMD:
|
||||
|
|
@ -4653,6 +4654,10 @@ ChipSet.prototype.set8042OutBuff = function(b, fNoDelay)
|
|||
/**
|
||||
* set8042OutPort(b)
|
||||
*
|
||||
* When ChipSet.KBC.CMD.WRITE_OUTPORT (0xD1) is written to port 0x64, the next byte written to port 0x60 comes here,
|
||||
* to the KBC's OUTPORT. One of the most important bits in the OUTPORT is the A20_ON bit (0x02): set it to turn A20 on,
|
||||
* clear it to turn A20 off.
|
||||
*
|
||||
* @this {ChipSet}
|
||||
* @param {number} b
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -790,8 +790,8 @@ Computer.prototype.powerOff = function(fSave, fShutdown)
|
|||
stateComputer.set(Computer.STATE_BROWSER, web.getUserAgent());
|
||||
|
||||
/*
|
||||
* Always power the CPU "down" first, just to insure it doesn't ask other
|
||||
* components to do anything after they're no longer ready.
|
||||
* Always power the CPU "down" first, just to help insure it doesn't ask other components to do anything
|
||||
* after they're no longer ready.
|
||||
*/
|
||||
if (this.cpu && this.cpu.powerDown) {
|
||||
if (fShutdown) this.cpu.stopCPU();
|
||||
|
|
@ -960,50 +960,53 @@ Computer.prototype.stop = function(ms, nCycles)
|
|||
Computer.prototype.setBinding = function(sHTMLType, sBinding, control)
|
||||
{
|
||||
var computer = this;
|
||||
|
||||
switch (sBinding) {
|
||||
case "save":
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function onClickSave() {
|
||||
var sUserID = computer.queryUserID(true);
|
||||
if (sUserID) {
|
||||
/*
|
||||
* I modified the test to include a check for sStatePath so that I could save new states
|
||||
* for machines with existing states; otherwise, I'd have no (easy) way of capturing and
|
||||
* updating their state. Making the machine (even temporarily) resumable would have been
|
||||
* one work-around, but it's not appropriate for some machines, as their state is simply
|
||||
* too large (for localStorage anyway, which is the default storage solution).
|
||||
*/
|
||||
var fSave = !!(computer.resume && !computer.sResumePath || computer.sStatePath);
|
||||
var sState = computer.powerOff(fSave);
|
||||
if (fSave) {
|
||||
computer.saveServerState(sUserID, sState);
|
||||
} else {
|
||||
computer.notice("Resume disabled, machine state not saved");
|
||||
}
|
||||
}
|
||||
case "save":
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function onClickSave() {
|
||||
var sUserID = computer.queryUserID(true);
|
||||
if (sUserID) {
|
||||
/*
|
||||
* This seemed like a handy alternative, but it turned out to be a no-go, at least for large states:
|
||||
*
|
||||
* var sState = computer.powerOff(true);
|
||||
* if (sState) {
|
||||
* sState = "data:text/json;charset=utf-8," + encodeURIComponent(sState);
|
||||
* window.open(sState);
|
||||
* }
|
||||
*
|
||||
* Perhaps if I embedded the data in a link on the current page instead; eg:
|
||||
*
|
||||
* $('<a href="' + sState + '" download="state.json">Download</a>').appendTo('#container');
|
||||
* I modified the test to include a check for sStatePath so that I could save new states
|
||||
* for machines with existing states; otherwise, I'd have no (easy) way of capturing and
|
||||
* updating their state. Making the machine (even temporarily) resumable would have been
|
||||
* one work-around, but it's not appropriate for some machines, as their state is simply
|
||||
* too large (for localStorage anyway, which is the default storage solution).
|
||||
*/
|
||||
};
|
||||
return true;
|
||||
case "reset":
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function onClickReset() {
|
||||
computer.onReset();
|
||||
};
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
var fSave = !!(computer.resume && !computer.sResumePath || computer.sStatePath);
|
||||
var sState = computer.powerOff(fSave);
|
||||
if (fSave) {
|
||||
computer.saveServerState(sUserID, sState);
|
||||
} else {
|
||||
computer.notice("Resume disabled, machine state not saved");
|
||||
}
|
||||
}
|
||||
/*
|
||||
* This seemed like a handy alternative, but it turned out to be a no-go, at least for large states:
|
||||
*
|
||||
* var sState = computer.powerOff(true);
|
||||
* if (sState) {
|
||||
* sState = "data:text/json;charset=utf-8," + encodeURIComponent(sState);
|
||||
* window.open(sState);
|
||||
* }
|
||||
*
|
||||
* Perhaps if I embedded the data in a link on the current page instead; eg:
|
||||
*
|
||||
* $('<a href="' + sState + '" download="state.json">Download</a>').appendTo('#container');
|
||||
*/
|
||||
};
|
||||
return true;
|
||||
|
||||
case "reset":
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function onClickReset() {
|
||||
computer.onReset();
|
||||
};
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5186,7 +5186,7 @@ if (DEBUGGER) {
|
|||
* doClear(sCmd)
|
||||
*
|
||||
* @this {Debugger}
|
||||
* @param {string} sCmd (eg, "cls" or "clear")
|
||||
* @param {string} [sCmd] (eg, "cls" or "clear")
|
||||
*/
|
||||
Debugger.prototype.doClear = function(sCmd)
|
||||
{
|
||||
|
|
@ -5251,6 +5251,7 @@ if (DEBUGGER) {
|
|||
*/
|
||||
console.log(s);
|
||||
} else {
|
||||
this.doClear();
|
||||
this.println(s);
|
||||
}
|
||||
return;
|
||||
|
|
@ -5728,6 +5729,7 @@ if (DEBUGGER) {
|
|||
* "biggest" being that the large disk images really need to be compressed first, because they
|
||||
* get "inflated" with use. See the dump() method in the Disk component for more details.
|
||||
*/
|
||||
this.doClear();
|
||||
this.println(drive.disk.toJSON());
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -683,6 +683,8 @@ HDC.prototype.initController = function(data, fHard)
|
|||
var i = 0;
|
||||
var fSuccess = true;
|
||||
|
||||
this.iDrive = -1;
|
||||
|
||||
/*
|
||||
* At this point, it's worth calling into question my decision to NOT split the HDC component into separate XTC
|
||||
* and ATC components, given all the differences, and given that I'm about to write some "if (ATC) else (XTC) ..."
|
||||
|
|
@ -702,7 +704,7 @@ HDC.prototype.initController = function(data, fHard)
|
|||
* or vice versa), we're under no obligation to use the same number of registers, or save/restore format, etc,
|
||||
* as the original XT controller.
|
||||
*/
|
||||
if (data == null) data = [0, 0, 0, 0, 0, 0, 0, 0, HDC.ATC.STATUS.READY, 0];
|
||||
if (data == null) data = [0, 0, 0, 0, 0, 0, 0, HDC.ATC.STATUS.READY, 0, [0, -1]];
|
||||
this.regError = data[i++];
|
||||
this.regWPreC = data[i++];
|
||||
this.regSecCnt = data[i++];
|
||||
|
|
@ -713,6 +715,11 @@ HDC.prototype.initController = function(data, fHard)
|
|||
this.regStatus = data[i++];
|
||||
this.regCommand = data[i++];
|
||||
this.regFDR = data[i++];
|
||||
if (typeof this.regFDR == "object") {
|
||||
var a = this.regFDR;
|
||||
this.regFDR = a[0];
|
||||
this.iDrive = a[1];
|
||||
}
|
||||
/*
|
||||
* Additional state is maintained by the Drive object (eg, abSector, ibSector)
|
||||
*/
|
||||
|
|
@ -763,6 +770,11 @@ HDC.prototype.initController = function(data, fHard)
|
|||
this.regConfig |= (drive.type & 0x3) << ((1 - iDrive) << 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.iDrive >= 0) {
|
||||
this.drive = this.aDrives[this.iDrive];
|
||||
}
|
||||
|
||||
if (DEBUG && this.messageEnabled()) {
|
||||
this.printMessage("HDC initialized for " + this.aDrives.length + " drive(s)");
|
||||
}
|
||||
|
|
@ -789,7 +801,7 @@ HDC.prototype.saveController = function()
|
|||
data[i++] = this.regDrvHd;
|
||||
data[i++] = this.regStatus;
|
||||
data[i++] = this.regCommand;
|
||||
data[i++] = this.regFDR;
|
||||
data[i++] = [this.regFDR, this.iDrive];
|
||||
} else {
|
||||
data[i++] = this.regConfig;
|
||||
data[i++] = this.regStatus;
|
||||
|
|
@ -1841,6 +1853,7 @@ HDC.prototype.doATC = function()
|
|||
var nSector = this.regSecNum;
|
||||
var nSectors = this.regSecCnt || 256;
|
||||
|
||||
this.iDrive = -1;
|
||||
this.drive = null;
|
||||
this.regError = HDC.ATC.ERROR.NONE;
|
||||
this.regStatus = HDC.ATC.STATUS.READY | HDC.ATC.STATUS.SEEK_OK;
|
||||
|
|
@ -1869,6 +1882,7 @@ HDC.prototype.doATC = function()
|
|||
drive.sector = null;
|
||||
drive.ibSector = 0;
|
||||
drive.errorCode = 0;
|
||||
this.iDrive = iDrive;
|
||||
this.drive = drive;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1467,19 +1467,25 @@ X86.fnLDS = function LDS(dst, src)
|
|||
*/
|
||||
X86.fnLEA = function LEA(dst, src)
|
||||
{
|
||||
/*
|
||||
* TODO: Until I bite the bullet and choose a truly invalid value for X86.ADDR_INVALID (eg, null),
|
||||
* this code must be disabled, because otherwise an instruction like "LEA ECX,[EAX-1]" will fail when
|
||||
* EAX is zero. And we can't have that.
|
||||
*
|
||||
if (this.regEA === X86.ADDR_INVALID) {
|
||||
/*
|
||||
* TODO: After reading http://www.os2museum.com/wp/undocumented-8086-opcodes/, it seems that this
|
||||
* form of LEA (eg, "LEA AX,DX") simply returns the last calculated EA. Since we always reset regEA
|
||||
* at the start of a new instruction, we would need to preserve the previous EA if we want to mimic
|
||||
* that (undocumented) behavior.
|
||||
*
|
||||
* And for completeness, we would have to extend EA tracking beyond the usual ModRM instructions
|
||||
* (eg, XLAT, instructions that modify the stack pointer, and string instructions). Anything else?
|
||||
*/
|
||||
//
|
||||
// TODO: After reading http://www.os2museum.com/wp/undocumented-8086-opcodes/, it seems that this
|
||||
// form of LEA (eg, "LEA AX,DX") simply returns the last calculated EA. Since we always reset regEA
|
||||
// at the start of a new instruction, we would need to preserve the previous EA if we want to mimic
|
||||
// that (undocumented) behavior.
|
||||
//
|
||||
// And for completeness, we would have to extend EA tracking beyond the usual ModRM instructions
|
||||
// (eg, XLAT, instructions that modify the stack pointer, and string instructions). Anything else?
|
||||
//
|
||||
X86.opUndefined.call(this);
|
||||
return dst;
|
||||
}
|
||||
*/
|
||||
this.nStepCycles -= this.cycleCounts.nOpCyclesLEA;
|
||||
return this.regEA;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue