Merge branch 'next-release'

This commit is contained in:
Jeff Parsons 2016-12-03 16:56:39 -08:00
commit a7185e660a
331 changed files with 14872 additions and 493 deletions

View file

@ -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();

View file

@ -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");
/*

View file

@ -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 + "...");

View file

@ -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();

View file

@ -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

View file

@ -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();
};
/**

View file

@ -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);
}

View file

@ -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);
};

View file

@ -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;
};

View file

@ -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;
};
/**

View file

@ -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;
};
/**

View file

@ -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();