Fixed PC-DOS 7.00 boot (diskette access still seems sluggish though)

This commit is contained in:
Jeff Parsons 2014-10-18 18:09:08 -05:00 committed by jeffpar
commit f214b45da0
5 changed files with 1246 additions and 1242 deletions

File diff suppressed because it is too large Load diff

View file

@ -48,22 +48,22 @@ if (typeof module !== 'undefined') {
* FDC Terms (see FDC.TERMS)
*
* C Cylinder Number the current or selected cylinder number
*
*
* D Data the data pattern to be written to a sector
*
*
* DS Drive Select the selected driver number encoded the same as bits 0 and 1 of the Digital Output
* Register (DOR); eg, DS0, DS1, DS2, or DS3
*
*
* DTL Data Length when N is 00, DTL is the data length to be read from or written to a sector
*
*
* EOT End Of Track the final sector number on a cylinder
*
*
* GPL Gap Length the length of gap 3 (spacing between sectors excluding the VCO synchronous field)
*
*
* H Head Address the head number, either 0 or 1, as specified in the ID field
*
*
* HD Head the selected head number, 0 or 1 (H = HD in all command words)
*
*
* HLT Head Load Time the head load time in the selected drive (2 to 256 milliseconds in 2-millisecond
* increments for the 1.2M-byte drive and 4 to 512 milliseconds in 4 millisecond increments
* for the 320K-byte drive)
@ -71,30 +71,30 @@ if (typeof module !== 'undefined') {
* HUT Head Unload Time the head unload time after a read or write operation (0 to 240 milliseconds in
* 16-millisecond increments for the 1.2M-byte drive and 0 to 480 milliseconds in
* 32-millisecond increments for the 320K-byte drive)
*
*
* MF FM or MFM Mode 0 selects FM mode and 1 selects MFM (MFM is selected only if it is implemented)
*
*
* MT Multitrack 1 selects multitrack operation (both HD0 and HD1 will be read or written)
*
*
* N Number the number of data bytes written in a sector
*
*
* NCN New Cylinder Number the new cylinder number for a Seek operation
*
*
* ND Non-Data Mode indicates an operation in the non-data mode
*
*
* PCN Present Cylinder Number the cylinder number at the completion of a Sense Interrupt Status command
* (present position of the head)
*
*
* R Record the sector number to be read or written
*
*
* SC Sectors Per Cylinder the number of sectors per cylinder
*
*
* SK Skip this stands for skip deleted-data address mark
*
*
* SRT Stepping Rate this 4 bit byte indicates the stepping rate for the diskette drive as follows:
* 1.2M-Byte Diskette Drive: 1111=1ms, 1110=2ms, 1101=3ms
* 320K-Byte Diskette Drive: 1111=2ms, 1110=4ms, 1101=6ms
*
*
* STP STP Scan Test if STP is 1, the data in contiguous sectors is compared with the data sent
* by the processor during a scan operation; if STP is 2, then alternate sections
* are read and compared
@ -162,23 +162,21 @@ function FDC(parmsFDC) {
/*
* The following array keeps track of every disk image we've ever mounted. Each entry in the
* array is another array whose elements are:
*
*
* [0]: name of disk
* [1]: path of disk
* [2]: array of deltas, uninitialized until the disk is unmounted and/or all state is saved
*
*
* See functions addDiskHistory() and updateDiskHistory().
*/
this.aDiskHistory = [];
/*
* If we didn't need auto-mount support, we could defer controller initialization until we received a powerUp() notification,
* at which point reset() would call initController(), or restore() would restore the controller; in that case, all we'd need
* to do here is call setReady().
* The remainder of FDC initialization now takes place in our initBus() handler, largely because we
* want initController() to have access to the ChipSet component, so that it can query switches and/or CMOS
* settings that determine the number of drives and their characteristics (eg, 40-track vs. 80-track),
* which it can then pass on to initDrive().
*/
this.initController();
if (!this.autoMount()) this.setReady();
}
Component.subclass(Component, FDC);
@ -212,10 +210,10 @@ if (DEBUG) {
/*
* FDC Digital Output Register (DOR) (0x3F2, write-only)
*
*
* NOTE: Reportedly, a drive's MOTOR had to be ON before the drive could be selected; however, outFDCOutput() no
* longer verifies that. Also, motor start time for original drives was 500ms, but we make no attempt to simulate that.
*
*
* On the MODEL_5170 "PC AT Fixed Disk and Diskette Drive Adapter", this port is called the Digital Output Register
* or DOR. It uses the same bit definitions as the original FDC Output Register, except that only two diskette drives
* are supported, hence bit 1 is always 0 (ie, FDC.REG_OUTPUT.DS2 and FDC.REG_OUTPUT.DS3 are not supported) and bits
@ -237,7 +235,7 @@ FDC.REG_OUTPUT.MOTOR_D3 = 0x80; // reserved on the MODEL_5170
/*
* FDC Main Status Register (0x3F4, read-only)
*
*
* On the MODEL_5170 "PC AT Fixed Disk and Diskette Drive Adapter", bits 2 and 3 are reserved, since that adapter
* supported a maximum of two diskette drives.
*/
@ -260,7 +258,7 @@ FDC.REG_DATA.PORT = 0x3F5;
/*
* FDC Digital Input Register (0x3F7, read-only, MODEL_5170 only)
*
*
* Bit 7 indicates a diskette change (the MODEL_5170 introduced change-line support). Bits 0-6 are for the selected
* hard disk drive, so this port must be shared with the HDC; bits 0-6 are valid for 50 microseconds after a write to
* the Drive Head Register.
@ -278,7 +276,7 @@ FDC.REG_INPUT.DISK_CHANGE = 0x80; // Diskette Change
/*
* FDC Diskette Control Register (0x3F7, write-only, MODEL_5170 only)
*
*
* Only bits 0-1 are used; bits 2-7 are reserved.
*/
FDC.REG_CONTROL = {};
@ -290,14 +288,14 @@ FDC.REG_CONTROL.RATEUNUSED = 0x03;
/*
* FDC Commands
*
*
* NOTE: FDC command bytes need to be masked with FDC.REG_DATA.CMD.MASK before comparing to the values below, since a
* number of commands use the following additional bits as follows:
*
*
* SK (0x20): Skip Deleted Data Address Mark
* MF (0x40): Modified Frequency Modulation (as opposed to FM or Frequency Modulation)
* MT (0x80): multi-track operation (ie, data processed under both head 0 and head 1)
*
*
* We don't support MT (Multi-Track) operations at this time, and the MF and SK designations cannot be supported as long
* as our diskette images contain only the original data bytes without any formatting information.
*/
@ -324,8 +322,8 @@ FDC.REG_DATA.CMD.MT = 0x80; // MT (Multi-Track; ie, data under b
/*
* FDC status/error results, generally assigned according to the corresponding ST0, ST1, ST2 or ST3 status bit.
*
* TODO: Determine when EQUIP_CHECK is *really* set; "77 step pulses" sounds suspiciously like a typo.
*
* TODO: Determine when EQUIP_CHECK is *really* set; "77 step pulses" sounds suspiciously like a typo.
*/
FDC.REG_DATA.RES = {};
FDC.REG_DATA.RES.NONE = 0x00000000; // ST0 (IC): Normal termination of command (NT)
@ -362,7 +360,7 @@ FDC.REG_DATA.RES.ST3 = 0xFF000000;
/*
* FDC Command Sequences
*
*
* For each command, cbReq indicates the total number of bytes in the command request sequence,
* including the first (command) byte; cbRes indicates total number of bytes in the response sequence.
*/
@ -451,7 +449,7 @@ FDC.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
};
}(this, control);
return true;
case "descDisk":
case "listDrives":
this.bindings[sBinding] = control;
@ -467,7 +465,7 @@ FDC.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
};
}(this, control);
return true;
case "loadDrive":
this.bindings[sBinding] = control;
control.onclick = function(fdc) {
@ -482,7 +480,7 @@ FDC.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
return;
}
var sDisketteName = controlDisks.options[controlDisks.selectedIndex].text;
/*
* If the special path of "?" is selected, then we want to prompt the user for a URL. Oh, and
* make sure we pass an empty string as the 2nd parameter to prompt(), so that IE won't display
@ -499,7 +497,7 @@ FDC.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
sDisketteName = str.getBaseName(sDiskettePath);
fdc.println("Attempting to load " + sDiskettePath + " as \"" + sDisketteName + "\"");
}
while (fdc.loadDiskette(iDrive, sDisketteName, sDiskettePath, false)) {
if (!window.confirm("Click OK to reload the original disk.\n(WARNING: All disk changes will be discarded)")) {
return;
@ -546,9 +544,18 @@ FDC.prototype.initBus = function(cmp, bus, cpu, dbg)
this.chipset = cmp.getComponentByType("ChipSet");
/*
* If we didn't need auto-mount support, we could defer controller initialization until we received a powerUp() notification,
* at which point reset() would call initController(), or restore() would restore the controller; in that case, all we'd need
* to do here is call setReady().
*/
this.initController();
bus.addPortInputTable(this, FDC.aPortInput);
bus.addPortOutputTable(this, FDC.aPortOutput);
if (DEBUGGER) cpu.addIntNotify(FDC.BIOS.INT_DISKETTE, this, this.intBIOSDiskette);
if (!this.autoMount()) this.setReady();
};
/**
@ -567,7 +574,7 @@ FDC.prototype.powerUp = function(data, fRepower)
if (this.cmp.fReload) {
/*
* If the computer's fReload flag is set, we're required to toss all currently
* loaded disks and remount all disks specified in the auto-mount configuration.
* loaded disks and remount all disks specified in the auto-mount configuration.
*/
this.unloadAllDrives(true);
this.autoMount(true);
@ -575,41 +582,29 @@ FDC.prototype.powerUp = function(data, fRepower)
} else {
if (!this.restore(data)) return false;
}
if (this.chipset) {
var iDrive;
this.nDrives = this.chipset.getSWFloppyDrives();
for (iDrive = 0; iDrive < this.nDrives; iDrive++) {
var drive = this.aDrives[iDrive];
drive.bType = this.chipset.getSWFloppyDriveType(iDrive);
if (drive.bType == ChipSet.CMOS.FDRIVE.DSHD) {
drive.nCylinders = 80;
}
/*
* Populate the HTML controls to match the actual (well, um, specified) number of floppy drives.
*/
var controlDrives;
if ((controlDrives = this.bindings['listDrives'])) {
while (controlDrives.firstChild) {
controlDrives.removeChild(controlDrives.firstChild);
}
/*
* Now that we finally have the SW1 settings, we can populate the HTML control
* to match the actual (well, um, specified) number of floppy drives in the system.
*/
var controlDrives;
if ((controlDrives = this.bindings['listDrives'])) {
while (controlDrives.firstChild) {
controlDrives.removeChild(controlDrives.firstChild);
}
controlDrives.innerHTML = "";
for (iDrive = 0; iDrive < this.nDrives; iDrive++) {
var controlOption = window.document.createElement("option");
controlOption['value'] = iDrive;
/*
* TODO: This conversion of drive number to drive letter, starting with A:, is very simplistic
* and will NOT match the drive mappings that DOS ultimately uses. We'll need to spiff this up at
* some point.
*/
controlOption.innerHTML = String.fromCharCode(0x41 + iDrive) + ":";
controlDrives.appendChild(controlOption);
}
if (this.nDrives > 0) {
controlDrives.value = "0";
this.displayDiskette(0);
}
controlDrives.innerHTML = "";
for (var iDrive = 0; iDrive < this.nDrives; iDrive++) {
var controlOption = window.document.createElement("option");
controlOption['value'] = iDrive;
/*
* TODO: This conversion of drive number to drive letter, starting with A:, is very simplistic
* and will NOT match the drive mappings that DOS ultimately uses. We'll need to spiff this up at
* some point.
*/
controlOption.innerHTML = String.fromCharCode(0x41 + iDrive) + ":";
controlDrives.appendChild(controlOption);
}
if (this.nDrives > 0) {
controlDrives.value = "0";
this.displayDiskette(0);
}
}
}
@ -684,36 +679,36 @@ FDC.prototype.restore = function(data)
*/
FDC.prototype.initController = function(data)
{
var i = 0;
var i = 0, iDrive;
var fSuccess = true;
if (data === undefined) {
data = [0, 0, FDC.REG_STATUS.RQM, new Array(9), 0, 0, 0, []];
}
/*
* Selected drive (from regOutput), which can only be selected if its motor is on (see regOutput).
*/
this.iDrive = data[i++];
i++; // unused slot (if reused, bias by +4, since it was formerly a unit #)
/*
* Defaults to FDC.REG_STATUS.RQM set (ready for command) and FDC.REG_STATUS.READ_DATA clear (data direction
* is from processor to the FDC Data Register).
*/
this.regStatus = data[i++];
/*
* There can be up to 9 command bytes, and 7 result bytes, so 9 data registers are sufficient for communicating
* in both directions (hence, the new Array(9) default above).
*/
this.regDataArray = data[i++];
/*
* Determines the next data byte to be received.
*/
this.regDataIndex = data[i++];
/*
* Determines the next data byte to be sent (internally, we use regDataIndex to read data bytes, up to this total).
*/
@ -723,35 +718,46 @@ FDC.prototype.initController = function(data)
/*
* Initialize the disk history (if available) before initializing the drives, so that any disk deltas can be
* applied to disk images that are already loaded.
* applied to disk images that are already loaded.
*/
var aDiskHistory = data[i++];
if (aDiskHistory != null) this.aDiskHistory = aDiskHistory;
/*
* We allocate the maximum number of drives; we won't know the actual number of drives until we're able to query
* the SW1 switch settings.
*/
if (this.aDrives === undefined) {
this.nDrives = 0; // this will be set later to the number of ACTUAL drives
this.nDrives = 4; // default to the maximum number of drives
if (this.chipset) this.nDrives = this.chipset.getSWFloppyDrives();
/*
* I would prefer to allocate only nDrives, but as discussed in the handling of the FDC.REG_DATA.CMD.SENSE_INT
* command, we're faced with situations where the controller must respond to any drive in the range 0-3, regardless
* how many drives are actually installed. We still rely upon nDrives to determine the number of drives displayed
* to the user, however.
*/
this.aDrives = new Array(4);
}
for (var iDrive = 0; iDrive < this.aDrives.length; iDrive++) {
if (this.aDrives[iDrive] === undefined) {
this.aDrives[iDrive] = {};
}
for (iDrive = 0; iDrive < this.aDrives.length; iDrive++) {
var drive = this.aDrives[iDrive];
if (drive === undefined) {
drive = this.aDrives[iDrive] = {};
drive.bType = this.chipset.getSWFloppyDriveType(iDrive);
drive.nCylinders = 40;
drive.nSectors = 9;
if (drive.bType == ChipSet.CMOS.FDRIVE.DSHD) {
drive.nCylinders = 80;
drive.nSectors = 18; // 15 for 1.2Mb diskettes, 18 for 1.44Mb diskettes, 8/9 for everything else
}
}
if (!this.initDrive(drive, iDrive, dataDrives[iDrive])) {
fSuccess = false;
}
}
/*
* regInput and regControl (port 0x3F7) were not present on controllers prior to MODEL_5170, which is why
* we don't include initializers for them in the default data array; we could eliminate them on older models,
* but we don't have access to the model info right now, and there's no real cost to always including them
* in the FDC state.
*
*
* The bigger compatibility question is whether to always include hooks for them (see aPortInput and aPortOutput).
*/
this.regInput = data[i++] || 0; // TODO: Determine if we should default to FDC.REG_INPUT.DISK_CHANGE instead of 0
@ -791,7 +797,7 @@ FDC.prototype.saveController = function()
* TODO: Consider a separate Drive class that both FDC and HDC can use, since there's a lot of commonality
* between the drive objects created by both controllers. This will clean up overall drive management and allow
* us to factor out some common Drive methods (eg, advanceSector()).
*
*
* @this {FDC}
* @param {Object} drive
* @param {number} iDrive
@ -802,7 +808,7 @@ FDC.prototype.initDrive = function(drive, iDrive, data)
{
var i = 0;
var fSuccess = true;
drive.iDrive = iDrive;
if (data === undefined) {
@ -816,16 +822,12 @@ FDC.prototype.initDrive = function(drive, iDrive, data)
if (typeof data[1] == "boolean") {
/*
* Note that when no data is provided (eg, when the controller is being reinitialized), we now take
* care to use drive.nCylinders as the default, falling back to a 40-track maximum ONLY when the drive
* hasn't been initialized. This preserves whatever maximum the powerUp() function may have obtained
* from the ChipSet component.
*
* TODO: We may need to make a similar accommodation for drive.nHeads and drive.nSectors down the road;
* they currently default to a maximum of 2 heads (see above) and 9 sectors/track (see below).
* care to preserve any drive defaults that initController() already obtained for us, falling back to
* bare minimums only when all else has failed.
*/
data[1] = [FDC.DEFAULT_DRIVE_NAME, drive.nCylinders || 40, data[3], 9, 512, data[1]];
data[1] = [FDC.DEFAULT_DRIVE_NAME, drive.nCylinders || 40, drive.nHeads || data[3], drive.nSectors || 9, drive.cbSector || 512, data[1]];
}
/*
* resCode used to be an FDC global, but in order to insulate FDC state from the operation of various functions
* that operate on drive objects (eg, readByte and writeByte), I've made it a per-drive variable. This choice,
@ -833,13 +835,13 @@ FDC.prototype.initDrive = function(drive, iDrive, data)
* approach, as long as it doesn't expose any incompatibilities that any software actually cares about.
*/
drive.resCode = data[i++];
/*
* Some additional drive properties/defaults that are largely for the Disk component's benefit.
*/
drive.name = data[i][0];
drive.nCylinders = data[i][1]; // cylinders
drive.nHeads = data[i][2]; // heads/cylinders
drive.nHeads = data[i][2]; // heads/cylinders
drive.nSectors = data[i][3]; // sectors/track
drive.cbSector = data[i][4]; // bytes/sector
drive.fRemovable = data[i][5];
@ -851,15 +853,15 @@ FDC.prototype.initDrive = function(drive, iDrive, data)
* We initialize this.iDrive (above) and drive.bHead and drive.bCylinder (below) to zero, but leave the rest undefined,
* awaiting their first FDC command. We do this because the initial SENSE_INT command returns a PCN, which will also
* be undefined unless we have at least zeroed both the current drive and the "present" cylinder on that drive.
*
*
* Alternatively, I could make PCN a global FDC variable. That may be closer to how the actual hardware operates,
* but I'm using per-drive variables so that the FDC component can be a good client to both the CPU and other components.
*
*
* COMPATIBILITY ALERT: The MODEL_5170 BIOS ("DSKETTE_SETUP") attempts to discern the drive type (double-density vs.
* high-capacity) by "slapping" the heads around. Literally (it uses a constant named "TRK_SLAP" equal to 48).
* After seeking to "TRK_SLAP", the BIOS performs a series of seeks, looking for the precise point where the heads
* return to track 0.
*
*
* Here's how it works: the BIOS seeks to track 48 (which is fine on an 80-track 1.2Mb high-capacity drive, but 9 tracks
* too far on a 40-track 360Kb double-density drive), then seeks to track 10, and then seeks in single-track increments
* up to 10 more times until the SENSE_DRIVE command returns ST3 with the TRACK0 bit set.
@ -869,11 +871,10 @@ FDC.prototype.initDrive = function(drive, iDrive, data)
* is updating a "logical" cylinder number, not the "physical" (actual) cylinder number. Presumably a RECALIBRATE
* command will bring the logical and physical values into sync, but once an out-of-bounds cylinder is requested, they
* will be out of sync.
*
*
* To simulate this, bCylinder is now treated as the "physical" cylinder (since that's how it's ALWAYS been used here),
* and bCylinderSeek will now track (pun intended) the "logical" cylinder that's programmed via SEEK commands.
*/
drive.bType = ChipSet.CMOS.FDRIVE.DSDD; // default; updated later once we have the actual ChipSet object
drive.bHead = data[i++];
drive.bCylinderSeek = data[i++]; // the data[] slot where we used to store drive.nHeads (or -1)
drive.bCylinder = data[i++];
@ -885,10 +886,10 @@ FDC.prototype.initDrive = function(drive, iDrive, data)
drive.bSector = data[i++];
drive.bSectorEnd = data[i++]; // aka EOT
drive.nBytes = data[i++];
/*
* The next group of properties are set by user requests to load/unload diskette images.
*
*
* NOTE: I now avoid reinitializing drive.disk in order to retain any previously mounted diskette across resets.
*
* drive.disk = null; // when a "disk" is "inserted" into the "drive", this is a Disk object
@ -911,7 +912,7 @@ FDC.prototype.initDrive = function(drive, iDrive, data)
/*
* If loadDiskette() must actually mount a *different* disk image at this late stage (ie, if it returns false),
* then we must mark ourselves as "not ready" again, and add another "wait for ready" test in Computer before
* finally powering the CPU. Otherwise, go ahead and restore any deltas to the current image.
* finally powering the CPU. Otherwise, go ahead and restore any deltas to the current image.
*/
if (this.loadDiskette(iDrive, sDisketteName, sDiskettePath, true)) {
if (drive.disk) {
@ -986,9 +987,9 @@ FDC.prototype.saveDrive = function(drive)
* Now we deviate from the 1.01a save format: instead of next storing all the deltas for the
* currently mounted disk (if any), we store only the name and path of the currently mounted disk
* (if any). Deltas for ALL disks, both currently mounted and previously mounted, are stored later.
*
*
* data[i++] = drive.disk? drive.disk.save() : null;
*
*
* To indicate this deviation, we store neither a null nor a delta array, but Computer.VERSION_102;
* if that value is not present, then the restore code will know it's dealing with a pre-v1.02 state.
*/
@ -1080,7 +1081,7 @@ FDC.prototype.seekDrive = function(drive, iSector, nSectors)
/*
* NOTE: We don't set bSectorEnd, as an FDC command would, but it's irrelevant, because we don't actually
* do anything with bSectorEnd at this point. Perhaps someday, when we faithfully honor/restrict requests
* to a single track (or a single cylinder, in the case of multi-track requests).
* to a single track (or a single cylinder, in the case of multi-track requests).
*/
drive.resCode = FDC.REG_DATA.RES.NONE;
/*
@ -1170,7 +1171,7 @@ FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAut
FDC.prototype.mountDiskette = function(drive, disk, sDisketteName, sDiskettePath)
{
drive.fBusy = false;
/*
* We shouldn't mount the diskette unless the drive is able to handle it; for example, DSDD (40-track)
* drives cannot read DSHD (80-track) diskettes.
@ -1179,23 +1180,23 @@ FDC.prototype.mountDiskette = function(drive, disk, sDisketteName, sDiskettePath
this.notice("Diskette \"" + sDisketteName + "\" too large for drive " + String.fromCharCode(0x41 + drive.iDrive));
disk = null;
}
if (disk) {
drive.disk = disk;
drive.sDisketteName = sDisketteName;
drive.sDiskettePath = sDiskettePath;
this.addDiskHistory(sDisketteName, sDiskettePath, disk);
/*
* Clearly, a successful mount implies a disk change, and I suppose that, technically, an *unsuccessful*
* mount should imply the same, but what would the real-world analog be? Inserting a piece of cardboard
* instead of an actual diskette? In any case, if we can do the user a favor by pretending (as far as the
* disk change line is concerned) that an unsuccessful mount never happened, let's do it.
*
*
* Successful unmounts are a different story, however; those *do* trigger a change. See unloadDrive().
*/
this.regInput |= FDC.REG_INPUT.DISK_CHANGE;
/*
* With the addition of notify(), users are now "alerted" whenever a diskette has finished loading;
* notify() is selective about its output, using print() if a print window is open, alert() otherwise.
@ -1205,12 +1206,12 @@ FDC.prototype.mountDiskette = function(drive, disk, sDisketteName, sDiskettePath
*/
this.notice("Mounted diskette \"" + sDisketteName + "\" in drive " + String.fromCharCode(0x41 + drive.iDrive), drive.fAutoMount);
}
if (drive.fAutoMount) {
drive.fAutoMount = false;
if (!--this.cAutoMount) this.setReady();
}
this.displayDiskette(drive.iDrive);
};
@ -1283,9 +1284,9 @@ FDC.prototype.unloadDrive = function(iDrive, fAutoUnload, fQuiet)
drive.sDisketteName = "";
drive.sDiskettePath = "";
drive.disk = null;
this.regInput |= FDC.REG_INPUT.DISK_CHANGE;
/*
* WARNING: This conversion of drive number to drive letter, starting with A:, is very simplistic
* and is not guaranteed to match the drive mapping that DOS ultimately uses.
@ -1422,17 +1423,17 @@ FDC.prototype.outFDCOutput = function(port, bOut, addrFrom)
* This no longer updates the internally selected drive (this.iDrive) based on regOutput, because (a) there seems
* to be no point, as all drive-related commands include their own "drive select" bits, and (b) it breaks the
* MODEL_5170 boot code. Here's why:
*
*
* Unlike previous models, the MODEL_5170 BIOS probes all installed diskette drives to determine drive type;
* ie, DSDD (40-track) or DSHD (80-track). So if there are two drives, the last selected drive will be drive 1.
* Immediately before booting, the BIOS issues an INT 0x13/AH=0 reset, which writes regOutput two times: first
* with FDC.REG_OUTPUT.ENABLE clear, and then with it set. However, both times, it ALSO loads the last selected
* drive number into regOutput's "drive select" bits.
*
*
* If we switched our selected drive to match regOutput, then the ST0 value we returned on an SENSE_INT command
* following the regOutput reset operation would indicate drive 1 instead of drive 0. But the BIOS requires
* the ST0 result from the SENSE_INT command ALWAYS be 0xC0 (not 0xC1), so the controller must not be propagating
* regOutput's "drive select" bits in the way I originally assumed.
* regOutput's "drive select" bits in the way I originally assumed.
*
* var iDrive = bOut & FDC.REG_OUTPUT.DS;
* if (bOut & (FDC.REG_OUTPUT.MOTOR_D0 << iDrive)) this.iDrive = iDrive;
@ -1493,7 +1494,7 @@ FDC.prototype.inFDCData = function(port, addrFrom)
FDC.prototype.outFDCData = function(port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "DATA[" + this.regDataTotal + "]");
if (this.regDataTotal < this.regDataArray.length) {
this.regDataArray[this.regDataTotal++] = bOut;
}
@ -1561,15 +1562,15 @@ FDC.prototype.doCmd = function()
* The only command bit of possible interest down the road might be the FDC.REG_DATA.CMD.MT (Multi-Track); the rest relate
* to storage format details that we cannot emulate as long as our diskette images contain nothing more than sector
* data without any formatting data.
*
* Similarly, we ignore parameters like SRT, HUT, HLT and the like, since our "motors" don't require physical delays;
*
* Similarly, we ignore parameters like SRT, HUT, HLT and the like, since our "motors" don't require physical delays;
* however, if timing issues become compatibility issues, we'll have to revisit those delays. In any case, the maximum
* speed of the simulation will still be limited by various spin-loops in the ROM BIOS that wait prescribed times, so even
* with infinitely fast hardware, the simulation will never run as fast as it theoretically could, unless we opt to identify
* those spin-loops and either patch them or skip over them.
*/
var bCmdMasked = bCmd & FDC.REG_DATA.CMD.MASK;
switch (bCmdMasked) {
case FDC.REG_DATA.CMD.SPECIFY: // 0x03
this.popSRT(); // SRT and HUT (encodings?)
@ -1579,7 +1580,7 @@ FDC.prototype.doCmd = function()
* No results are provided by this command, and fIRQ should remain false
*/
break;
case FDC.REG_DATA.CMD.SENSE_DRIVE: // 0x04
bDrive = this.popCmd(FDC.TERMS.DS);
bHeadSelect = (bDrive >> 2) & 0x1;
@ -1588,7 +1589,7 @@ FDC.prototype.doCmd = function()
this.beginResult();
this.pushST3(drive);
break;
case FDC.REG_DATA.CMD.WRITE_DATA: // 0x05
case FDC.REG_DATA.CMD.READ_DATA: // 0x06
bDrive = this.popCmd(FDC.TERMS.DS);
@ -1619,7 +1620,7 @@ FDC.prototype.doCmd = function()
this.pushResult(n, FDC.TERMS.N);
fIRQ = true;
break;
case FDC.REG_DATA.CMD.RECALIBRATE: // 0x07
bDrive = this.popCmd(FDC.TERMS.DS);
this.iDrive = (bDrive & 0x3);
@ -1629,7 +1630,7 @@ FDC.prototype.doCmd = function()
this.beginResult(); // no results provided; this command is typically followed by FDC.REG_DATA.CMD.SENSE_INT
fIRQ = true;
break;
case FDC.REG_DATA.CMD.SENSE_INT: // 0x08
drive = this.aDrives[this.iDrive];
drive.bHead = 0; // this command is documented as ALWAYS returning a head address of 0 in ST0; see pushST0()
@ -1642,11 +1643,11 @@ FDC.prototype.doCmd = function()
* in a row, and expects ST0 to contain a different drive number after each command (first 0, then 1,
* then 2, and finally 3). What makes this doubly weird is SENSE INTERRUPT STATUS (unlike SENSE
* DRIVE STATUS) is a drive-agnostic command.
*
*
* Didn't the original PC AT "HFCOMBO" controller limit support to TWO diskette drives max?
* And even if the PC AT supported other FDC controllers that DID support up to FOUR diskette drives,
* why should "DISK_RESET" hard-code a 4-drive loop?
*
*
* Well, whatever. All this head-scratching doesn't change the fact that I apparently have to
* "auto-increment" the internal drive number (this.iDrive) after each SENSE INTERRUPT STATUS command.
*/
@ -1655,13 +1656,13 @@ FDC.prototype.doCmd = function()
* No interrupt is generated by this command, so fIRQ should remain false.
*/
break;
case FDC.REG_DATA.CMD.READ_ID: // 0x0A
/*
* This command is used by "SETUP_DBL" in the MODEL_5170_REV3 BIOS to determine if a double-density
* (40-track) diskette has been inserted in a high-density (80-track) drive; ie, whether "double stepping"
* is required, since only 40 of the 80 possible "steps" are valid for a double-density diskette.
*
*
* To start, we'll focus on making this work in the normal case (80-track diskette in 80-track drive).
*/
bDrive = this.popCmd(FDC.TERMS.DS);
@ -1689,7 +1690,7 @@ FDC.prototype.doCmd = function()
this.pushResult(n, FDC.TERMS.N);
fIRQ = true;
break;
case FDC.REG_DATA.CMD.FORMAT_TRACK: // 0x0D
bDrive = this.popCmd(FDC.TERMS.DS);
bHeadSelect = (bDrive >> 2) & 0x1;
@ -1712,7 +1713,7 @@ FDC.prototype.doCmd = function()
this.pushResult(n, FDC.TERMS.N);
fIRQ = true;
break;
case FDC.REG_DATA.CMD.SEEK: // 0x0F
bDrive = this.popCmd(FDC.TERMS.DS);
bHeadSelect = (bDrive >> 2) & 0x1;
@ -1723,10 +1724,10 @@ FDC.prototype.doCmd = function()
* As discussed in initDrive(), we can no longer simply set bCylinder to the specified NCN;
* instead, we must calculate the delta between bCylinderSeek and the NCN, and adjust bCylinder
* by that amount. Then we simply move the NCN into bCylinderSeek without any range checking.
*
* Since bCylinder is now expressly defined as the "physical" cylinder number, it must never be
* allowed to exceed the physical boundaries of the drive (ie, never lower than 0, and never greater
* than or equal to nCylinders).
*
* Since bCylinder is now expressly defined as the "physical" cylinder number, it must never
* be allowed to exceed the physical boundaries of the drive (ie, never lower than 0, and never
* greater than or equal to nCylinders).
*/
bCylinder = this.popCmd(FDC.TERMS.NCN);
drive.bCylinder += bCylinder - drive.bCylinderSeek;
@ -1742,10 +1743,10 @@ FDC.prototype.doCmd = function()
if (drive.bCylinder == 0) {
drive.resCode |= FDC.REG_DATA.RES.TRACK0;
}
this.beginResult(); // like FDC.REG_DATA.CMD.RECALIBRATE, no results are provided
this.beginResult(); // like FDC.REG_DATA.CMD.RECALIBRATE, no results are provided
fIRQ = true;
break;
default:
if (DEBUG) {
this.messageDebugger("FDC operation unsupported (command=0x: " + str.toHexByte(bCmd) + ")");
@ -1760,7 +1761,7 @@ FDC.prototype.doCmd = function()
* After the Execution Phase (eg, DMA Terminal Count has occurred, or the EOT sector has been read/written),
* an interrupt is supposed to occur, signaling the beginning of the Result Phase. Once the first byte of the
* result has been read, the interrupt is cleared (see inFDCData).
*
*
* TODO: Technically, interrupt request status should be cleared by the FDC.REG_DATA.CMD.SENSE_INT command; in fact,
* if that command is issued and no interrupt was pending, then FDC.REG_DATA.RES.INVALID should be returned (via ST0).
*/
@ -2014,9 +2015,9 @@ FDC.prototype.doWrite = function(drive)
FDC.prototype.doFormat = function(drive)
{
drive.resCode = FDC.REG_DATA.RES.NOT_READY | FDC.REG_DATA.RES.INCOMPLETE;
//if (DEBUG) this.messageDebugger("doFormat()");
if (drive.disk) {
drive.sector = null;
drive.resCode = FDC.REG_DATA.RES.NONE;
@ -2186,7 +2187,7 @@ FDC.prototype.writeFormat = function(drive, b)
return -1;
}
}
drive.cSectorsFormatted++;
drive.cSectorsFormatted++;
}
if (drive.cSectorsFormatted >= drive.bSectorEnd) b = -1;
return b;
@ -2276,7 +2277,7 @@ FDC.prototype.messagePort = function(port, bOut, addrFrom, name, bIn)
/*
* Port input notification table
*
*
* TODO: Even though port 0x3F7 was not present on controllers prior to MODEL_5170, I'm taking the easy
* way out and always emulating it. So, consider an FDC parameter to disable that feature for stricter compatibility.
*/
@ -2288,7 +2289,7 @@ FDC.aPortInput = {
/*
* Port output notification table
*
*
* TODO: Even though port 0x3F7 was not present on controllers prior to MODEL_5170, I'm taking the easy
* way out and always emulating it. So, consider an FDC parameter to disable that feature for stricter compatibility.
*/

File diff suppressed because it is too large Load diff