Fixed CMOS date initialization (Model 5170 successfully gets current date/time now)

This commit is contained in:
Jeff Parsons 2014-10-05 18:26:29 -07:00 committed by jeffpar
commit 4ff357669d
3 changed files with 255 additions and 194 deletions

View file

@ -72,7 +72,7 @@ if (typeof module !== 'undefined') {
* SW2[1-4] (bits 3-0) "NNNNxxxx": number of 32Kb blocks of I/O expansion RAM present
*
* TODO: There are cryptic references to SW2[5] in the original (5150) TechRef, and apparently the 8255A PPI can
* be programmed to return it (which we support), but its purpose is unclear to me (see PPI_B.ENABLE_SW2).
* be programmed to return it (which we support), but its purpose remains unclear to me (see PPI_B.ENABLE_SW2).
*
* For example, sw1="01110011" indicates that all SW1 DIP switches are ON, except for SW1[1], SW1[5] and SW1[6],
* which are OFF. Internally, the order of these bits must reversed (to 11001110) and then inverted (to 00110001)
@ -150,7 +150,7 @@ function ChipSet(parmsChipSet)
this.model = (this.model !== undefined? parseInt(this.model, 10) : ChipSet.MODEL_5150);
/*
* Given that the ROM BIOS is hard-coded to load boot sectors @0000:7C00, the minimum amount of system RAM
* Given that the ROM BIOS is hard-coded to load boot sectors @ 0000:7C00, the minimum amount of system RAM
* required to boot is therefore 32Kb. Whether that's actually enough to run any or all versions of PC-DOS is
* a separate question. FYI, with only 16Kb, the ROM BIOS will still try to boot, and fail miserably.
*/
@ -178,6 +178,9 @@ function ChipSet(parmsChipSet)
this.cDMACs = this.cPICs = 1;
if (this.model >= ChipSet.MODEL_5170) {
this.cDMACs = this.cPICs = 2;
/*
* Initialize minimal support for the MODEL_5170's HFCOMBO card (see ChipSet.HFCOMBO below for details)
*/
this.regsHFCombo = {
bCtrl: 0x00, // port 0x1F4
bStatus: 0x7F // port 0x1F7
@ -732,8 +735,8 @@ ChipSet.CMOS.DIAG.RESERVED = 0x03;
ChipSet.FDRIVE = { // abCMOSData[ChipSet.CMOS.ADDR.FDRIVE] values (drive 0 value in high nibble, drive 1 value in low nibble)
NONE: 0, // no drive
DSDD: 1, // double-sided double-density drive (48 TPI, 40-track, 360Kb max)
DSHC: 2 // double-sided high-capacity drive (96 TPI, 80-track, 1.2Mb max)
DSDD: 1, // double-sided double-density drive (48 TPI, 40 tracks, 360Kb max)
DSHD: 2 // double-sided high-density drive (96 TPI, 80 tracks, 1.2Mb max)
};
/*
@ -803,31 +806,38 @@ ChipSet.COPROC.PORT_CLEAR = 0xF0; // clear the coprocessor's "busy" state
ChipSet.COPROC.PORT_RESET = 0xF1; // reset the coprocessor
/*
* Ports used by MODEL_5170 BIOS for "Combo Hard File/Diskette Card" check (@F000:144C)
* Ports used by MODEL_5170 BIOS for "Combo Hard File/Diskette Card" check (@ F000:144C)
*
* The ChipSet component provides minimal boot-time support for the "IBM Personal Computer
* AT Fixed Disk and Diskette Drive Adapter", aka the HFCOMBO card, until we're able to fork
* the HDC component into a new HDCombo component to deal with the "Fixed Disk" portion
* of the HFCOMBO card. Fortunately, the "Diskette Drive Adapter" portion of the card is
* quite compatible with the existing FDC component, so that component can be used as-is,
* with minor tweaks.
* extremely compatible with the existing FDC component, so that component can be used as-is,
* with only minor tweaks.
*
* Initially, we intercepted reads for HFCOMBO's STATUS port simply to reduce boot time;
* otherwise, our default "unknown port" response of 0xFF would maximize boot delay. To solve
* that, the STATUS port simply needs to return a byte with bit 7 clear, so that the BIOS
* will then attempt to write/read the CTRL port.
* otherwise, the default "unknown port" response of 0xFF would lengthen boot time. To solve
* that, the STATUS port simply needed to return a byte with bit 7 clear, so that the BIOS
* would then attempt to write/read the CTRL port.
*
* Next, we initially treated the HFCOMBO's CTRL port as an "unknown port", because again,
* we didn't need HDC support and it didn't seem to affect FDC support. But it turns out
* that FDC support IS affected, because if the BIOS doesn't set the "DUAL" bit (bit 0) of the
* "HFCNTRL" byte at 40:8F, then when it comes time later to report the diskette drive type,
* the "DISK_TYPE" function (@F000:273D) will branch to one of two almost-identical blocks of
* code -- specifically, the block that disallows diskette drive types >= 2 instead of >= 3.
* the "DISK_TYPE" function (@ F000:273D) will branch to one of two almost-identical blocks of
* code -- specifically, the block that disallows diskette drive types >= 2 (ChipSet.FDRIVE.DSDD)
* instead of >= 3 (ChipSet.FDRIVE.DSHD).
*/
ChipSet.HFCOMBO = {};
ChipSet.HFCOMBO.CTRL = {PORT: 0x1F4};
ChipSet.HFCOMBO.STATUS = {PORT: 0x1F7};
/*
* ChipSet-related BIOS interrupts, functions, and other parameters
*/
ChipSet.BIOS = {};
ChipSet.BIOS.RTC = 0x1A;
/**
* @this {ChipSet}
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
@ -884,16 +894,11 @@ ChipSet.prototype.initBus = function(cmp, bus, cpu, dbg)
this.dbg = dbg;
this.cmp = cmp;
this.kbd = cmp.getComponentByType("Keyboard");
if (DEBUGGER && dbg) {
var chipset = this;
dbg.messageInit(ChipSet);
dbg.messageDump(ChipSet.MESSAGE_PIC, function onDumpPIC() {
chipset.dumpPIC();
});
dbg.messageDump(ChipSet.MESSAGE_TIMER, function onDumpTimer() {
chipset.dumpTimer();
});
}
/*
* This divisor is invariant, so we calculate it as soon as we're able to query the CPU's base speed.
*/
this.nTicksDivisor = Math.round(cpu.getCyclesPerSecond() / ChipSet.TIMER_TICKS_PER_SEC);
bus.addPortInputTable(this, ChipSet.aPortInput);
bus.addPortOutputTable(this, ChipSet.aPortOutput);
if (this.model < ChipSet.MODEL_5170) {
@ -903,10 +908,25 @@ ChipSet.prototype.initBus = function(cmp, bus, cpu, dbg)
bus.addPortInputTable(this, ChipSet.aPortInput5170);
bus.addPortOutputTable(this, ChipSet.aPortOutput5170);
}
/*
* This divisor is invariant, so we calculate it as soon as we're able to query the CPU's base speed.
*/
this.nTicksDivisor = Math.round(cpu.getCyclesPerSecond() / ChipSet.TIMER_TICKS_PER_SEC);
if (DEBUGGER) {
if (dbg) {
var chipset = this;
dbg.messageInit(ChipSet);
dbg.messageDump(ChipSet.MESSAGE_PIC, function onDumpPIC()
{
chipset.dumpPIC();
});
dbg.messageDump(ChipSet.MESSAGE_TIMER, function onDumpTimer()
{
chipset.dumpTimer();
});
dbg.messageDump(ChipSet.MESSAGE_CHIPSET, function onDumpCMOS()
{
chipset.dumpCMOS();
});
}
cpu.addInterruptNotify(ChipSet.BIOS.RTC, this, this.intBIOSRTC);
}
};
/**
@ -1075,8 +1095,10 @@ ChipSet.prototype.initRTCDate = function(sDate)
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_WEEK_DAY] = date.getDay() + 1;
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MONTH_DAY] = date.getDate();
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MONTH] = date.getMonth() + 1;
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_YEAR] = date.getFullYear() % 100;
var nYear = date.getFullYear();
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_YEAR] = nYear % 100;
var nCentury = (nYear / 100);
this.abCMOSData[ChipSet.CMOS.ADDR.CENTURY_DATE] = (nCentury % 10) | ((nCentury / 10) << 4);
this.nCyclesCMOSLastUpdate = -1;
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSA] = 0x26; // hard-coded default; refer to ChipSet.CMOS.STATUSA.DV and ChipSet.CMOS.STATUSA.RS
@ -1652,7 +1674,7 @@ ChipSet.prototype.getSWFloppyDrives = function(fInit)
*
* @this {ChipSet}
* @param {number} iDrive (0-based)
* @return {number} one of the ChipSet.FDRIVE values (ie, NONE: 0, DSDD: 1, DSHC: 2)
* @return {number} one of the ChipSet.FDRIVE values (ie, NONE: 0, DSDD: 1, DSHD: 2)
*/
ChipSet.prototype.getSWFloppyDriveType = function(iDrive)
{
@ -1660,7 +1682,7 @@ ChipSet.prototype.getSWFloppyDriveType = function(iDrive)
* TODO: For MODEL_5170, we default all floppy drive types to High Capacity, but more control would be nice.
*/
if (iDrive < this.getSWFloppyDrives()) {
return (this.model < ChipSet.MODEL_5170? ChipSet.FDRIVE.DSDD : ChipSet.FDRIVE.DSHC);
return (this.model < ChipSet.MODEL_5170? ChipSet.FDRIVE.DSDD : ChipSet.FDRIVE.DSHD);
}
return ChipSet.FDRIVE.NONE;
};
@ -1849,6 +1871,27 @@ ChipSet.prototype.dumpTimer = function()
}
};
/**
* dumpCMOS()
*
* @this {ChipSet}
*/
ChipSet.prototype.dumpCMOS = function()
{
if (DEBUGGER) {
var sDump = "";
for (var p in ChipSet.CMOS.ADDR) {
var iCMOS = ChipSet.CMOS.ADDR[p];
if (iCMOS >= 0 && iCMOS < ChipSet.CMOS.ADDR.MASK) {
var b = (iCMOS <= ChipSet.CMOS.ADDR.RTC_STATUSD? this.getRTCByte(iCMOS) : this.abCMOSData[iCMOS]);
if (sDump) sDump += '\n';
sDump += str.pad(p + "(" + str.toHexByte(iCMOS) + "):", 19) + str.toHexByte(b);
}
}
this.dbg.message(sDump);
}
};
/**
* inDMAChannelAddr(iDMAC, iChannel, port, addrFrom)
*
@ -1948,13 +1991,13 @@ ChipSet.prototype.outDMAChannelCount = function(iDMAC, iChannel, port, bOut, add
* Bits 47 are set whenever their corresponding channel is requesting service."
*
* TRIVIA: This hook wasn't installed when I was testing with the MODEL_5150 ROM BIOS, and it
* didn't matter, but the MODEL_5160 ROM BIOS checks it several times, including @F000:E156, where
* didn't matter, but the MODEL_5160 ROM BIOS checks it several times, including @ F000:E156, where
* it verifies that TIMER1 didn't request service on channel 0.
*/
ChipSet.prototype.inDMAStatus = function(iDMAC, port, addrFrom)
{
/*
* HACK: Unlike the MODEL_5150, the MODEL_5160 ROM BIOS checks DMA channel 0 for TC (@F000:E4DF)
* HACK: Unlike the MODEL_5150, the MODEL_5160 ROM BIOS checks DMA channel 0 for TC (@ F000:E4DF)
* after running a number of unrelated tests, since enough time would have passed for channel 0 to
* have reached TC at least once. So I simply OR in a hard-coded TC bit for channel 0 every time
* status is read.
@ -2855,9 +2898,9 @@ ChipSet.prototype.outTimer = function(iTimer, bOut, addrFrom)
}
/*
* HACK to detect lower-than-normal initial timer counts and reduce the length of CPU bursts using
* HACK to detect lower-than-normal initial timer counts and reduce the length of CPU bursts, using
* cpu.setBurstDivisor(). Alternatively, the CPU could ask us for a cycle limit, via getTimerCycleLimit(),
* prior to starting a new burst, but for now, this hack actually performs better (see "BASICA DONKEY.BAS").
* prior to starting a new burst, but this seems to perform better (see "BASICA DONKEY.BAS").
*/
if (iTimer == ChipSet.TIMER0.INDEX) {
var countInit = this.getTimerInit(ChipSet.TIMER0.INDEX);
@ -2874,21 +2917,12 @@ ChipSet.prototype.outTimer = function(iTimer, bOut, addrFrom)
/*
* HACK to satisfy the quick h/w interrupt turn-around expected by the ROM BIOS when it sets TIMER0 to a
* low test count (0x16); since we typically don't update any of the timers until after we've finished a
* burst of CPU cycles, we originally solved that particular problem by forcing a h/w interrupt to be
* simulated immediately, by reducing the starting cycle count for TIMER0 to an earlier point in time and
* then immediately calling updateTimer().
* burst of CPU cycles, we reduce the current burst cycle count, so that the burst will end at roughly the
* same time a timer interrupt is expected. Note that in some cases, if the number of cycles remaining
* in the current burst is less than the target, this will have the effect of *lengthening* the current
* burst instead of shortening it, but stepCPU() should be OK with that.
*
* if (bOut == 0x16) {
* timer.nStartCycles -= 4 * bOut; // used a multiplier of 4 since there were always 4 cycles per tick
* this.updateTimer(iTimer);
* }
*
* However, a cleaner solution is to reduce the current burst cycle count instead, so that a timer interrupt
* will be simulated at the appropriate time, rather than immediately. Note that in some cases, if the number
* of cycles remaining in the current burst is less than the target, this will have the effect of *lengthening*
* the current burst instead of shortening it, but stepCPU() should be OK with that.
*
* Also notice how this complements the setBurstDivisor() HACK above: while that code is concerned with how
* Notice how this complements the setBurstDivisor() HACK above: while that code is concerned with how
* to deal with low timer counts prior to starting new bursts, here we're concerned with low timer counts
* (in particular, single-byte LSB counts) programmed in the middle of a burst.
*
@ -2950,14 +2984,14 @@ ChipSet.prototype.outTimerCtrl = function(port, bOut, addrFrom)
this.setTimerMode(iTimer, bcd, mode, rw);
/*
* The 5150 ROM BIOS code @F000:E285 ("TEST.7") would fail after a warm boot (eg, after a CTRL-ALT-DEL) because
* The 5150 ROM BIOS code @ F000:E285 ("TEST.7") would fail after a warm boot (eg, after a CTRL-ALT-DEL) because
* it assumed that no TIMER0 interrupt would occur between the point it unmasked the TIMER0 interrupt and the
* point it started reprogramming TIMER0.
*
* Similarly, the 5160 ROM BIOS @F000:E35D ("8253 TIMER CHECKOUT") would fail after initializing the EGA BIOS,
* Similarly, the 5160 ROM BIOS @ F000:E35D ("8253 TIMER CHECKOUT") would fail after initializing the EGA BIOS,
* because the EGA BIOS uses TIMER0 during its diagnostics; as in the previous example, by the time the 8253
* test code runs later, there's now a pending TIMER0 interrupt, which triggers an interrupt as soon as IRQ0 is
* unmasked @F000:E364.
* unmasked @ F000:E364.
*
* After looking at this problem at bit more closely the second time around (while debugging the EGA BIOS),
* it turns out I missed an important 8253 feature: whenever a new MODE0 control word OR a new MODE0 count
@ -2969,7 +3003,7 @@ ChipSet.prototype.outTimerCtrl = function(port, bOut, addrFrom)
if (iTimer == ChipSet.TIMER0.INDEX) this.clearIRR(ChipSet.IRQ.TIMER0);
/*
* Another TIMER0 HACK: The "CASSETTE DATA WRAP TEST" @F000:E51E occasionally reports an error when the second of
* Another TIMER0 HACK: The "CASSETTE DATA WRAP TEST" @ F000:E51E occasionally reports an error when the second of
* two TIMER0 counts it latches is greater than the first. You would think the ROM BIOS would expect this, since
* TIMER0 can reload its count at any time. Is the ROM BIOS assuming that TIMER0 was initialized sufficiently
* recently that this should never happen? I'm not sure, but for now, let's try resetting TIMER0's count immediately
@ -3203,7 +3237,7 @@ ChipSet.prototype.updateTimer = function(iTimer, fCycleReset)
}
/*
* Early implementation of this mode was minimal because when using this mode, the ROM BIOS simply wanted
* to see the count changing; it wasn't looking for interrupts. See ROM BIOS "TEST.03" code @F000:E0DE,
* to see the count changing; it wasn't looking for interrupts. See ROM BIOS "TEST.03" code @ F000:E0DE,
* where TIMER1 is programmed for MODE2, LSB (the same settings, incidentally, used immediately afterward
* for TIMER1 in conjunction with DMA channel 0 memory refreshes).
*
@ -3383,7 +3417,7 @@ ChipSet.prototype.updatePPIB = function(bOut)
this.bPPIB = bOut;
if (fNewSpeaker != fOldSpeaker) {
/*
* Originally, this code didn't catch the "ERROR_BEEP" case @F000:EC34, which first turns both PPI_B.CLK_TIMER2 (0x01)
* Originally, this code didn't catch the "ERROR_BEEP" case @ F000:EC34, which first turns both PPI_B.CLK_TIMER2 (0x01)
* and PPI_B.SPK_TIMER2 (0x02) off, then turns on only PPI_B.SPK_TIMER2 (0x02), then restores the original port value.
*
* So, when the ROM BIOS keyboard buffer got full, we didn't issue a BEEP alert. I've fixed that by limiting the test
@ -3950,6 +3984,53 @@ ChipSet.prototype.inHFCStatus = function(port, addrFrom)
return b;
};
/**
* intBIOSRTC(addr)
*
* INT 0x1A Quick Reference:
*
* AH
* ----
* 0x00 Get current clock count in CX:DX
* 0x01 Set current clock count from CX:DX
* 0x02 Get real-time clock using BCD (CH=hours, CL=minutes, DH=seconds)
* 0x03 Set real-time clock using BCD (CH=hours, CL=minutes, DH=seconds, DL=1 if Daylight Savings Time option)
* 0x04 Get real-time date using BCD (CH=century, CL=year, DH=month, DL=day)
* 0x05 Set real-time date using BCD (CH=century, CL=year, DH=month, DL=day)
* 0x06 Set alarm using BCD (CH=hours, CL=minutes, DH=seconds)
* 0x07 Reset alarm
*
* @this {ChipSet}
* @param {number} addr
* @return {boolean} true to proceed with the INT 0x1A software interrupt, false to skip
*/
ChipSet.prototype.intBIOSRTC = function(addr)
{
if (DEBUGGER) {
var AH = this.cpu.regAX >> 8;
if (this.dbg && this.dbg.messageEnabled(this.dbg.MESSAGE_CHIPSET)) {
this.dbg.message("ChipSet.intBIOSRTC(AH=" + str.toHexByte(AH) + ") at " + str.toHexAddr(addr - this.cpu.segCS.base, this.cpu.segCS.sel));
this.cpu.addInterruptReturn(addr, function(chipset, nCycles) {
return function onBIOSRTCReturn(nLevel) {
nCycles = chipset.cpu.getCycles() - nCycles;
var sResult = "C=" + (chipset.cpu.getCF()? 1 : 0);
var CL = chipset.cpu.regDX & 0xff;
var CH = chipset.cpu.regDX >> 8;
var DL = chipset.cpu.regDX & 0xff;
var DH = chipset.cpu.regDX >> 8;
if (AH == 0x02 || AH == 0x03) {
sResult += " CH(hour)=" + str.toHexWord(CH) + " CL(min)=" + str.toHexByte(CL) + " DH(sec)=" + str.toHexByte(DH);
} else if (AH == 0x04 || AH == 0x05) {
sResult += " CX(year)=" + str.toHexWord(chipset.cpu.regCX) + " DH(month)=" + str.toHexByte(DH) + " DL(day)=" + str.toHexByte(DL);
}
chipset.messageDebugger("ChipSet.intBIOSRTC(" + nLevel + "): " + sResult + " (cycles=" + nCycles + ")");
};
}(this, this.cpu.getCycles()));
}
}
return true;
};
/**
* parseSwitches(s, def)
*

View file

@ -74,15 +74,15 @@ if (typeof module !== 'undefined') {
*
* 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)
* 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 the new cylinder number for a seek operation
* NCN New Cylinder 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
* 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
@ -183,19 +183,13 @@ function FDC(parmsFDC) {
Component.subclass(Component, FDC);
/*
* FDC BIOS interrupts, functions, and other parameters
*/
FDC.BIOS = {};
FDC.BIOS.DISKETTE_INT = 0x13;
FDC.DEFAULT_DRIVE_NAME = "Floppy Drive";
/*
* FDC Digital Output Register (DOR) (0x3F2, write-only)
*
* NOTE: Reportedly, a drive's MOTOR bit had to be ON before the the drive could be selected, so outFDCOutput()
* verifies that. Also, motor start time for early model drives was 500ms, but we make no attempt to simulate that.
* 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
@ -358,6 +352,12 @@ FDC.aCmdSeqs = {
0x0F: {cbWrite: 3, cbRead: 0, name: "SEEK"}
};
/*
* FDC BIOS interrupts, functions, and other parameters
*/
FDC.BIOS = {};
FDC.BIOS.DISKETTE_INT = 0x13;
/**
* setBinding(sHTMLClass, sHTMLType, sBinding, control)
*
@ -538,7 +538,7 @@ FDC.prototype.powerUp = function(data, fRepower)
for (iDrive = 0; iDrive < this.nDrives; iDrive++) {
var drive = this.aDrives[iDrive];
drive.bType = this.chipset.getSWFloppyDriveType(iDrive);
if (drive.bType == ChipSet.FDRIVE.DSHC) {
if (drive.bType == ChipSet.FDRIVE.DSHD) {
drive.nCylinders = 80;
}
}
@ -1363,14 +1363,14 @@ FDC.prototype.outFDCOutput = function(port, bOut, addrFrom)
* 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 DSHC (80-track). So if there are two drives, the last selected drive will be drive 1.
* Immediately before booting, the BIOS issues an INT 0x3/AH=0 reset, which writes regOutput two times: first
* 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 # into regOutput's "drive select" bits.
* 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 INT_STATUS command
* following the regOutput reset operation would indicate drive 1 instead of drive 0. But the BIOS requires
* the ST0 result from the INT_STATUS command ALWAYS be 0xC0, not 0xC1, so the controller must not be propagating
* the ST0 result from the INT_STATUS command ALWAYS be 0xC0 (not 0xC1), so the controller must not be propagating
* regOutput's "drive select" bits in the way I originally assumed.
*/
// var iDrive = bOut & FDC.REG_OUTPUT.DS;
@ -1483,12 +1483,14 @@ FDC.prototype.outFDCControl = function(port, bOut, addrFrom)
/**
* intBIOSDiskette(addr)
*
* NOTE: This function tries to differentiate FDC requests from HDC requests, by whether the INT 0x13 drive number in DL is < 0x80;
* however, not all INT 0x13 functions required a drive number in DL, and not all callers supplied one.
* NOTE: This function tries to differentiate FDC requests from HDC requests, by whether the INT 0x13 drive number
* in DL is < 0x80; however, not all INT 0x13 functions required a drive number in DL, and not all callers supplied one.
*
* INT 0x13 Quick Reference:
*
* AH: 0x00 Reset
* AH
* ----
* 0x00 Reset
* 0x01 Get status (from last operation)
* 0x02 Read sectors
* 0x03 Write sectors
@ -1497,12 +1499,12 @@ FDC.prototype.outFDCControl = function(port, bOut, addrFrom)
*
* For Read, Write, Verify and Format commands:
*
* DL: drive number (0-3 allowed, value checked)
* DH: head number (0-1 allowed, not value checked)
* CH: track number (0-39 allowed, not value checked [which is good, because high-density diskettes go up to 80 tracks])
* CL: sector number (1-8 allowed, not value checked [which is good, because support for 9-sector tracks was later added])
* AL: number of sectors (max of 8, not value checked)
* ES:BX: sector buffer
* DL drive number (0-3 allowed, value checked)
* DH head number (0-1 allowed, not value checked)
* CH track number (0-39 allowed, not value checked [which is good, because high-density diskettes go up to 80 tracks])
* CL sector number (1-8 allowed, not value checked [which is good, because support for 9-sector tracks was later added])
* AL number of sectors (max of 8, not value checked)
* ES:BX sector buffer
*
* @this {FDC}
* @param {number} addr
@ -1522,7 +1524,9 @@ FDC.prototype.intBIOSDiskette = function(addr)
// this.cpu.haltCPU();
this.cpu.addInterruptReturn(addr, function (fdc, nCycles) {
return function onBIOSDisketteReturn(nLevel) {
fdc.intBIOSDisketteReturn(nCycles, nLevel);
nCycles = fdc.cpu.getCycles() - nCycles;
fdc.messageDebugger("FDC.intBIOS(" + nLevel + "): C=" + (fdc.cpu.getCF()? 1 : 0) + " (cycles=" + nCycles + ")");
// if (DEBUG && nCycles > 10000) fdc.cpu.haltCPU();
};
}(this, this.cpu.getCycles()));
}
@ -1530,22 +1534,6 @@ FDC.prototype.intBIOSDiskette = function(addr)
return true;
};
/**
* intBIOSDisketteReturn(nCycles, nLevel)
*
* @this {FDC}
* @param {number} nCycles
* @param {number} nLevel
*/
FDC.prototype.intBIOSDisketteReturn = function(nCycles, nLevel)
{
if (DEBUGGER) {
nCycles = this.cpu.getCycles() - nCycles;
this.messageDebugger("FDC.intBIOSReturn(" + nLevel + "): C=" + (this.cpu.getCF()? 1 : 0) + " (cycles=" + nCycles + ")");
// if (DEBUG && nCycles > 10000) this.cpu.haltCPU();
}
};
/**
* doCmd()
*
@ -1622,11 +1610,12 @@ FDC.prototype.doCmd = function()
drive = this.aDrives[this.iDrive];
drive.bCylinder = drive.bCylinderSeek = 0;
drive.resCode = FDC.REG_DATA.RES.SEEK_END | FDC.REG_DATA.RES.TRACK0;
this.beginResult(); // no results are provided; this command is typically followed by FDC.REG_DATA.CMD.INT_STATUS
this.beginResult(); // no results provided; this command is typically followed by FDC.REG_DATA.CMD.INT_STATUS
fIRQ = true;
break;
case FDC.REG_DATA.CMD.INT_STATUS: // 0x08 (SENSE INTERRUPT STATUS)
drive = this.aDrives[this.iDrive];
drive.bHead = 0; // this command is documented as ALWAYS returning a head address of 0 in ST0; see pushST0()
this.beginResult();
this.pushST0(drive);
this.pushResult(drive.bCylinder, "PCN");// no interrupt is generated by this command, so fIRQ should remain false
@ -1784,7 +1773,7 @@ FDC.prototype.pushResult = function(bResult, name)
*/
FDC.prototype.pushST0 = function(drive)
{
this.pushResult(drive.iDrive | drive.bHead | (drive.resCode & FDC.REG_DATA.RES.ST0), "ST0");
this.pushResult(drive.iDrive | (drive.bHead << 2) | (drive.resCode & FDC.REG_DATA.RES.ST0), "ST0");
};
/**
@ -1889,7 +1878,8 @@ FDC.prototype.doRead = function(drive)
{
/*
* With only NOT_READY and INCOMPLETE set, an empty drive causes DOS to report "General Failure";
* with the addition of NO_DATA, DOS reports "Sector not found".
* with the addition of NO_DATA, DOS reports "Sector not found". The traditional "Drive not ready"
* error message is not triggered by anything we return here, but simply by BIOS commands timing out.
*/
drive.resCode = FDC.REG_DATA.RES.NOT_READY | FDC.REG_DATA.RES.INCOMPLETE;

View file

@ -105,57 +105,6 @@ function HDC(parmsHDC) {
Component.subclass(Component, HDC);
/*
* HDC BIOS interrupts, functions, and other parameters
*/
HDC.BIOS = {};
HDC.BIOS.DISK_INT = 0x13;
HDC.BIOS.DISK_CMD = {};
HDC.BIOS.DISK_CMD.RESET = 0x00;
HDC.BIOS.DISK_CMD.GET_STATUS = 0x01;
HDC.BIOS.DISK_CMD.READ_SECTORS = 0x02;
HDC.BIOS.DISK_CMD.WRITE_SECTORS = 0x03;
HDC.BIOS.DISK_CMD.VERIFY_SECTORS = 0x04;
HDC.BIOS.DISK_CMD.FORMAT_TRACK = 0x05;
HDC.BIOS.DISK_CMD.FORMAT_BAD = 0x06;
HDC.BIOS.DISK_CMD.FORMAT_DRIVE = 0x07;
HDC.BIOS.DISK_CMD.GET_DRIVEPARMS = 0x08;
HDC.BIOS.DISK_CMD.SET_DRIVEPARMS = 0x09;
HDC.BIOS.DISK_CMD.READ_LONG = 0x0A;
HDC.BIOS.DISK_CMD.WRITE_LONG = 0x0B;
HDC.BIOS.DISK_CMD.SEEK = 0x0C;
HDC.BIOS.DISK_CMD.ALT_RESET = 0x0D;
HDC.BIOS.DISK_CMD.READ_BUFFER = 0x0E;
HDC.BIOS.DISK_CMD.WRITE_BUFFER = 0x0F;
HDC.BIOS.DISK_CMD.TEST_READY = 0x10;
HDC.BIOS.DISK_CMD.RECALIBRATE = 0x11;
HDC.BIOS.DISK_CMD.RAM_DIAGNOSTIC = 0x12;
HDC.BIOS.DISK_CMD.DRV_DIAGNOSTIC = 0x13;
HDC.BIOS.DISK_CMD.CTL_DIAGNOSTIC = 0x14;
/*
* When the HDC BIOS overwrites the ROM BIOS INT 0x13 address, it saves the original INT 0x13 address
* in the INT 0x40 vector. The HDC BIOS's plan was simple, albeit slightly flawed: assign fixed disks
* drive numbers >= 0x80, and whenever someone calls INT 0x13 with a drive number < 0x80, invoke the
* original INT 0x13 diskette code via INT 0x40 and return via RET 2.
*
* Unfortunately, not all original INT 0x13 functions required a drive number in DL (eg, the "reset"
* function, where AH=0). And the HDC BIOS knew this, which is why, in the case of the "reset" function,
* the HDC BIOS performs BOTH an INT 0x40 diskette reset AND an HDC reset -- it can't be sure which
* controller the caller really wants to reset.
*
* An unfortunate side-effect of this behavior: when the HDC BIOS is initialized for the first time, it may
* issue several resets internally, depending on whether there are 0, 1 or 2 hard disks installed, and each
* of those resets also triggers completely useless diskette resets, each wasting up to two seconds waiting
* for the FDC to interrupt. The FDC tries to interrupt, but it can't, because at this early stage of
* ROM BIOS initialization, IRQ_FDC hasn't been unmasked yet.
*
* My work-around: have the HDC component hook INT 0x40, and every time an INT 0x40 is issued with AH=0 and
* IRQ_FDC masked, eat the INT 0x40 interrupt.
*/
HDC.BIOS.DISKETTE_INT = 0x40;
/*
* HDC defaults, in case drive parameters weren't specified
*/
@ -330,6 +279,41 @@ if (DEBUG) {
};
}
/*
* HDC BIOS interrupts, functions, and other parameters
*/
HDC.BIOS = {};
HDC.BIOS.DISK_INT = 0x13;
HDC.BIOS.DISK_CMD = {};
HDC.BIOS.DISK_CMD.RESET = 0x00;
HDC.BIOS.DISK_CMD.GET_STATUS = 0x01;
HDC.BIOS.DISK_CMD.READ_SECTORS = 0x02;
HDC.BIOS.DISK_CMD.WRITE_SECTORS = 0x03;
HDC.BIOS.DISK_CMD.VERIFY_SECTORS = 0x04;
HDC.BIOS.DISK_CMD.FORMAT_TRACK = 0x05;
HDC.BIOS.DISK_CMD.FORMAT_BAD = 0x06;
HDC.BIOS.DISK_CMD.FORMAT_DRIVE = 0x07;
HDC.BIOS.DISK_CMD.GET_DRIVEPARMS = 0x08;
HDC.BIOS.DISK_CMD.SET_DRIVEPARMS = 0x09;
HDC.BIOS.DISK_CMD.READ_LONG = 0x0A;
HDC.BIOS.DISK_CMD.WRITE_LONG = 0x0B;
HDC.BIOS.DISK_CMD.SEEK = 0x0C;
HDC.BIOS.DISK_CMD.ALT_RESET = 0x0D;
HDC.BIOS.DISK_CMD.READ_BUFFER = 0x0E;
HDC.BIOS.DISK_CMD.WRITE_BUFFER = 0x0F;
HDC.BIOS.DISK_CMD.TEST_READY = 0x10;
HDC.BIOS.DISK_CMD.RECALIBRATE = 0x11;
HDC.BIOS.DISK_CMD.RAM_DIAGNOSTIC = 0x12;
HDC.BIOS.DISK_CMD.DRV_DIAGNOSTIC = 0x13;
HDC.BIOS.DISK_CMD.CTL_DIAGNOSTIC = 0x14;
/*
* When the HDC BIOS overwrites the ROM BIOS INT 0x13 address, it saves the original INT 0x13 address
* in the INT 0x40 vector.
*/
HDC.BIOS.DISKETTE_INT = 0x40;
/**
* setBinding(sHTMLClass, sHTMLType, sBinding, control)
*
@ -1068,7 +1052,7 @@ HDC.prototype.outHDCPulse = function(port, bOut, addrFrom) {
*/
this.regPulse = bOut;
/*
* The HDC BIOS "COMMAND" function (@C800:0562) waits for these ALL status bits after writing to both regPulse
* The HDC BIOS "COMMAND" function (@ C800:0562) waits for these ALL status bits after writing to both regPulse
* and regPattern, so we must oblige it.
*/
/*
@ -1108,25 +1092,28 @@ HDC.prototype.outHDCNoise = function(port, bOut, addrFrom) {
/**
* intBIOSDisk(addr)
*
* NOTE: This function tries to differentiate HDC requests from FDC requests, by whether the INT 0x13 drive number in DL is >= 0x80
* NOTE: This function differentiates HDC requests from FDC requests, based on whether the INT 0x13 drive number
* in DL is >= 0x80.
*
* HACK: The HDC BIOS code for both INT 0x13/AH=0x00 and INT 0x13/AH=0x09 calls "INIT_DRV" @C800:0427, which is hard-coded
* to issue the HDC.REG_DATA.CMD.INIT_DRIVE command for BOTH drives 0 and 1 (aka drive numbers 0x80 and 0x81), regardless of
* the drive number specified in DL; this means that the HDC.REG_DATA.CMD.INIT_DRIVE command must always succeed for drive 1
* if it also succeeds for drive 0 -- even if there is no drive 1. Bizarre, but OK, whatever.
* HACK: The HDC BIOS code for both INT 0x13/AH=0x00 and INT 0x13/AH=0x09 calls "INIT_DRV" @ C800:0427, which is
* hard-coded to issue the HDC.REG_DATA.CMD.INIT_DRIVE command for BOTH drives 0 and 1 (aka drive numbers 0x80 and
* 0x81), regardless of the drive number specified in DL; this means that the HDC.REG_DATA.CMD.INIT_DRIVE command
* must always succeed for drive 1 if it also succeeds for drive 0 -- even if there is no drive 1. Bizarre, but OK,
* whatever.
*
* So assuming we a have drive 0, when the power-on diagnostics in "DISK_SETUP" @C800:0003 call INT 0x13/AH=0x09 @C800:00DB
* for drive 0, it must succeed. No problem. But when "DISK_SETUP" starts probing for additional drives, it first issues
* INT 0x13/AH=0x00, followed by INT 0x13/AH=0x11, and finally INT 0x13/AH=0x09. If the first (AH=0x00) or third (AH=0x09)
* INT 0x13 fails, it quickly moves on (ie, it jumps to "POD_DONE"). But as we just discussed, both those operations call "INIT_DRV",
* which can't return an error. This means the only function that can return an error in this context is the recalibrate function
* (AH=0x11). That sucks, because the way the HDC BIOS is written, it will loop for anywhere from 1.5 seconds to 25 seconds
* (depending on whether the controller is part of the "System Unit" or not; see port 0x213), attempting to recalibrate drive 1
* until it finally times out.
* So assuming we a have drive 0, when the power-on diagnostics in "DISK_SETUP" @ C800:0003 call INT 0x13/AH=0x09
* @ C800:00DB for drive 0, it must succeed. No problem. But when "DISK_SETUP" starts probing for additional drives,
* it first issues INT 0x13/AH=0x00, followed by INT 0x13/AH=0x11, and finally INT 0x13/AH=0x09. If the first
* (AH=0x00) or third (AH=0x09) INT 0x13 fails, it quickly moves on (ie, it jumps to "POD_DONE"). But as we just
* discussed, both those operations call "INIT_DRV", which can't return an error. This means the only function that
* can return an error in this context is the recalibrate function (AH=0x11). That sucks, because the way the HDC
* BIOS is written, it will loop for anywhere from 1.5 seconds to 25 seconds (depending on whether the controller
* is part of the "System Unit" or not; see port 0x213), attempting to recalibrate drive 1 until it finally times out.
*
* Normally, you'll only experience the 1.5 second delay, but even so, it's a ridiculous waste of time and a lot of useless
* INT 0x13 calls. So I monitor INT 0x13/AH=0x00 for DL >= 0x80 and set a special HDC.REG_DATA.CMD.INIT_DRIVE override flag
* (iDriveAllowFail) that will allow that command to fail, and in theory, make the the HDC BIOS "DISK_SETUP" code much more efficient.
* Normally, you'll only experience the 1.5 second delay, but even so, it's a ridiculous waste of time and a lot of
* useless INT 0x13 calls. So I monitor INT 0x13/AH=0x00 for DL >= 0x80 and set a special HDC.REG_DATA.CMD.INIT_DRIVE
* override flag (iDriveAllowFail) that will allow that command to fail, and in theory, make the the HDC BIOS
* "DISK_SETUP" code much more efficient.
*
* @this {HDC}
* @param {number} addr
@ -1135,16 +1122,16 @@ HDC.prototype.outHDCNoise = function(port, bOut, addrFrom) {
HDC.prototype.intBIOSDisk = function(addr) {
var AH = this.cpu.regAX >> 8;
var DL = this.cpu.regDX & 0xff;
if (!AH && DL > 0x80) {
this.iDriveAllowFail = DL - 0x80;
}
if (!AH && DL > 0x80) this.iDriveAllowFail = DL - 0x80;
if (DEBUGGER) {
if (this.dbg && this.dbg.messageEnabled(this.dbg.MESSAGE_HDC) && DL >= 0x80) {
this.dbg.message("HDC.intBIOSDisk(AX=" + str.toHexWord(this.cpu.regAX) + ",DL=" + str.toHexByte(DL) + ") at " + str.toHexAddr(addr - this.cpu.segCS.base, this.cpu.segCS.sel));
// this.cpu.haltCPU();
this.cpu.addInterruptReturn(addr, function (hdc, nCycles) {
return function onBIOSDiskReturn(nLevel) {
hdc.intBIOSDiskReturn(nCycles, nLevel);
nCycles = hdc.cpu.getCycles() - nCycles;
hdc.messageDebugger("HDC.intBIOSDisk(" + nLevel + "): C=" + (hdc.cpu.getCF()? 1 : 0) + " (cycles=" + nCycles + ")");
// if (DEBUG && nCycles > 10000) hdc.cpu.haltCPU();
};
}(this, this.cpu.getCycles()));
}
@ -1152,27 +1139,30 @@ HDC.prototype.intBIOSDisk = function(addr) {
return true;
};
/**
* intBIOSDiskReturn(nCycles, nLevel)
*
* @this {HDC}
* @param {number} nCycles
* @param {number} nLevel
*/
HDC.prototype.intBIOSDiskReturn = function(nCycles, nLevel) {
if (DEBUGGER) {
nCycles = this.cpu.getCycles() - nCycles;
this.messageDebugger("HDC.intBIOSDiskReturn(" + nLevel + "): C=" + (this.cpu.getCF()? 1 : 0) + " (cycles=" + nCycles + ")");
// if (DEBUG && nCycles > 10000) this.cpu.haltCPU();
}
};
/**
* intBIOSDiskette(addr)
*
* Every time an INT 0x40 is issued with AH=0 and IRQ_FDC masked, eat the INT 0x40 interrupt.
* When the HDC BIOS overwrites the ROM BIOS INT 0x13 address, it saves the original INT 0x13 address
* in the INT 0x40 vector. This function intercepts calls to that vector to work around a minor nuisance.
*
* For more details on why this is necessary, see the definition of HDC.BIOS.DISKETTE_INT (above)
* The HDC BIOS's plan was simple, albeit slightly flawed: assign fixed disks drive numbers >= 0x80,
* and whenever someone calls INT 0x13 with a drive number < 0x80, invoke the original INT 0x13 diskette
* code via INT 0x40 and return via RET 2.
*
* Unfortunately, not all original INT 0x13 functions required a drive number in DL (eg, the "reset"
* function, where AH=0). And the HDC BIOS knew this, which is why, in the case of the "reset" function,
* the HDC BIOS performs BOTH an INT 0x40 diskette reset AND an HDC reset -- it can't be sure which
* controller the caller really wants to reset.
*
* An unfortunate side-effect of this behavior: when the HDC BIOS is initialized for the first time, it may
* issue several resets internally, depending on whether there are 0, 1 or 2 hard disks installed, and each
* of those resets also triggers completely useless diskette resets, each wasting up to two seconds waiting
* for the FDC to interrupt. The FDC tries to interrupt, but it can't, because at this early stage of
* ROM BIOS initialization, IRQ_FDC hasn't been unmasked yet.
*
* My work-around: have the HDC component hook INT 0x40, and every time an INT 0x40 is issued with AH=0 and
* IRQ_FDC masked, bypass the INT 0x40 interrupt. This is as close as PCjs has come to patching any BIOS code
* (something I refuse to do), and even here, I'm not doing it out of necessity, just annoyance.
*
* @this {HDC}
* @param {number} addr