Merge branch 'next-release'
This commit is contained in:
commit
a7185e660a
331 changed files with 14872 additions and 493 deletions
|
|
@ -479,8 +479,8 @@ SerialPort8080.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
* receiveStatus(pins): called when our control signals have changed; aliased internally to updateStatus(pins)
|
||||
*
|
||||
* For now, we're not going to worry about communication in the other direction, because when the target component
|
||||
* performs its own initConnection(), it will find our receiveData() function, at which point communication in both
|
||||
* directions should be established.
|
||||
* performs its own initConnection(), it will find our receiveData() and receiveStatus() functions, at which point
|
||||
* communication in both directions should be established, and the circle of life complete.
|
||||
*
|
||||
* For added robustness, if the target machine initializes much more slowly than we do, and our connection attempt
|
||||
* fails, that's OK, because when it finally initializes, its initConnection() will call our initConnection();
|
||||
|
|
|
|||
|
|
@ -517,7 +517,7 @@ FDC.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
|||
};
|
||||
return true;
|
||||
|
||||
case "loadDrive":
|
||||
case "loadDisk":
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
control.onclick = function onClickLoadDrive(event) {
|
||||
|
|
@ -530,7 +530,7 @@ FDC.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
|||
};
|
||||
return true;
|
||||
|
||||
case "saveDrive":
|
||||
case "saveDisk":
|
||||
/*
|
||||
* Yes, technically, this feature does not require "Local disk support" (which is really a reference
|
||||
* to FileReader support), but since fLocalDisks is also false for all mobile devices, and since there
|
||||
|
|
@ -575,7 +575,7 @@ FDC.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
|||
};
|
||||
return true;
|
||||
|
||||
case "mountDrive":
|
||||
case "mountDisk":
|
||||
if (!this.fLocalDisks) {
|
||||
if (DEBUG) this.log("Local disk support not available");
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -568,7 +568,7 @@ HDC.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
|||
var drive = hdc.aDrives && hdc.aDrives[iDrive];
|
||||
if (drive && drive.disk) {
|
||||
/*
|
||||
* Note the similarity (and hence factoring opportunity) between this code and the FDC's "saveDrive" binding.
|
||||
* Note the similarity (and hence factoring opportunity) between this code and the FDC's "saveDisk" binding.
|
||||
*/
|
||||
var disk = drive.disk;
|
||||
if (DEBUG) hdc.println("saving disk " + disk.sDiskPath + "...");
|
||||
|
|
|
|||
|
|
@ -477,8 +477,8 @@ SerialPort.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
* receiveStatus(pins): called when our control signals have changed; aliased internally to updateStatus(pins)
|
||||
*
|
||||
* For now, we're not going to worry about communication in the other direction, because when the target component
|
||||
* performs its own initConnection(), it will find our receiveData() function, at which point communication in both
|
||||
* directions should be established.
|
||||
* performs its own initConnection(), it will find our receiveData() and receiveStatus() functions, at which point
|
||||
* communication in both directions should be established, and the circle of life complete.
|
||||
*
|
||||
* For added robustness, if the target machine initializes much more slowly than we do, and our connection attempt
|
||||
* fails, that's OK, because when it finally initializes, its initConnection() will call our initConnection();
|
||||
|
|
|
|||
|
|
@ -203,6 +203,13 @@ CPUPDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
this.flags.autoStart = (sAutoStart == "true"? true : (sAutoStart == "false"? false : !!sAutoStart));
|
||||
}
|
||||
|
||||
/*
|
||||
* Start running automatically on power-up, assuming there's no Debugger and no "Run" button.
|
||||
*/
|
||||
if ((!DEBUGGER || !this.dbg) && this.bindings["run"] === undefined) {
|
||||
this.flags.autoStart = true;
|
||||
}
|
||||
|
||||
this.setReady();
|
||||
};
|
||||
|
||||
|
|
@ -328,10 +335,10 @@ CPUPDP11.prototype.powerDown = function(fSave, fShutdown)
|
|||
*/
|
||||
CPUPDP11.prototype.autoStart = function()
|
||||
{
|
||||
/*
|
||||
* Start running automatically on power-up, assuming there's no Debugger and no "Run" button
|
||||
*/
|
||||
if (this.flags.autoStart || (!DEBUGGER || !this.dbg) && this.bindings["run"] === undefined) {
|
||||
if (this.flags.running) {
|
||||
return true;
|
||||
}
|
||||
if (this.flags.autoStart) {
|
||||
/*
|
||||
* We used to also set fUpdateFocus when calling startCPU(), on the assumption that in the "auto-starting"
|
||||
* context, a machine without focus is like a day without sunshine, but in reality, focus should only be
|
||||
|
|
|
|||
|
|
@ -481,13 +481,13 @@ CPUStatePDP11.prototype.setMMR3 = function(newMMR3)
|
|||
};
|
||||
|
||||
/**
|
||||
* setReset(addr, fReset)
|
||||
* setReset(addr, fStart)
|
||||
*
|
||||
* @this {CPUStatePDP11}
|
||||
* @param {number} addr
|
||||
* @param {boolean} [fReset] (true if called in the context of a complete reset, obviating the need to notify the Debugger)
|
||||
* @param {boolean|undefined} fStart (true if a "startable" image was just loaded, false if not)
|
||||
*/
|
||||
CPUStatePDP11.prototype.setReset = function(addr, fReset)
|
||||
CPUStatePDP11.prototype.setReset = function(addr, fStart)
|
||||
{
|
||||
this.addrReset = addr;
|
||||
|
||||
|
|
@ -495,18 +495,34 @@ CPUStatePDP11.prototype.setReset = function(addr, fReset)
|
|||
this.setPC(addr);
|
||||
this.setPSW(0);
|
||||
|
||||
if (!fReset && this.dbg) {
|
||||
if (fStart) {
|
||||
/*
|
||||
* TODO: Review the decision to always stop the CPU if the Debugger is loaded. Note that
|
||||
* when stopCPU() stops a running CPU, the Debugger gets notified, so again, no need to notify it here.
|
||||
*
|
||||
* TODO: There are more serious problems to deal with if another component is slamming a new PC down
|
||||
* the CPU's throat (presumably while also dropping some new code into RAM) while the CPU was still running;
|
||||
* we should probably force a complete reset if the CPU is running, but for now, it's up to the user
|
||||
* to hit the reset button themselves.
|
||||
* TODO: Review. I'm zeroing R2-R5 in the fStart case (ie, for boot code) simply because that's what I
|
||||
* saw the PDP-11 Boot Monitor doing (see /apps/pdp11/boot/monitor/BOOTMON.mac).
|
||||
*/
|
||||
if (!this.stopCPU()) this.dbg.updateStatus();
|
||||
for (var i = 2; i <= 5; i++) {
|
||||
this.regsGen[i] = 0;
|
||||
}
|
||||
if (!this.isRunning()) this.startCPU();
|
||||
}
|
||||
else {
|
||||
if (this.dbg) {
|
||||
/*
|
||||
* TODO: Review the decision to always stop the CPU if the Debugger is loaded. Note that
|
||||
* when stopCPU() stops a running CPU, the Debugger gets notified, so again, no need to notify it here.
|
||||
*
|
||||
* TODO: There are more serious problems to deal with if another component is slamming a new PC down
|
||||
* the CPU's throat (presumably while also dropping some new code into RAM) while the CPU was still running;
|
||||
* we should probably force a complete reset if the CPU is running, but for now, it's up to the user
|
||||
* to hit the reset button themselves.
|
||||
*/
|
||||
if (!this.stopCPU()) this.dbg.updateStatus();
|
||||
}
|
||||
else if (fStart === false) {
|
||||
this.stopCPU();
|
||||
}
|
||||
}
|
||||
if (!this.isRunning() && this.panel) this.panel.stop();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1437,7 +1437,7 @@ if (DEBUGGER) {
|
|||
* is calling us in a loop, in which case it will perform its own updateDisplays() when it's done.
|
||||
*/
|
||||
if (fUpdateDisplays !== false) {
|
||||
if (this.panel && this.panel.stop) this.panel.stop();
|
||||
if (this.panel) this.panel.stop();
|
||||
this.cmp.updateDisplays(-1);
|
||||
}
|
||||
|
||||
|
|
@ -3924,7 +3924,7 @@ if (DEBUGGER) {
|
|||
* displayLiveRegs enabled, so once the repeat count has been exhausted, we must perform
|
||||
* a final updateDisplays().
|
||||
*/
|
||||
if (dbg.panel && dbg.panel.stop) dbg.panel.stop();
|
||||
if (dbg.panel) dbg.panel.stop();
|
||||
dbg.cmp.updateDisplays(-1);
|
||||
dbg.setBusy(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ if (NODE) {
|
|||
* The PC11 component has the following component-specific (parms) properties:
|
||||
*
|
||||
* autoMount: a JSON-encoded object containing 'name' and 'path' properties, describing a
|
||||
* tape resource to automatically attach at startup (only the "attach" operation is supported
|
||||
* for autoMount; if you want to "load" a tape image directly into RAM at startup, you must
|
||||
* tape resource to automatically load at startup (only the "load" operation is supported
|
||||
* for autoMount; if you want to "read" a tape image directly into RAM at startup, you must
|
||||
* ask the RAM component to do that).
|
||||
*
|
||||
* baudReceive: the default number of bits/second that the device should receive data at;
|
||||
|
|
@ -67,11 +67,13 @@ function PC11(parms)
|
|||
{
|
||||
Component.call(this, "PC11", parms, PC11);
|
||||
|
||||
this.sDevice = "PTR"; // TODO: Make the device name configurable
|
||||
|
||||
/*
|
||||
* We record any 'autoMount' object now, but we no longer parse it until initBus(), because the
|
||||
* Computer's getMachineParm() service may have an override for us.
|
||||
*/
|
||||
this.configMount = parms['autoMount'] || null;
|
||||
this.configMount = this.parseConfig(parms['autoMount']);
|
||||
this.cAutoMount = 0;
|
||||
this.nBaudReceive = parms['baudReceive'] || PDP11.PC11.PRS.BAUD;
|
||||
|
||||
|
|
@ -124,6 +126,30 @@ PC11.CSSCLASS = {
|
|||
PROGRESS_BAR: PDP11.CSSCLASS + "-progress-bar"
|
||||
};
|
||||
|
||||
/**
|
||||
* parseConfig(config)
|
||||
*
|
||||
* @this {PC11}
|
||||
* @param {*} config
|
||||
* @return {*}
|
||||
*/
|
||||
PC11.prototype.parseConfig = function(config)
|
||||
{
|
||||
if (config && typeof config == "string") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be right here, where we're parsing
|
||||
* this JSON-encoded data.
|
||||
*/
|
||||
config = eval("(" + config + ")");
|
||||
} catch (e) {
|
||||
Component.error(this.type + " auto-mount error: " + e.message + " (" + config + ")");
|
||||
config = null;
|
||||
}
|
||||
}
|
||||
return config || {};
|
||||
};
|
||||
|
||||
/**
|
||||
* setBinding(sType, sBinding, control, sValue)
|
||||
*
|
||||
|
|
@ -170,18 +196,18 @@ PC11.prototype.setBinding = function(sType, sBinding, control, sValue)
|
|||
return true;
|
||||
|
||||
/*
|
||||
* "loadTape" operation must do pretty much everything that the "attachTape" does, but whereas the attach
|
||||
* operation records the bytes in aTapeData, the load operation stuffs them directly into the machine's memory;
|
||||
* "readTape" operation must do pretty much everything that the "loadTape" does, but whereas the load
|
||||
* operation records the bytes in aTapeData, the read operation stuffs them directly into the machine's memory;
|
||||
* the former sets nTapeTarget to TARGET.READER, while the latter sets it to TARGET.MEMORY.
|
||||
*/
|
||||
case "loadTape":
|
||||
case "readTape":
|
||||
nTapeTarget = PC11.TARGET.MEMORY;
|
||||
/* falls through */
|
||||
|
||||
case "attachTape":
|
||||
case "loadTape":
|
||||
if (!nTapeTarget) nTapeTarget = PC11.TARGET.READER;
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = function onClickLoadTape(event) {
|
||||
control.onclick = function onClickReadTape(event) {
|
||||
var controlTapes = pc11.bindings["listTapes"];
|
||||
if (controlTapes) {
|
||||
var sTapeName = controlTapes.options[controlTapes.selectedIndex].text;
|
||||
|
|
@ -263,20 +289,15 @@ PC11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
|
||||
var pc11 = this;
|
||||
|
||||
this.configMount = this.cmp.getMachineParm('autoMount') || this.configMount;
|
||||
var configMount = this.parseConfig(this.cmp.getMachineParm('autoMount'));
|
||||
|
||||
if (this.configMount) {
|
||||
if (typeof this.configMount == "string") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be right here, where we're parsing
|
||||
* this JSON-encoded data.
|
||||
*/
|
||||
this.configMount = eval("(" + this.configMount + ")");
|
||||
} catch (e) {
|
||||
Component.error("PC11 auto-mount error: " + e.message + " (" + this.configMount + ")");
|
||||
this.configMount = null;
|
||||
}
|
||||
/*
|
||||
* Add only devices from the machine-wide autoMount configuration that match devices managed by this component.
|
||||
*/
|
||||
if (configMount) {
|
||||
for (var sDevice in configMount) {
|
||||
if (sDevice != this.sDevice) continue;
|
||||
this.configMount[sDevice] = configMount[sDevice];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -332,7 +353,7 @@ PC11.prototype.powerDown = function(fSave, fShutdown)
|
|||
/**
|
||||
* reset()
|
||||
*
|
||||
* TODO: Consider making our reset() handler ALSO restore the original attached tape, in much the same
|
||||
* TODO: Consider making our reset() handler ALSO restore the original loaded tape, in much the same
|
||||
* way the RAM component now restores the original predefined memory or tape image after resetting the RAM.
|
||||
*
|
||||
* @this {PC11}
|
||||
|
|
@ -353,9 +374,10 @@ PC11.prototype.reset = function()
|
|||
PC11.prototype.autoMount = function(fRemount)
|
||||
{
|
||||
if (!fRemount) this.cAutoMount = 0;
|
||||
if (this.configMount) {
|
||||
var sTapePath = this.configMount['path'] || "";
|
||||
var sTapeName = this.configMount['name'] || this.findTape(sTapePath);
|
||||
var configMount = this.configMount[this.sDevice];
|
||||
if (configMount) {
|
||||
var sTapePath = configMount['path'] || "";
|
||||
var sTapeName = configMount['name'] || this.findTape(sTapePath);
|
||||
if (sTapePath && sTapeName) {
|
||||
/*
|
||||
* TODO: Provide a way to autoMount tapes into MEMORY as well as READER.
|
||||
|
|
@ -460,7 +482,7 @@ PC11.prototype.loadTape = function(sTapeName, sTapePath, nTapeTarget, fAutoMount
|
|||
* Now that we're calling parseTape() again (so that the current tape can either be restarted on
|
||||
* the reader or reloaded into RAM), we can also rely on it to display an appropriate status message, too.
|
||||
*
|
||||
* this.status(this.nTapeTarget == PC11.TARGET.READER? "tape attached" : "tape loaded");
|
||||
* this.status(this.nTapeTarget == PC11.TARGET.READER? "tape loaded" : "tape read");
|
||||
*/
|
||||
this.parseTape(this.sTapeName, this.sTapePath, this.nTapeTarget, this.aBytes, this.addrLoad, this.addrExec);
|
||||
}
|
||||
|
|
@ -705,23 +727,28 @@ PC11.prototype.parseTape = function(sTapeName, sTapePath, nTapeTarget, aBytes, a
|
|||
*
|
||||
* For example, the "Absolute Loader" tape is NOT itself in the Absolute Loader format. You just have
|
||||
* to know that in order to load that tape, you must first load the appropriate "Bootstrap Loader" (which
|
||||
* DOES include its own hard-coded load address), attach the "Absolute Loader" tape, and then run the
|
||||
* DOES include its own hard-coded load address), load the "Absolute Loader" tape, and then run the
|
||||
* "Bootstrap Loader".
|
||||
*/
|
||||
if (!this.ram || !this.ram.loadImage(aBytes, addrLoad, addrExec)) {
|
||||
this.sTapeName = "";
|
||||
this.sTapePath = "";
|
||||
this.sTapeSource = PC11.SOURCE.NONE;
|
||||
this.nTapeTarget = PC11.TARGET.NONE;
|
||||
this.notice('No load address available for tape "' + sTapeName + '"');
|
||||
if (!this.ram || !this.ram.loadImage(aBytes, addrLoad, addrExec, null, false)) {
|
||||
/*
|
||||
* This doesn't seem to serve any purpose, other than to be annoying, because perhaps you accidentally
|
||||
* clicked "Read" instead of "Load"....
|
||||
*
|
||||
* this.sTapeName = "";
|
||||
* this.sTapePath = "";
|
||||
* this.sTapeSource = PC11.SOURCE.NONE;
|
||||
* this.nTapeTarget = PC11.TARGET.NONE;
|
||||
*/
|
||||
this.notice('No valid memory address for tape "' + sTapeName + '"');
|
||||
return;
|
||||
}
|
||||
this.status('Loaded tape "' + sTapeName + '"');
|
||||
this.status('Read tape "' + sTapeName + '"');
|
||||
return;
|
||||
}
|
||||
this.iTapeData = 0;
|
||||
this.aTapeData = aBytes;
|
||||
this.status('Attached tape "' + sTapeName + '"');
|
||||
this.status('Loaded tape "' + sTapeName + '"');
|
||||
this.displayProgress(0);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ RAMPDP11.prototype.reset = function()
|
|||
};
|
||||
|
||||
/**
|
||||
* loadImage(aBytes, addrLoad, addrExec, addrInit, fReset)
|
||||
* loadImage(aBytes, addrLoad, addrExec, addrInit, fStart)
|
||||
*
|
||||
* If the array contains a PAPER tape image in the "Absolute Format," load it as specified
|
||||
* by the format; otherwise, load it as-is using the address(es) supplied.
|
||||
|
|
@ -270,11 +270,12 @@ RAMPDP11.prototype.reset = function()
|
|||
* @param {number|null} [addrLoad]
|
||||
* @param {number|null} [addrExec] (this CAN override any starting address INSIDE the image)
|
||||
* @param {number|null} [addrInit]
|
||||
* @param {boolean} [fReset]
|
||||
* @param {boolean} [fStart]
|
||||
* @return {boolean} (true if loaded, false if not)
|
||||
*/
|
||||
RAMPDP11.prototype.loadImage = function(aBytes, addrLoad, addrExec, addrInit, fReset)
|
||||
RAMPDP11.prototype.loadImage = function(aBytes, addrLoad, addrExec, addrInit, fStart)
|
||||
{
|
||||
var fStop = false;
|
||||
var fLoaded = false;
|
||||
/*
|
||||
* Data on tapes in the "Absolute Format" is organized into blocks; each block begins with
|
||||
|
|
@ -343,7 +344,7 @@ RAMPDP11.prototype.loadImage = function(aBytes, addrLoad, addrExec, addrInit, fR
|
|||
}
|
||||
if (!cbData) {
|
||||
if (addr & 0x1) {
|
||||
this.cpu.stopCPU();
|
||||
fStop = true;
|
||||
} else {
|
||||
if (addrExec == null) addrExec = addr;
|
||||
}
|
||||
|
|
@ -375,7 +376,13 @@ RAMPDP11.prototype.loadImage = function(aBytes, addrLoad, addrExec, addrInit, fR
|
|||
* image, but we know that the directions for that diagnostic say to "Start and Restart at 200",
|
||||
* so we have manually inserted an "exec":128 in the JSON containing the image.
|
||||
*/
|
||||
if (addrExec != null) this.cpu.setReset(addrExec, fReset);
|
||||
if (addrExec == null || fStop) {
|
||||
this.cpu.stopCPU();
|
||||
fStart = false;
|
||||
}
|
||||
if (addrExec != null) {
|
||||
this.cpu.setReset(addrExec, fStart);
|
||||
}
|
||||
}
|
||||
return fLoaded;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ function RK11(parms)
|
|||
* We record any 'autoMount' object now, but we no longer parse it until initBus(),
|
||||
* because the Computer's getMachineParm() service may have an override for us.
|
||||
*/
|
||||
this.configMount = parms['autoMount'] || {};
|
||||
this.configMount = this.parseConfig(parms['autoMount']);
|
||||
this.cAutoMount = 0;
|
||||
|
||||
/*
|
||||
|
|
@ -88,6 +88,30 @@ RK11.SOURCE = {
|
|||
REMOTE: "??"
|
||||
};
|
||||
|
||||
/**
|
||||
* parseConfig(config)
|
||||
*
|
||||
* @this {RK11}
|
||||
* @param {*} config
|
||||
* @return {*}
|
||||
*/
|
||||
RK11.prototype.parseConfig = function(config)
|
||||
{
|
||||
if (config && typeof config == "string") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be right here, where we're parsing
|
||||
* this JSON-encoded data.
|
||||
*/
|
||||
config = eval("(" + config + ")");
|
||||
} catch (e) {
|
||||
Component.error(this.type + " auto-mount error: " + e.message + " (" + config + ")");
|
||||
config = null;
|
||||
}
|
||||
}
|
||||
return config || {};
|
||||
};
|
||||
|
||||
/**
|
||||
* setBinding(sType, sBinding, control, sValue)
|
||||
*
|
||||
|
|
@ -109,7 +133,7 @@ RK11.prototype.setBinding = function(sType, sBinding, control, sValue)
|
|||
|
||||
control.onchange = function onChangeListDisks(event) {
|
||||
var controlDesc = rk11.bindings["descDisk"];
|
||||
var controlOption = control.options[control.selectedIndex];
|
||||
var controlOption = control.options && control.options[control.selectedIndex];
|
||||
if (controlDesc && controlOption) {
|
||||
var dataValue = {};
|
||||
var sValue = controlOption.getAttribute("data-value");
|
||||
|
|
@ -143,12 +167,12 @@ RK11.prototype.setBinding = function(sType, sBinding, control, sValue)
|
|||
};
|
||||
return true;
|
||||
|
||||
case "loadDrive":
|
||||
case "loadDisk":
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
control.onclick = function onClickLoadDrive(event) {
|
||||
var controlDisks = rk11.bindings["listDisks"];
|
||||
if (controlDisks) {
|
||||
if (controlDisks && controlDisks.options) {
|
||||
var sDiskName = controlDisks.options[controlDisks.selectedIndex].text;
|
||||
var sDiskPath = controlDisks.value;
|
||||
rk11.loadSelectedDrive(sDiskName, sDiskPath);
|
||||
|
|
@ -156,7 +180,15 @@ RK11.prototype.setBinding = function(sType, sBinding, control, sValue)
|
|||
};
|
||||
return true;
|
||||
|
||||
case "saveDrive":
|
||||
case "bootDisk":
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
control.onclick = function onClickBootDisk(event) {
|
||||
rk11.bootSelectedDisk();
|
||||
};
|
||||
return true;
|
||||
|
||||
case "saveDisk":
|
||||
/*
|
||||
* Yes, technically, this feature does not require "Local disk support" (which is really a reference
|
||||
* to FileReader support), but since fLocalDisks is also false for all mobile devices, and since there
|
||||
|
|
@ -201,7 +233,7 @@ RK11.prototype.setBinding = function(sType, sBinding, control, sValue)
|
|||
};
|
||||
return true;
|
||||
|
||||
case "mountDrive":
|
||||
case "mountDisk":
|
||||
if (!this.fLocalDisks) {
|
||||
if (DEBUG) this.log("Local disk support not available");
|
||||
/*
|
||||
|
|
@ -263,21 +295,7 @@ RK11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
|
||||
var configMount = this.cmp.getMachineParm('autoMount');
|
||||
if (configMount) {
|
||||
if (typeof configMount == "string") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be right here, where we're parsing
|
||||
* this JSON-encoded data.
|
||||
*/
|
||||
configMount = eval("(" + configMount + ")");
|
||||
} catch (e) {
|
||||
Component.error(this.type + " auto-mount error: " + e.message + " (" + configMount + ")");
|
||||
configMount = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
var configMount = this.parseConfig(this.cmp.getMachineParm('autoMount'));
|
||||
|
||||
/*
|
||||
* Add only drives from the machine-wide autoMount configuration that match drives managed by this component.
|
||||
|
|
@ -527,6 +545,39 @@ RK11.prototype.loadSelectedDrive = function(sDiskName, sDiskPath, file)
|
|||
this.loadDrive(iDrive, sDiskName, sDiskPath, false, file);
|
||||
};
|
||||
|
||||
/**
|
||||
* bootSelectedDisk()
|
||||
*
|
||||
* @this {RK11}
|
||||
*/
|
||||
RK11.prototype.bootSelectedDisk = function()
|
||||
{
|
||||
var drive;
|
||||
var controlDrives = this.bindings["listDrives"];
|
||||
var iDrive = controlDrives && str.parseInt(controlDrives.value, 10);
|
||||
|
||||
if (iDrive == null || iDrive < 0 || iDrive >= this.aDrives.length || !(drive = this.aDrives[iDrive])) {
|
||||
this.notice("Unable to boot the selected drive");
|
||||
return;
|
||||
}
|
||||
if (!drive.disk) {
|
||||
this.notice("Load a disk into the drive first");
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* NOTE: We're calling setReset() BEFORE reading the boot code in order to eliminate any side-effects
|
||||
* of the previous state of either the controller OR the CPU; for example, we don't want any previous MMU
|
||||
* or UNIBUS Map registers affecting the simulated readData() call. Also, some boot code (eg, RSTS/E)
|
||||
* expects the controller to be in a READY state; since setReset() triggers a call to our reset() handler,
|
||||
* a READY state is assured, and the readData() call shouldn't do anything to change that.
|
||||
*/
|
||||
this.cpu.setReset(0, true);
|
||||
var err = this.readData(drive, 0, 0, 0, 512, 0);
|
||||
if (err) {
|
||||
this.notice("Unable to read the boot sector (" + err + ")");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* loadDrive(iDrive, sDiskName, sDiskPath, fAutoMount, file)
|
||||
*
|
||||
|
|
@ -639,7 +690,7 @@ RK11.prototype.finishLoadDrive = function onLoadDrive(drive, disk, sDiskName, sD
|
|||
* With the addition of notify(), users are now "alerted" whenever a disk has finished loading;
|
||||
* notify() is selective about its output, using print() if a print window is open, alert() otherwise.
|
||||
*/
|
||||
this.notice("Mounted disk \"" + sDiskName + "\" in drive " + this.getDriveName(drive.iDrive), drive.fAutoMount || fAutoMount);
|
||||
this.notice("Loaded disk \"" + sDiskName + "\" in drive " + this.getDriveName(drive.iDrive), drive.fAutoMount || fAutoMount);
|
||||
|
||||
/*
|
||||
* Since you usually want the Computer to have focus again after loading a new disk, let's try automatically
|
||||
|
|
@ -941,6 +992,7 @@ RK11.prototype.processCommand = function()
|
|||
|
||||
case PDP11.RK11.FUNC.WRITE:
|
||||
if (!sFunc) sFunc = "WRITE";
|
||||
if (!fnReadWrite) fnReadWrite = this.writeData;
|
||||
|
||||
iCylinder = (this.regRKDA & PDP11.RK11.RKDA.CA) >> PDP11.RK11.RKDA.SHIFT.CA;
|
||||
iHead = (this.regRKDA & PDP11.RK11.RKDA.HS) >> PDP11.RK11.RKDA.SHIFT.HS;
|
||||
|
|
@ -950,8 +1002,6 @@ RK11.prototype.processCommand = function()
|
|||
|
||||
if (this.messageEnabled()) this.printMessage(this.type + ": " + sFunc + "(" + iCylinder + ":" + iHead + ":" + iSector + ") @" + str.toOct(addr) + "-" + str.toOct(addr + ((nWords - 1) << 1)), true);
|
||||
|
||||
if (!fnReadWrite) fnReadWrite = this.writeData;
|
||||
|
||||
if (iCylinder >= drive.nCylinders) {
|
||||
this.regRKER |= PDP11.RK11.RKER.DRE | PDP11.RK11.RKER.NXC;
|
||||
this.regRKCS |= PDP11.RK11.RKCS.HE | PDP11.RK11.RKCS.ERR;
|
||||
|
|
@ -1023,9 +1073,9 @@ RK11.prototype.endReadWrite = function(err, iCylinder, iHead, iSector, nWords, a
|
|||
* @param {number} iSector
|
||||
* @param {number} nWords
|
||||
* @param {number} addr
|
||||
* @param {boolean} fCheck
|
||||
* @param {function(...)} done
|
||||
* @return {boolean} true if complete, false if queued
|
||||
* @param {boolean} [fCheck]
|
||||
* @param {function(...)} [done]
|
||||
* @return {boolean|number} true if complete, false if queued (or if no done() is supplied, the error code, if any)
|
||||
*/
|
||||
RK11.prototype.readData = function(drive, iCylinder, iHead, iSector, nWords, addr, fCheck, done)
|
||||
{
|
||||
|
|
@ -1084,7 +1134,7 @@ RK11.prototype.readData = function(drive, iCylinder, iHead, iSector, nWords, add
|
|||
}
|
||||
}
|
||||
}
|
||||
return done(err, iCylinder, iHead, iSector, nWords, addr);
|
||||
return done? done(err, iCylinder, iHead, iSector, nWords, addr) : err;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1097,9 +1147,9 @@ RK11.prototype.readData = function(drive, iCylinder, iHead, iSector, nWords, add
|
|||
* @param {number} iSector
|
||||
* @param {number} nWords
|
||||
* @param {number} addr
|
||||
* @param {boolean} fCheck
|
||||
* @param {function(...)} done
|
||||
* @return {boolean} true if complete, false if queued
|
||||
* @param {boolean} [fCheck]
|
||||
* @param {function(...)} [done]
|
||||
* @return {boolean|number} true if complete, false if queued (or if no done() is supplied, the error code, if any)
|
||||
*/
|
||||
RK11.prototype.writeData = function(drive, iCylinder, iHead, iSector, nWords, addr, fCheck, done)
|
||||
{
|
||||
|
|
@ -1157,7 +1207,7 @@ RK11.prototype.writeData = function(drive, iCylinder, iHead, iSector, nWords, ad
|
|||
}
|
||||
}
|
||||
}
|
||||
return done(err, iCylinder, iHead, iSector, nWords, addr);
|
||||
return done? done(err, iCylinder, iHead, iSector, nWords, addr) : err;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ function RL11(parms)
|
|||
* We record any 'autoMount' object now, but we no longer parse it until initBus(),
|
||||
* because the Computer's getMachineParm() service may have an override for us.
|
||||
*/
|
||||
this.configMount = parms['autoMount'] || {};
|
||||
this.configMount = this.parseConfig(parms['autoMount']);
|
||||
this.cAutoMount = 0;
|
||||
|
||||
/*
|
||||
|
|
@ -90,6 +90,30 @@ RL11.SOURCE = {
|
|||
REMOTE: "??"
|
||||
};
|
||||
|
||||
/**
|
||||
* parseConfig(config)
|
||||
*
|
||||
* @this {RL11}
|
||||
* @param {*} config
|
||||
* @return {*}
|
||||
*/
|
||||
RL11.prototype.parseConfig = function(config)
|
||||
{
|
||||
if (config && typeof config == "string") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be right here, where we're parsing
|
||||
* this JSON-encoded data.
|
||||
*/
|
||||
config = eval("(" + config + ")");
|
||||
} catch (e) {
|
||||
Component.error(this.type + " auto-mount error: " + e.message + " (" + config + ")");
|
||||
config = null;
|
||||
}
|
||||
}
|
||||
return config || {};
|
||||
};
|
||||
|
||||
/**
|
||||
* setBinding(sType, sBinding, control, sValue)
|
||||
*
|
||||
|
|
@ -111,7 +135,7 @@ RL11.prototype.setBinding = function(sType, sBinding, control, sValue)
|
|||
|
||||
control.onchange = function onChangeListDisks(event) {
|
||||
var controlDesc = rl11.bindings["descDisk"];
|
||||
var controlOption = control.options[control.selectedIndex];
|
||||
var controlOption = control.options && control.options[control.selectedIndex];
|
||||
if (controlDesc && controlOption) {
|
||||
var dataValue = {};
|
||||
var sValue = controlOption.getAttribute("data-value");
|
||||
|
|
@ -145,12 +169,12 @@ RL11.prototype.setBinding = function(sType, sBinding, control, sValue)
|
|||
};
|
||||
return true;
|
||||
|
||||
case "loadDrive":
|
||||
case "loadDisk":
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
control.onclick = function onClickLoadDrive(event) {
|
||||
var controlDisks = rl11.bindings["listDisks"];
|
||||
if (controlDisks) {
|
||||
if (controlDisks && controlDisks.options) {
|
||||
var sDiskName = controlDisks.options[controlDisks.selectedIndex].text;
|
||||
var sDiskPath = controlDisks.value;
|
||||
rl11.loadSelectedDrive(sDiskName, sDiskPath);
|
||||
|
|
@ -158,7 +182,15 @@ RL11.prototype.setBinding = function(sType, sBinding, control, sValue)
|
|||
};
|
||||
return true;
|
||||
|
||||
case "saveDrive":
|
||||
case "bootDisk":
|
||||
this.bindings[sBinding] = control;
|
||||
|
||||
control.onclick = function onClickBootDisk(event) {
|
||||
rl11.bootSelectedDisk();
|
||||
};
|
||||
return true;
|
||||
|
||||
case "saveDisk":
|
||||
/*
|
||||
* Yes, technically, this feature does not require "Local disk support" (which is really a reference
|
||||
* to FileReader support), but since fLocalDisks is also false for all mobile devices, and since there
|
||||
|
|
@ -203,7 +235,7 @@ RL11.prototype.setBinding = function(sType, sBinding, control, sValue)
|
|||
};
|
||||
return true;
|
||||
|
||||
case "mountDrive":
|
||||
case "mountDisk":
|
||||
if (!this.fLocalDisks) {
|
||||
if (DEBUG) this.log("Local disk support not available");
|
||||
/*
|
||||
|
|
@ -265,21 +297,7 @@ RL11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
this.cpu = cpu;
|
||||
this.dbg = dbg;
|
||||
|
||||
var configMount = this.cmp.getMachineParm('autoMount');
|
||||
if (configMount) {
|
||||
if (typeof configMount == "string") {
|
||||
try {
|
||||
/*
|
||||
* The most likely source of any exception will be right here, where we're parsing
|
||||
* this JSON-encoded data.
|
||||
*/
|
||||
configMount = eval("(" + configMount + ")");
|
||||
} catch (e) {
|
||||
Component.error(this.type + " auto-mount error: " + e.message + " (" + configMount + ")");
|
||||
configMount = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
var configMount = this.parseConfig(this.cmp.getMachineParm('autoMount'));
|
||||
|
||||
/*
|
||||
* Add only drives from the machine-wide autoMount configuration that match drives managed by this component.
|
||||
|
|
@ -529,6 +547,39 @@ RL11.prototype.loadSelectedDrive = function(sDiskName, sDiskPath, file)
|
|||
this.loadDrive(iDrive, sDiskName, sDiskPath, false, file);
|
||||
};
|
||||
|
||||
/**
|
||||
* bootSelectedDisk()
|
||||
*
|
||||
* @this {RL11}
|
||||
*/
|
||||
RL11.prototype.bootSelectedDisk = function()
|
||||
{
|
||||
var drive;
|
||||
var controlDrives = this.bindings["listDrives"];
|
||||
var iDrive = controlDrives && str.parseInt(controlDrives.value, 10);
|
||||
|
||||
if (iDrive == null || iDrive < 0 || iDrive >= this.aDrives.length || !(drive = this.aDrives[iDrive])) {
|
||||
this.notice("Unable to boot the selected drive");
|
||||
return;
|
||||
}
|
||||
if (!drive.disk) {
|
||||
this.notice("Load a disk into the drive first");
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* NOTE: We're calling setReset() BEFORE reading the boot code in order to eliminate any side-effects
|
||||
* of the previous state of either the controller OR the CPU; for example, we don't want any previous MMU
|
||||
* or UNIBUS Map registers affecting the simulated readData() call. Also, some boot code (eg, RSTS/E)
|
||||
* expects the controller to be in a READY state; since setReset() triggers a call to our reset() handler,
|
||||
* a READY state is assured, and the readData() call shouldn't do anything to change that.
|
||||
*/
|
||||
this.cpu.setReset(0, true);
|
||||
var err = this.readData(drive, 0, 0, 0, 512, 0);
|
||||
if (err) {
|
||||
this.notice("Unable to read the boot sector (" + err + ")");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* loadDrive(iDrive, sDiskName, sDiskPath, fAutoMount, file)
|
||||
*
|
||||
|
|
@ -639,7 +690,7 @@ RL11.prototype.finishLoadDrive = function onLoadDrive(drive, disk, sDiskName, sD
|
|||
* With the addition of notify(), users are now "alerted" whenever a disk has finished loading;
|
||||
* notify() is selective about its output, using print() if a print window is open, alert() otherwise.
|
||||
*/
|
||||
this.notice("Mounted disk \"" + sDiskName + "\" in drive " + this.getDriveName(drive.iDrive), drive.fAutoMount || fAutoMount);
|
||||
this.notice("Loaded disk \"" + sDiskName + "\" in drive " + this.getDriveName(drive.iDrive), drive.fAutoMount || fAutoMount);
|
||||
|
||||
/*
|
||||
* Since you usually want the Computer to have focus again after loading a new disk, let's try automatically
|
||||
|
|
@ -817,7 +868,7 @@ RL11.prototype.initController = function(data)
|
|||
{
|
||||
var i = 0;
|
||||
if (!data) data = [];
|
||||
this.regRLCS = data[i++] || 0;
|
||||
this.regRLCS = data[i++] || (PDP11.RL11.RLCS.DRDY | PDP11.RL11.RLCS.CRDY);
|
||||
this.regRLBA = data[i++] || 0;
|
||||
this.regRLDA = data[i++] || 0;
|
||||
this.tmpRLDA = data[i++] || 0;
|
||||
|
|
@ -901,8 +952,8 @@ RL11.prototype.initDrive = function(drive, iDrive, data)
|
|||
*/
|
||||
RL11.prototype.processCommand = function()
|
||||
{
|
||||
var fnReadWrite;
|
||||
var fInterrupt = true;
|
||||
var fnReadWrite, sFunc = "";
|
||||
var iDrive = (this.regRLCS & PDP11.RL11.RLCS.DS) >> PDP11.RL11.RLCS.SHIFT.DS;
|
||||
var drive = this.aDrives[iDrive];
|
||||
var disk = drive.disk;
|
||||
|
|
@ -913,7 +964,7 @@ RL11.prototype.processCommand = function()
|
|||
*
|
||||
* 1) Normally both set
|
||||
* 2) CRDY is cleared to process a command
|
||||
* 3) DRDY is cleared to command is in process
|
||||
* 3) DRDY is cleared to indicate a command in process
|
||||
*/
|
||||
this.regRLCS &= ~PDP11.RL11.RLCS.DRDY;
|
||||
|
||||
|
|
@ -954,11 +1005,14 @@ RL11.prototype.processCommand = function()
|
|||
break;
|
||||
|
||||
case PDP11.RL11.FUNC.RDATA:
|
||||
sFunc = "READ";
|
||||
fnReadWrite = this.readData;
|
||||
/* falls through */
|
||||
|
||||
case PDP11.RL11.FUNC.WDATA:
|
||||
if (!sFunc) sFunc = "WRITE";
|
||||
if (!fnReadWrite) fnReadWrite = this.writeData;
|
||||
|
||||
iCylinder = this.regRLDA >> PDP11.RL11.RLDA.SHIFT.RW_CA;
|
||||
iHead = (this.regRLDA & PDP11.RL11.RLDA.RW_HS)? 1 : 0;
|
||||
iSector = this.regRLDA & PDP11.RL11.RLDA.RW_SA;
|
||||
|
|
@ -966,12 +1020,11 @@ RL11.prototype.processCommand = function()
|
|||
this.regRLCS |= PDP11.RL11.ERRC.HNF | PDP11.RL11.RLCS.ERR;
|
||||
break;
|
||||
}
|
||||
addr = (((this.regRLBE & PDP11.RL11.RLBE.MASK)) << 16) | this.regRLBA; // 22 bit mode
|
||||
nWords = (0x10000 - this.regRLMP) & 0xffff;
|
||||
var pos = ((((iCylinder << 1) + iHead) * drive.nSectors) + iSector) * 128;
|
||||
if (DEBUG && (this.messageEnabled(MessagesPDP11.READ) || this.messageEnabled(MessagesPDP11.WRITE))) {
|
||||
console.log((fnReadWrite == this.readData? "readData" : "writeData") + "(pos=" + pos + ",addr=" + str.toOct(addr) + ",bytes=" + (nWords * 2) + ")");
|
||||
}
|
||||
addr = (((this.regRLBE & PDP11.RL11.RLBE.MASK)) << 16) | this.regRLBA; // 22 bit mode
|
||||
|
||||
if (this.messageEnabled()) this.printMessage(this.type + ": " + sFunc + "(" + iCylinder + ":" + iHead + ":" + iSector + ") @" + str.toOct(addr) + "-" + str.toOct(addr + ((nWords - 1) << 1)), true);
|
||||
|
||||
fInterrupt = fnReadWrite.call(this, drive, iCylinder, iHead, iSector, nWords, addr, this.endReadWrite.bind(this));
|
||||
break;
|
||||
|
||||
|
|
@ -1021,8 +1074,8 @@ RL11.prototype.endReadWrite = function(err, iCylinder, iHead, iSector, nWords, a
|
|||
* @param {number} iSector
|
||||
* @param {number} nWords
|
||||
* @param {number} addr
|
||||
* @param {function(...)} done
|
||||
* @return {boolean} true if complete, false if queued
|
||||
* @param {function(...)} [done]
|
||||
* @return {boolean|number} true if complete, false if queued (or if no done() is supplied, the error code, if any)
|
||||
*/
|
||||
RL11.prototype.readData = function(drive, iCylinder, iHead, iSector, nWords, addr, done)
|
||||
{
|
||||
|
|
@ -1091,7 +1144,7 @@ RL11.prototype.readData = function(drive, iCylinder, iHead, iSector, nWords, add
|
|||
console.log("checksum: " + (checksum|0));
|
||||
}
|
||||
|
||||
return done(err, iCylinder, iHead, iSector, nWords, addr);
|
||||
return done? done(err, iCylinder, iHead, iSector, nWords, addr) : err;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1104,8 +1157,8 @@ RL11.prototype.readData = function(drive, iCylinder, iHead, iSector, nWords, add
|
|||
* @param {number} iSector
|
||||
* @param {number} nWords
|
||||
* @param {number} addr
|
||||
* @param {function(...)} done
|
||||
* @return {boolean} true if complete, false if queued
|
||||
* @param {function(...)} [done]
|
||||
* @return {boolean|number} true if complete, false if queued (or if no done() is supplied, the error code, if any)
|
||||
*/
|
||||
RL11.prototype.writeData = function(drive, iCylinder, iHead, iSector, nWords, addr, done)
|
||||
{
|
||||
|
|
@ -1173,7 +1226,7 @@ RL11.prototype.writeData = function(drive, iCylinder, iHead, iSector, nWords, ad
|
|||
console.log("checksum: " + (checksum|0));
|
||||
}
|
||||
|
||||
return done(err, iCylinder, iHead, iSector, nWords, addr);
|
||||
return done? done(err, iCylinder, iHead, iSector, nWords, addr) : err;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -354,8 +354,8 @@ SerialPortPDP11.prototype.initBus = function(cmp, bus, cpu, dbg)
|
|||
* receiveStatus(pins): called when our control signals have changed; aliased internally to updateStatus(pins)
|
||||
*
|
||||
* For now, we're not going to worry about communication in the other direction, because when the target component
|
||||
* performs its own initConnection(), it will find our receiveData() function, at which point communication in both
|
||||
* directions should be established.
|
||||
* performs its own initConnection(), it will find our receiveData() and receiveStatus() functions, at which point
|
||||
* communication in both directions should be established, and the circle of life complete.
|
||||
*
|
||||
* For added robustness, if the target machine initializes much more slowly than we do, and our connection attempt
|
||||
* fails, that's OK, because when it finally initializes, its initConnection() will call our initConnection();
|
||||
|
|
|
|||
Loading…
Reference in a new issue