Fixed CMOS date initialization (Model 5170 successfully gets current date/time now)
This commit is contained in:
parent
367db06996
commit
4ff357669d
3 changed files with 255 additions and 194 deletions
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -809,25 +812,32 @@ ChipSet.COPROC.PORT_RESET = 0xF1; // reset the coprocessor
|
|||
* 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.
|
||||
* 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)
|
||||
*
|
||||
|
|
@ -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.
|
||||
*
|
||||
|
|
@ -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)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
*
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue