mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-02-14 04:04:29 +00:00
1. Move project from Google Code to GitHub (https://github.com/pkimpel/retro-b5500/). Update links and help pages; convert wiki pages to GitHub's MarkDown format. 2. Implement emulator-hosted memory dump to a tape image that can be saved and input into the B5500 DUMP/ANALYZE utility for analysis. Activated by clicking the NOT READY button on the Console. 3. Fix bad assignments to Processor X register in arithmetic ops (affected only SyllableDebugger script). 4. Remove IndexedDB.openDatabase() version parameter so the B5500ColdLoader and tools/ scripts will work in non-Firefox browsers. 5. Add a "?db" query string parameter to the tools/scripts so these scripts can open disk subsystems other than B5500DiskUnit. 6. Correct pre-allocated file locations and ESU card in tools/COLDSTART-XIII.card. 7. Implement new double-click mechanism to copy and clear the contents of card punch, datacom terminal, and line-printer output areas to a temporary window for subsequent copying or saving. 8. Correct handling of Ctrl-B (break), Ctrl-D (disconnect request), Ctrl-E (WRU), Ctrl-L (clear input buffer), and Ctrl-Q (alternate end-of-message) in B5500DatacomUnit. 9. Implement reporting of Model IB (slow, bulk) disk in B5500DiskUnit readInterrogate. 10. Implement detection of browser IndexedDB quota-exceeded errors in B5500DiskUnit (primarily to handle the fixed 2GB limit for off-line storage in Firefox). 11. Correct problem when line printer exhausted paper and FORM FEED triple-click did not clear the condition. 12. Eliminate BOT marker sensed in result for tape drive Write Interrogate operation -- Mark XIII and XV MCPs treat this as an error and will not purge blank tapes because of it. 13. Fix double-click of SPO INPUT REQUEST button either sending a duplicate interrupt to the system or the second click moving focus from the SPO input box. 14. Further tuning of delay-deviation adjustment mechanism in B5500SetCallback.js. 15. Reinstate ability of SPO to wrap long outputs to additional lines (apparently lost with new SPO input mechanism in 1.00). 16. Commit preliminary COOLSTART-XIII.card and MCPTAPEDISK-XIII.card decks.
581 lines
28 KiB
JavaScript
581 lines
28 KiB
JavaScript
/***********************************************************************
|
|
* retro-b5500/emulator B5500DiskUnit.js
|
|
************************************************************************
|
|
* Copyright (c) 2013, Nigel Williams and Paul Kimpel.
|
|
* Licensed under the MIT License, see
|
|
* http://www.opensource.org/licenses/mit-license.php
|
|
************************************************************************
|
|
* B5500 Disk File Control Unit module.
|
|
*
|
|
* Defines a peripheral unit type for the Disk File Control Unit (DFCU) used
|
|
* with Burroughs Model-I and Model-IB Head-per-Track disk units. The DFCU is
|
|
* the addressable unit to the B5500. This module manages the Electronic Units
|
|
* (EU) and Storage Units (SU) that make up the physical disk storage facility.
|
|
*
|
|
* Physical storage in this implementation is provided by a W3C IndexedDB
|
|
* database local to the browser in which the emulator is running. There may
|
|
* be multiple of these databases, but only one may be selected for use by an
|
|
* instance of the emulator at a time. The database will be initialized to a
|
|
* default configuration the first time the emulator is used, and may be
|
|
* modified using the system configuration UI in the B5500 Console, but see
|
|
* below for considerations when using an existing database from emulator
|
|
* versions 0.20 and earlier.
|
|
*
|
|
* The database consists of a CONFIG object store and some number of "EUn"
|
|
* object stores, where n is in 0..19. The CONFIG store contains an "EUn" member
|
|
* for each such object store that specifies the characteristics of that EU:
|
|
*
|
|
* size: is the capacity of the EU in segments:
|
|
* 40000-200000 for Model-I disk
|
|
* 80000-400000 for Model-IB (slow) disk.
|
|
* slow: indicates Model-I (false) or Model-IB (true) disk,
|
|
* lockoutMask: is a binary integer, the low-order 20 bits of which
|
|
* represent the 20 disk lockout switches. A bit in this
|
|
* mask will be 1 if the associated switch is on. If
|
|
* this integer is negative, that indicates the master
|
|
* lockout switch is on. [not presently implemented]
|
|
*
|
|
* There may be gaps in the EU numbering, but the EU sizes should be specified
|
|
* in increments of 40,000 up to a maximum of 200,000 (for Model-I) or 400,000
|
|
* (for Model-IB "slow" disks). The configuration UI enforces this automatically.
|
|
*
|
|
* The Model-I SU was the original Head-per-Track disk module. Model-IB disk
|
|
* offered twice the storage capacity, but rotated at half the speed, allowing
|
|
* a higher bit density on the disk surface, but maintaining the same effective
|
|
* character transfer rate (96,000 characters/second) through the DFCU.
|
|
*
|
|
* For emulator versions 0.20 and earlier, the CONFIG structure had a simpler
|
|
* structure. Each EU member was a number indicating the size of the EU in
|
|
* segments. In order to allow backward compatibility with older emulator
|
|
* versions, the configuration UI will attempt to preserve this older format,
|
|
* and this device driver will accept that format, assuming Model-I disk and
|
|
* a lockoutMask of 0. This will allow older versions of the emulator to
|
|
* continue to use the IndexedDB database. Once the configuration of such a
|
|
* database is changed, however, it will no longer be compatible with the
|
|
* older version of the emulator, as the older emulator requires the IndexedDB
|
|
* database version to be 1.
|
|
*
|
|
* Within an EU, segments are represented in the database as 240-byte Uint8Array
|
|
* objects, each with a database key corresponding to its numeric segment address.
|
|
* The segments in an EU are not pre-allocated, but are created as they are
|
|
* written by IDB put() methods. When reading, any unallocated segments are
|
|
* returned with their bytes set to 0x23 (#), which will be translated by the
|
|
* IOU to BIC "0" for alpha mode and BIC "#" for binary mode.
|
|
*
|
|
* Note that all disk I/O is done in units of 240-character segments. The
|
|
* interface with the I/O Unit uses lengths in terms of characters, however.
|
|
* All lengths SHOULD be multiples of 240 characters; any other values will be
|
|
* rounded up to the next multiple of 240. The I/O Unit is responsible for any
|
|
* padding or truncation to account for differences between the segment count
|
|
* and word count in the IOD. This implementation ignores binary vs. alpha mode
|
|
* and assumes the IOU does any necessary translation.
|
|
*
|
|
* The starting disk segment address for an I/O is passed in the "control"
|
|
* parameter to each of the I/O methods. This is an an alphanumeric value in
|
|
* the B5500 memory and I/O Unit. The I/O unit translates this value to binary
|
|
* for the "control" parameter. The low-order six decimal digits of the value
|
|
* comprise the segment address within the EU. The seventh decimal digit is the
|
|
* EU number. Any other portion of the value is ignored.
|
|
*
|
|
* The DFCU's "read check" operation is asynchronous with respect to the IOU, and
|
|
* the I/O operation itself completes almost immediately. Because of this, any
|
|
* error reporting for the read check is deferred until the next I/O operation
|
|
* (typically an interrogate) against the unit. Therefore, the error mask is
|
|
* cleared at the end of each disk I/O operation (except for read check) instead
|
|
* of at the beginning, and new errors are OR-ed with any errors persisting from
|
|
* the prior operation.
|
|
*
|
|
* This module attempts to simulate actual disk activity times by delaying the
|
|
* finish() call by an amount of time computed from the numbers of sectors
|
|
* requested and the 96KC average transfer rate produced by the DFCU, plus a
|
|
* random distribution across an SU's 40ms max rotational latency time.
|
|
*
|
|
* When there are two DFCUs in the system, the way that they address the EUs
|
|
* depends on the presence of a Disk File Exchange (DFX). When a DFX is present,
|
|
* either DFCU may access any EU in the range 0-9. EUs 10-19 are not accessible.
|
|
* This limits storage to a maximum of 10 EUs (480 million characters). When a DFX
|
|
* is not present, DKA will address EU 0-9 and DKB will address EU 10-19,
|
|
* providing for a maximum of 20 EUs (960 million characters).
|
|
*
|
|
* This implementation supports configurations with or without a DFX. Note,
|
|
* however, that software support for a DFX is enabled by an MCP compile-time
|
|
* option ("$SET DFX=TRUE"). The setting of the MCP's DFX option *MUST MATCH*
|
|
* the DFX setting in the system configuration.
|
|
*
|
|
* W A R N I N G !
|
|
* ---------------
|
|
* Attempting to run a DFX-enabled MCP on a non-DFX hardware
|
|
* configuration, or vice versa, will likely corrupt the data
|
|
* in the disk subsystem and require a Cold Start to resolve.
|
|
*
|
|
* The File Protect Memory (FPM), used with shared-disk systems is not supported
|
|
* at present. Disk write lockout is also not supported.
|
|
*
|
|
************************************************************************
|
|
* 2013-01-19 P.Kimpel
|
|
* Original version, cloned from B5500DummyUnit.js.
|
|
* 2014-08-25 P.Kimpel
|
|
* Adapt to new EU configuration object format and selectable databases.
|
|
***********************************************************************/
|
|
"use strict";
|
|
|
|
/**************************************/
|
|
function B5500DiskUnit(mnemonic, index, designate, statusChange, signal, options) {
|
|
/* Constructor for the DiskUnit object */
|
|
|
|
this.mnemonic = mnemonic; // Unit mnemonic
|
|
this.index = index; // Ready-mask bit number
|
|
this.designate = designate; // IOD unit designate number
|
|
this.statusChange = statusChange; // external function to call for ready-status change
|
|
this.signal = signal; // external function to call for special signals (e.g,. SPO input request)
|
|
this.options = options; // device options from system configuration
|
|
|
|
this.timer = 0; // setCallback() token
|
|
this.initiateStamp = 0; // timestamp of last initiation (set by IOUnit)
|
|
this.config = null; // copy of CONFIG store contents
|
|
this.db = null; // the IDB database object
|
|
this.euPrefix = // prefix for EU object store names
|
|
(mnemonic=="DKA" || options.DFX ? "EU" : "EU1");
|
|
|
|
this.stdFinish = B5500CentralControl.bindMethod(this, B5500DiskUnit.prototype.stdFinish);
|
|
|
|
this.clear();
|
|
this.openDatabase(); // attempt to open the IDB database
|
|
}
|
|
|
|
B5500DiskUnit.prototype.charXferRate = 96; // avg. transfer rate [characters/ms = KC/sec]
|
|
B5500DiskUnit.prototype.modelILatency = 40; // Model-I disk max rotational latency [ms]
|
|
B5500DiskUnit.prototype.modelIBLatency = 80; // Model-IB disk max rotational latency [ms]
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.prototype.clear = function clear() {
|
|
/* Initializes (and if necessary, creates) the processor state */
|
|
|
|
this.ready = false; // ready status
|
|
this.busy = false; // busy status
|
|
|
|
this.errorMask = 0; // error mask for finish()
|
|
this.finish = null; // external function to call for I/O completion
|
|
this.startStamp = null; // I/O starting timestamp
|
|
};
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.prototype.stdFinish = function stdFinish(errorMask, length) {
|
|
/* Standard error reporting and I/O finish routine for the disk unit */
|
|
|
|
this.finish(this.errorMask | (errorMask || 0), length);
|
|
this.errorMask = 0;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.genericIDBError = function genericIDBError(ev) {
|
|
/* Formats a generic alert when otherwise-unhandled database errors occur */
|
|
|
|
this.stdFinish(0x20, 0); // set a generic disk-parity error
|
|
alert("Disk \"" + this.mnemonic + "\" database error: " + ev.target.result.error);
|
|
};
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.prototype.copySegment = function copySegment(seg, buffer, offset) {
|
|
/* Copies the bytes from a single segment Uint8Array object to "buffer" starting
|
|
at "offset" for 240 bytes. If "seg" is undefined, copies zero bytes instead */
|
|
var x;
|
|
|
|
if (seg) {
|
|
for (x=0; x<240; x++) {
|
|
buffer[offset+x] = seg[x];
|
|
}
|
|
} else {
|
|
for (x=offset+239; x>=offset; x--) {
|
|
buffer[x] = 0x23; // ASCII "#", translates as alpha to BIC "0" = @00
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.prototype.loadStorageConfig = function loadStorageConfig(storageConfig) {
|
|
/* Loads the storage configuration object from the storage database and
|
|
sets up the internal representation of that object for use by the driver */
|
|
var config = B5500Util.deepCopy(storageConfig);
|
|
var eu;
|
|
var euRex = /^EU\d{1,2}$/;
|
|
var name;
|
|
|
|
for (name in config) { // for each property in the config
|
|
if (name.search(euRex) == 0) { // filter name for "EUn" or "EU1n"
|
|
eu = config[name];
|
|
eu.maxLatency = (eu.slow ? this.modelIBLatency : this.modelILatency);
|
|
eu.charXferRate = this.charXferRate;
|
|
}
|
|
}
|
|
this.config = config;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.prototype.openDatabase = function openDatabase() {
|
|
/* Attempts to open the disk subsystem database specified by
|
|
this.options.storageName. If successful, loads the EU configuration,
|
|
sets this.db to the IDB object, and sets the DFCU to ready status */
|
|
var dsc = new B5500DiskStorageConfig();
|
|
var that = this;
|
|
|
|
function openStorageDB(config) {
|
|
var req;
|
|
|
|
if (!config) {
|
|
that.config = null;
|
|
alert(that.mnemonic + ": CONFIG structure does not exist in\ndatabase \"" +
|
|
that.options.storageName + "\" -- must recreate storage DB");
|
|
} else {
|
|
req = indexedDB.open(that.options.storageName); // accept any database version
|
|
|
|
req.onerror = function idbOpenOnerror(ev) {
|
|
alert("Cannot open " + that.mnemonic + " Disk Subsystem\ndatabase \"" +
|
|
that.options.storageName + "\":\n" + ev.target.error);
|
|
};
|
|
|
|
req.onblocked = function idbOpenOnblocked(ev) {
|
|
alert(that.mnemonic + " Disk Subsystem open is blocked -- CANNOT CONTINUE");
|
|
};
|
|
|
|
req.onupgradeneeded = function idbOpenOnupgradeneeded(ev) {
|
|
req.transaction.abort();
|
|
ev.target.result.close();
|
|
alert(that.mnemonic + " Disk Subsystem missing or requires version upgrade -- CANNOT CONTINUE");
|
|
};
|
|
|
|
req.onsuccess = function idbOpenOnsuccess(ev) {
|
|
// Save the DB object reference globally for later use
|
|
that.db = ev.target.result;
|
|
// Set up the generic error handler
|
|
that.db.onerror = B5500CentralControl.bindMethod(that, that.genericIDBError);
|
|
that.loadStorageConfig(config);
|
|
that.statusChange(1); // now report the DFCU as ready to Central Control
|
|
dsc.closeStorageDB();
|
|
dsc = null;
|
|
};
|
|
}
|
|
}
|
|
|
|
this.statusChange(0); // initially force DFCU status to not ready
|
|
dsc.getStorageConfig(this.options.storageName,
|
|
B5500CentralControl.bindMethod(this, openStorageDB));
|
|
};
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.prototype.read = function read(finish, buffer, length, mode, control) {
|
|
/* Initiates a read operation on the unit. "length" is in characters; segment address
|
|
is in "control". "mode" is ignored (any translation would have been done by IOU) */
|
|
var bx = 0; // current buffer offset
|
|
var eu; // EU characteristics object
|
|
var finishTime; // predicted time of I/O completion, ms
|
|
var range; // key range for multi-segment read
|
|
var req; // IDB request object
|
|
var that = this; // local object context
|
|
var txn; // IDB transaction object
|
|
|
|
this.finish = finish; // for global error handler
|
|
var segs = Math.floor((length+239)/240);
|
|
var segAddr = control % 1000000; // starting seg address
|
|
var euNumber = (control % 10000000 - segAddr)/1000000;
|
|
var euName = this.euPrefix + euNumber;
|
|
var endAddr = segAddr+segs-1; // ending seg address
|
|
|
|
eu = this.config[euName];
|
|
if (!eu) { // EU does not exist
|
|
this.stdFinish(0x20, 0); // set D27F for EU not ready/not present
|
|
} else if (segAddr < 0) {
|
|
this.stdFinish(0x20, 0); // set D27F for invalid starting seg address
|
|
} else {
|
|
if (endAddr >= eu.size) { // if read is past end of disk
|
|
this.errorMask |= 0x20; // set D27F for invalid seg address
|
|
segs = eu.size-segAddr; // compute number of segs possible to read
|
|
length = segs*240; // recompute length and ending seg address
|
|
endAddr = eu.size-1;
|
|
}
|
|
finishTime = this.initiateStamp +
|
|
Math.random()*eu.maxLatency + segs*240/eu.charXferRate;
|
|
|
|
if (segs < 1) { // No length specified, so just finish the I/O
|
|
this.stdFinish(0, 0);
|
|
} else if (segs < 2) { // A single-segment read
|
|
req = this.db.transaction(euName).objectStore(euName).get(segAddr);
|
|
req.onsuccess = function singleReadOnsuccess(ev) {
|
|
that.copySegment(ev.target.result, buffer, 0);
|
|
that.timer = setCallback(that.mnemonic, that, finishTime - performance.now(),
|
|
function singleReadTimeout() {
|
|
this.stdFinish(0, length);
|
|
});
|
|
}
|
|
} else { // A multi-segment read
|
|
range = IDBKeyRange.bound(segAddr, endAddr);
|
|
txn = this.db.transaction(euName);
|
|
|
|
req = txn.objectStore(euName).openCursor(range);
|
|
req.onsuccess = function rangeReadOnsuccess(ev) {
|
|
var cursor = ev.target.result;
|
|
|
|
if (cursor) { // found a segment at some address in range
|
|
// Fill buffer with zeroes for any unallocated segments
|
|
while (cursor.key > segAddr) {
|
|
that.copySegment(null, buffer, bx);
|
|
bx += 240;
|
|
segAddr++;
|
|
}
|
|
// Copy the segment data to the buffer and request next seg
|
|
that.copySegment(cursor.value, buffer, bx);
|
|
bx += 240;
|
|
segAddr++;
|
|
cursor.continue();
|
|
} else { // at end of range
|
|
// Fill buffer with zeroes for any remaining segments in range
|
|
while (endAddr > segAddr) {
|
|
that.copySegment(null, buffer, bx);
|
|
bx += 240;
|
|
segAddr++;
|
|
}
|
|
that.timer = setCallback(that.mnemonic, that, finishTime - performance.now(),
|
|
function rangeReadTimeout() {
|
|
this.stdFinish(0, length);
|
|
});
|
|
}
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.prototype.space = function space(finish, length, control) {
|
|
/* Initiates a space operation on the unit */
|
|
|
|
finish(this.errorMask | 0x04, 0); // report unit not ready
|
|
this.errorMask = 0;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.prototype.write = function write(finish, buffer, length, mode, control) {
|
|
/* Initiates a write operation on the unit. "length" is in characters; segment address
|
|
is in "control". "mode" is ignored (any translation will done by the IOU) */
|
|
var bx = 0; // current buffer offset
|
|
var eu; // EU characteristics object
|
|
var finishTime; // predicted time of I/O completion, ms
|
|
var req; // IDB request object
|
|
var that = this; // local object context
|
|
var txn; // IDB transaction object
|
|
|
|
this.finish = finish; // for global error handler
|
|
var segs = Math.floor((length+239)/240);
|
|
var segAddr = control % 1000000; // starting seg address
|
|
var euNumber = (control % 10000000 - segAddr)/1000000;
|
|
var euName = this.euPrefix + euNumber;
|
|
var endAddr = segAddr+segs-1; // ending seg address
|
|
|
|
eu = this.config[euName];
|
|
if (!eu) { // EU does not exist
|
|
console.log(euName + " does not exist");
|
|
this.stdFinish(0x20, 0); // set D27F for EU not ready
|
|
} else if (segAddr < 0) {
|
|
console.log(euName + " invalid starting addr");
|
|
this.stdFinish(0x20, 0); // set D27F for invalid starting seg address
|
|
} else {
|
|
if (endAddr >= eu.size) { // if read is past end of disk
|
|
this.errorMask |= 0x20; // set D27F for invalid seg address
|
|
segs = eu.size-segAddr; // compute number of segs possible to read
|
|
length = segs*240; // recompute length and ending seg address
|
|
endAddr = eu.size-1;
|
|
}
|
|
finishTime = this.initiateStamp +
|
|
Math.random()*eu.maxLatency + segs*240/eu.charXferRate;
|
|
|
|
if (segs < 1) {
|
|
// No length specified, so just finish the I/O
|
|
this.stdFinish(0, 0);
|
|
} else {
|
|
// Do the write
|
|
txn = this.db.transaction(euName, "readwrite")
|
|
txn.onerror = function writeTxnOnError(ev) {
|
|
console.log(euName + " write txn onerror", ev);
|
|
that.stdFinish(0x20, 0);
|
|
};
|
|
txn.onabort = function writeTxnOnAbort(ev) {
|
|
console.log(euName + " write txn onabort", ev);
|
|
that.stdFinish(0x20, 0);
|
|
};
|
|
txn.oncomplete = function writeComplete(ev) {
|
|
that.timer = setCallback(that.mnemonic, that, finishTime - performance.now(),
|
|
function writeTimeout() {
|
|
this.stdFinish(0, length);
|
|
});
|
|
};
|
|
eu = txn.objectStore(euName);
|
|
while (segAddr<=endAddr) {
|
|
eu.put(buffer.subarray(bx, bx+240), segAddr);
|
|
bx += 240;
|
|
++segAddr;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.prototype.erase = function erase(finish, length) {
|
|
/* Initiates an erase operation on the unit */
|
|
|
|
finish(this.errorMask | 0x04, 0); // report unit not ready
|
|
this.errorMask = 0;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.prototype.rewind = function rewind(finish) {
|
|
/* Initiates a rewind operation on the unit */
|
|
|
|
finish(this.errorMask | 0x04, 0); // report unit not ready
|
|
this.errorMask = 0;
|
|
};
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.prototype.readCheck = function readCheck(finish, length, control) {
|
|
/* Initiates a read check operation on the unit. "length" is in characters;
|
|
segment address is in "control". "mode" is ignored. This is essentially a
|
|
read without any data transfer to memory. Note that the errorMask is NOT
|
|
zeroed at the end of the I/O -- it will be reported with the next I/O */
|
|
var eu; // EU characteristics object
|
|
var finishTime; // predicted time of I/O completion, ms
|
|
var range; // key range for multi-segment read
|
|
var req; // IDB request object
|
|
var that = this; // local object context
|
|
var txn; // IDB transaction object
|
|
|
|
this.finish = finish; // for global error handler
|
|
var segs = Math.floor((length+239)/240);
|
|
var segAddr = control % 1000000; // starting seg address
|
|
var euNumber = (control % 10000000 - segAddr)/1000000;
|
|
var euName = this.euPrefix + euNumber;
|
|
var endAddr = segAddr+segs-1; // ending seg address
|
|
|
|
this.errorMask = 0; // clear any prior error mask
|
|
eu = this.config[euName];
|
|
if (!eu) { // EU does not exist
|
|
finish(this.errorMask | 0x20, 0); // set D27F for EU not ready
|
|
// DO NOT clear the error mask here
|
|
this.signal();
|
|
} else if (segAddr < 0) {
|
|
finish(this.errorMask | 0x20, 0); // set D27F for invalid starting seg address
|
|
// DO NOT clear the error mask here
|
|
this.signal();
|
|
} else {
|
|
if (endAddr >= eu.size) { // if read is past end of disk
|
|
this.errorMask |= 0x20; // set D27F for invalid seg address
|
|
segs = eu.size-segAddr; // compute number of segs possible to read
|
|
length = segs*240; // recompute length and ending seg address
|
|
endAddr = eu.size-1;
|
|
}
|
|
finishTime = this.initiateStamp +
|
|
Math.random()*eu.maxLatency + segs*240/eu.charXferRate;
|
|
|
|
if (segs < 1) { // No length specified, so just finish the I/O
|
|
finish(this.errorMask, 0);
|
|
// DO NOT clear the error mask -- will return it on the next interrogate
|
|
this.signal();
|
|
} else { // A multi-segment read
|
|
range = IDBKeyRange.bound(segAddr, endAddr);
|
|
txn = this.db.transaction(euName);
|
|
req = txn.objectStore(euName).openCursor(range);
|
|
req.onsuccess = function readCheckOnsuccess(ev) {
|
|
var cursor = ev.target.result;
|
|
|
|
if (cursor) { // found a segment at some address in range
|
|
cursor.continue();
|
|
} else { // at end of range
|
|
that.timer = setCallback(that.mnemonic, that, finishTime - performance.now(),
|
|
function readCheckTimeout() {
|
|
this.signal();
|
|
// DO NOT clear the error mask
|
|
});
|
|
}
|
|
};
|
|
|
|
// Post I/O complete now -- DFCU will signal when read check is finished
|
|
finish(this.errorMask, length);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.prototype.readInterrogate = function readInterrogate(finish, control) {
|
|
/* Initiates a read interrogate operation on the unit. This serves only to
|
|
check the addresss for validity and to return any errorMask from a prior
|
|
read check operation. This implementation assumes completion will be delayed
|
|
by a random amount of time based on rotational latency for the EU to search for
|
|
the address */
|
|
var eu; // EU characteristics object
|
|
var segAddr = control % 1000000; // starting seg address
|
|
var euNumber = (control % 10000000 - segAddr)/1000000;
|
|
var euName = this.euPrefix + euNumber;
|
|
|
|
this.finish = finish; // for global error handler
|
|
eu = this.config[euName];
|
|
if (!eu) { // EU does not exist
|
|
this.stdFinish(0x20, 0); // set D27F for EU not ready
|
|
} else {
|
|
if (segAddr < 0 || segAddr >= eu.size) { // if read is past end of disk
|
|
this.errorMask |= 0x20; // set D27F for invalid seg address
|
|
} else if (eu.slow) {
|
|
this.errorMask |= 0x10; // set D28F (lockout bit) to indicate Mod IB (slow) disk
|
|
}
|
|
this.timer = setCallback(this.mnemonic, this,
|
|
Math.random()*eu.maxLatency + this.initiateStamp - performance.now(),
|
|
function readInterrogateTimeout() {
|
|
this.stdFinish(0, 0);
|
|
});
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.prototype.writeInterrogate = function writeInterrogate(finish, control) {
|
|
/* Initiates a write interrogate operation on the unit. This serves only to
|
|
check the addresss for validity and to return any errorMask from a prior
|
|
read check operation. This implementation assumes completion will be delayed
|
|
by a random amount of time based on rotational latency for the EU to search for
|
|
the address */
|
|
|
|
/* Note: until disk write lockout is implemented, this operation is identical
|
|
to readInterrogate() */
|
|
|
|
var eu; // EU characteristics object
|
|
var segAddr = control % 1000000; // starting seg address
|
|
var euNumber = (control % 10000000 - segAddr)/1000000;
|
|
var euName = this.euPrefix + euNumber;
|
|
|
|
this.finish = finish; // for global error handler
|
|
eu = this.config[euName];
|
|
if (!eu) { // EU does not exist
|
|
this.stdFinish(0x20, 0); // set D27F for EU not ready
|
|
} else {
|
|
if (segAddr < 0 || segAddr >= eu.size) { // if read is past end of disk
|
|
this.errorMask |= 0x20; // set D27F for invalid seg address
|
|
}
|
|
this.timer = setCallback(this.mnemonic, this,
|
|
Math.random()*eu.maxLatency + this.initiateStamp - performance.now(),
|
|
function writeInterrogateTimeout() {
|
|
this.stdFinish(0, 0);
|
|
});
|
|
}
|
|
};
|
|
|
|
/**************************************/
|
|
B5500DiskUnit.prototype.shutDown = function shutDown() {
|
|
/* Shuts down the device */
|
|
|
|
if (this.timer) {
|
|
clearCallback(this.timer);
|
|
}
|
|
if (this.db) {
|
|
if (!this.db.closed) {
|
|
this.db.close();
|
|
this.db = null;
|
|
}
|
|
}
|
|
// this device has no window to close
|
|
};
|