Improved PDP-11 save/restore support (added to Device, Panel, RK11, and Serial)

This commit is contained in:
Jeff Parsons 2017-01-26 10:04:44 -08:00 committed by Jeff Parsons
commit d182c7eced
15 changed files with 850 additions and 635 deletions

View file

@ -684,7 +684,7 @@ FDC.prototype.initBus = function(cmp, bus, cpu, dbg)
FDC.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) {
if (!data || !this.restore) {
if (!data) {
this.reset();
if (this.cmp.fReload) {
/*
@ -798,7 +798,7 @@ FDC.prototype.initController = function(data)
var i = 0, iDrive;
var fSuccess = true;
if (data === undefined) {
if (!data) {
data = [0, 0, FDC.REG_STATUS.RQM, new Array(9), 0, 0, 0, []];
}

View file

@ -668,7 +668,7 @@ HDC.prototype.initBus = function(cmp, bus, cpu, dbg)
HDC.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) {
if (!data || !this.restore) {
if (!data) {
this.initController();
if (this.cmp.fReload) {
/*

View file

@ -727,7 +727,7 @@ class ComputerPDP11 extends Component {
* TODO: Do we not care about the return value here? (ie, is checking fRestoreError sufficient)?
*/
this.powerRestore(this.cpu, stateComputer, fRepower, fRestore);
this.updateDisplays();
this.updateDisplays(-2);
this.cpu.autoStart();
}

View file

@ -577,10 +577,11 @@ class CPUStatePDP11 extends CPUPDP11 {
{
this.addrReset = addr;
this.resetCPU();
this.setPC(addr);
this.setPSW(0);
this.resetCPU();
if (fStart) {
/*
* TODO: Review. I'm zeroing R2-R5 in the fStart case (ie, for boot code) simply because that's what I
@ -677,7 +678,7 @@ class CPUStatePDP11 extends CPUPDP11 {
restore(data)
{
/*
* ES6 ALERT: Gotta love these destructuring assignments, which make it easy to perform the
* ES6 ALERT: Love these destructuring assignments, which make it easy to perform the
* inverse of what save() does when it collects a bunch of object properties into an array.
*/
[

View file

@ -2752,7 +2752,9 @@ class DebuggerPDP11 extends Debugger {
if (sParm == 'n') {
this.nBreakInstructions = this.parseValue(sAddr);
this.println("break after " + this.nBreakInstructions + " instruction(s)");
if (this.nBreakInstructions != null) {
this.println("break after " + this.nBreakInstructions + " instruction(s)");
}
return;
}

View file

@ -241,7 +241,7 @@ class DevicePDP11 extends Component {
restore(data)
{
/*
* ES6 ALERT: Gotta love these destructuring assignments, which make it easy to perform the
* ES6 ALERT: Love these destructuring assignments, which make it easy to perform the
* inverse of what save() does when it collects a bunch of object properties into an array.
*/
[

View file

@ -36,6 +36,7 @@ if (NODE) {
var Str = require("../../shared/es6/strlib");
var Web = require("../../shared/es6/weblib");
var Component = require("../../shared/es6/component");
var State = require("../../shared/es6/state");
var PDP11 = require("./defines");
var BusPDP11 = require("./bus");
var MessagesPDP11 = require("./messages");
@ -80,6 +81,7 @@ class PanelPDP11 extends Component {
this.regDisplay = 0;
this.regSwitches = 0;
this.regAddr = this.regData = 0;
this.ledAddr = this.ledData = -1;
/*
* The panel hardware has the following additional (supported) state; note that there are several
@ -246,6 +248,7 @@ class PanelPDP11 extends Component {
* Simulate a call to our stop() handler, to update the panel's ADDRESS register with the current PC.
*/
this.stop();
this.setDR(0);
}
/**
@ -396,10 +399,11 @@ class PanelPDP11 extends Component {
*/
if (this.fBindings) PanelPDP11.init();
/*
* TODO: Until we implement a restore() function, all we can do is reset()
*/
this.reset();
if (!data) {
this.reset();
} else {
if (!this.restore(data)) return false;
}
}
return true;
}
@ -414,6 +418,45 @@ class PanelPDP11 extends Component {
*/
powerDown(fSave, fShutdown)
{
return fSave? this.save() : true;
}
/**
* save()
*
* This implements save support for the PanelPDP11 component.
*
* @this {PanelPDP11}
* @return {Object}
*/
save()
{
var state = new State(this);
state.set(0, [
this.getAR(),
this.getDR(),
this.getSR()
]);
return state.data();
}
/**
* restore(data)
*
* This implements restore support for the PanelPDP11 component.
*
* @this {PanelPDP11}
* @param {Object} data
* @return {boolean} true if successful, false if failure
*/
restore(data)
{
var a = data[0];
if (a) {
this.setAR(a[0]);
this.setDR(a[1]);
this.setSR(a[2]);
}
return true;
}
@ -865,7 +908,10 @@ class PanelPDP11 extends Component {
updateAddr(value)
{
this.regAddr = value & this.bus.nBusMask;
this.updateLEDArray("A", this.regAddr, 22);
if (this.ledAddr !== this.regAddr) {
this.ledAddr = this.regAddr;
this.updateLEDArray("A", this.ledAddr, 22);
}
return this.regAddr;
}
@ -879,7 +925,10 @@ class PanelPDP11 extends Component {
updateData(value)
{
this.regData = value & 0xffff;
this.updateLEDArray("D", this.regData, 16);
if (this.ledData !== this.regData) {
this.ledData = this.regData;
this.updateLEDArray("D", this.ledData, 16);
}
return this.regData;
}
@ -996,7 +1045,7 @@ class PanelPDP11 extends Component {
* Called by the Computer component at intervals to update registers, LEDs, etc.
*
* @this {PanelPDP11}
* @param {number} [nUpdate] (< 0 for forced, > 0 for periodic, 0 otherwise)
* @param {number} [nUpdate] (-2 for power on, -1 for forced, > 0 for periodic, 0 or undefined otherwise)
*/
updateDisplay(nUpdate)
{
@ -1032,7 +1081,13 @@ class PanelPDP11 extends Component {
* TODO: There is currently no mechanism for selecting regData over regDisplay;
* we are acting as if the DATASEL switch setting is locked to "DISPLAY REGISTER".
*/
this.updateAddr(nUpdate > 0 && fRunning && !fWaiting? this.cpu.getLastAddr() : this.regAddr);
if (nUpdate < -1) {
this.regAddr = this.cpu.regsGen[7];
} else if (nUpdate > 0 && fRunning && !fWaiting) {
this.regAddr = this.cpu.getLastAddr();
}
this.updateAddr(this.regAddr);
this.updateData(this.regDisplay);
var bits = this.cpu.getMMUState();

View file

@ -79,10 +79,24 @@ class RK11 extends Component {
this.fLocalDisks = (!Web.isMobile() && window && 'FileReader' in window);
this.sDiskSource = RK11.SOURCE.NONE;
/*
* The following array keeps track of every disk image we've ever mounted. Each entry in the
* array is another array whose elements are:
*
* [0]: name of disk
* [1]: path of disk
* [2]: array of deltas, uninitialized until the disk is unmounted and/or all state is saved
*
* See functions addDiskHistory() and updateDiskHistory().
*/
this.aDiskHistory = [];
this.irq = null;
/*
* Define all the properties to be initialized by initController()
*
* TODO: Determine what we should really be doing with the RKDB register.
*/
this.regRKDS = this.regRKER = this.regRKCS = this.regRKWC = this.regRKBA = this.regRKDA = this.regRKDB = 0;
}
@ -436,7 +450,7 @@ class RK11 extends Component {
save()
{
var state = new State(this);
// state.set(0, this.saveController());
state.set(0, this.saveController());
return state.data();
}
@ -454,17 +468,156 @@ class RK11 extends Component {
return this.initController(data[0]);
}
/**
* initController(a)
*
* @this {RK11}
* @param {Array} [a]
* @return {boolean} true if successful, false if failure
*/
initController(a)
{
var fSuccess = true;
if (!a) {
a = [(RK11.RKDS.RK05 | RK11.RKDS.SOK | RK11.RKDS.RRDY), 0, (RK11.RKCS.CRDY), 0, 0, 0, 0];
}
/*
* ES6 ALERT: Love these destructuring assignments, which make it easy to perform the
* inverse of what save() does when it collects a bunch of object properties into an array.
*/
[
this.regRKDS,
this.regRKER,
this.regRKCS,
this.regRKWC,
this.regRKBA,
this.regRKDA,
this.regRKDB
] = a;
/*
* Initialize the disk history (if available) before initializing the drives, so that any disk deltas can be
* applied to disk images that are already loaded.
*/
if (a[7]) {
this.aDiskHistory = a[7];
}
for (var iDrive = 0; iDrive < this.aDrives.length; iDrive++) {
var drive = this.aDrives[iDrive];
if (drive === undefined) {
drive = this.aDrives[iDrive] = {};
}
if (!this.initDrive(drive, iDrive)) {
fSuccess = false;
}
}
return fSuccess;
}
/**
* saveController()
*
* TODO: Implement.
* Basically, the inverse of initController().
*
* @this {RK11}
* @return {Array}
*/
saveController()
{
return [];
return [
this.regRKDS,
this.regRKER,
this.regRKCS,
this.regRKWC,
this.regRKBA,
this.regRKDA,
this.regRKDB,
this.saveDeltas()
];
}
/**
* saveDeltas()
*
* This returns an array of entries, one for each disk image we've ever mounted, including any deltas; ie:
*
* [name, path, deltas]
*
* aDiskHistory contains exactly that, except that deltas may not be up-to-date for any currently mounted
* disk image(s), so we call updateHistory() for all those disks, and then aDiskHistory is ready to be saved.
*
* @this {RK11}
* @return {Array}
*/
saveDeltas()
{
for (var iDrive = 0; iDrive < this.aDrives.length; iDrive++) {
var drive = this.aDrives[iDrive];
if (drive.disk) {
this.updateDiskHistory(drive.sDiskName, drive.sDiskPath, drive.disk);
}
}
return this.aDiskHistory;
}
/**
* initDrive(drive, iDrive, data)
*
* TODO: Consider a separate Drive class that any drive controller can use, since there's a lot of commonality
* between the drive objects created by disk controllers. This will clean up overall drive management and allow
* us to factor out some common Drive methods.
*
* Also, either use or eliminate the data parameter; if there are no drive properties that need to be preserved
* across save/restore, then eliminate it.
*
* @this {RK11}
* @param {Object} drive
* @param {number} iDrive
* @param {Array|undefined} [data]
* @return {boolean} true if successful, false if failure
*/
initDrive(drive, iDrive, data)
{
var i = 0;
var fSuccess = true;
drive.iDrive = iDrive;
drive.name = this.idComponent;
drive.fBusy = drive.fLocal = false;
/*
* NOTE: We initialize the following drive properties to their MAXIMUMs; disks may have
* these or SMALLER values (subject to the limits of what the controller supports, of course).
*/
drive.nCylinders = 203;
drive.nHeads = 2;
drive.nSectors = 12;
drive.cbSector = 512;
drive.fRemovable = true;
/*
* The next group of properties are set by various controller command sequences.
*/
drive.bHead = 0;
drive.bCylinder = 0;
drive.bSector = 1;
drive.bSectorEnd = drive.nSectors; // aka EOT
drive.nBytes = drive.cbSector;
/*
* The next group of properties are managed by worker functions (eg, doRead()) to maintain state across DMA requests.
*/
drive.ibSector = 0;
drive.sector = null;
if (!drive.disk) drive.sDiskPath = ""; // ensure this is initialized to a default that displayDisk() can deal with
drive.status = RK11.RKDS.RK05 | RK11.RKDS.SOK | RK11.RKDS.RRDY;
return fSuccess;
}
/**
@ -637,8 +790,6 @@ class RK11 extends Component {
*/
finishLoadDrive(drive, disk, sDiskName, sDiskPath, fAutoMount)
{
var aDiskInfo;
drive.fBusy = false;
if (disk) {
@ -669,23 +820,7 @@ class RK11 extends Component {
* of a local disk image, it will map all such disks to "Local Disk", and any attempt to "Mount" such
* a disk, will essentially result in a "Disk not found" error.
*/
// this.addDiskHistory(sDiskName, sDiskPath, disk);
/*
* For a local disk (ie, one loaded via mountDrive()), the disk.restore() performed by addDiskHistory()
* may have altered the disk geometry, so refresh the disk info.
*/
// aDiskInfo = disk.info();
/*
* Clearly, a successful mount implies a disk change, and I suppose that, technically, an *unsuccessful*
* mount should imply the same, but what would the real-world analog be? Inserting a piece of cardboard
* instead of an actual disk? In any case, if we can do the user a favor by pretending (as far as the
* disk change line is concerned) that an unsuccessful mount never happened, let's do it.
*
* Successful unmounts are a different story, however; those *do* trigger a change. See unloadDrive().
*/
// this.regInput |= FDC.REG_INPUT.DISK_CHANGE;
this.addDiskHistory(sDiskName, sDiskPath, disk);
/*
* With the addition of notify(), users are now "alerted" whenever a disk has finished loading;
@ -826,7 +961,7 @@ class RK11 extends Component {
/*
* Before we toss the disk's information, capture any deltas that may have occurred.
*/
// this.updateDiskHistory(drive.sDiskName, drive.sDiskPath, drive.disk);
this.updateDiskHistory(drive.sDiskName, drive.sDiskPath, drive.disk);
drive.sDiskName = "";
drive.sDiskPath = "";
@ -851,7 +986,7 @@ class RK11 extends Component {
*/
unloadAllDrives(fDiscard)
{
// if (fDiscard) this.aDiskHistory = [];
if (fDiscard) this.aDiskHistory = [];
for (var iDrive = 0; iDrive < this.aDrives.length; iDrive++) {
this.unloadDrive(iDrive, true);
@ -859,89 +994,85 @@ class RK11 extends Component {
}
/**
* initController(data)
* addDiskHistory(sDiskName, sDiskPath, disk)
*
* @this {RK11}
* @param {Array} [data]
* @return {boolean} true if successful, false if failure
* @param {string} sDiskName
* @param {string} sDiskPath
* @param {DiskPDP11} disk containing corresponding disk image
*/
initController(data)
addDiskHistory(sDiskName, sDiskPath, disk)
{
var i = 0;
if (!data) data = [];
this.regRKDS = data[i++] || (RK11.RKDS.RK05 | RK11.RKDS.SOK | RK11.RKDS.RRDY);
this.regRKER = data[i++] || 0;
this.regRKCS = data[i++] || (RK11.RKCS.CRDY);
this.regRKWC = data[i++] || 0;
this.regRKBA = data[i++] || 0;
this.regRKDA = data[i++] || 0;
this.regRKDB = data[i] || 0; // TODO: Determine if there's anything we should be doing to the RKDB register
var fSuccess = true;
for (var iDrive = 0; iDrive < this.aDrives.length; iDrive++) {
var drive = this.aDrives[iDrive];
if (drive === undefined) {
drive = this.aDrives[iDrive] = {};
}
if (!this.initDrive(drive, iDrive)) {
fSuccess = false;
var i;
this.assert(!!sDiskPath);
for (i = 0; i < this.aDiskHistory.length; i++) {
if (this.aDiskHistory[i][1] == sDiskPath) {
var nChanges = disk.restore(this.aDiskHistory[i][2]);
if (DEBUG && this.messageEnabled()) {
this.printMessage("disk '" + sDiskName + "' restored from history (" + nChanges + " changes)");
}
return;
}
}
return fSuccess;
if (DEBUG && this.messageEnabled()) {
this.printMessage("disk '" + sDiskName + "' added to history (nothing to restore)");
}
this.aDiskHistory[i] = [sDiskName, sDiskPath, []];
}
/**
* initDrive(drive, iDrive, data)
*
* TODO: Consider a separate Drive class that any drive controller can use, since there's a lot of commonality
* between the drive objects created by disk controllers. This will clean up overall drive management and allow
* us to factor out some common Drive methods.
* removeDiskHistory(sDiskName, sDiskPath)
*
* @this {RK11}
* @param {Object} drive
* @param {number} iDrive
* @param {Array|undefined} [data]
* @return {boolean} true if successful, false if failure
* @param {string} sDiskName
* @param {string} sDiskPath
*/
initDrive(drive, iDrive, data)
removeDiskHistory(sDiskName, sDiskPath)
{
var i = 0;
var fSuccess = true;
drive.iDrive = iDrive;
drive.name = this.idComponent;
drive.fBusy = drive.fLocal = false;
var i;
for (i = 0; i < this.aDiskHistory.length; i++) {
if (this.aDiskHistory[i][1] == sDiskPath) {
this.aDiskHistory.splice(i, 1);
if (DEBUG && this.messageEnabled()) {
this.printMessage("disk '" + sDiskName + "' removed from history");
}
return;
}
}
if (DEBUG && this.messageEnabled()) {
this.printMessage("unable to remove disk '" + sDiskName + "' from history (" + sDiskPath + ")");
}
}
/**
* updateDiskHistory(sDiskName, sDiskPath, disk)
*
* @this {RK11}
* @param {string} sDiskName
* @param {string} sDiskPath
* @param {DiskPDP11} disk containing corresponding disk image, with possible deltas
*/
updateDiskHistory(sDiskName, sDiskPath, disk)
{
var i;
for (i = 0; i < this.aDiskHistory.length; i++) {
if (this.aDiskHistory[i][1] == sDiskPath) {
this.aDiskHistory[i][2] = disk.save();
if (DEBUG && this.messageEnabled()) {
this.printMessage("disk '" + sDiskName + "' updated in history");
}
return;
}
}
/*
* NOTE: We initialize the following drive properties to their MAXIMUMs; disks may have
* these or SMALLER values (subject to the limits of what the controller supports, of course).
* I used to report this as an error (at least in the DEBUG release), but it's no longer really
* an error, because if we're trying to re-mount a clean copy of a disk, we toss its history, then
* unload, and then reload/remount. And since unloadDrive's normal behavior is to call updateDiskHistory()
* before unloading, the fact that the disk is no longer listed here can't be treated as an error.
*/
drive.nCylinders = 203;
drive.nHeads = 2;
drive.nSectors = 12;
drive.cbSector = 512;
drive.fRemovable = true;
/*
* The next group of properties are set by various controller command sequences.
*/
drive.bHead = 0;
drive.bCylinder = 0;
drive.bSector = 1;
drive.bSectorEnd = drive.nSectors; // aka EOT
drive.nBytes = drive.cbSector;
/*
* The next group of properties are managed by worker functions (eg, doRead()) to maintain state across DMA requests.
*/
drive.ibSector = 0;
drive.sector = null;
if (!drive.disk) drive.sDiskPath = ""; // ensure this is initialized to a default that displayDisk() can deal with
drive.status = RK11.RKDS.RK05 | RK11.RKDS.SOK | RK11.RKDS.RRDY;
return fSuccess;
if (DEBUG && this.messageEnabled()) {
this.printMessage("unable to update disk '" + sDiskName + "' in history (" + sDiskPath + ")");
}
}
/**

View file

@ -485,32 +485,48 @@ class SerialPortPDP11 extends Component {
}
/**
* initState(data)
* initState(a)
*
* @this {SerialPortPDP11}
* @param {Array} [data]
* @param {Array} [a]
* @return {boolean} true if successful, false if failure
*/
initState(data)
initState(a)
{
this.regRBUF = 0;
this.regRCSR = PDP11.DL11.RCSR.CTS; // TODO: I didn't use to set this initially; is this wise?
this.regXCSR = PDP11.DL11.XCSR.READY;
this.abReceive = [];
if (!a) {
a = [0, PDP11.DL11.RCSR.CTS, PDP11.DL11.XCSR.READY, this.abReceive];
}
/*
* ES6 ALERT: Love these destructuring assignments, which make it easy to perform the
* inverse of what save() does when it collects a bunch of object properties into an array.
*/
[
this.regRBUF,
this.regRCSR,
this.regXCSR,
this.abReceive
] = a;
return true;
}
/**
* saveRegisters()
*
* TODO: Implement.
* Basically, the inverse of initState().
*
* @this {SerialPortPDP11}
* @return {Array}
*/
saveRegisters()
{
return [];
return [
this.regRBUF,
this.regRCSR,
this.regXCSR,
this.abReceive
];
}
/**