mirror of
https://github.com/pkimpel/retro-b5500.git
synced 2026-02-13 03:34:29 +00:00
1. Alter method of writing disk sectors to IndexedDB, to avoid dragging along the entire 16KB IO Unit buffer area and unintentionally inflating host disk usage by 30-60X (ouch). This was causing Quota Exceeded errors in recent versions of Firefox. 2. Add onabort traps in B5500DiskUnit to catch QuotaExceeded errors. 3. Modify delay-deviation adjustment mechanism in B5500SetCallback to avoid oscillating between positive and negative cumulative deviations. 4. Correct tape reel angular motion in B5500MagTapeDrive, especially during reverse tape movement. 5. Fix bug with reporting memory parity error during tape I/O, should that ever occur. 6. Reset CPA Algol Glyphs option in default system configuration template. 7. Allow tools/B5500LibMaintDecoder to examine an entire .bcd tape image file instead of just the first 64KB. 8. Add USE SAVEPBT to default options in tools/COLDSTART-XIII deck. 9. Eliminate extraneous "schema update successful" alert when altering a disk subsystem configuration. 10. Commit minor corrections to source/B65ESPOL/SOURCE.alg_m from Richard Fehlinger.
596 lines
29 KiB
JavaScript
596 lines
29 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.sectorBuf = new Uint8Array(240); // sector buffer used by write()
|
|
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
|
|
|
|
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.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/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 {
|
|
txn = this.db.transaction(euName);
|
|
txn.onerror = function writeTxnOnError(ev) {
|
|
console.log(euName + " read txn onerror: " + ev.target.error.name);
|
|
that.stdFinish(0x20, 0);
|
|
};
|
|
txn.onabort = function writeTxnOnAbort(ev) {
|
|
console.log(euName + " read txn onabort: " + ev.target.error.name);
|
|
that.stdFinish(0x20, 0);
|
|
};
|
|
if (segs < 2) { // A single-segment read
|
|
req = txn.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);
|
|
|
|
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 sectorBuf = this.sectorBuf; // local copy
|
|
var that = this; // local object context
|
|
var txn; // IDB transaction object
|
|
var x; // loop index for sectorBuf copy
|
|
|
|
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.finish = finish; // for global error handler
|
|
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.target.error.name);
|
|
that.stdFinish(0x20, 0);
|
|
};
|
|
txn.onabort = function writeTxnOnAbort(ev) {
|
|
console.log(euName + " write txn onabort: " + ev.target.error.name);
|
|
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) {
|
|
for (x=0; x<240; ++x) {
|
|
sectorBuf[x] = buffer[bx++];
|
|
}
|
|
eu.put(sectorBuf, segAddr);
|
|
++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
|
|
};
|