/**
* @fileoverview Implements the PCjs ChipSet component.
* @author Jeff Parsons
* @version 1.0
* Created 2012-Sep-14
*
* Copyright © 2012-2014 Jeff Parsons
*
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
* at and .
*
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCjs. If not,
* see .
*
* You are required to include the above copyright notice in every source code file of every
* copy or modified version of this work, and to display that copyright notice on every screen
* that loads or runs any version of this software (see Computer.sCopyright).
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of the
* PCjs program for purposes of the GNU General Public License, and the author does not claim
* any copyright as to their contents.
*/
"use strict";
if (typeof module !== 'undefined') {
var str = require("../../shared/lib/strlib");
var usr = require("../../shared/lib/usrlib");
var web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
var State = require("./state");
}
/**
* ChipSet(parmsChipSet)
*
* The ChipSet component has the following component-specific (parmsChipSet) properties:
*
* model: 5150, 5160 or 5170 (should correspond to a ChipSet.MODEL_* constant)
* sw1: 8-character binary string representing the SW1 DIP switches (SW1[1-8])
* sw2: 8-character binary string representing the SW2 DIP switches (SW2[1-8]) (MODEL_5150 only)
* sound: true to enable (experimental) sound support (default); false to disable
* scaleTimers: true to divide timer cycle counts by the CPU's cycle multiplier (default is false)
* floppies: array of floppy drive sizes in Kb (default is "[360, 360]" if no sw1 value provided)
* monitor: none|tv|color|mono (if no sw1 value provided, default is "ega" for 5170, "mono" otherwise)
* rtcDate: optional RTC date/time (in GMT) to use on reset; use the ISO 8601 format; eg: "2014-10-01T08:00:00"
*
* The conventions used for the sw1 and sw2 strings are that the left-most character represents DIP switch [1],
* the right-most character represents DIP switch [8], and "1" means the DIP switch is ON and "0" means it is OFF.
*
* Internally, we convert the above strings into binary values that the 8255A PPI returns, where DIP switch [1]
* is bit 0 and DIP switch [8] is bit 7, and 0 indicates the switch is ON and 1 indicates it is OFF.
*
* For reference, here's how the SW1 and SW2 switches correspond to the internal 8255A PPI bit values:
*
* SW1[1] (bit 0) "0xxxxxxx" (1): IPL, "1xxxxxxx" (0): No IPL
* SW1[2] (bit 1) reserved
* SW1[3,4] (bits 3-2) "xx11xxxx" (00): 16Kb, "xx01xxxx" (01): 32Kb, "xx10xxxx" (10): 48Kb, "xx00xxxx" (11): 64Kb
* SW1[5,6] (bits 5-4) "xxxx11xx" (00): none, "xxxx01xx" (01): tv, "xxxx10xx" (10): color, "xxxx00xx" (11): mono
* SW1[7,8] (bits 7-6) "xxxxxx11" (00): 1 FD, "xxxxxx01" (01): 2 FD, "xxxxxx10" (10): 3 FD, "xxxxxx00" (11): 4 FD
*
* Note: FD refers to floppy drive, and IPL refers to an "Initial Program Load" floppy drive.
*
* 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 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)
* to yield the value that the 8255A PPI returns. Reading the final value right-to-left, 00110001 indicates an
* IPL floppy drive, 1X of RAM (where X is 16Kb on a MODEL_5150 and 64Kb on a MODEL_5160), MDA, and 1 floppy drive.
*
* WARNING: It is possible to set SW1 to indicate more memory than the RAM component has been configured to provide.
* This is a configuration error which will cause the machine to crash after reporting a "201" error code (memory
* test failure), which is presumably what a real machine would do if it was similarly misconfigured. Surprisingly,
* the BIOS forges ahead, setting SP to the top of the memory range indicated by SW1 (via INT 0x12), but the lack of
* a valid stack causes the system to crash after the next IRET. The BIOS should have either halted or modified
* the actual memory size to match the results of the memory test.
*
* This component provides support for many of the following components (except where a separate component is noted).
* This list is taken from p.1-8 ("System Unit") of the IBM 5160 (PC XT) Technical Reference Manual (as revised
* April 1983), only because I didn't see a similar listing in the original 5150 TechRef.
*
* Port(s) Description
* ------- -----------
* 000-00F DMA Chip 8237A-5 [see below]
* 020-021 Interrupt 8259A [see below]
* 040-043 Timer 8253-5 [see below]
* 060-063 PPI 8255A-5 [see below]
* 080-083 DMA Page Registers [see below]
* 0Ax [1] NMI Mask Register [see below]
* 0Cx Reserved
* 0Ex Reserved
* 200-20F Game Control
* 210-217 Expansion Unit
* 220-24F Reserved
* 278-27F Reserved
* 2F0-2F7 Reserved
* 2F8-2FF Asynchronous Communications (Secondary) [see the SerialPort component]
* 300-31F Prototype Card
* 320-32F Hard Drive Controller (XTC) [see the HDC component]
* 378-37F Printer
* 380-38C [2] SDLC Communications
* 380-389 [2] Binary Synchronous Communications (Secondary)
* 3A0-3A9 Binary Synchronous Communications (Primary)
* 3B0-3BF IBM Monochrome Display/Printer [see the Video component]
* 3C0-3CF Reserved
* 3D0-3DF Color/Graphics (Motorola 6845) [see the Video component]
* 3EO-3E7 Reserved
* 3FO-3F7 Floppy Drive Controller [see the FDC component]
* 3F8-3FF Asynchronous Communications (Primary) [see the SerialPort component]
*
* [1] At power-on time, NMI is masked off, perhaps because models 5150 and 5160 also tie coprocessor
* interrupts to NMI. Suppressing NMI by default seems odd, because that would also suppress memory
* parity errors. TODO: Determine whether "power-on time" refers to the initial power-on state of the
* NMI Mask Register or the state that the BIOS "POST" (Power-On Self-Test) sets.
*
* [2] These devices cannot be used together since their port addresses overlap.
*
* MODEL_5170 Description
* ---------- -----------
* 070 [3] CMOS Address ChipSet.CMOS.ADDR.PORT
* 071 CMOS Data ChipSet.CMOS.DATA.PORT
* 0F0 Coprocessor Clear Busy (output 0x00)
* 0F1 Coprocessor Reset (output 0x00)
* 1F0-1F7 Hard Drive Controller (ATC) [see the HDC component]
*
* [3] Port 0x70 doubles as the NMI Mask Register: output a CMOS address with bit 7 clear to enable NMI
* or with bit 7 set to disable NMI (apparently the inverse of the older NMI Mask Register at port 0xA0).
* Also, apparently unlike previous models, the MODEL_5170 POST leaves NMI enabled. And fortunately, the
* coprocessor interrupt line is no longer tied to NMI (it uses IRQ 13).
*
* @constructor
* @extends Component
* @param {Object} parmsChipSet
*/
function ChipSet(parmsChipSet)
{
Component.call(this, "ChipSet", parmsChipSet, ChipSet);
this.model = parmsChipSet['model'];
this.model = (this.model !== undefined? parseInt(this.model, 10) : ChipSet.MODEL_5150);
/*
* SW1 describes the number of floppy drives, the amount of base memory, the primary monitor type,
* and (on the MODEL_5160) whether or not a coprocessor is installed. If no SW1 settings are provided,
* we look for individual 'floppies' and 'monitor' settings and build a default SW1 value.
*
* The defaults below select max memory, monochrome monitor (EGA monitor for MODEL_5170), and two floppies.
* Don't get too excited about "max memory" either: on a MODEL_5150, the max was 64Kb, and on a MODEL_5160,
* the max was 256Kb. However, the RAM component is free to install as much base memory as it likes,
* overriding the SW1 memory setting.
*
* 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.
*/
this.sw1Init = 0;
var sw1 = parmsChipSet['sw1'];
if (sw1) {
this.sw1Init = this.parseSwitches(sw1, ChipSet.PPI_SW.MEMORY.X4 | ChipSet.PPI_SW.MONITOR.MONO);
} else {
this.aFloppyDrives = [360, 360];
var aFloppyDrives = parmsChipSet['floppies'];
if (aFloppyDrives && aFloppyDrives.length !== undefined) this.aFloppyDrives = aFloppyDrives;
var nDrives = this.aFloppyDrives.length;
if (nDrives) {
this.sw1Init |= ChipSet.PPI_SW.FDRIVE.IPL;
nDrives--;
this.sw1Init |= ((nDrives & 0x3) << ChipSet.PPI_SW.FDRIVE.SHIFT);
}
var sMonitor = parmsChipSet['monitor'] || (this.model < ChipSet.MODEL_5170? "mono" : "ega");
if (sMonitor && ChipSet.aMonitorSwitches[sMonitor] !== undefined) {
this.sw1Init |= (ChipSet.aMonitorSwitches[sMonitor] << ChipSet.PPI_SW.MONITOR.SHIFT);
}
}
/*
* SW2 describes the number of 32Kb blocks of I/O expansion RAM that's present in the system. The MODEL_5150 ROM BIOS
* only checked/supported the first four switches, so the maximum amount of additional RAM specifiable was 15 * 32Kb,
* or 480Kb. With a maximum of 64Kb on the motherboard, the MODEL_5150 ROM BIOS could support a grand total of 544Kb.
*
* For MODEL_5160 (PC XT) and up, memory expansion cards had their own configuration switches, and the motherboard SW2
* switches for I/O expansion RAM were eliminated. Instead, the ROM BIOS scans the entire address space (up to 0xA0000)
* looking for additional memory. As a result, the only mechanism we provide for adding RAM (above the maximum of 256Kb
* supported on the motherboard) is the "size" parameter of the RAM component. NOTE: If you use the "size" parameter,
* you will not be able to dynamically alter the memory configuration; the RAM component will ignore any changes to SW1.
*/
this.sw2Init = this.parseSwitches(parmsChipSet['sw2'] || "11110000", 0);
/*
* The SW1 memory setting is actually just a multiplier: it's multiplied by 16Kb on a MODEL_5150, 64Kb otherwise.
*/
this.kbSW = (this.model == ChipSet.MODEL_5150? 16 : 64);
this.cDMACs = this.cPICs = 1;
if (this.model >= ChipSet.MODEL_5170) {
this.cDMACs = this.cPICs = 2;
}
this.fScaleTimers = parmsChipSet['scaleTimers'] || false;
this.sRTCDate = parmsChipSet['rtcDate'];
/*
* Here, I'm finally getting around to trying the Web Audio API. Fortunately, based on what little I know about
* sound generation, using the API to make the same noises as the IBM PC speaker should be straightforward.
*
* To start, we create an audio context, unless the 'sound' parameter has been explicitly set to false.
*
* From:
*
* http://developer.apple.com/library/safari/#documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/PlayingandSynthesizingSounds/PlayingandSynthesizingSounds.html
*
* "Similar to how HTML5 canvas requires a context on which lines and curves are drawn, Web Audio requires an audio context
* on which sounds are played and manipulated. This context will be the parent object of further audio objects to come....
* Your audio context is typically created when your page initializes and should be long-lived. You can play multiple sounds
* coming from multiple sources within the same context, so it is unnecessary to create more than one audio context per page."
*/
this.fSpeaker = false;
if (parmsChipSet['sound']) {
if (window && 'webkitAudioContext' in window) {
// noinspection JSPotentiallyInvalidConstructorUsage
this.contextAudio = new webkitAudioContext();
} else {
if (DEBUG) this.log("webkitAudioContext not available");
}
}
/*
* I used to defer ChipSet's reset() to powerUp(), which then gave us the option of doing either
* reset() OR restore(), instead of both. However, on MODEL_5170 machines, the initial CMOS data
* needs to be created earlier, so that when other components are initializing their state (eg, when
* HDC calls setCMOSDriveType() or RAM calls addCMOSMemory()), the CMOS will be ready to take their calls.
*/
this.reset(true);
this.setReady();
}
Component.subclass(Component, ChipSet);
/*
* Supported Models
*
* Unless otherwise noted, all BIOS references refer to the *original* BIOS released with each model
*/
ChipSet.MODEL_5150 = 5150; // used in reference to the 1st 5150 BIOS, dated Apr 24, 1981
ChipSet.MODEL_5160 = 5160; // used in reference to the 1st 5160 BIOS, dated Nov 8, 1982
ChipSet.MODEL_5170 = 5170; // used in reference to the 1st 5170 BIOS, dated Jan 10, 1984
/*
* The following are fake model numbers, used only to document issues/features of note in later BIOS revisions
*/
ChipSet.MODEL_5170_REV2 = 5170.2; // used in reference to the 2nd 5170 BIOS, dated Jun 10, 1985
ChipSet.MODEL_5170_REV3 = 5170.3; // used in reference to the 3rd 5170 BIOS, dated Nov 15, 1985
/*
* Values returned by ChipSet.getSWVideoMonitor()
*/
ChipSet.MONITOR = {
NONE: 0,
TV: 1, // Composite monitor (lower resolution; no support)
COLOR: 2, // Color Display (5153)
MONO: 3, // Monochrome Display (5151)
EGACOLOR: 4, // Enhanced Color Display (5154) in High-Res Mode
EGAEMULATION: 6 // Enhanced Color Display (5154) in Emulation Mode
};
/*
* Lookup table for converting ChipSet "monitor" values into the corresponding SW1 switch bits
* (they must be shifted left by ChipSet.PPI_SW.MONITOR.SHIFT before OR'ing them into sw1/sw1Init).
*/
ChipSet.aMonitorSwitches = {
"none": 0x0,
"tv": 0x1,
"color": 0x2,
"mono": 0x3,
"ega": 0x0
};
/*
* 8237A DMA Controller (DMAC) I/O ports
*
* MODEL_5150 and up uses DMA channel 0 for memory refresh cycles and channel 2 for the FDC
* MODEL_5160 and up uses DMA channel 3 for HDC transfers (XTC only)
* MODEL_5170 and up contain *two* DMA Controllers, which we refer to as DMA0 and DMA1; channel 4
* on DMA1 is used to "cascade" channels 0-3 from DMA0, so only channels 5-7 are available on DMA1
*
* QUESTION: Why does the MODEL_5150 ROM BIOS set the page register for channel 1 (port 0x83) to zero?
*
* For FDC DMA notes, refer to: http://wiki.osdev.org/ISA_DMA
* For general DMA notes, refer to: http://www.freebsd.org/doc/en/books/developers-handbook/dma.html
*/
ChipSet.DMA0 = {
INDEX: 0,
PORT: {
CH0_ADDR: 0x00, // OUT: starting address IN: current address
CH0_COUNT: 0x01, // OUT: starting word count IN: remaining word count
CH1_ADDR: 0x02, // OUT: starting address IN: current address
CH1_COUNT: 0x03, // OUT: starting word count IN: remaining word count
CH2_ADDR: 0x04, // OUT: starting address IN: current address
CH2_COUNT: 0x05, // OUT: starting word count IN: remaining word count
CH3_ADDR: 0x06, // OUT: starting address IN: current address
CH3_COUNT: 0x07, // OUT: starting word count IN: remaining word count
CMD_STATUS: 0x08, // OUT: command register IN: status register
REQUEST: 0x09,
MASK: 0x0A,
MODE: 0x0B,
CLEAR_FF: 0x0C,
MASTER_CLR: 0x0D,
CLEAR_MASK: 0x0E, // TODO: Provide handlers
ALL_MASK: 0x0F, // TODO: Provide handlers
CH2_PAGE: 0x81, // OUT: DMA channel 2 page register
CH3_PAGE: 0x82, // OUT: DMA channel 3 page register
CH1_PAGE: 0x83, // OUT: DMA channel 1 page register
CH0_PAGE: 0x87 // OUT: DMA channel 0 page register (unusable; See "The Inside Out" book, p.246)
}
};
ChipSet.DMA1 = {
INDEX: 1,
PORT: {
CH6_PAGE: 0x89, // OUT: DMA channel 6 page register (MODEL_5170)
CH7_PAGE: 0x8A, // OUT: DMA channel 7 page register (MODEL_5170)
CH5_PAGE: 0x8B, // OUT: DMA channel 5 page register (MODEL_5170)
CH4_PAGE: 0x8F, // OUT: DMA channel 4 page register (MODEL_5170; unusable; aka "refresh" page register?)
CH4_ADDR: 0xC0, // OUT: starting address IN: current address
CH4_COUNT: 0xC2, // OUT: starting word count IN: remaining word count
CH5_ADDR: 0xC4, // OUT: starting address IN: current address
CH5_COUNT: 0xC6, // OUT: starting word count IN: remaining word count
CH6_ADDR: 0xC8, // OUT: starting address IN: current address
CH6_COUNT: 0xCA, // OUT: starting word count IN: remaining word count
CH7_ADDR: 0xCC, // OUT: starting address IN: current address
CH7_COUNT: 0xCE, // OUT: starting word count IN: remaining word count
CMD_STATUS: 0xD0, // OUT: command register IN: status register
REQUEST: 0xD2,
MASK: 0xD4,
MODE: 0xD6,
CLEAR_FF: 0xD8,
MASTER_CLR: 0xDA,
CLEAR_MASK: 0xDC, // TODO: Provide handlers
ALL_MASK: 0xDE // TODO: Provide handlers
}
};
ChipSet.DMA_CMD = {
M2M_ENABLE: 0x01,
CH0HOLD_ENABLE: 0x02,
CTRL_DISABLE: 0x04,
COMP_TIMING: 0x08,
ROT_PRIORITY: 0x10,
EXT_WRITE_SEL: 0x20,
DREQ_ACTIVE_LO: 0x40,
DACK_ACTIVE_HI: 0x80
};
ChipSet.DMA_MASK = {
CHANNEL: 0x03,
CHANNEL_SET: 0x04
};
ChipSet.DMA_MODE = {
CHANNEL: 0x03,
XFER: 0x0C,
XFER_VERIFY: 0x00,
XFER_WRITE: 0x04,
XFER_READ: 0x08,
AUTOINIT: 0x10,
DECREMENT: 0x20,
MODE: 0xC0,
MODE_DEMAND: 0x00,
MODE_SINGLE: 0x40,
MODE_BLOCK: 0x80,
MODE_CASCADE: 0xC0
};
ChipSet.DMA_FDC = 0x02; // DMA channel assigned to the Floppy Drive Controller (FDC)
ChipSet.DMA_HDC = 0x03; // DMA channel assigned to the Hard Drive Controller (HDC; XTC only)
/*
* 8259A Programmable Interrupt Controller (PIC) I/O ports
*
* Internal registers:
*
* ICW1 Initialization Command Word 1 (sent to port ChipSet.PIC_LO)
* ICW2 Initialization Command Word 2 (sent to port ChipSet.PIC_HI)
* ICW3 Initialization Command Word 3 (sent to port ChipSet.PIC_HI)
* ICW4 Initialization Command Word 4 (sent to port ChipSet.PIC_HI)
* IMR Interrupt Mask Register
* IRR Interrupt Request Register
* ISR Interrupt Service Register
* IRLow (IR having lowest priority; IR+1 will have highest priority; default is 7)
*
* Note that ICW2 effectively contains the starting IDT vector number (ie, for IRQ 0),
* which must be multiplied by 4 to calculate the vector offset, since every vector is 4 bytes long.
*
* Also, since the low 3 bits of ICW2 are ignored in 8086/8088 mode (ie, they are effectively
* treated as zeros), this means that the starting IDT vector can only be a multiple of 8.
*
* So, if ICW2 is set to 0x08, the starting vector number (ie, for IRQ 0) will be 0x08, and the
* 4-byte address for the corresponding ISR will be located at offset 0x20 in the real-mode IDT.
*
* ICW4 is typically set to 0x09, indicating 8086 mode, non-automatic EOI, buffered/slave mode.
*
* QUESTION: Why did the original ROM BIOS choose buffered/slave over buffered/master? Did it simply
* not matter in pre-AT systems with only one PIC, or am I misreading something?
*
* TODO: Consider support for level-triggered PIC interrupts, even though the original IBM PCs
* (up through MODEL_5170) used only edge-triggered interrupts.
*/
ChipSet.PIC0 = { // all models: the "master" PIC
INDEX: 0,
PORT_LO: 0x20,
PORT_HI: 0x21
};
ChipSet.PIC1 = { // MODEL_5170 and up: the "slave" PIC
INDEX: 1,
PORT_LO: 0xA0,
PORT_HI: 0xA1
};
ChipSet.PIC_LO = { // ChipSet.PIC1.PORT_LO or ChipSet.PIC2.PORT_LO
ICW1: 0x10, // set means ICW1
ICW1_ICW4: 0x01, // ICW4 needed (otherwise ICW4 must be sent)
ICW1_SNGL: 0x02, // single PIC (and therefore no ICW3; otherwise there is another "cascaded" PIC)
ICW1_ADI: 0x04, // call address interval is 4 (otherwise 8; presumably ignored in 8086/8088 mode)
ICW1_LTIM: 0x08, // level-triggered interrupt mode (otherwise edge-triggered mode, which is what PCs use)
OCW2: 0x00, // bit 3 (PIC_LO.OCW3) and bit 4 (ChipSet.PIC_LO.ICW1) are clear in an OCW2 command byte
OCW2_IR_LVL: 0x07,
OCW2_OP_MASK: 0xE0, // of the following valid OCW2 operations, the first 4 are EOI commands (all have ChipSet.PIC_LO.OCW2_EOI set)
OCW2_EOI: 0x20, // non-specific EOI (end-of-interrupt)
OCW2_EOI_SPEC: 0x60, // specific EOI
OCW2_EOI_ROT: 0xA0, // rotate on non-specific EOI
OCW2_EOI_ROTSPEC: 0xE0, // rotate on specific EOI
OCW2_SET_ROTAUTO: 0x80, // set rotate in automatic EOI mode
OCW2_CLR_ROTAUTO: 0x00, // clear rotate in automatic EOI mode
OCW2_SET_PRI: 0xC0, // bits 0-2 specify the lowest priority interrupt
OCW3: 0x08, // bit 3 (PIC_LO.OCW3) is set and bit 4 (PIC_LO.ICW1) clear in an OCW3 command byte (bit 7 should be clear, too)
OCW3_READ_IRR: 0x02, // read IRR register
OCW3_READ_ISR: 0x03, // read ISR register
OCW3_READ_CMD: 0x03,
OCW3_POLL_CMD: 0x04, // poll
OCW3_SMM_RESET: 0x40, // special mask mode: reset
OCW3_SMM_SET: 0x60, // special mask mode: set
OCW3_SMM_CMD: 0x60
};
ChipSet.PIC_HI = { // ChipSet.PIC1.PORT_HI or ChipSet.PIC2.PORT_HI
ICW2_VECTOR: 0xF8, // starting vector number (bits 0-2 are effectively treated as zeros in 8086/8088 mode)
ICW4_8086: 0x01,
ICW4_AUTO_EOI: 0x02,
ICW4_MASTER: 0x04,
ICW4_BUFFERED: 0x08,
ICW4_FULLY_NESTED: 0x10,
OCW1_IMR: 0xFF
};
/*
* The priorities of IRQs 0-7 are normally high to low, unless the master PIC has been reprogrammed.
* Also, if a slave PIC is present, the priorities of IRQs 8-15 fall between the priorities of IRQs 1 and 3.
*
* As the MODEL_5170 TechRef states:
*
* "Interrupt requests are prioritized, with IRQ9 through IRQ12 and IRQ14 through IRQ15 having the
* highest priority (IRQ9 is the highest) and IRQ3 through IRQ7 having the lowest priority (IRQ7 is
* the lowest).
*
* Interrupt 13 (IRQ.COPROC) is used on the system board and is not available on the I/O channel.
* Interrupt 8 (IRQ.RTC) is used for the real-time clock."
*
* This priority scheme is a byproduct of IRQ8 through IRQ15 (slave PIC interrupts) being tied to IRQ2 of
* the master PIC. As a result, the two other system board interrupts, IRQ0 and IRQ1, continue to have the
* highest priority, by default.
*/
ChipSet.IRQ = {
TIMER0: 0x00,
KBD: 0x01,
SLAVE: 0x02,
COM2: 0x03,
COM1: 0x04,
XTC: 0x05, // MODEL_5160 uses this for its HDC; MODEL_5170 designates it for LPT2
FDC: 0x06,
LPT1: 0x07,
RTC: 0x08,
IRQ2: 0x09,
COPROC: 0x0D,
ATC: 0x0E // MODEL_5170 uses this for its HDC
};
/*
* 8253 Programmable Interval Timer (PIT) I/O ports
*/
ChipSet.TIMER0 = {
INDEX: 0,
PORT: 0x40 // used for time-of-day (prior to MODEL_5170)
};
ChipSet.TIMER1 = {
INDEX: 1,
PORT: 0x41 // used for memory refresh
};
ChipSet.TIMER2 = {
INDEX: 2,
PORT: 0x42 // used speaker tone generation
};
ChipSet.TIMER_CTRL = {
PORT: 0x43, // write-only control register (use the Read-Back command to get status)
BCD: 0x01,
MODE: 0x0E,
MODE0: 0x00, // interrupt on terminal count
MODE1: 0x02, // programmable one-shot
MODE2: 0x04, // rate generator
MODE3: 0x06, // square wave generator
MODE4: 0x08, // software-triggered strobe
MODE5: 0x0A, // hardware-triggered strobe
RW: 0x30,
RW_LATCH: 0x00,
RW_LSB: 0x10,
RW_MSB: 0x20,
RW_BOTH: 0x30,
SC: 0xC0,
SC_CTR0: 0x00,
SC_CTR1: 0x40,
SC_CTR2: 0x80,
SC_BACK: 0xC0
};
ChipSet.TIMER_TICKS_PER_SEC = 1193181;
/*
* 8255A Programmable Peripheral Interface (PPI) I/O ports, for Cassette/Speaker/Keyboard/SW1/etc
*
* Normally, 0x99 is written to PPI_CTRL.PORT, indicating that PPI_A.PORT and PPI_C.PORT are INPUT ports
* and PPI_B.PORT is an OUTPUT port.
*
* However, the MODEL_5160 ROM BIOS initially writes 0x89 instead, making PPI_A.PORT an OUTPUT port.
* I'm guessing that's just part of some "diagnostic mode", because all it writes to PPI_A.PORT are a series
* of "checkpoint" values (ie, 0x01, 0x02, and 0x03) before updating PPI_CTRL.PORT with the usual 0x99.
*/
ChipSet.PPI_A = { // this.bPPIA
PORT: 0x60 // INPUT: keyboard scan code (PPI_B.CLEAR_KBD must be clear)
};
ChipSet.PPI_B = { // this.bPPIB
PORT: 0x61, // OUTPUT (although it has to be treated as INPUT, too: the keyboard interrupt handler reads it, OR's PPI_B.CLEAR_KBD, writes it, and then rewrites the original read value)
CLK_TIMER2: 0x01, // ALL: set to enable clock to TIMER2
SPK_TIMER2: 0x02, // ALL: set to connect output of TIMER2 to speaker (MODEL_5150: clear for cassette)
ENABLE_SW2: 0x04, // MODEL_5150: set to enable SW2[1-4] through PPI_C.PORT, clear to enable SW2[5]; MODEL_5160: unused (there is no SW2 switch block on the MODEL_5160 motherboard)
CASS_MOTOR_OFF: 0x08, // MODEL_5150: cassette motor off
ENABLE_SW_HI: 0x08, // MODEL_5160: clear to read SW1[1-4], set to read SW1[5-8]
DISABLE_RW_MEM: 0x10, // ALL: clear to enable RAM parity check, set to disable
DISABLE_IO_CHK: 0x20, // ALL: clear to enable I/O channel check, set to disable
CLK_KBD: 0x40, // ALL: clear to force keyboard clock low
CLEAR_KBD: 0x80 // ALL: clear to enable keyboard scan codes (MODEL_5150: set to enable SW1 through PPI_A.PORT)
};
ChipSet.PPI_C = { // this.bPPIC
PORT: 0x62, // INPUT (see below)
SW: 0x0F, // MODEL_5150: SW2[1-4] or SW2[5], depending on whether PPI_B.ENABLE_SW2 is set or clear; MODEL_5160: SW1[1-4] or SW1[5-8], depending on whether PPI_B.ENABLE_SW_HI is clear or set
CASS_DATA_IN: 0x10,
TIMER2_OUT: 0x20,
IO_CHANNEL_CHK: 0x40, // used by NMI handler to detect I/O channel errors
RW_PARITY_CHK: 0x80 // used by NMI handler to detect R/W memory parity errors
};
ChipSet.PPI_CTRL = { // this.bPPICtrl
PORT: 0x63, // OUTPUT: initialized to 0x99, defining PPI_A and PPI_C as INPUT and PPI_B as OUTPUT
A_IN: 0x10,
B_IN: 0x02,
C_IN_LO: 0x01,
C_IN_HI: 0x08,
B_MODE: 0x04,
A_MODE: 0x60
};
/*
* On the MODEL_5150, the following PPI_SW bits are exposed through PPI_A.
*
* On the MODEL_5160, either the low or high 4 bits are exposed through PPI_C.SW, if PPI_B.ENABLE_SW_HI is clear or set.
*/
ChipSet.PPI_SW = {
FDRIVE: {
IPL: 0x01, // MODEL_5150: IPL ("Initial Program Load") floppy drive attached; MODEL_5160: "Loop on POST"
ONE: 0x00, // 1 floppy drive attached (or 0 drives if PPI_SW.FDRIVE_IPL is not set -- MODEL_5150 only)
TWO: 0x40, // 2 floppy drives attached
THREE: 0x80, // 3 floppy drives attached
FOUR: 0xC0, // 4 floppy drives attached
MASK: 0xC0,
SHIFT: 6
},
COPROC: 0x02, // MODEL_5150: reserved; MODEL_5160: coprocessor installed
MEMORY: { // MODEL_5150: "X" is 16Kb; MODEL_5160: "X" is 64Kb
X1: 0x00, // 16Kb or 64Kb
X2: 0x04, // 32Kb or 128Kb
X3: 0x08, // 48Kb or 192Kb
X4: 0x0C, // 64Kb or 256Kb
MASK: 0x0C,
SHIFT: 2
},
MONITOR: {
TV: 0x10,
COLOR: 0x20,
MONO: 0x30,
MASK: 0x30,
SHIFT: 4
}
};
/*
* 8042 Keyboard Controller I/O ports (MODEL_5170)
*
* On the MODEL_5170, port 0x60 is designated KBC.DATA rather than PPI_A, although the BIOS also refers to it
* as "PORT_A: 8042 KEYBOARD SCAN/DIAG OUTPUTS"). This is the 8042's output buffer and should be read only when
* KBC.STATUS.OUTBUFF_FULL is set.
*
* Similarly, port 0x61 is designated KBC.RWREG rather than PPI_B; the BIOS also refers to it as "PORT_B: 8042
* READ WRITE REGISTER", but it is not otherwise discussed in the MODEL_5170 TechRef's 8042 documentation.
* There are brief references to bits 0 and 1 (KBC.RWREG.CLK_TIMER2 and KBC.RWREG.SPK_TIMER2), and the BIOS sets
* bits 2-7 to "DISABLE PARITY CHECKERS" (principally KBC.RWREG.DISABLE_CHK, which are bits 2 and 3); why the BIOS
* also sets bits 4-7 (or if those bits are even settable) is unclear, since it uses 11111100B rather than defined
* constants.
*
* The bottom line: on a MODEL_5170, port 0x61 is still used for speaker control and parity checking, so we use
* the same register (bPPIB) but install different I/O handlers. It's also bi-directional: at one point, the BIOS
* reads KBC.RWREG.REFRESH_BIT (bit 4) to verify that it's alternating.
*
* PPI_C and PPI_CTRL don't seem to be documented or used by the MODEL_5170 BIOS, so I'm assuming they're obsolete.
*
* NOTE: For more information on the 8042 Controller, including information on undocumented commands, refer to the
* documents in /devices/pc/keyboard/, as well as the following websites:
*
* http://halicery.com/8042/8042_INTERN_TXT.htm
* http://www.os2museum.com/wp/?p=589 ("IBM PC/AT 8042 Keyboard Controller Commands")
*/
ChipSet.KBC = {
DATA: { // this.b8042OutBuff (PPI_A on previous models, still referred to as "PORT A" by the MODEL_5170 BIOS)
PORT: 0x60,
CMD: { // this.b8042CmdData (KBC.DATA.CMD "data bytes" written to port 0x60, after writing a KBC.CMD byte to port 0x64)
PC_COMPAT: 0x40, // generate IBM PC-compatible scan codes
PC_MODE: 0x20,
NO_CLOCK: 0x10, // disable keyboard by driving "clock" line low
NO_INHIBIT: 0x08, // disable inhibit function
SYS_FLAG: 0x04, // this value is propagated to ChipSet.KBC.STATUS.SYS_FLAG
INT_ENABLE: 0x01 // generate an interrupt when the controller places data in the output buffer
},
SELF_TEST: { // result of ChipSet.KBC.CMD.SELF_TEST command (0xAA)
OK: 0x55
},
INTF_TEST: { // result of ChipSet.KBC.CMD.INTF_TEST command (0xAB)
OK: 0x00, // no error
CLOCK_LO: 0x01, // keyboard clock line stuck low
CLOCK_HI: 0x02, // keyboard clock line stuck high
DATA_LO: 0x03, // keyboard data line stuck low
DATA_HI: 0x04 // keyboard data line stuck high
}
},
INPORT: { // this.b8042InPort
UNDEFINED: 0x0F, // undefined
ENABLE_256KB: 0x10, // enable 2nd 256Kb of system board RAM
MFG_OFF: 0x20, // manufacturing jumper not installed
MONO: 0x40, // monochrome monitor is primary display
KBD_ON: 0x80 // keyboard not inhibited
},
OUTPORT: { // this.b8042OutPort
NO_RESET: 0x01, // set by default
A20_ON: 0x02, // set by default
OUTBUFF_FULL: 0x10, // output buffer full
INBUFF_EMPTY: 0x20, // input buffer empty
KBD_CLOCK: 0x40, // keyboard clock (output)
KBD_DATA: 0x80 // keyboard data (output)
},
TESTPORT: { // generated "on the fly"
KBD_CLOCK: 0x01, // keyboard clock (input)
KBD_DATA: 0x02 // keyboard data (input)
},
RWREG: { // this.bPPIB (since CLK_TIMER2 and SPK_TIMER2 are in both PPI_B and KBC.RWREG)
PORT: 0x61,
CLK_TIMER2: 0x01, // set to enable clock to TIMER2
SPK_TIMER2: 0x02, // set to connect output of TIMER2 to speaker
DISABLE_CHK: 0x0C, // set these bits to disable I/O and RAM parity checks, clear them to enable checks
REFRESH_BIT: 0x10, // indicates memory refresh
IO_CHK: 0x40, // indicates I/O check
PARITY_CHK: 0x80, // indicates RAM parity check
PARITY_ERR: 0xC0
},
CMD: { // this.b8042InBuff (on write to port 0x64, interpret this as a CMD)
PORT: 0x64,
READ_CMD: 0x20,
WRITE_CMD: 0x60, // followed by a command byte written to KBC.DATA.PORT (see KBC.DATA.CMD)
SELF_TEST: 0xAA, // self-test (KBC.DATA.SELF_TEST.OK is placed in the output buffer if no errors)
INTF_TEST: 0xAB, // interface test
DIAG_DUMP: 0xAC, // diagnostic dump
DISABLE_KBD: 0xAD, // disable keyboard
ENABLE_KBD: 0xAE, // enable keyboard
READ_INPORT: 0xC0, // read input port and place data in output buffer (use only if output buffer empty)
READ_OUTPORT: 0xD0, // read output port and place data in output buffer (use only if output buffer empty)
WRITE_OUTPORT: 0xD1, // next byte written to KBC.DATA.PORT (port 0x60) is placed in the output port (see KBC.DATA.OUTPUT)
READ_TEST: 0xE0,
PULSE_OUTPORT: 0xF0 // this is the 1st of 16 commands (0xF0-0xFF) that pulse bits 0-3 of the output port
},
STATUS: { // this.b8042Status (on read from port 0x64)
PORT: 0x64,
OUTBUFF_FULL: 0x01,
INBUFF_FULL: 0x02, // set if the controller has received but not yet read data written to the input buffer (not normally set)
SYS_FLAG: 0x04,
CMD_FLAG: 0x08, // set on write to KBC.CMD (port 0x64), clear on write to KBC.DATA (port 0x60)
NO_INHIBIT: 0x10,
XMT_TIMEOUT: 0x20,
RCV_TIMEOUT: 0x40,
PARITY_ERR: 0x80, // last byte of data received had EVEN parity (ODD parity is normally expected)
OUTBUFF_DELAY: 0x100
}
};
/*
* MC146818A RTC/CMOS Ports (MODEL_5170)
*
* Write a CMOS address to ChipSet.CMOS.ADDR.PORT, then read/write data from/to ChipSet.CMOS.DATA.PORT.
*
* The ADDR port also controls NMI: write an address with bit 7 clear to enable NMI or set to disable NMI.
*/
ChipSet.CMOS = {
ADDR: { // this.bCMOSAddr
PORT: 0x70,
RTC_SEC: 0x00,
RTC_SEC_ALRM: 0x01,
RTC_MIN: 0x02,
RTC_MIN_ALRM: 0x03,
RTC_HOUR: 0x04,
RTC_HOUR_ALRM: 0x05,
RTC_WEEK_DAY: 0x06,
RTC_MONTH_DAY: 0x07,
RTC_MONTH: 0x08,
RTC_YEAR: 0x09,
RTC_STATUSA: 0x0A,
RTC_STATUSB: 0x0B,
RTC_STATUSC: 0x0C,
RTC_STATUSD: 0x0D,
DIAG: 0x0E,
SHUTDOWN: 0x0F,
FDRIVE: 0x10,
HDRIVE: 0x12,
EQUIP: 0x14,
BASEMEM_LO: 0x15,
BASEMEM_HI: 0x16, // the BASEMEM values indicate the total Kb of base memory, up to 0x280 (640Kb)
EXTMEM_LO: 0x17,
EXTMEM_HI: 0x18, // the EXTMEM values indicate the total Kb of extended memory, up to 0x3C00 (15Mb)
CHKSUM_HI: 0x2E,
CHKSUM_LO: 0x2F, // CMOS bytes included in the checksum calculation: 0x10-0x2D
EXTMEM2_LO: 0x30,
EXTMEM2_HI: 0x31,
CENTURY_DATE: 0x32, // BCD value for the current century (eg, 0x19 for 20th century, 0x20 for 21st century)
BOOT_INFO: 0x33, // 0x80 if 128Kb expansion memory installed, 0x40 if Setup Utility wants an initial setup message
MASK: 0x3F,
TOTAL: 0x40,
NMI_DISABLE: 0x80
},
DATA: { // this.abCMOSData
PORT: 0x71
},
STATUSA: { // abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSA]
UIP: 0x80, // bit 7: 1 indicates Update-In-Progress, 0 indicates date/time ready to read
DV: 0x70, // bits 6-4 (DV2-DV0) are programmed to 010 to select a 32.768Khz time base
RS: 0x0F // bits 3-0 (RS3-RS0) are programmed to 0110 to select a 976.562us interrupt rate
},
STATUSB: { // abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSB]
SET: 0x80, // bit 7: 1 to set any/all of the 14 time-bytes
PIE: 0x40, // bit 6: 1 for Periodic Interrupt Enable
AIE: 0x20, // bit 5: 1 for Alarm Interrupt Enable
UIE: 0x10, // bit 4: 1 for Update-Ended Interrupt Enable
SQWE: 0x08, // bit 3: 1 for Square Wave Enabled (as set by the STATUSA rate selection bits)
BINARY: 0x04, // bit 2: 1 for binary Date Mode, 0 for BCD Date Mode
HOUR24: 0x02, // bit 1: 1 for 24-hour mode, 0 for 12-hour mode
DST: 0x01 // bit 0: 1 for Daylight Savings Time enabled
},
STATUSC: { // abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSC] TODO: Does reading this register clear these interrupt conditions? (see F000:01C6 in the MODEL_5170 BIOS)
IRQF: 0x80, // bit 7
PF: 0x40, // bit 6: 1 indicates Periodic Interrupt
AF: 0x20, // bit 5: 1 indicates Alarm Interrupt
UF: 0x10, // bit 4: 1 indicates Update-Ended Interrupt
RESERVED: 0x0F
},
STATUSD: { // abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSD]
VRB: 0x80, // bit 7: 1 indicates Valid RAM Bit (0 implies power was and/or is lost)
RESERVED: 0x7F
},
DIAG: { // abCMOSData[ChipSet.CMOS.ADDR.DIAG]
RTCFAIL: 0x80, // bit 7: 1 indicates RTC lost power
CHKSUMFAIL: 0x40, // bit 6: 1 indicates bad CMOS checksum
CONFIGFAIL: 0x20, // bit 5: 1 indicates bad CMOS configuration info
MEMSIZEFAIL: 0x10, // bit 4: 1 indicates memory size miscompare
HDRIVEFAIL: 0x08, // bit 3: 1 indicates hard drive controller or drive init failure
TIMEFAIL: 0x04, // bit 2: 1 indicates time failure
RESERVED: 0x03
},
FDRIVE: { // abCMOSData[ChipSet.CMOS.ADDR.FDRIVE]
D0_MASK: 0xF0, // Drive 0 type in high nibble
D1_MASK: 0x0F, // Drive 1 type in lower nibble
NONE: 0, // no drive
/*
* There's at least one floppy drive type that IBM didn't bother defining a CMOS drive type for:
* single-sided drives that were only capable of storing 160Kb (or 180Kb when using 9 sectors/track).
* So, as you can see in getSWFloppyDriveType(), we lump all standard diskette capacities <= 360Kb
* into the FD360 bucket.
*/
FD360: 1, // 5.25-inch double-sided double-density (DSDD 48TPI) drive: 40 tracks, 9 sectors/track, 360Kb max
FD1200: 2, // 5.25-inch double-sided high-density (DSHD 96TPI) drive: 80 tracks, 15 sectors/track, 1200Kb max
FD720: 3, // 3.5-inch drive capable of storing 80 tracks and up to 9 sectors/track, 720Kb max
FD1440: 4 // 3.5-inch drive capable of storing 80 tracks and up to 18 sectors/track, 1440Kb max
},
/*
* HDRIVE types are defined by table in the HDC component, which uses setCMOSDriveType() to update the CMOS
*/
HDRIVE: { // abCMOSData[ChipSet.CMOS.ADDR.HDRIVE]
D0_MASK: 0xF0, // Drive 0 type in high nibble
D1_MASK: 0x0F // Drive 1 type in lower nibble
},
/*
* The CMOS equipment flags use the same format as the older PPI equipment flags
*/
EQUIP: { // abCMOSData[ChipSet.CMOS.ADDR.EQUIP]
MONITOR: ChipSet.PPI_SW.MONITOR, // PPI_SW.MONITOR.MASK == 0x30
COPROC: ChipSet.PPI_SW.COPROC, // PPI_SW.COPROC == 0x02
FDRIVE: ChipSet.PPI_SW.FDRIVE // PPI_SW.FDRIVE.IPL == 0x01 and PPI_SW.FDRIVE.MASK = 0xC0
}
};
/*
* Manufacturing Test Ports (MODEL_5170)
*
* The MODEL_5170 TechRef lists 0x80-0x9F as the range for DMA page registers, but that seems a bit
* overbroad; at one point, it says:
*
* "I/O address hex 080 is used as a diagnostic-checkpoint port or register.
* This port corresponds to a read/write register in the DMA page register (74LS6I2)."
*
* 0x80 is the neighborhood, but that particular port is not documented as a DMA page register.
* We'll refer to it as manufacturing port (see bMFGData). Be aware that the MODEL_5170 BIOS is littered
* with manufacturing test ("MFG_TST") code which, if enabled, writes to other DMA page registers,
* perhaps treating them as scratch registers.
*/
ChipSet.MFG = { // this.bMFGData
PORT: 0x80
};
/*
* NMI Mask Register (MODEL_5150 and MODEL_5160 only)
*/
ChipSet.NMI = { // this.bNMI
PORT: 0xA0,
ENABLE: 0x80,
DISABLE: 0x00
};
/*
* Coprocessor Control Registers (MODEL_5170)
*/
ChipSet.COPROC = { // TODO: Define a variable for this
PORT_CLEAR: 0xF0, // clear the coprocessor's "busy" state
PORT_RESET: 0xF1 // reset the coprocessor
};
/*
* ChipSet-related BIOS interrupts, functions, and other parameters
*/
ChipSet.BIOS = {
INT_RTC: 0x1A
};
/**
* @this {ChipSet}
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "sw1")
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
ChipSet.prototype.setBinding = function(sHTMLClass, sHTMLType, sBinding, control)
{
switch (sBinding) {
case "sw1":
this.bindings[sBinding] = control;
this.addSwitches(sBinding, control, 8, this.sw1Init, {
0: (this.model == ChipSet.MODEL_5150? "Bootable Floppy Drive" : "Loop on POST"),
1: (this.model == ChipSet.MODEL_5150? "Reserved" : "Coprocessor"),
2: "Base Memory Size", // up to 64Kb on a MODEL_5150, 256Kb on a MODEL_5160
4: "Monitor Type",
6: "Number of Floppy Drives"
});
return true;
case "sw2":
if (this.model == ChipSet.MODEL_5150) {
this.bindings[sBinding] = control;
this.addSwitches(sBinding, control, 8, this.sw2Init, {
0: "Expansion Memory Size", // up to 480Kb, which, when combined with 64Kb of MODEL_5150 base memory, gives a maximum of 544Kb
4: "Reserved"
});
return true;
}
break;
case "swdesc":
this.bindings[sBinding] = control;
return true;
default:
break;
}
return false;
};
/**
* initBus(cmp, bus, cpu, dbg)
*
* @this {ChipSet}
* @param {Computer} cmp
* @param {Bus} bus
* @param {X86CPU} cpu
* @param {Debugger} dbg
*/
ChipSet.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;
this.cmp = cmp;
this.kbd = cmp.getComponentByType("Keyboard");
/*
* 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) {
bus.addPortInputTable(this, ChipSet.aPortInput5150);
bus.addPortOutputTable(this, ChipSet.aPortOutput5150);
} else {
bus.addPortInputTable(this, ChipSet.aPortInput5170);
bus.addPortOutputTable(this, ChipSet.aPortOutput5170);
}
if (DEBUGGER) {
if (dbg) {
var chipset = this;
dbg.messageDump(Debugger.MESSAGE_PIC, function onDumpPIC() {
chipset.dumpPIC();
});
dbg.messageDump(Debugger.MESSAGE_TIMER, function onDumpTimer() {
chipset.dumpTimer();
});
dbg.messageDump(Debugger.MESSAGE_CMOS, function onDumpCMOS() {
chipset.dumpCMOS();
});
}
cpu.addIntNotify(ChipSet.BIOS.INT_RTC, this, this.intBIOSRTC);
}
};
/**
* powerUp(data, fRepower)
*
* @this {ChipSet}
* @param {Object|null} data
* @param {boolean} [fRepower]
* @return {boolean} true if successful, false if failure
*/
ChipSet.prototype.powerUp = function(data, fRepower)
{
if (!fRepower) {
if (!data) {
this.reset();
} else {
if (!this.restore(data)) return false;
}
}
return true;
};
/**
* powerDown(fSave)
*
* @this {ChipSet}
* @param {boolean} fSave
* @return {Object|boolean}
*/
ChipSet.prototype.powerDown = function(fSave)
{
return fSave && this.save? this.save() : true;
};
/**
* reset(fHard)
*
* @this {ChipSet}
* @param {boolean} [fHard] true if a machine reset (not just a soft reset)
*/
ChipSet.prototype.reset = function(fHard)
{
/*
* We propagate the sw1Init/sw2Init values to sw1/sw2 at reset; the user is only
* allowed to tweak sw1Init/sw2Init, which doesn't take effect until the next reset.
*/
var i;
this.sw1 = this.sw1Init;
this.sw2 = this.sw2Init;
this.updateSwitchDesc();
/*
* DMA Controller initialization
*/
this.aDMACs = new Array(this.cDMACs);
for (i = 0; i < this.cDMACs; i++) this.initDMAController(i);
/*
* PIC initialization
*/
this.aPICs = new Array(this.cPICs);
this.initPIC(ChipSet.PIC0.INDEX, ChipSet.PIC0.PORT_LO);
if (this.cPICs > 1) this.initPIC(ChipSet.PIC1.INDEX, ChipSet.PIC1.PORT_LO);
/*
* Timer initialization
*/
this.bTimerCtrl = undefined; // tracks writes to port 0x43
this.aTimers = new Array(3);
for (i = 0; i < this.aTimers.length; i++) {
this.initTimer(i);
}
/*
* PPI and other misc ports
*/
this.bPPIA = undefined; // tracks writes to port 0x60, in case PPI_CTRL.A_IN is not set
this.bPPIB = undefined; // tracks writes to port 0x61, in case PPI_CTRL.B_IN is not set
this.bPPIC = undefined; // tracks writes to port 0x62, in case PPI_CTRL.C_IN_LO or PPI_CTRL.C_IN_HI is not set
this.bPPICtrl = undefined; // tracks writes to port 0x63 (eg, 0x99); read-only
this.bNMI = ChipSet.NMI.DISABLE;// tracks writes to the NMI Mask Register
/*
* ChipSet state introduced by the MODEL_5170
*/
if (this.model >= ChipSet.MODEL_5170) {
/*
* The 8042 input buffer is treated as a "command byte" when written via port 0x64 and as a "data byte"
* when written via port 0x60. So, whenever the KBC.CMD.WRITE_CMD "command byte" is written to the input
* buffer, the subsequent command data byte is saved in b8042CmdData. Similarly, for KBC.CMD.WRITE_OUTPORT,
* the subsequent data byte is saved in b8042OutPort.
*
* TODO: Consider a UI for the Keyboard INHIBIT switch. By default, our keyboard is never inhibited
* (ie, locked). Also, note that the hardware changes this bit only when new data is sent to b8042OutBuff.
*/
this.b8042Status = ChipSet.KBC.STATUS.NO_INHIBIT;
this.b8042InBuff = 0;
this.b8042CmdData = ChipSet.KBC.DATA.CMD.NO_CLOCK;
this.b8042OutBuff = 0;
/*
* TODO: Provide more control over these 8042 "Input Port" bits (eg, the keyboard lock)
*/
this.b8042InPort = ChipSet.KBC.INPORT.MFG_OFF | ChipSet.KBC.INPORT.KBD_ON;
if (this.getSWMemorySize() >= 512) this.b8042InPort |= ChipSet.KBC.INPORT.ENABLE_256KB;
if (this.getSWVideoMonitor() == ChipSet.MONITOR.MONO) this.b8042InPort |= ChipSet.KBC.INPORT.MONO;
this.b8042OutPort = ChipSet.KBC.OUTPORT.NO_RESET | ChipSet.KBC.OUTPORT.A20_ON;
this.bMFGData = 0;
this.abDMAPageSpare = new Array(7);
this.bCMOSAddr = 0; // NMI is enabled, since the ChipSet.CMOS.ADDR.NMI_DISABLE bit is not set in bCMOSAddr
/*
* Now that we call reset() from the ChipSet constructor, enabling other components to update
* their own CMOS information as needed, we must distinguish between the initial ("hard") reset
* and any later ("soft") resets (eg, from powerUp() calls), and make sure the latter preserves
* existing CMOS information.
*/
if (fHard) this.abCMOSData = new Array(ChipSet.CMOS.ADDR.TOTAL);
this.initRTCDate(this.sRTCDate);
/*
* initCMOSData() will initialize a variety of "legacy" CMOS bytes, but it will NOT overwrite any memory
* size or hard drive type information that might have been set, via addCMOSMemory() or setCMOSDriveType().
*/
this.initCMOSData();
}
if (DEBUGGER && MAXDEBUG) {
/*
* Arrays for interrupt counts (one count per IRQ) and timer data
*/
this.acInterrupts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
this.acTimersFired = [0, 0, 0];
this.acTimer0Counts = [];
}
};
/**
* initRTCDate(sDate)
*
* Initialize the RTC portion of the CMOS registers to match the specified date/time (or if none is specified,
* the current date/time). The date/time should be expressed in the ISO 8601 format; eg: "2011-10-10T14:48:00".
*
* NOTE: There are two approaches we could take here: always store the RTC bytes in binary, and convert them
* to/from BCD on-demand (ie, as the simulation reads/writes the CMOS RTC registers); or init/update them in the
* format specified by CMOS_STATUSB.BINARY (1 for binary, 0 for BCD). Both approaches require BCD conversion
* functions, but the former seems more efficient, in part because the periodic calls to updateRTCDate() won't
* require any conversions.
*
* We take the same approach with the CMOS_STATUSB.HOUR24 setting: internally, we always operate in 24-hour mode,
* but externally, we convert the RTC hour values to the 12-hour format as needed.
*
* Thus, all I/O to the RTC bytes must be routed through the getRTCByte() and setRTCByte() functions, to ensure
* that all the necessary on-demand conversions occur.
*
* @this {ChipSet}
* @param {string} [sDate]
*/
ChipSet.prototype.initRTCDate = function(sDate)
{
/*
* NOTE: I've already been burned once by a JavaScript library function that did NOT treat an undefined
* parameter (ie, a parameter === undefined) the same as an omitted parameter (eg, the async parameter in
* xmlHTTP.open() in IE), so I'm taking no chances here: if sDate is undefined, then explicitly call Date()
* with no parameters.
*/
var date = sDate? new Date(sDate) : new Date();
/*
* Example of a valid Date string:
*
* 2014-10-01T08:00:00 (interpreted as GMT, resulting in "Wed Oct 01 2014 01:00:00 GMT-0700 (PDT)")
*
* Examples of INVALID Date strings:
*
* 2014-10-01T08:00:00PST
* 2014-10-01T08:00:00-0700 (actually, this DOES work in Chrome, but NOT in Safari)
*
* In the case of INVALID Date strings, the Date object is invalid, but there's no obvious test for an "invalid"
* object, so I've adapted the following test from StackOverflow.
*
* See http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript
*/
if (Object.prototype.toString.call(date) !== "[object Date]" || isNaN(date.getTime())) {
date = new Date();
this.println("CMOS date invalid (" + sDate + "), using " + date);
} else if (sDate) {
this.println("CMOS date: " + date);
}
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_SEC] = date.getSeconds();
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_SEC_ALRM] = 0;
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MIN] = date.getMinutes();
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MIN_ALRM] = 0;
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_HOUR] = date.getHours();
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_HOUR_ALRM] = 0;
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;
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
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSB] = ChipSet.CMOS.STATUSB.HOUR24; // default to BCD mode (ChipSet.CMOS.STATUSB.BINARY not set)
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSC] = 0x00;
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSD] = ChipSet.CMOS.STATUSD.VRB;
};
/**
* getRTCByte(iRTC)
*
* @param {number} iRTC
* @return {number} b
*/
ChipSet.prototype.getRTCByte = function(iRTC)
{
Component.assert(iRTC >= 0 && iRTC <= ChipSet.CMOS.ADDR.RTC_STATUSD);
var b = this.abCMOSData[iRTC];
if (iRTC < ChipSet.CMOS.ADDR.RTC_STATUSA) {
var f12HourValue = false;
if (iRTC == ChipSet.CMOS.ADDR.RTC_HOUR || iRTC == ChipSet.CMOS.ADDR.RTC_HOUR_ALRM) {
if (!(this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSB] & ChipSet.CMOS.STATUSB.HOUR24)) {
if (b < 12) {
b = (!b? 12 : b);
} else {
b -= 12;
b = (!b? 0x8c : b + 0x80);
}
f12HourValue = true;
}
}
if (!(this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSB] & ChipSet.CMOS.STATUSB.BINARY)) {
/*
* We're in BCD mode, so we must convert b from BINARY to BCD. But first:
*
* If b is a 12-hour value (ie, we're in 12-hour mode) AND the hour is a PM value
* (ie, in the range 0x81-0x8C), then it must be adjusted to yield 81-92 in BCD.
*
* AM hour values (0x01-0x0C) need no adjustment; they naturally convert to 01-12 in BCD.
*/
if (f12HourValue && b > 0x80) {
b -= (0x81 - 81);
}
b = (b % 10) | ((b / 10) << 4);
}
} else {
if (iRTC == ChipSet.CMOS.ADDR.RTC_STATUSA) {
/*
* HACK: Perform a mindless toggling of the "Update-In-Progress" bit, so that it's flipped
* on the next read; this makes the MODEL_5170 BIOS ("POST2_RTCUP") happy.
*/
this.abCMOSData[iRTC] ^= ChipSet.CMOS.STATUSA.UIP;
}
}
return b;
};
/**
* setRTCByte(iRTC, b)
*
* @param {number} iRTC
* @param {number} b proposed byte to write
* @return {number} actual byte to write
*/
ChipSet.prototype.setRTCByte = function(iRTC, b)
{
Component.assert(iRTC >= 0 && iRTC <= ChipSet.CMOS.ADDR.RTC_STATUSD);
if (iRTC < ChipSet.CMOS.ADDR.RTC_STATUSA) {
var fBCD = false;
if (!(this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSB] & ChipSet.CMOS.STATUSB.BINARY)) {
/*
* We're in BCD mode, so we must convert b from BCD to BINARY (we assume it's valid
* BCD; ie, that both nibbles contain only 0-9, not A-F).
*/
b = (b >> 4) * 10 + (b & 0xf);
fBCD = true;
}
if (iRTC == ChipSet.CMOS.ADDR.RTC_HOUR || iRTC == ChipSet.CMOS.ADDR.RTC_HOUR_ALRM) {
if (fBCD) {
/*
* If the original BCD hour was 0x81-0x92, then the previous BINARY-to-BCD conversion
* transformed it to 0x51-0x5C, so we must add 0x30.
*/
if (b > 12) {
Component.assert(b >= 0x51 && b <= 0x5c);
b += 0x30;
}
}
if (!(this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSB] & ChipSet.CMOS.STATUSB.HOUR24)) {
if (b <= 12) {
b = (b == 12? 0 : b);
} else {
b -= (0x80 - 12);
b = (b == 24? 12 : b);
}
}
}
}
return b;
};
/**
* updateRTCDate()
*
* @this {ChipSet}
*/
ChipSet.prototype.updateRTCDate = function()
{
var nCyclesDelta = 0;
var nCyclesPerSecond = this.cpu.getCyclesPerSecond();
var nCyclesUpdate = this.cpu.getCycles(this.fScaleTimers);
/*
* If nCyclesCMOSLastUpdate hasn't been set yet (ie, if this is our first updateRTCDate() call),
* then do nothing except initialize nCyclesCMOSLastUpdate.
*/
if (this.nCyclesCMOSLastUpdate >= 0) {
nCyclesDelta = nCyclesUpdate - this.nCyclesCMOSLastUpdate;
Component.assert(nCyclesDelta >= 0);
var nSecondsDelta = Math.floor(nCyclesDelta / nCyclesPerSecond);
/*
* We trust that updateRTCDate() is being called as part of updateAllTimers(), and is therefore
* being called often enough to ensure that nSecondsDelta will never be greater than one. In fact,
* it would always be LESS than one if it weren't ALSO for the fact that we plow any "unused" cycles
* (nCyclesDelta % nCyclesPerSecond) back into nCyclesCMOSLastUpdate, so that we will eventually
* see a one-second delta.
*/
Component.assert(nSecondsDelta <= 1);
if (nSecondsDelta) {
if (++this.abCMOSData[ChipSet.CMOS.ADDR.RTC_SEC] >= 60) {
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_SEC] = 0;
if (++this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MIN] >= 60) {
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MIN] = 0;
if (++this.abCMOSData[ChipSet.CMOS.ADDR.RTC_HOUR] >= 24) {
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_HOUR] = 0;
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_WEEK_DAY] = (this.abCMOSData[ChipSet.CMOS.ADDR.RTC_WEEK_DAY] % 7) + 1;
var nDayMax = usr.getMonthDays(this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MONTH], this.abCMOSData[ChipSet.CMOS.ADDR.RTC_YEAR]);
if (++this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MONTH_DAY] > nDayMax) {
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MONTH_DAY] = 1;
if (++this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MONTH] > 12) {
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MONTH] = 1;
this.abCMOSData[ChipSet.CMOS.ADDR.RTC_YEAR] = (this.abCMOSData[ChipSet.CMOS.ADDR.RTC_YEAR] + 1) % 100;
}
}
}
}
}
}
}
this.nCyclesCMOSLastUpdate = nCyclesUpdate - (nCyclesDelta % nCyclesPerSecond);
};
/**
* initCMOSData()
*
* Initialize all the CMOS configuration bytes in the range 0x0E-0x2F (TODO: Decide what to do about 0x30-0x3F)
*
* Note that the MODEL_5170 "SETUP" utility is normally what sets all these bytes, including the checksum, and then
* the BIOS verifies it, but since we want our machines to pass BIOS verification "out of the box", we go the extra
* mile here, even though it's not really our responsibility.
*
* @this {ChipSet}
*/
ChipSet.prototype.initCMOSData = function()
{
/*
* On all reset() calls, the RAM component(s) will (re)add their totals, so we have to make sure that
* the addition always starts with 0. That also means that ChipSet must always be initialized before RAM.
*/
var iCMOS;
for (iCMOS = ChipSet.CMOS.ADDR.BASEMEM_LO; iCMOS <= ChipSet.CMOS.ADDR.EXTMEM_HI; iCMOS++) {
this.abCMOSData[iCMOS] = 0;
}
/*
* Make sure all the "checksummed" CMOS bytes are initialized (not just the handful we set below) to ensure
* that the checksum will be valid.
*/
for (iCMOS = ChipSet.CMOS.ADDR.DIAG; iCMOS < ChipSet.CMOS.ADDR.CHKSUM_HI; iCMOS++) {
if (this.abCMOSData[iCMOS] === undefined) this.abCMOSData[iCMOS] = 0;
}
/*
* We propagate all compatible "legacy" SW1 bits to the CMOS_EQUIP byte using the old SW masks, but any further
* access to CMOS_ADDR.EQUIP should use the new CMOS_EQUIP flags (eg, CMOS_EQUIP.COPROC, CMOS_EQUIP.MONITOR.CGA80, etc).
*/
this.abCMOSData[ChipSet.CMOS.ADDR.EQUIP] = this.sw1 & (ChipSet.PPI_SW.MONITOR.MASK | ChipSet.PPI_SW.COPROC | ChipSet.PPI_SW.FDRIVE.IPL | ChipSet.PPI_SW.FDRIVE.MASK);
this.abCMOSData[ChipSet.CMOS.ADDR.FDRIVE] = (this.getSWFloppyDriveType(0) << 4) | this.getSWFloppyDriveType(1);
/*
* The final step is calculating the CMOS checksum, which we then store into the CMOS as a courtesy, so that the
* user doesn't get unnecessary CMOS errors.
*/
this.updateCMOSChecksum();
};
/**
* setCMOSByte(iCMOS, b)
*
* This is ONLY for use by components that need to update CMOS configuration bytes to match their internal configuration.
*
* @this {ChipSet}
* @param {number} iCMOS
* @param {number} b
* @return {boolean} true if successful, false if not (eg, CMOS not initialized yet, or no CMOS on this machine)
*/
ChipSet.prototype.setCMOSByte = function(iCMOS, b)
{
if (this.abCMOSData) {
Component.assert(iCMOS >= ChipSet.CMOS.ADDR.FDRIVE && iCMOS < ChipSet.CMOS.ADDR.CHKSUM_HI);
this.abCMOSData[iCMOS] = b;
this.updateCMOSChecksum();
return true;
}
return false;
};
/**
* addCMOSMemory(addr, size)
*
* For use by the RAM component, to dynamically update the CMOS memory configuration.
*
* @this {ChipSet}
* @param {number} addr (if 0, BASEMEM_LO/BASEMEM_HI is updated; if >= 0x100000, then EXTMEM_LO/EXTMEM_HI is updated)
* @param {number} size (in bytes; we convert to Kb)
* @return {boolean} true if successful, false if not (eg, CMOS not initialized yet, or no CMOS on this machine)
*/
ChipSet.prototype.addCMOSMemory = function(addr, size)
{
if (this.abCMOSData) {
var iCMOS = (addr < 0x100000? ChipSet.CMOS.ADDR.BASEMEM_LO : ChipSet.CMOS.ADDR.EXTMEM_LO);
var wKb = this.abCMOSData[iCMOS] | (this.abCMOSData[iCMOS+1] << 8);
wKb += (size >> 10);
this.abCMOSData[iCMOS] = wKb & 0xff;
this.abCMOSData[iCMOS+1] = wKb >> 8;
this.updateCMOSChecksum();
return true;
}
return false;
};
/**
* setCMOSDriveType(iDrive, bType)
*
* For use by the HDC component, to update the CMOS drive configuration to match HDC's internal configuration.
*
* TODO: Consider extending this to support FDC drive updates, so that the FDC can specify diskette drive types
* (ie, FD360 or FD1200) in the same way that HDC does. However, historically, the ChipSet has been responsible for
* floppy drive configuration, at least in terms of *number* of drives, through the use of SW1 settings, and we've
* continued that tradition with the addition of the ChipSet 'floppies' parameter, which allows both the number *and*
* capacity of drives to be specified with a simple array (eg, [360, 360] for two 360Kb drives).
*
* @this {ChipSet}
* @param {number} iDrive
* @param {number} bType
* @return {boolean} true if successful, false if not (eg, CMOS not initialized yet, or no CMOS on this machine)
*/
ChipSet.prototype.setCMOSDriveType = function(iDrive, bType)
{
if (this.abCMOSData) {
var b = this.abCMOSData[ChipSet.CMOS.ADDR.HDRIVE];
Component.assert(bType > 0 && bType < 0xf);
if (iDrive) {
b = (b & ChipSet.CMOS.HDRIVE.D0_MASK) | bType;
} else {
b = (b & ChipSet.CMOS.HDRIVE.D1_MASK) | (bType << 4);
}
this.setCMOSByte(ChipSet.CMOS.ADDR.HDRIVE, b);
return true;
}
return false;
};
/**
* updateCMOSChecksum()
*
* This sums all the CMOS bytes from 0x10-0x2D, creating a 16-bit checksum. That's a total of 30 (unsigned) 8-bit
* values which could sum to at most 30*255 or 7650 (0x1DE2). Since there's no way that can overflow 16 bits, we don't
* worry about masking it with 0xffff.
*
* WARNING: The IBM PC AT TechRef, p.1-53 (p.75) claims that the checksum is on bytes 0x10-0x20, but that's simply wrong.
*
* @this {ChipSet}
*/
ChipSet.prototype.updateCMOSChecksum = function()
{
var wChecksum = 0;
for (var iCMOS = ChipSet.CMOS.ADDR.FDRIVE; iCMOS < ChipSet.CMOS.ADDR.CHKSUM_HI; iCMOS++) {
wChecksum += this.abCMOSData[iCMOS];
}
this.abCMOSData[ChipSet.CMOS.ADDR.CHKSUM_LO] = wChecksum & 0xff;
this.abCMOSData[ChipSet.CMOS.ADDR.CHKSUM_HI] = wChecksum >> 8;
};
/**
* save()
*
* @this {ChipSet}
* @return {Object}
*
* This implements save support for the ChipSet component.
*/
ChipSet.prototype.save = function()
{
var state = new State(this);
state.set(0, [this.sw1Init, this.sw2Init, this.sw1, this.sw2]);
state.set(1, [this.saveDMAControllers()]);
state.set(2, [this.savePICs()]);
state.set(3, [this.bTimerCtrl, this.saveTimers()]);
state.set(4, [this.bPPIA, this.bPPIB, this.bPPIC, this.bPPICtrl, this.bNMI]);
if (this.model >= ChipSet.MODEL_5170) {
state.set(5, [this.b8042Status, this.b8042InBuff, this.b8042CmdData,
this.b8042OutBuff, this.b8042InPort, this.b8042OutPort]);
state.set(6, [this.bMFGData, this.abDMAPageSpare, this.bCMOSAddr, this.abCMOSData, this.nCyclesCMOSLastUpdate]);
}
return state.data();
};
/**
* restore(data)
*
* @this {ChipSet}
* @param {Object} data
* @return {boolean} true if successful, false if failure
*
* This implements restore support for the ChipSet component.
*/
ChipSet.prototype.restore = function(data)
{
var a, i;
a = data[0];
this.sw1Init = a[0];
this.sw2Init = a[1];
this.sw1 = a[2];
this.sw2 = a[3];
a = data[1];
this.aDMACs = new Array(this.cDMACs);
for (i = 0; i < this.cDMACs; i++) {
this.initDMAController(i, a.length == 1? a[0][i] : a);
}
a = data[2];
this.aPICs = new Array(this.cPICs);
for (i = 0; i < this.cPICs; i++) {
this.initPIC(i, i === 0? ChipSet.PIC0.PORT_LO : ChipSet.PIC1.PORT_LO, a[0][i]);
}
a = data[3];
this.bTimerCtrl = a[0];
this.aTimers = new Array(3);
for (i = 0; i < this.aTimers.length; i++) {
this.initTimer(i, a[1][i]);
}
a = data[4];
this.bPPIA = a[0];
this.bPPIB = a[1];
this.bPPIC = a[2];
this.bPPICtrl = a[3];
this.bNMI = a[4];
a = data[5];
if (a) {
Component.assert(this.model >= ChipSet.MODEL_5170);
this.b8042Status = a[0];
this.b8042InBuff = a[1];
this.b8042CmdData = a[2];
this.b8042OutBuff = a[3];
this.b8042InPort = a[4];
this.b8042OutPort = a[5];
}
a = data[6];
if (a) {
Component.assert(this.model >= ChipSet.MODEL_5170);
this.bMFGData = a[0];
this.abDMAPageSpare = a[1];
this.bCMOSAddr = a[2];
this.abCMOSData = a[3];
this.nCyclesCMOSLastUpdate = a[4];
/*
* TODO: Decide whether restore() should faithfully preserve the RTC date/time that save() saved,
* or always reinitialize the date/time, or give the user (or the machine configuration) the option.
*
* For now, we're always reinitializing the RTC date. Alternatively, we could selectively update
* the CMOS bytes above, instead of overwriting them all, in which case this extra call to initRTCDate()
* could be avoided.
*/
this.initRTCDate();
}
return true;
};
/**
* initDMAController(iDMAC, aState)
*
* @this {ChipSet}
* @param {number} iDMAC
* @param {Array} [aState]
*/
ChipSet.prototype.initDMAController = function(iDMAC, aState)
{
var controller = this.aDMACs[iDMAC] = {};
if (!aState || aState.length != 5) aState = [0, undefined, undefined, 0, []];
controller.bStatus = aState[0];
controller.bCmd = aState[1];
controller.bReq = aState[2];
controller.bIndex = aState[3];
controller.nChannelBase = iDMAC << 2;
controller.aChannels = new Array(4);
for (var iChannel = 0; iChannel < controller.aChannels.length; iChannel++) {
this.initDMAChannel(controller, iChannel, aState[4][iChannel]);
}
};
/**
* initDMAChannel(controller, iChannel, aState)
*
* @this {ChipSet}
* @param {Object} controller
* @param {number} iChannel
* @param {Array} [aState]
*/
ChipSet.prototype.initDMAChannel = function(controller, iChannel, aState)
{
var channel = controller.aChannels[iChannel] = {};
if (aState === undefined || aState.length != 8) {
aState = [true, [], [], [], []];
}
channel.controller = controller;
channel.iChannel = iChannel;
channel.masked = aState[0];
channel.addrInit = aState[1];
channel.countInit = aState[2];
channel.addrCurrent = aState[3];
channel.countCurrent = aState[4];
channel.mode = aState[5];
channel.bPage = aState[6];
// aState[7] is deprecated
this.initDMAFunction(channel, aState[8], aState[9]);
};
/**
* initDMAFunction(channel)
*
* @param {Object} channel
* @param {Component|string} component
* @param {string} sFunction
* @param {Object} [obj]
* @return {*}
*/
ChipSet.prototype.initDMAFunction = function(channel, component, sFunction, obj)
{
if (typeof component == "string") {
component = Component.getComponentByID(component);
}
if (component) {
channel.done = null;
channel.sDevice = component.id;
channel.sFunction = sFunction;
channel.component = component;
channel.fnTransfer = component[sFunction];
channel.obj = obj;
}
return channel.fnTransfer;
};
/**
* saveDMAControllers()
*
* @this {ChipSet}
* @return {Array}
*/
ChipSet.prototype.saveDMAControllers = function()
{
var data = [];
for (var iDMAC = 0; iDMAC < this.aDMACs; iDMAC++) {
var a = [];
var controller = this.aDMACs[iDMAC];
a[0] = controller.bStatus;
a[1] = controller.bCmd;
a[2] = controller.bReq;
a[3] = controller.bIndex;
a[4] = this.saveDMAChannels(controller);
data[iDMAC] = a;
}
return data;
};
/**
* saveDMAChannels(controller)
*
* @this {ChipSet}
* @param {Object} controller
* @return {Array}
*/
ChipSet.prototype.saveDMAChannels = function(controller)
{
var data = [];
for (var iChannel = 0; iChannel < controller.aChannels.length; iChannel++) {
var a = [];
var channel = controller.aChannels[iChannel];
a[0] = channel.masked;
a[1] = channel.addrInit;
a[2] = channel.countInit;
a[3] = channel.addrCurrent;
a[4] = channel.countCurrent;
a[5] = channel.mode;
a[6] = channel.bPage;
a[8] = channel.sDevice;
a[9] = channel.sFunction;
data[iChannel] = a;
}
return data;
};
/**
* initPIC(iPIC, aState)
*
* @this {ChipSet}
* @param {number} iPIC
* @param {number} port
* @param {Array} [aState]
*/
ChipSet.prototype.initPIC = function(iPIC, port, aState)
{
var pic = this.aPICs[iPIC] = {};
if (!aState || aState.length != 8) aState = [0, [undefined, undefined, undefined, undefined]];
pic.port = port;
pic.nIRQBase = iPIC << 3;
pic.nDelay = aState[0];
pic.aICW = aState[1];
pic.nICW = aState[2];
pic.bIMR = aState[3];
pic.bIRR = aState[4];
pic.bISR = aState[5];
pic.bIRLow = aState[6];
pic.bOCW3 = aState[7];
};
/**
* savePICs()
*
* @this {ChipSet}
* @return {Array}
*/
ChipSet.prototype.savePICs = function()
{
var data = [];
for (var iPIC = 0; iPIC < this.aPICs.length; iPIC++) {
var a = [];
var pic = this.aPICs[iPIC];
a[0] = pic.nDelay;
a[1] = pic.aICW;
a[2] = pic.nICW;
a[3] = pic.bIMR;
a[4] = pic.bIRR;
a[5] = pic.bISR;
a[6] = pic.bIRLow;
a[7] = pic.bOCW3;
data[iPIC] = a;
}
return data;
};
/**
* initTimer(iTimer, aState)
*
* @this {ChipSet}
* @param {number} iTimer
* @param {Array} [aState]
*/
ChipSet.prototype.initTimer = function(iTimer, aState)
{
var timer = this.aTimers[iTimer] = {};
if (aState === undefined || aState.length != 13) {
aState = [ [], [], [], [] ];
}
timer.countInit = aState[0];
timer.countStart = aState[1];
timer.countCurrent = aState[2];
timer.countLatched = aState[3];
timer.bcd = aState[4];
timer.mode = aState[5];
timer.rw = aState[6];
timer.countIndex = aState[7];
timer.countBytes = aState[8];
timer.fOUT = aState[9];
timer.fLatched = aState[10];
timer.fCounting = aState[11];
timer.nStartCycles = aState[12];
};
/**
* saveTimers()
*
* @this {ChipSet}
* @return {Array}
*/
ChipSet.prototype.saveTimers = function()
{
var data = [];
for (var iTimer = 0; iTimer < this.aTimers.length; iTimer++) {
var a = [];
var timer = this.aTimers[iTimer];
a[0] = timer.countInit;
a[1] = timer.countStart;
a[2] = timer.countCurrent;
a[3] = timer.countLatched;
a[4] = timer.bcd;
a[5] = timer.mode;
a[6] = timer.rw;
a[7] = timer.countIndex;
a[8] = timer.countBytes;
a[9] = timer.fOUT;
a[10] = timer.fLatched;
a[11] = timer.fCounting;
a[12] = timer.nStartCycles;
data[iTimer] = a;
}
return data;
};
/**
* getSWMemorySize(fInit)
*
* @this {ChipSet}
* @param {boolean|undefined} [fInit] is true for init switch value(s) only, current value(s) otherwise
* @return {number} number of Kb of specified memory (NOT necessarily the same as installed memory; see RAM component)
*/
ChipSet.prototype.getSWMemorySize = function(fInit)
{
var sw1 = (fInit? this.sw1Init : this.sw1);
var sw2 = (fInit? this.sw2Init : this.sw2);
return (((sw1 & ChipSet.PPI_SW.MEMORY.MASK) >> ChipSet.PPI_SW.MEMORY.SHIFT) + 1) * this.kbSW + (sw2 & ChipSet.PPI_C.SW) * 32;
};
/**
* getSWFloppyDrives(fInit)
*
* @this {ChipSet}
* @param {boolean|undefined} [fInit] is true for init switch value(s) only, current value(s) otherwise
* @return {number} number of floppy drives specified by SW1 (range is 0 to 4)
*/
ChipSet.prototype.getSWFloppyDrives = function(fInit)
{
var sw1 = (fInit? this.sw1Init : this.sw1);
return ((this.model != ChipSet.MODEL_5150) || (sw1 & ChipSet.PPI_SW.FDRIVE.IPL))? ((sw1 & ChipSet.PPI_SW.FDRIVE.MASK) >> ChipSet.PPI_SW.FDRIVE.SHIFT) + 1 : 0;
};
/**
* getSWFloppyDriveType(iDrive)
*
* @this {ChipSet}
* @param {number} iDrive (0-based)
* @return {number} one of the ChipSet.CMOS.FDRIVE.FD* values (FD360, FD1200, etc)
*/
ChipSet.prototype.getSWFloppyDriveType = function(iDrive)
{
if (iDrive < this.getSWFloppyDrives()) {
if (!this.aFloppyDrives) {
return ChipSet.CMOS.FDRIVE.FD360;
}
if (iDrive < this.aFloppyDrives.length) {
switch(this.aFloppyDrives[iDrive]) {
case 160:
case 180:
case 320:
case 360:
return ChipSet.CMOS.FDRIVE.FD360;
case 720:
return ChipSet.CMOS.FDRIVE.FD720;
case 1200:
return ChipSet.CMOS.FDRIVE.FD1200;
case 1440:
return ChipSet.CMOS.FDRIVE.FD1440;
}
}
Component.assert(false); // we should never get here (else something is out of out sync)
}
return ChipSet.CMOS.FDRIVE.NONE;
};
/**
* getSWFloppyDriveSize(iDrive)
*
* @this {ChipSet}
* @param {number} iDrive (0-based)
* @return {number} capacity of drive in Kb (eg, 360, 1200, 1440, etc), or 0 if none
*/
ChipSet.prototype.getSWFloppyDriveSize = function(iDrive)
{
if (iDrive < this.getSWFloppyDrives()) {
if (!this.aFloppyDrives) {
return 360;
}
if (iDrive < this.aFloppyDrives.length) {
return this.aFloppyDrives[iDrive];
}
Component.assert(false); // we should never get here (else something is out of out sync)
}
return 0;
};
/**
* getSWVideoMonitor(fInit)
*
* @this {ChipSet}
* @param {boolean|undefined} [fInit] is true for init switch value(s) only, current value(s) otherwise
* @return {number} one of ChipSet.MONITOR.*
*/
ChipSet.prototype.getSWVideoMonitor = function(fInit)
{
var sw1 = (fInit? this.sw1Init : this.sw1);
return (sw1 & ChipSet.PPI_SW.MONITOR.MASK) >> ChipSet.PPI_SW.MONITOR.SHIFT;
};
/**
* addSwitches(s, control, n, v, oTips)
*
* @this {ChipSet}
* @param {string} s is the name of the control
* @param {Object} control is the HTML control DOM object
* @param {number} n is the number of switches to add
* @param {number} v contains the current value(s) of the switches
* @param {Object} oTips contains tooltips for the various cells
*/
ChipSet.prototype.addSwitches = function(s, control, n, v, oTips)
{
var sHTML = "";
var sCellClass = PCJSCLASS + "-bitCell";
for (var i = 1; i <= n; i++) {
var sCellClasses = sCellClass;
if (!i) sCellClasses += " " + PCJSCLASS + "-bitCellLeft";
var sCellID = s + "-" + i;
sHTML += "" + i + "
\n";
}
control.innerHTML = sHTML;
var aeCells = Component.getElementsByClass(control, sCellClass);
var sTip = null;
for (i = 0; i < aeCells.length; i++) {
if (oTips !== undefined && oTips[i] !== undefined) {
sTip = oTips[i];
}
if (sTip) aeCells[i].setAttribute("title", sTip);
this.setSwitch(aeCells[i], (v & (0x1 << i))? false : true);
aeCells[i].onclick = function(chipset, eSwitch) {
/*
* If we defined the onclick handler below as "function(e)" instead of simply "function()", then we could
* also receive an event object (e); however, IE reportedly requires that we examine a global (window.event)
* instead. If that's true, and if we ever care to get more details about the click event, then we might
* have to worry about that (eg, define a local var: "var event = window.event || e").
*/
return function onClickSwitch() {
chipset.toggleSwitch(eSwitch);
};
}(this, aeCells[i]);
}
};
/**
* getSwitch(control)
*
* @this {ChipSet}
* @param {Object} control is an HTML control DOM object
* @return {boolean} true if the switch represented by e is "on", false if "off"
*/
ChipSet.prototype.getSwitch = function(control)
{
return control.getAttribute("data-value") == "1";
};
/**
* setSwitch(control, f)
*
* @this {ChipSet}
* @param {Object} control is an HTML control DOM object
* @param {boolean} f is true if the switch represented by e should be "on", false if "off"
*/
ChipSet.prototype.setSwitch = function(control, f)
{
control.setAttribute("data-value", f? "1" : "0");
control.style.color = (f? "#ffffff" : "#000000");
control.style.backgroundColor = (f? "#000000" : "#ffffff");
};
/**
* toggleSwitch(control)
*
* @this {ChipSet}
* @param {Object} control is an HTML control DOM object
*/
ChipSet.prototype.toggleSwitch = function(control)
{
var f = !this.getSwitch(control);
this.setSwitch(control, f);
var sID = control.getAttribute("id");
var asParts = sID.split("-");
var b = (0x1 << (parseInt(asParts[1], 10) - 1));
switch (asParts[0]) {
case "sw1":
this.sw1Init = (this.sw1Init & ~b) | (f? 0 : b);
break;
case "sw2":
this.sw2Init = (this.sw2Init & ~b) | (f? 0 : b);
break;
default:
break;
}
this.updateSwitchDesc();
};
/**
* updateSwitchDesc()
*
* @this {ChipSet}
*/
ChipSet.prototype.updateSwitchDesc = function()
{
var controlDesc = this.bindings["swdesc"];
/*
* TODO: Monitor type 0 used to be "No" (as in "No Monitor"), which was correct in the pre-EGA world,
* but in the post-EGA world, it depends. We could ask the Video component for a definitive answer, but
* but what we print here isn't that critical. Most people won't even bother with a Control Panel,
* which is really the only beneficiary of this code.
*/
var asMonitorTypes = {
0: "Enhanced Color",
1: "TV",
2: "Color",
3: "Monochrome"
};
if (controlDesc !== undefined) {
var sHTML = "";
sHTML += this.getSWMemorySize(true) + "Kb";
sHTML += ", " + asMonitorTypes[this.getSWVideoMonitor(true)] + " Monitor";
sHTML += ", " + this.getSWFloppyDrives(true) + " Floppy Drives";
if (this.sw1 !== undefined && this.sw1 != this.sw1Init || this.sw2 !== undefined && this.sw2 != this.sw2Init)
sHTML += " (Reset required)";
controlDesc.innerHTML = sHTML;
}
};
/**
* dumpPIC()
*
* @this {ChipSet}
*/
ChipSet.prototype.dumpPIC = function()
{
if (DEBUGGER) {
for (var iPIC = 0; iPIC < this.aPICs.length; iPIC++) {
var pic = this.aPICs[iPIC];
var sDump = "PIC" + iPIC + ":";
for (var i = 0; i < pic.aICW.length; i++) {
var b = pic.aICW[i];
sDump += " IC" + (i + 1) + "=" + str.toHexByte(b);
}
sDump += " IMR=" + str.toHexByte(pic.bIMR) + " IRR=" + str.toHexByte(pic.bIRR) + " ISR=" + str.toHexByte(pic.bISR);
this.dbg.message(sDump);
}
}
};
/**
* dumpTimer()
*
* @this {ChipSet}
*/
ChipSet.prototype.dumpTimer = function()
{
if (DEBUGGER) {
for (var iTimer = 0; iTimer < this.aTimers.length; iTimer++) {
this.updateTimer(iTimer);
var timer = this.aTimers[iTimer];
var sDump = "TIMER" + iTimer + ":";
var count = 0;
if (timer.countBytes !== undefined) {
for (var i = 0; i <= timer.countBytes; i++) {
count |= (timer.countCurrent[i] << (i * 8));
}
}
sDump += " MODE" + timer.mode + " BYTES=" + timer.countBytes + " COUNT=" + str.toHexWord(count);
this.dbg.message(sDump);
}
}
};
/**
* dumpCMOS()
*
* @this {ChipSet}
*/
ChipSet.prototype.dumpCMOS = function()
{
if (DEBUGGER) {
var sDump = "";
for (var iCMOS = 0; iCMOS < ChipSet.CMOS.ADDR.TOTAL; iCMOS++) {
var b = (iCMOS <= ChipSet.CMOS.ADDR.RTC_STATUSD? this.getRTCByte(iCMOS) : this.abCMOSData[iCMOS]);
if (sDump) sDump += '\n';
sDump += "CMOS[0x" + str.toHexByte(iCMOS) + "]: 0x" + str.toHexByte(b);
}
this.dbg.message(sDump);
}
};
/**
* inDMAChannelAddr(iDMAC, iChannel, port, addrFrom)
*
* @this {ChipSet}
* @param {number} iDMAC
* @param {number} iChannel
* @param {number} port
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inDMAChannelAddr = function(iDMAC, iChannel, port, addrFrom)
{
var controller = this.aDMACs[iDMAC];
var channel = controller.aChannels[iChannel];
var b = channel.addrCurrent[controller.bIndex];
controller.bIndex ^= 0x1;
this.messagePort(port, null, addrFrom, "DMA" + iDMAC + ".CHANNEL" + iChannel + ".ADDR[" + controller.bIndex + "]", Debugger.MESSAGE_DMA, b);
return b;
};
/**
* outDMAChannelAddr(iDMAC, iChannel, port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} iDMAC
* @param {number} iChannel
* @param {number} port
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outDMAChannelAddr = function outDMAChannelAddr(iDMAC, iChannel, port, bOut, addrFrom)
{
var controller = this.aDMACs[iDMAC];
this.messagePort(port, bOut, addrFrom, "DMA" + iDMAC + ".CHANNEL" + iChannel + ".ADDR[" + controller.bIndex + "]", Debugger.MESSAGE_DMA);
var channel = controller.aChannels[iChannel];
channel.addrCurrent[controller.bIndex] = channel.addrInit[controller.bIndex] = bOut;
controller.bIndex ^= 0x1;
};
/**
* inDMAChannelCount(iDMAC, iChannel, port, addrFrom)
*
* @this {ChipSet}
* @param {number} iDMAC
* @param {number} iChannel
* @param {number} port
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inDMAChannelCount = function(iDMAC, iChannel, port, addrFrom)
{
var controller = this.aDMACs[iDMAC];
var channel = controller.aChannels[iChannel];
var b = channel.countCurrent[controller.bIndex];
controller.bIndex ^= 0x1;
this.messagePort(port, null, addrFrom, "DMA" + iDMAC + ".CHANNEL" + iChannel + ".COUNT[" + controller.bIndex + "]", Debugger.MESSAGE_DMA, b);
return b;
};
/**
* outDMAChannelCount(iDMAC, iChannel, port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} iDMAC
* @param {number} iChannel (ports 0x01, 0x03, 0x05, 0x07)
* @param {number} port
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outDMAChannelCount = function(iDMAC, iChannel, port, bOut, addrFrom)
{
var controller = this.aDMACs[iDMAC];
this.messagePort(port, bOut, addrFrom, "DMA" + iDMAC + ".CHANNEL" + iChannel + ".COUNT[" + controller.bIndex + "]", Debugger.MESSAGE_DMA);
var channel = controller.aChannels[iChannel];
channel.countCurrent[controller.bIndex] = channel.countInit[controller.bIndex] = bOut;
controller.bIndex ^= 0x1;
};
/**
* inDMAStatus(iDMAC, port, addrFrom)
*
* @this {ChipSet}
* @param {number} iDMAC
* @param {number} port
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*
* From the 8237A spec:
*
* "The Status register is available to be read out of the 8237A by the microprocessor.
* It contains information about the status of the devices at this point. This information includes
* which channels have reached a terminal count and which channels have pending DMA requests.
*
* Bits 0–3 are set every time a TC is reached by that channel or an external EOP is applied.
* These bits are cleared upon Reset and on each Status Read.
*
* Bits 4–7 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
* 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)
* 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.
*/
var controller = this.aDMACs[iDMAC];
var b = controller.bStatus | 0x1;
controller.bStatus &= ~0xf;
this.messagePort(port, null, addrFrom, "DMA" + iDMAC + ".STATUS", Debugger.MESSAGE_DMA, b);
return b;
};
/**
* outDMACmd(iDMAC, port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} iDMAC
* @param {number} port
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outDMACmd = function(iDMAC, port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "DMA" + iDMAC + ".CMD", Debugger.MESSAGE_DMA);
this.aDMACs[iDMAC].bCmd = bOut;
};
/**
* outDMAReq(iDMAC, port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} iDMAC
* @param {number} port
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*
* From the 8237A spec:
*
* "The 8237A can respond to requests for DMA service which are initiated by software as well as by a DREQ.
* Each channel has a request bit associated with it in the 4-bit Request register. These are non-maskable and subject
* to prioritization by the Priority Encoder network. Each register bit is set or reset separately under software
* control or is cleared upon generation of a TC or external EOP. The entire register is cleared by a Reset.
*
* To set or reset a bit the software loads the proper form of the data word.... In order to make a software request,
* the channel must be in Block Mode."
*/
ChipSet.prototype.outDMAReq = function(iDMAC, port, bOut, addrFrom)
{
var controller = this.aDMACs[iDMAC];
this.messagePort(port, bOut, addrFrom, "DMA" + iDMAC + ".REQ", Debugger.MESSAGE_DMA);
/*
* Bits 0-1 contain the channel number
*/
var iChannel = (bOut & 0x3);
/*
* Bit 2 is the request bit (0 to reset, 1 to set), which must be propagated to the corresponding bit (4-7) in the status register
*/
var iChannelBit = ((bOut & 0x4) << (iChannel + 2));
controller.bStatus = (controller.bStatus & ~(0x10 << iChannel)) | iChannelBit;
controller.bReq = bOut;
};
/**
* outDMAMask(iDMAC, port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} iDMAC
* @param {number} port
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outDMAMask = function(iDMAC, port, bOut, addrFrom)
{
var controller = this.aDMACs[iDMAC];
this.messagePort(port, bOut, addrFrom, "DMA" + iDMAC + ".MASK", Debugger.MESSAGE_DMA);
var iChannel = bOut & ChipSet.DMA_MASK.CHANNEL;
var channel = controller.aChannels[iChannel];
channel.masked = (bOut & ChipSet.DMA_MASK.CHANNEL_SET? true : false);
if (!channel.masked) this.requestDMA(controller.nChannelBase + iChannel);
};
/**
* outDMAMode(iDMAC, port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} iDMAC
* @param {number} port
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outDMAMode = function(iDMAC, port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "DMA" + iDMAC + ".MODE", Debugger.MESSAGE_DMA);
var iChannel = bOut & ChipSet.DMA_MODE.CHANNEL;
this.aDMACs[iDMAC].aChannels[iChannel].mode = bOut;
};
/**
* outDMAIndex(iDMAC, port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} iDMAC
* @param {number} port
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*
* Any write to this port simply resets the controller's "first/last flip-flop", which determines whether
* the even or odd byte of a DMA address or count register will be accessed next.
*/
ChipSet.prototype.outDMAIndex = function(iDMAC, port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "DMA" + iDMAC + ".INDEX", Debugger.MESSAGE_DMA);
this.aDMACs[iDMAC].bIndex = 0;
};
/**
* outDMAClear(iDMAC, port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} iDMAC
* @param {number} port
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outDMAClear = function(iDMAC, port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "DMA" + iDMAC + ".CLEAR", Debugger.MESSAGE_DMA);
/*
* The value written to this port doesn't matter; any write triggers a "master clear" operation
*/
var controller = this.aDMACs[iDMAC];
for (var i = 0; i < controller.aChannels.length; i++) {
this.initDMAChannel(controller, i);
}
};
/**
* inDMAPageReg(iDMAC, iChannel, port, addrFrom)
*
* @this {ChipSet}
* @param {number} iDMAC
* @param {number} iChannel
* @param {number} port
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inDMAPageReg = function(iDMAC, iChannel, port, addrFrom)
{
var bIn = this.aDMACs[iDMAC].aChannels[iChannel].bPage;
this.messagePort(port, null, addrFrom, "DMA" + iDMAC + ".CHANNEL" + iChannel + ".PAGE", Debugger.MESSAGE_DMA, bIn);
return bIn;
};
/**
* outDMAPageReg(iDMAC, iChannel, port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} iDMAC
* @param {number} iChannel
* @param {number} port
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outDMAPageReg = function(iDMAC, iChannel, port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "DMA" + iDMAC + ".CHANNEL" + iChannel + ".PAGE", Debugger.MESSAGE_DMA);
this.aDMACs[iDMAC].aChannels[iChannel].bPage = bOut;
};
/**
* inDMAPageSpare(iSpare, port, addrFrom)
*
* @this {ChipSet}
* @param {number} iSpare
* @param {number} port
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inDMAPageSpare = function(iSpare, port, addrFrom)
{
var bIn = this.abDMAPageSpare[iSpare];
this.messagePort(port, null, addrFrom, "DMA.SPARE" + iSpare + ".PAGE", Debugger.MESSAGE_DMA, bIn);
return bIn;
};
/**
* outDMAPageSpare(iSpare, port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} iSpare
* @param {number} port
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outDMAPageSpare = function(iSpare, port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "DMA.SPARE" + iSpare + ".PAGE", Debugger.MESSAGE_DMA);
this.abDMAPageSpare[iSpare] = bOut;
};
/**
* checkDMA()
*
* Called by the CPU whenever INTR.DMA is set.
*
* @return {boolean} true if one or more async DMA channels are still active (unmasked), false to reset INTR.DMA
*/
ChipSet.prototype.checkDMA = function()
{
var fActive = false;
for (var iDMAC = 0; iDMAC < this.aDMACs; iDMAC++) {
var controller = this.aDMACs[iDMAC];
for (var iChannel = 0; iChannel < controller.aChannels.length; iChannel++) {
var channel = controller.aChannels[iChannel];
if (!channel.masked) {
this.advanceDMA(channel);
if (!channel.masked) fActive = true;
}
}
}
return fActive;
};
/**
* connectDMA(iDMAChannel, component, sFunction, obj)
*
* @param {number} iDMAChannel
* @param {Component|string} component
* @param {string} sFunction
* @param {Object} obj (eg, when the HDC connects, it passes a drive object)
*/
ChipSet.prototype.connectDMA = function(iDMAChannel, component, sFunction, obj)
{
var iDMAC = iDMAChannel >> 2;
var controller = this.aDMACs[iDMAC];
var iChannel = iDMAChannel & 0x3;
var channel = controller.aChannels[iChannel];
this.initDMAFunction(channel, component, sFunction, obj);
};
/**
* requestDMA(iDMAChannel, done)
*
* @this {ChipSet}
* @param {number} iDMAChannel
* @param {function(boolean)} [done]
*
* For DMA_MODE_XFER_WRITE transfers, fnTransfer(-1) must return bytes as long as we request them (although it may
* return -1 if it runs out of bytes prematurely).
*
* Similarly, for DMA_MODE_XFER_READ transfers, fnTransfer(b) must accept bytes as long as we deliver them (although
* it is certainly free to ignore bytes it no longer wants).
*/
ChipSet.prototype.requestDMA = function(iDMAChannel, done)
{
var iDMAC = iDMAChannel >> 2;
var controller = this.aDMACs[iDMAC];
var iChannel = iDMAChannel & 0x3;
var channel = controller.aChannels[iChannel];
if (!channel.component || !channel.fnTransfer || !channel.obj) {
if (DEBUG) this.messageDebugger("requestDMA(" + iDMAChannel + "): not connected to a component", Debugger.MESSAGE_DMA);
if (done) done(true);
return;
}
/*
* We can't simply slam done into channel.done; that would be fine if requestDMA() was called only by functions
* like HDC.doRead() and HDC.doWrite(), but we're also called whenever a DMA channel is unmasked, and in those cases,
* we need to preserve whatever handler may have been previously set.
*
* However, in an effort to ensure we don't end up with stale done handlers, connectDMA() will reset channel.done.
*/
if (done) channel.done = done;
if (channel.masked) {
if (DEBUG) this.messageDebugger("requestDMA(" + iDMAChannel + "): channel masked, request queued", Debugger.MESSAGE_DMA);
return;
}
/*
* Let's try to do async DMA without asking the CPU for help...
*
* this.cpu.setDMA(true);
*/
this.advanceDMA(channel, true);
};
/**
* advanceDMA(channel, fInit)
*
* @param {Object} channel
* @param {boolean} [fInit]
*/
ChipSet.prototype.advanceDMA = function(channel, fInit)
{
if (fInit) {
channel.count = (channel.countCurrent[1] << 8) | channel.countCurrent[0];
channel.xfer = (channel.mode & ChipSet.DMA_MODE.XFER);
channel.fWarning = channel.fError = false;
if (DEBUG && DEBUGGER) {
channel.cbDebug = channel.count + 1;
channel.sAddrDebug = (DEBUG && DEBUGGER? null : undefined);
}
}
/*
* To support async DMA without requiring help from the CPU (ie, without relying upon cpu.setDMA()), we require that
* the data transfer functions provide an fAsync parameter to their callbacks; fAsync must be true if the callback was
* truly asynchronous (ie, it had to wait for a remote I/O request to finish), or false if the data was already available
* and the callback was performed synchronously.
*
* Whenever a callback is issued asynchronously, we will immediately daisy-chain another pair of updateDMA()/advanceDMA()
* calls, which will either finish the DMA operation if no more remote I/O requests are required, or will queue up another
* I/O request, which will in turn trigger another async callback. Thus, the DMA request keeps itself going without
* requiring any special assistance from the CPU via setDMA().
*/
var obj = this;
var fAsyncRequest = false;
var controller = channel.controller;
var iDMAChannel = controller.nChannelBase + channel.iChannel;
while (true) {
if (channel.count >= 0) {
var b;
var addr = (channel.bPage << 16) | (channel.addrCurrent[1] << 8) | channel.addrCurrent[0];
if (DEBUG && DEBUGGER && channel.sAddrDebug === null) {
channel.sAddrDebug = str.toHex(addr >> 4, 4) + ":" + str.toHex(addr & 0xf, 4);
if (this.dbg && this.dbg.messageEnabled(Debugger.MESSAGE_DMA | (iDMAChannel == ChipSet.DMA_FDC? Debugger.MESSAGE_FDC : (iDMAChannel == ChipSet.DMA_HDC? Debugger.MESSAGE_HDC : Debugger.MESSAGE_LOG))) && channel.xfer != ChipSet.DMA_MODE.XFER_WRITE) {
this.dbg.message("advanceDMA(" + iDMAChannel + ") transferring " + channel.cbDebug + " bytes from " + channel.sAddrDebug);
this.dbg.doDump("db", channel.sAddrDebug, "l" + Math.floor((channel.cbDebug + 15) / 16));
}
}
if (channel.xfer == ChipSet.DMA_MODE.XFER_WRITE) {
fAsyncRequest = true;
(function advanceDMAWrite(addrCur) {
channel.fnTransfer.call(channel.component, channel.obj, -1, function onTransferDMA(b, fAsync) {
if (b < 0) {
if (!channel.fWarning) {
if (DEBUG) obj.messageDebugger("advanceDMA(" + iDMAChannel + ") ran out of data, assuming 0xff", Debugger.MESSAGE_DMA);
channel.fWarning = true;
}
/*
* TODO: Determine whether to abort, as we do for DMA_MODE_XFER_READ.
*/
b = 0xff;
}
if (!channel.masked) {
/*
* While it makes sense to call bus.setByteDirect(), since DMA deals with physical memory,
* we lose the ability to trap accesses with write breakpoints by not using obj.cpu.setByte().
*
* TODO: Consider providing a Bus memory interface that honors write breakpoints.
*/
obj.bus.setByteDirect(addrCur, b);
}
fAsyncRequest = fAsync;
if (fAsync) {
setTimeout(function() {
if (!obj.updateDMA(channel)) {
obj.advanceDMA(channel);
}
}, 0);
}
});
}(addr));
}
else if (channel.xfer == ChipSet.DMA_MODE.XFER_READ) {
/*
* While it makes sense to call bus.getByteDirect(), since DMA deals with physical memory,
* we lose the ability to trap accesses with read breakpoints by not using obj.cpu.getByte().
*
* TODO: Determine whether we should support async dmaWrite() functions (currently not required),
* and consider providing a Bus memory interface that honors read breakpoints.
*/
b = obj.bus.getByteDirect(addr);
if (channel.fnTransfer.call(channel.component, channel.obj, b) < 0) {
/*
* In this case, I think I have no choice but to terminate the DMA operation in response to a failure,
* because the ROM BIOS FDC.REG_DATA.CMD.FORMAT_TRACK command specifies a count that is MUCH too large (a side-effect
* of the ROM BIOS using the same "DMA_SETUP" code for reads, writes AND formats).
*/
channel.fError = true;
}
}
else {
if (DEBUG) this.messageDebugger("advanceDMA(" + iDMAChannel + ") unsupported xfer mode: " + str.toHexWord(channel.xfer), Debugger.MESSAGE_DMA);
channel.fError = true;
}
}
if (fAsyncRequest || this.updateDMA(channel)) break;
}
};
/**
* updateDMA(channel)
*
* @param {Object} channel
* @return {boolean} true if DMA operation complete, false if not
*/
ChipSet.prototype.updateDMA = function(channel)
{
if (!channel.fError && --channel.count >= 0) {
if (channel.mode & ChipSet.DMA_MODE.DECREMENT) {
channel.addrCurrent[0]--;
if (channel.addrCurrent[0] < 0) {
channel.addrCurrent[0] = 0xff;
channel.addrCurrent[1]--;
if (channel.addrCurrent[1] < 0) channel.addrCurrent[1] = 0xff;
}
} else {
channel.addrCurrent[0]++;
if (channel.addrCurrent[0] > 0xff) {
channel.addrCurrent[0] = 0x00;
channel.addrCurrent[1]++;
if (channel.addrCurrent[1] > 0xff) channel.addrCurrent[1] = 0x00;
}
}
/*
* In situations where an HDC DMA operation took too long, the Fixed Disk BIOS would give up, but the DMA operation would continue.
*
* TODO: Verify that the Fixed Disk BIOS shuts down (ie, re-masks) a DMA channel for failed requests, and that this handles those failures.
*/
if (!channel.masked) return false;
}
var controller = channel.controller;
var iDMAChannel = controller.nChannelBase + channel.iChannel;
controller.bStatus = (controller.bStatus & ~(0x10 << channel.iChannel)) | (0x1 << channel.iChannel);
/*
* EOP is supposed to automatically (re)mask the channel, unless it's set for auto-initialize.
*/
if (!(channel.mode & ChipSet.DMA_MODE.AUTOINIT)) {
channel.masked = true;
channel.component = channel.obj = null;
}
if (DEBUG && DEBUGGER && this.dbg && this.dbg.messageEnabled(Debugger.MESSAGE_DMA | (iDMAChannel == ChipSet.DMA_FDC? Debugger.MESSAGE_FDC : (iDMAChannel == ChipSet.DMA_HDC? Debugger.MESSAGE_HDC : Debugger.MESSAGE_LOG))) && channel.xfer == ChipSet.DMA_MODE.XFER_WRITE && channel.sAddrDebug) {
this.dbg.message("updateDMA(" + iDMAChannel + ") transferred " + channel.cbDebug + " bytes to " + channel.sAddrDebug);
this.dbg.doDump("db", channel.sAddrDebug, "l" + Math.floor((channel.cbDebug + 15) / 16));
}
if (channel.done) {
channel.done(!channel.fError);
channel.done = null;
}
/*
* While it might make sense to call cpu.setDMA() here, it's simpler to let the CPU issue one more call
* to chipset.checkDMA() and let the CPU update INTR.DMA on its own, based on the return value from checkDMA().
*/
return true;
};
/**
* inPICLo(iPIC, addrFrom)
*
* @this {ChipSet}
* @param {number} iPIC
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inPICLo = function(iPIC, addrFrom)
{
var b = 0;
var pic = this.aPICs[iPIC];
if (pic.bOCW3 !== undefined) {
var bReadReg = pic.bOCW3 & ChipSet.PIC_LO.OCW3_READ_CMD;
switch (bReadReg) {
case ChipSet.PIC_LO.OCW3_READ_IRR:
b = pic.bIRR;
break;
case ChipSet.PIC_LO.OCW3_READ_ISR:
b = pic.bISR;
break;
default:
break;
}
}
this.messagePort(pic.port, null, addrFrom, "PIC" + iPIC, Debugger.MESSAGE_PIC, b);
return b;
};
/**
* outPICLo(iPIC, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} iPIC
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outPICLo = function(iPIC, bOut, addrFrom)
{
var pic = this.aPICs[iPIC];
this.messagePort(pic.port, bOut, addrFrom, "PIC" + iPIC, Debugger.MESSAGE_PIC);
if (bOut & ChipSet.PIC_LO.ICW1) {
/*
* This must be an ICW1...
*/
pic.nICW = 0;
pic.aICW[pic.nICW++] = bOut;
/*
* I used to do the rest of this initialization in outPICHi(), once all the ICW commands had been received,
* but a closer reading of the 8259A spec indicates that that should happen now, on receipt on ICW1.
*
* Also, on p.10 of that spec, it says "The Interrupt Mask Register is cleared". I originally took that to
* mean that all interrupts were masked, but based on what MS-DOS 4.0M expects to happen after this code runs:
*
* 0070:44C6 B013 MOV AL,13
* 0070:44C8 E620 OUT 20,AL
* 0070:44CA B050 MOV AL,50
* 0070:44CC E621 OUT 21,AL
* 0070:44CE B009 MOV AL,09
* 0070:44D0 E621 OUT 21,AL
*
* (ie, it expects its next call to INT 0x13 will still generate an interrupt), I've decided the spec
* must be read literally, meaning that all IMR bits must be zeroed. Unmasking all possible interrupts by
* default seems unwise to me, but who am I to judge....
*/
pic.bIMR = 0x00;
pic.bIRLow = 7;
/*
* TODO: I'm also zeroing both IRR and ISR, even though that's not actually mentioned as part of the ICW
* sequence, because they need to be (re)initialized at some point. However, if some component is currently
* requesting an interrupt, what should I do about that? Originally, I had decided to clear them ONLY if they
* were still undefined, but that change appeared to break the ROM BIOS handling of CTRL-ALT-DEL, so I'm back
* to unconditionally zeroing them.
*/
pic.bIRR = pic.bISR = 0;
/*
* The spec also says that "Special Mask Mode is cleared and Status Read is set to IRR". I attempt to insure
* the latter, but as for special mask mode... well, that mode isn't supported yet.
*/
pic.bOCW3 = ChipSet.PIC_LO.OCW3 | ChipSet.PIC_LO.OCW3_READ_IRR;
}
else if (!(bOut & ChipSet.PIC_LO.OCW3)) {
/*
* This must be an OCW2...
*/
var bOCW2 = bOut & ChipSet.PIC_LO.OCW2_OP_MASK;
if (bOCW2 & ChipSet.PIC_LO.OCW2_EOI) {
/*
* This OCW2 must be an EOI command...
*/
var nIRL, bIREnd = 0;
if ((bOCW2 & ChipSet.PIC_LO.OCW2_EOI_SPEC) == ChipSet.PIC_LO.OCW2_EOI_SPEC) {
/*
* More "specifically", a specific EOI command...
*/
nIRL = bOut & ChipSet.PIC_LO.OCW2_IR_LVL;
bIREnd = 1 << nIRL;
} else {
/*
* Less "specifically", a non-specific EOI command. The search for the highest priority in-service
* interrupt must start with whichever interrupt is opposite the lowest priority interrupt (normally 7,
* but technically whatever bIRLow is currently set to). For example:
*
* If bIRLow is 7, then the priority order is: 0, 1, 2, 3, 4, 5, 6, 7.
* If bIRLow is 6, then the priority order is: 7, 0, 1, 2, 3, 4, 5, 6.
* If bIRLow is 5, then the priority order is: 6, 7, 0, 1, 2, 3, 4, 5.
* etc.
*/
nIRL = pic.bIRLow + 1;
while (true) {
nIRL &= 0x7;
var bIR = 1 << nIRL;
if (pic.bISR & bIR) {
bIREnd = bIR;
break;
}
if (nIRL++ == pic.bIRLow) break;
}
if (DEBUG && !bIREnd) nIRL = null; // for unexpected non-specific EOI commands, there's no IRQ to report
}
var nIRQ = (nIRL == null? undefined : pic.nIRQBase + nIRL);
if (pic.bISR & bIREnd) {
if (DEBUG) this.messageDebugger("outPIC" + iPIC + "(" + str.toHexByte(pic.port) + "): IRQ " + nIRQ + " going out of service", Debugger.MESSAGE_PIC, nIRQ);
pic.bISR &= ~bIREnd;
this.checkIRR(iPIC);
} else {
if (DEBUG) this.messageDebugger("outPIC" + iPIC + "(" + str.toHexByte(pic.port) + "): unexpected EOI command, IRQ " + nIRQ + " not in service", Debugger.MESSAGE_PIC);
}
/*
* TODO: Support EOI commands with automatic rotation (eg, ChipSet.PIC_LO.OCW2_EOI_ROT and ChipSet.PIC_LO.OCW2_EOI_ROTSPEC)
*/
}
else if (bOCW2 == ChipSet.PIC_LO.OCW2_SET_PRI) {
/*
* This OCW2 changes the lowest priority interrupt to the specified level (the default is 7)
*/
pic.bIRLow = bOut & ChipSet.PIC_LO.OCW2_IR_LVL;
}
else {
/*
* TODO: Remaining commands to support: ChipSet.PIC_LO.OCW2_SET_ROTAUTO and ChipSet.PIC_LO.OCW2_CLR_ROTAUTO
*/
if (DEBUG) this.messageDebugger("outPIC" + iPIC + "(" + str.toHexByte(pic.port) + "): unsupported OCW2 command: " + str.toHexByte(bOut), Debugger.MESSAGE_PIC);
}
} else {
/*
* This must be an OCW3 request. If it's a "Read Register" command (PIC_LO.OCW3_READ_CMD), inPICLo() will take care it.
*
* TODO: If OCW3 specified a "Poll" command (PIC_LO.OCW3_POLL_CMD) or a "Special Mask Mode" command (PIC_LO.OCW3_SMM_CMD),
* that's unfortunate, because I don't support them yet.
*/
if (bOut & (ChipSet.PIC_LO.OCW3_POLL_CMD | ChipSet.PIC_LO.OCW3_SMM_CMD)) {
if (DEBUG) this.messageDebugger("outPIC" + iPIC + "(" + str.toHexByte(pic.port) + "): unsupported OCW3 command: " + str.toHexByte(bOut), Debugger.MESSAGE_PIC);
}
pic.bOCW3 = bOut;
}
};
/**
* inPICHi(iPIC, addrFrom)
*
* @this {ChipSet}
* @param {number} iPIC
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inPICHi = function(iPIC, addrFrom)
{
var pic = this.aPICs[iPIC];
var b = pic.bIMR;
this.messagePort(pic.port+1, null, addrFrom, "PIC" + iPIC, Debugger.MESSAGE_PIC, b);
return b;
};
/**
* outPICHi(iPIC, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} iPIC
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outPICHi = function(iPIC, bOut, addrFrom)
{
var pic = this.aPICs[iPIC];
this.messagePort(pic.port+1, bOut, addrFrom, "PIC" + iPIC, Debugger.MESSAGE_PIC);
if (pic.nICW < pic.aICW.length) {
pic.aICW[pic.nICW++] = bOut;
if (pic.nICW == 2 && (pic.aICW[0] & ChipSet.PIC_LO.ICW1_SNGL))
pic.nICW++;
if (pic.nICW == 3 && !(pic.aICW[0] & ChipSet.PIC_LO.ICW1_ICW4))
pic.nICW++;
}
else {
/*
* We have all our ICW "words" (ie, bytes), so this must be an OCW1 write (which is simply an IMR write)
*/
pic.bIMR = bOut;
/*
* See the CPU's delayINTR() function for an explanation of why this explicit delay is necessary.
*/
this.cpu.delayINTR();
/*
* Alas, we need a longer delay for the MODEL_5170's "KBD_RESET" function (F000:17D2), which must drop
* into a loop and decrement CX at least once after unmasking the KBD IRQ. The "KBD_RESET" function on
* previous models could be handled with a 4-instruction delay provided by the Keyboard.resetDevice() call
* to setIRR(), but the MODEL_5170 needs a roughly 6-instruction delay after it unmasks the KBD IRQ.
*/
this.checkIRR(iPIC, !iPIC && bOut == 0xFD? 6 : 0);
}
};
/**
* checkIRR(iPIC, nDelay)
*
* @this {ChipSet}
* @param {number} iPIC
* @param {number} [nDelay] is an optional number of instructions to delay acknowledgment of the IRQ (see getIRRVector)
*/
ChipSet.prototype.checkIRR = function(iPIC, nDelay)
{
/*
* Look for any IRR bits that aren't masked and aren't already in service
*/
var pic = this.aPICs[iPIC];
var bIR = ((pic.bISR | pic.bIMR) ^ 0xff) & pic.bIRR;
this.cpu.updateINTR(!!bIR);
if (bIR && nDelay) pic.nDelay = nDelay;
};
/**
* setIRR(nIRQ, nDelay)
*
* @this {ChipSet}
* @param {number} nIRQ (IRQ 0-7 implies iPIC 0, and IRQ 8-15 implies iPIC 1)
* @param {number} [nDelay] is an optional number of instructions to delay acknowledgment of the IRQ (see getIRRVector)
*/
ChipSet.prototype.setIRR = function(nIRQ, nDelay)
{
var iPIC = nIRQ >> 3;
var nIRL = nIRQ & 0x7;
var pic = this.aPICs[iPIC];
pic.bIRR |= 1 << nIRL;
if (DEBUG) this.messageDebugger("setIRR(" + nIRQ + ")", Debugger.MESSAGE_PIC, nIRQ);
pic.nDelay = nDelay || 0;
/*
* When any slave IRR goes high, I'm assuming that the master's slave IRR line should go high as well
*/
if (iPIC == 1) this.aPICs[0].bIRR |= 0x4;
this.checkIRR(iPIC);
};
/**
* clearIRR(nIRQ)
*
* @this {ChipSet}
* @param {number} nIRQ (IRQ 0-7 implies iPIC 0, which is all we currently support anyway)
*/
ChipSet.prototype.clearIRR = function(nIRQ)
{
var iPIC = nIRQ >> 3;
var nIRL = nIRQ & 0x7;
var pic = this.aPICs[iPIC];
var bIRR = (1 << nIRL);
if (pic.bIRR & bIRR) {
pic.bIRR &= ~bIRR;
if (DEBUG) this.messageDebugger("clearIRR(" + nIRQ + ")", Debugger.MESSAGE_PIC, nIRQ);
/*
* When all slave IRRs go low, I'm assuming that the master's slave IRR line should go low as well
*/
if (iPIC == 1 && !pic.bIRR) this.aPICs[0].bIRR &= ~0x4;
/*
* NOTE: I don't think calling checkIRR(), and by extension, cpu.updateINTR(false), is strictly necessary,
* because when the CPU gets around to acknowledging the INTR signal, it still has to call getIRRVector(), which
* will inform the CPU that there are no longer any requested interrupts. However, some small efficiency may be
* gained by clearing INTR sooner rather than later. So that's what we'll do.
*/
this.checkIRR(iPIC);
}
};
/**
* checkIMR(nIRQ)
*
* @this {ChipSet}
* @param {number} nIRQ
* @return {boolean} true if the specified IRQ is masked, false if not
*/
ChipSet.prototype.checkIMR = function(nIRQ)
{
var iPIC = nIRQ >> 3;
var nIRL = nIRQ & 0x7;
var pic = this.aPICs[iPIC];
return (pic.bIMR & (0x1 << nIRL))? true : false;
};
/**
* getIRRVector()
*
* getIRRVector() is called by the CPU whenever PS_IF is set and OP_NOINTR is clear. Ordinarily, an immediate
* response would seem perfectly reasonable, but unfortunately, there are places in the original ROM BIOS like
* "KBD_RESET" (F000:E688) that enable interrupts but still expect nothing to happen for several more instructions.
*
* So, in addition to the two normal responses (an IDT vector #, or -1 indicating no pending interrupts), we must
* support a third response (-2) that basically means: don't change the CPU interrupt state, just keep calling until
* we return one of the first two responses. The number of times we delay our normal response is determined by the
* component that originally called setIRR with an optional delay parameter.
*
* @this {ChipSet}
* @param {number} [iPIC]
* @return {number} IDT vector # of the next highest-priority interrupt, -1 if none, or -2 for "please try your call again later"
*/
ChipSet.prototype.getIRRVector = function(iPIC)
{
if (iPIC === undefined) iPIC = 0;
/*
* Look for any IRR bits that aren't masked and aren't already in service...
*/
var nIDT = -1;
var pic = this.aPICs[iPIC];
if (!pic.nDelay) {
var bIR = pic.bIRR & ((pic.bISR | pic.bIMR) ^ 0xff);
/*
* The search for the next highest priority requested interrupt (that's also not in-service and not masked)
* must start with whichever interrupt is opposite the lowest priority interrupt (normally 7, but technically
* whatever bIRLow is currently set to). For example:
*
* If bIRLow is 7, then the priority order is: 0, 1, 2, 3, 4, 5, 6, 7.
* If bIRLow is 6, then the priority order is: 7, 0, 1, 2, 3, 4, 5, 6.
* If bIRLow is 5, then the priority order is: 6, 7, 0, 1, 2, 3, 4, 5.
* etc.
*
* This process is similar to the search performed by non-specific EOIs, except those apply only to a single
* PIC (which is why a slave interrupt must be EOI'ed twice: once for the slave PIC and again for the master),
* whereas here we must search across all PICs.
*/
var nIRL = pic.bIRLow + 1;
while (true) {
nIRL &= 0x7;
var bIRNext = 1 << nIRL;
if (bIR & bIRNext) {
if (!iPIC && nIRL == 2) {
/*
* Slave interrupts are tied to the master PIC on IRQ2; query the slave PIC for the vector #
*/
nIDT = this.getIRRVector(1);
} else {
/*
* Get the starting IDT vector # from ICW2 and add the IR level to obtain the target IDT vector #
*/
nIDT = pic.aICW[1] + nIRL;
}
if (nIDT >= 0) {
pic.bISR |= bIRNext;
pic.bIRR &= ~bIRNext;
}
var nIRQ = pic.nIRQBase + nIRL;
if (DEBUG) this.messageDebugger("getIRRVector(): IRQ " + nIRQ + " going into service", Debugger.MESSAGE_PIC, nIRQ);
if (MAXDEBUG && DEBUGGER) {
this.acInterrupts[nIRQ]++;
}
break;
}
if (nIRL++ == pic.bIRLow) break;
}
} else {
nIDT = -2;
pic.nDelay--;
}
return nIDT;
};
/**
* inTimer(iTimer, addrFrom)
*
* @this {ChipSet}
* @param {number} iTimer (ports 0x40, 0x41, 0x42)
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inTimer = function(iTimer, addrFrom)
{
var b;
var timer = this.aTimers[iTimer];
if (timer.countIndex == timer.countBytes) this.resetTimerIndex(iTimer);
if (timer.fLatched) {
return timer.countLatched[timer.countIndex++];
}
this.updateTimer(iTimer);
b = timer.countCurrent[timer.countIndex++];
this.messagePort(ChipSet.TIMER0.PORT + iTimer, null, addrFrom, "TIMER" + iTimer, Debugger.MESSAGE_TIMER, b);
return b;
};
/**
* outTimer(iTimer, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} iTimer (ports 0x40, 0x41, 0x42)
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outTimer = function(iTimer, bOut, addrFrom)
{
this.messagePort(ChipSet.TIMER0.PORT + iTimer, bOut, addrFrom, "TIMER" + iTimer, Debugger.MESSAGE_TIMER);
var timer = this.aTimers[iTimer];
if (timer.countIndex == timer.countBytes) this.resetTimerIndex(iTimer);
timer.countInit[timer.countIndex++] = bOut;
if (timer.countIndex == timer.countBytes) {
/*
* In general, writing a new count to a timer that's already counting isn't supposed to affect the current
* count, with the notable exceptions of MODE0 and MODE4.
*/
if (!timer.fCounting || timer.mode == ChipSet.TIMER_CTRL.MODE0 || timer.mode == ChipSet.TIMER_CTRL.MODE4) {
timer.fLatched = false;
timer.countCurrent[0] = timer.countStart[0] = timer.countInit[0];
timer.countCurrent[1] = timer.countStart[1] = timer.countInit[1];
timer.nStartCycles = this.cpu.getCycles(this.fScaleTimers);
timer.fCounting = true;
/*
* I believe MODE0 is the only mode where "OUT" (fOUT) starts out "low" (false); for the rest of the modes,
* "OUT" (fOUT) starts "high" (true). It's also my understanding that the way edge-triggered interrupts work
* on the original PC is that an interrupt is requested only when the corresponding "OUT" transitions from
* "low" to "high".
*/
timer.fOUT = (timer.mode != ChipSet.TIMER_CTRL.MODE0);
/*
* TODO: Determine if there are situations/modes where I should NOT automatically clear IRQ0 on behalf of TIMER0.
*/
if (iTimer == ChipSet.TIMER0.INDEX) this.clearIRR(ChipSet.IRQ.TIMER0);
}
if (iTimer == ChipSet.TIMER2.INDEX) {
this.setSpeaker();
}
/*
* 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 this seems to perform better (see "BASICA DONKEY.BAS").
*/
if (iTimer == ChipSet.TIMER0.INDEX) {
var countInit = this.getTimerInit(ChipSet.TIMER0.INDEX);
/*
* Prevent the divisor from becoming too large (and we of course want to avoid a divide-by-zero);
* we'll use the initial count that BASICA likes to program as a baseline.
*/
if (countInit >= 0x800) {
this.cpu.setBurstDivisor(Math.round(0x10000 / countInit));
}
}
if (iTimer == ChipSet.TIMER0.INDEX && timer.mode == ChipSet.TIMER_CTRL.MODE0 && timer.rw == ChipSet.TIMER_CTRL.RW_LSB) {
/*
* 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 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.
*
* 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.
*
* The MODEL_5170 BIOS performs a virtually identical test ("TEST.18"), although unsurprisingly, it uses an
* initial timer count that is explicitly twice that other of earlier models (0x16 * 2 = 0x2C). Fortunately,
* it still uses an LSB-only count; however, the original hack calculated the burst-cycle threshold using a
* hard-coded multiplier of 4, which is incorrect for MODEL_5170; the correct model-independent multiplier to
* use is nTicksDivisor.
*/
this.cpu.setBurstCycles(bOut * this.nTicksDivisor);
}
}
};
/**
* inTimerCtrl(port, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x43)
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number|null} simulated port value
*/
ChipSet.prototype.inTimerCtrl = function(port, addrFrom)
{
this.messagePort(port, null, addrFrom, "TIMER_CTRL", Debugger.MESSAGE_TIMER);
if (DEBUG) this.messageDebugger("TIMER_CTRL: Read-Back command not supported (yet)", Debugger.MESSAGE_TIMER);
return null;
};
/**
* outTimerCtrl(port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x43)
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outTimerCtrl = function(port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "TIMER_CTRL", Debugger.MESSAGE_TIMER);
this.bTimerCtrl = bOut;
/*
* Extract the SC (Select Counter) bits
*/
var iTimer = (bOut & ChipSet.TIMER_CTRL.SC) >> 6;
if (iTimer == 0x3) {
if (DEBUG) this.messageDebugger("TIMER_CTRL: Read-Back command not supported (yet)", Debugger.MESSAGE_TIMER);
return;
}
/*
* Extract the BCD, MODE, and RW bits, which we simply store as-is (see setTimerMode)
*/
var bcd = (bOut & ChipSet.TIMER_CTRL.BCD);
var mode = (bOut & ChipSet.TIMER_CTRL.MODE);
var rw = (bOut & ChipSet.TIMER_CTRL.RW);
if (!rw) {
this.latchTimer(iTimer);
} else {
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
* 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,
* 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.
*
* 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
* is written, fOUT (which is what drives IRQ0) goes low. So, by simply adding an appropriate clearIRR() call
* both here and in outTimer(), this annoying problem seems to be gone.
*
* TODO: Determine if there are situations/modes where I should NOT automatically clear IRQ0 on behalf of TIMER0.
*/
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
* 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
* after TIMER2 has been reprogrammed for the test in question (ie, when interrupts are masked and PPIB is set as
* shown below).
*
* FWIW, I believe the cassette hardware was discontinued after MODEL_5150, and even if the test fails, it's non-fatal;
* the ROM BIOS displays an error (131) and moves on.
*/
if (iTimer == ChipSet.TIMER2.INDEX) {
var pic = this.aPICs[0];
if (pic.bIMR == 0xff && this.bPPIB == (ChipSet.PPI_B.CLK_TIMER2 | ChipSet.PPI_B.ENABLE_SW2 | ChipSet.PPI_B.CASS_MOTOR_OFF | ChipSet.PPI_B.CLK_KBD)) {
var timer = this.aTimers[0];
timer.countStart[0] = timer.countInit[0];
timer.countStart[1] = timer.countInit[1];
timer.nStartCycles = this.cpu.getCycles(this.fScaleTimers);
if (DEBUG) this.messageDebugger("TIMER0 count reset @" + timer.nStartCycles + " cycles", Debugger.MESSAGE_TIMER);
}
}
}
};
/**
* getTimerInit(iTimer)
*
* @this {ChipSet}
* @param {number} iTimer
* @return {number} initial timer count
*/
ChipSet.prototype.getTimerInit = function(iTimer)
{
var timer = this.aTimers[iTimer];
var countInit = (timer.countInit[1] << 8) | timer.countInit[0];
if (!countInit) countInit = (timer.countBytes == 1? 0x100 : 0x10000);
return countInit;
};
/**
* getTimerStart(iTimer)
*
* @this {ChipSet}
* @param {number} iTimer
* @return {number} starting timer count (from the initial timer count for the current countdown)
*/
ChipSet.prototype.getTimerStart = function(iTimer)
{
var timer = this.aTimers[iTimer];
var countStart = (timer.countStart[1] << 8) | timer.countStart[0];
if (!countStart) countStart = (timer.countBytes == 1? 0x100 : 0x10000);
return countStart;
};
/**
* getTimerCycleLimit(iTimer)
*
* @this {ChipSet}
* @param {number} iTimer
* @return {number} number of cycles remaining for the specified timer, zero if no limit (or timer inactive)
*
ChipSet.prototype.getTimerCycleLimit = function(iTimer)
{
var timer = this.aTimers[iTimer];
return timer.fCounting? (this.getTimerStart(iTimer) * this.nTicksDivisor) : 0;
};
*/
/**
* latchTimer(iTimer)
*
* @this {ChipSet}
* @param {number} iTimer
*/
ChipSet.prototype.latchTimer = function(iTimer)
{
/*
* Update the timer's current count
*/
this.updateTimer(iTimer);
/*
* Now we can latch it
*/
var timer = this.aTimers[iTimer];
timer.countLatched[0] = timer.countCurrent[0];
timer.countLatched[1] = timer.countCurrent[1];
timer.fLatched = true;
/*
* VERIFY: That a latch request resets the timer index
*/
this.resetTimerIndex(iTimer);
};
/**
* setTimerMode(iTimer, bcd, mode, rw)
*
* FYI: After setting a timer's mode, the CPU must set the timer's count before it becomes operational;
* ie, before fCounting becomes true.
*
* @this {ChipSet}
* @param {number} iTimer
* @param {number} bcd
* @param {number} mode
* @param {number} rw
*/
ChipSet.prototype.setTimerMode = function(iTimer, bcd, mode, rw)
{
var timer = this.aTimers[iTimer];
timer.rw = rw;
timer.mode = mode;
timer.bcd = bcd;
timer.countInit = [0, 0];
timer.countCurrent = [0, 0];
timer.countLatched = [0, 0];
timer.fOUT = false;
timer.fLatched = false;
timer.fCounting = false;
this.resetTimerIndex(iTimer);
};
/**
* resetTimerIndex(iTimer)
*
* @this {ChipSet}
* @param {number} iTimer
*/
ChipSet.prototype.resetTimerIndex = function(iTimer)
{
var timer = this.aTimers[iTimer];
timer.countIndex = (timer.rw == ChipSet.TIMER_CTRL.RW_MSB? 1 : 0);
timer.countBytes = (timer.rw == ChipSet.TIMER_CTRL.RW_BOTH? 2 : 1);
};
/**
* updateTimer(iTimer, fCycleReset)
*
* updateTimer() calculates and updates a timer's current count purely on an "on-demand" basis; we don't actually
* adjust timer counters every 4 CPU cycles, since updating timers that frequently would be prohibitively slow. If
* you're single-stepping the CPU, then yes, updateTimer() will be called after every stepCPU(), via updateAllTimers(),
* but if we're doing our job correctly here, the frequency of calls to updateTimer() should not affect timer counts
* across otherwise identical runs.
*
* TODO: Implement support for all TIMER modes, and verify that all the modes currently implemented are "up to spec";
* they're close enough to make the ROM BIOS happy, but beyond that, I've done very little.
*
* @this {ChipSet}
* @param {number} iTimer
* 0: Time-of-Day interrupt (~18.2 interrupts/second)
* 1: DMA refresh
* 2: Sound/Cassette
* @param {boolean|undefined} [fCycleReset] is true if a cycle-count reset is about to occur
* @return {Object} timer
*/
ChipSet.prototype.updateTimer = function(iTimer, fCycleReset)
{
var timer = this.aTimers[iTimer];
/*
* Every timer's counting state is gated by its own fCounting flag; TIMER2 is further gated by PPI_B's
* CLK_TIMER2 bit.
*/
if (timer.fCounting && (iTimer != ChipSet.TIMER2.INDEX || (this.bPPIB & ChipSet.PPI_B.CLK_TIMER2))) {
/*
* We determine the current timer count based on how many instruction cycles have elapsed since we started
* the timer. Timers are supposed to be "ticking" at a rate of 1193181.8181 times per second, which is
* the system clock of 14.31818Mhz, divided by 12.
*
* Similarly, for an 8088, there are supposed to be 4.77Mhz instruction cycles per second, which comes from
* the system clock of 14.31818Mhz, divided by 3.
*
* If we divide 4,772,727 CPU cycles per second by 1,193,181 ticks per second, we get 4 cycles per tick,
* which agrees with the ratio of the clock divisors: 12 / 3 == 4.
*
* However, if getCycles() is being called with fScaleTimers == true AND the CPU is running faster than its
* base cycles-per-second setting, then getCycles() will divide the cycle count by the CPU's cycle multiplier,
* so that the timers fire with the same real-world frequency that the user expects. However, that will
* break any code (eg, the ROM BIOS diagnostics) that assumes that the timers are ticking once every 4 cycles
* (or more like every 5 cycles on a 6Mhz 80286).
*
* So, when using a machine with the ChipSet "scaletimers" property set, make sure you reset the machine's
* speed prior to rebooting, otherwise you're likely to see ROM BIOS errors. Ditto for any application code
* that makes similar assumptions about the relationship between CPU and timer speeds.
*
* In general, you're probably better off NOT using the "scaletimers" property, and simply allowing the timers
* to tick faster as you increase CPU speed (which is why fScaleTimers defaults to false).
*/
var nCycles = this.cpu.getCycles(this.fScaleTimers);
/*
* Instead of maintaining partial tick counts, we calculate a fresh countCurrent from countStart every
* time we're called, using the cycle count recorded when the timer was initialized. countStart is set
* to countInit when fCounting is first set, and then it is refreshed from countInit at the expiration of
* every count, so that if someone loaded a new countInit in the meantime (eg, BASICA), we'll pick it up.
*
* For the original MODEL_5170, the number of cycles per tick is approximately 6,000,000 / 1,193,181,
* or 5.028575, so we can no longer always divide cycles by 4 with a simple right-shift by 2. The proper
* divisor (eg, 4 for MODEL_5150 and MODEL_5160, 5 for MODEL_5170, etc) is nTicksDivisor, which initBus()
* calculates using the base CPU speed returned by cpu.getCyclesPerSecond().
*/
var ticks = ((nCycles - timer.nStartCycles) / this.nTicksDivisor) | 0;
if (ticks < 0) {
if (DEBUG) this.messageDebugger("updateTimer(" + iTimer + "): negative tick count (" + ticks + ")", Debugger.MESSAGE_TIMER);
timer.nStartCycles = nCycles;
ticks = 0;
}
var countInit = this.getTimerInit(iTimer);
var countStart = this.getTimerStart(iTimer);
var fFired = false;
var count = countStart - ticks;
/*
* NOTE: This mode is used by ROM BIOS test code that wants to verify timer interrupts are arriving
* neither too slowly nor too quickly. As a result, I've had to add some corresponding trickery
* in outTimer() to force interrupt simulation immediately after a low initial count (0x16) has been set.
*/
if (timer.mode == ChipSet.TIMER_CTRL.MODE0) {
if (count <= 0) count = 0;
if (DEBUG) this.messageDebugger("updateTimer(" + iTimer + "): MODE0 timer count=" + count, Debugger.MESSAGE_TIMER);
if (!count) {
timer.fOUT = true;
timer.fCounting = false;
if (!iTimer) {
fFired = true;
this.setIRR(ChipSet.IRQ.TIMER0);
if (MAXDEBUG && DEBUGGER) this.acTimersFired[iTimer]++;
}
}
}
/*
* 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,
* where TIMER1 is programmed for MODE2, LSB (the same settings, incidentally, used immediately afterward
* for TIMER1 in conjunction with DMA channel 0 memory refreshes).
*
* Now this mode generates interrupts. Note that "OUT" goes "low" when the count reaches 1, then "high"
* one tick later, at which point the count is reloaded and counting continues.
*
* Chances are, we will often miss the exact point at which the count becomes 1 (or more importantly, one
* tick later, when the count *would* become 0, since that's when "OUT" transitions from "low" to "high"),
* but as with MODE3, hopefully no one will mind.
*
* FYI, technically, it appears that the count is never supposed to reach 0, and that an initial count of 1
* is "illegal", whatever that means.
*/
else
if (timer.mode == ChipSet.TIMER_CTRL.MODE2) {
timer.fOUT = (count != 1); // yes, this line does seem rather pointless....
if (count <= 0) {
count = countInit + count;
if (count <= 0) {
// this.messageDebugger("updateTimer(" + iTimer + "): underflow=" + count, Debugger.MESSAGE_TIMER);
count = countInit;
}
timer.countStart[0] = count & 0xff;
timer.countStart[1] = count >> 8;
timer.nStartCycles = nCycles;
if (!iTimer && timer.fOUT) {
fFired = true;
this.setIRR(ChipSet.IRQ.TIMER0);
if (MAXDEBUG && DEBUGGER) this.acTimersFired[iTimer]++;
}
}
}
/*
* NOTE: This is the normal mode for TIMER0, which the ROM BIOS uses to generate h/w interrupts roughly
* 18.2 times per second. In this mode, the count must be decremented twice as fast (hence the extra ticks
* subtraction below, in addition to the subtraction above), but IRQ_TIMER0 is raised only on alternate
* iterations; ie, only when fOUT transitions to true ("high"). The equal alternating fOUT states is why
* this mode is referred to as "square wave" mode.
*
* TODO: Implement the correct behavior for this mode when the count is ODD. In that case, fOUT is supposed
* to be "high" for (N + 1) / 2 ticks and "low" for (N - 1) / 2 ticks.
*/
else
if (timer.mode == ChipSet.TIMER_CTRL.MODE3) {
count -= ticks;
if (count <= 0) {
timer.fOUT = !timer.fOUT;
count = countInit + count;
if (count <= 0) {
// this.messageDebugger("updateTimer(" + iTimer + "): underflow=" + count, Debugger.MESSAGE_TIMER);
count = countInit;
}
if (MAXDEBUG && DEBUGGER && !iTimer) {
var nCycleDelta = 0;
if (this.acTimer0Counts.length > 0)
nCycleDelta = nCycles - this.acTimer0Counts[0][1];
this.acTimer0Counts.push([count, nCycles, nCycleDelta]);
}
timer.countStart[0] = count & 0xff;
timer.countStart[1] = count >> 8;
timer.nStartCycles = nCycles;
if (!iTimer && timer.fOUT) {
fFired = true;
this.setIRR(ChipSet.IRQ.TIMER0);
if (MAXDEBUG && DEBUGGER) this.acTimersFired[iTimer]++;
}
}
}
if (DEBUG && DEBUGGER && this.dbg && this.dbg.messageEnabled(Debugger.MESSAGE_TIMER)) {
this.log("TIMER" + iTimer + " count: " + count + ", ticks: " + ticks + ", fired: " + (fFired? "true" : "false"));
}
timer.countCurrent[0] = count & 0xff;
timer.countCurrent[1] = count >> 8;
if (fCycleReset) this.nStartCycles = 0;
}
return timer;
};
/**
* updateAllTimers(fCycleReset)
*
* @this {ChipSet}
* @param {boolean|undefined} [fCycleReset] is true if a cycle-count reset is about to occur
*/
ChipSet.prototype.updateAllTimers = function(fCycleReset)
{
for (var iTimer = 0; iTimer < this.aTimers.length; iTimer++) {
this.updateTimer(iTimer, fCycleReset);
}
if (this.model >= ChipSet.MODEL_5170) this.updateRTCDate();
};
/**
* inPPIA(port, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x60)
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inPPIA = function(port, addrFrom)
{
var b = this.bPPIA;
if (this.bPPICtrl & ChipSet.PPI_CTRL.A_IN) {
if (this.bPPIB & ChipSet.PPI_B.CLEAR_KBD) {
b = this.sw1;
}
else if (this.kbd) {
b = this.kbd.readScanCode();
}
}
this.messagePort(port, null, addrFrom, "PPI_A", Debugger.MESSAGE_CHIPSET, b);
return b;
};
/**
* outPPIA(port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x60)
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outPPIA = function(port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "PPI_A", Debugger.MESSAGE_CHIPSET);
this.bPPIA = bOut;
};
/**
* inPPIB(port, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x61)
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inPPIB = function(port, addrFrom)
{
var b = this.bPPIB;
this.messagePort(port, null, addrFrom, "PPI_B", Debugger.MESSAGE_CHIPSET, b);
return b;
};
/**
* outPPIB(port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x61)
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outPPIB = function(port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "PPI_B", Debugger.MESSAGE_CHIPSET);
this.updatePPIB(bOut);
if (this.kbd) this.kbd.setEnable((bOut & ChipSet.PPI_B.CLEAR_KBD)? false : true, (bOut & ChipSet.PPI_B.CLK_KBD)? true : false);
};
/**
* updatePPIB(bOut)
*
* On MODEL_5170 and up, this updates the "simulated" PPI_B. The only common (and well-documented) PPI_B bits
* across all models are PPI_B.CLK_TIMER2 and PPI_B.SPK_TIMER2, so its possible that this function may need to
* limit its updates to just those bits, and move any model-specific requirements back into the appropriate I/O
* handlers (PPIB or 8042RWReg). We'll see.
*
* @this {ChipSet}
* @param {number} bOut
*/
ChipSet.prototype.updatePPIB = function(bOut)
{
var fNewSpeaker = !!(bOut & ChipSet.PPI_B.SPK_TIMER2);
var fOldSpeaker = !!(this.bPPIB & ChipSet.PPI_B.SPK_TIMER2);
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)
* 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
* to PPI_B.SPK_TIMER2 and ignoring PPI_B.CLK_TIMER2.
*/
this.setSpeaker(fNewSpeaker);
}
};
/**
* inPPIC(port, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x62)
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inPPIC = function(port, addrFrom)
{
var b = 0;
/*
* If you ever wanted to simulate I/O channel errors or R/W memory parity errors, you could
* add either PPI_C.IO_CHANNEL_CHK (0x40) or PPI_C.RW_PARITY_CHK (0x80) to the return value (b).
*/
if (this.model == ChipSet.MODEL_5150) {
if (this.bPPIB & ChipSet.PPI_B.ENABLE_SW2) {
b |= this.sw2 & ChipSet.PPI_C.SW;
} else {
b |= (this.sw2 >> 4) & 0x1; // QUESTION: Does any component actually care about SW2[5] on a MODEL_5150?
}
} else {
if (this.bPPIB & ChipSet.PPI_B.ENABLE_SW_HI) {
b |= this.sw1 >> 4;
} else {
b |= this.sw1 & 0xf;
}
}
if (this.bPPIB & ChipSet.PPI_B.CLK_TIMER2) {
var timer = this.updateTimer(ChipSet.TIMER2.INDEX);
if (timer.fOUT) {
if (this.bPPIB & ChipSet.PPI_B.SPK_TIMER2)
b |= ChipSet.PPI_C.TIMER2_OUT;
else
b |= ChipSet.PPI_C.CASS_DATA_IN;
}
}
/*
* The ROM BIOS polls this port incessantly during its memory tests, checking for memory parity errors
* (which of course we never report), so we further restrict these port messages to MESSAGE_MEM.
*/
this.messagePort(port, null, addrFrom, "PPI_C", Debugger.MESSAGE_CHIPSET | Debugger.MESSAGE_MEM, b);
return b;
};
/**
* outPPIC(port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x62)
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.outPPIC = function(port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "PPI_C", Debugger.MESSAGE_CHIPSET);
this.bPPIC = bOut;
};
/**
* inPPICtrl(port, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x63)
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inPPICtrl = function(port, addrFrom)
{
var b = this.bPPICtrl;
this.messagePort(port, null, addrFrom, "PPI_CTRL", Debugger.MESSAGE_CHIPSET, b);
return b;
};
/**
* outPPICtrl(port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x63)
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
*/
ChipSet.prototype.outPPICtrl = function(port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "PPI_CTRL", Debugger.MESSAGE_CHIPSET);
this.bPPICtrl = bOut;
};
/**
* in8042OutBuff(port, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x60)
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.in8042OutBuff = function(port, addrFrom)
{
var b = this.b8042OutBuff;
this.messagePort(port, null, addrFrom, "8042_OUTBUFF", Debugger.MESSAGE_8042, b);
this.b8042Status &= ~(ChipSet.KBC.STATUS.OUTBUFF_FULL | ChipSet.KBC.STATUS.OUTBUFF_DELAY);
var bNext = this.kbd && this.kbd.readScanCode(true);
if (bNext) this.set8042OutBuff(bNext);
return b;
};
/**
* out8042InBuffData(port, bOut, addrFrom)
*
* This writes to the 8042's input buffer; using this port (ie, 0x60 instead of 0x64) designates the
* the byte as a KBC.DATA.CMD "data byte". Before clearing KBC.STATUS.CMD_FLAG, however, we see if it's set,
* and then based on the previous KBC.CMD "command byte", we do whatever needs to be done with this "data byte".
*
* @this {ChipSet}
* @param {number} port (0x60)
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
*/
ChipSet.prototype.out8042InBuffData = function(port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "8042_INBUF.DATA", Debugger.MESSAGE_8042);
if (this.b8042Status & ChipSet.KBC.STATUS.CMD_FLAG) {
switch (this.b8042InBuff) {
case ChipSet.KBC.CMD.WRITE_CMD:
this.set8042CmdData(bOut);
break;
case ChipSet.KBC.CMD.WRITE_OUTPORT:
this.set8042OutPort(bOut);
break;
/*
* This case is reserved for command bytes that the 8042 is not expecting, which should therefore be passed on
* to the Keyboard itself.
*
* Here's some relevant MODEL_5170 ROM BIOS code, "XMIT_8042" (missing from the original MODEL_5170 ROM BIOS listing),
* which sends a command code in AL to the Keyboard and waits for a response, returning it in AL. Note that
* the only "success" exit path from this function involves LOOPing 64K times before finally reading the Keyboard's
* response; either the hardware and/or this code seems a bit brain-damaged if that's REALLY what you had to do to ensure
* a valid response....
*
* F000:1B25 86E0 XCHG AH,AL
* F000:1B27 2BC9 SUB CX,CX
* F000:1B29 E464 IN AL,64
* F000:1B2B A802 TEST AL,02 ; WAIT FOR INBUFF_FULL TO BE CLEAR
* F000:1B2D E0FA LOOPNZ 1B29
* F000:1B2F E334 JCXZ 1B65 ; EXIT WITH ERROR (CX == 0)
* F000:1B31 86E0 XCHG AH,AL
* F000:1B33 E660 OUT 60,AL ; SAFE TO WRITE KEYBOARD CMD TO INBUFF NOW
* F000:1B35 2BC9 SUB CX,CX
* F000:1B37 E464 IN AL,64
* F000:1B39 8AE0 MOV AH,AL
* F000:1B3B A801 TEST AL,01
* F000:1B3D 7402 JZ 1B41
* F000:1B3F E460 IN AL,60 ; READ PORT 0x60 IF OUTBUFF_FULL SET ("FLUSH"?)
* F000:1B41 F6C402 TEST AH,02
* F000:1B44 E0F1 LOOPNZ 1B37
* F000:1B46 751D JNZ 1B65 ; EXIT WITH ERROR (CX == 0)
* F000:1B48 B306 MOV BL,06
* F000:1B4A 2BC9 SUB CX,CX
* F000:1B4C E464 IN AL,64
* F000:1B4E A801 TEST AL,01
* F000:1B50 E1FA LOOPZ 1B4C
* F000:1B52 7508 JNZ 1B5C ; PROCEED TO EXIT NOW THAT OUTBUFF_FULL IS SET
* F000:1B54 FECB DEC BL
* F000:1B56 75F4 JNZ 1B4C
* F000:1B58 FEC3 INC BL
* F000:1B5A EB09 JMP 1B65 ; EXIT WITH ERROR (CX == 0)
* F000:1B5C 2BC9 SUB CX,CX
* F000:1B5E E2FE LOOP 1B5E ; LOOOOOOPING....
* F000:1B60 E460 IN AL,60
* F000:1B62 83E901 SUB CX,0001 ; EXIT WITH SUCCESS (CX != 0)
* F000:1B65 C3 RET
*
* But WAIT, the FUN doesn't end there. After this function returns, "KBD_RESET" waits for a Keyboard interrupt
* to occur, hoping for scan code 0xAA as the Keyboard's final response. "KBD_RESET" also returns CX to the caller,
* and the caller ("TEST.21") assumes there was no interrupt if CX is zero.
*
* MOV AL,0FDH
* OUT INTA01,AL
* MOV INTR_FLAG,0
* STI
* MOV BL,10
* SUB CX,CX
* G11: TEST [1NTR_FLAG],02H
* JNZ G12
* LOOP G11
* DEC BL
* JNZ G11
* ...
*
* However, if [INTR_FLAG] is set immediately, the above code will exit immediately, without ever decrementing CX.
* CX can be zero not only if the loop exhausted it, but also if no looping was required; the latter is not an
* error, but "TEST.21" assumes that it is.
*/
default:
this.set8042CmdData(this.b8042CmdData & ~ChipSet.KBC.DATA.CMD.NO_CLOCK);
if (this.kbd) this.set8042OutBuff(this.kbd.sendCmd(bOut));
break;
}
}
this.b8042InBuff = bOut;
this.b8042Status &= ~ChipSet.KBC.STATUS.CMD_FLAG;
};
/**
* in8042RWReg(port, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x61)
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.in8042RWReg = function(port, addrFrom)
{
/*
* Normally, we return whatever was last written to this port, but we do need to mask the
* two upper-most bits (KBC.RWREG.PARITY_ERR), as those are output-only bits used to signal
* parity errors.
*
* Also, "TEST.09" of the MODEL_5170 BIOS expects the REFRESH_BIT to alternate, so we used to
* do this:
*
* this.bPPIB ^= ChipSet.KBC.RWREG.REFRESH_BIT;
*
* However, the MODEL_5170_REV3 BIOS not only checks REFRESH_BIT in "TEST.09", but includes
* an additional test right before "TEST.11A", which requires the bit change "a bit less"
* frequently. This new test sets CX to zero, and at the end of the test (@F000:05B8), CX
* must be in the narrow range of 0xF600 through 0xF9FD.
*
* In fact, the new "WAITF" function @F000:1A3A tells us exactly how frequently REFRESH_BIT
* is expected to change now. That function performs a "FIXED TIME WAIT", where CX is a
* "COUNT OF 15.085737us INTERVALS TO WAIT".
*
* So we now tie the state of the REFRESH_BIT to bit 6 of the current CPU cycle count,
* effectively toggling the bit after every 64 cycles. On an 8Mhz CPU that can do 8 cycles
* in 1us, 64 cycles represents 8us, so that might be a bit fast for "WAITF", but bit 6
* is the only choice that also satisfies the pre-"TEST.11A" test as well.
*/
var b = this.bPPIB & ~(ChipSet.KBC.RWREG.PARITY_ERR | ChipSet.KBC.RWREG.REFRESH_BIT) | ((this.cpu.getCycles() & 0x40)? ChipSet.KBC.RWREG.REFRESH_BIT : 0);
/*
* Thanks to the WAITF function, this has become a very "busy" port, so let's not generate messages
* unless both MESSAGE_8042 *and* MESSAGE_LOG are set.
*/
this.messagePort(port, null, addrFrom, "8042_RWREG", Debugger.MESSAGE_8042 | Debugger.MESSAGE_LOG, b);
return b;
};
/**
* out8042RWReg(port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x61)
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
*/
ChipSet.prototype.out8042RWReg = function(port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "8042_RWREG", Debugger.MESSAGE_8042);
this.updatePPIB(bOut);
};
/**
* in8042Status(port, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x64)
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.in8042Status = function(port, addrFrom)
{
this.messagePort(port, null, addrFrom, "8042_STATUS", Debugger.MESSAGE_8042, this.b8042Status);
var b = this.b8042Status & 0xff;
/*
* There's code in the 5170 BIOS (F000:03BF) that writes an 8042 command (0xAA), waits for
* KBC.STATUS.INBUFF_FULL to go clear (which it always is, because we always accept commands
* immediately), then checks KBC.STATUS.OUTBUFF_FULL and performs a "flush" on port 0x60 if
* it's set, then waits for KBC.STATUS.OUTBUFF_FULL *again*. Unfortunately, the "flush" throws
* away our response if we respond immediately.
*
* So now when out8042InBuffCmd() has a response, it sets KBC.STATUS.OUTBUFF_DELAY instead
* (which is outside the 0xff range of bits we return); when we see KBC.STATUS.OUTBUFF_DELAY,
* we clear it and set KBC.STATUS.OUTBUFF_FULL, which will be returned on the next read.
*
* This provides a single poll delay, so that the aforementioned "flush" won't toss our response.
* If longer delays are needed down the road, we may need to set a delay count in the upper (hidden)
* bits of b8042Status, instead of using a single "OUTBUFF_DELAY" bit.
*/
if (this.b8042Status & ChipSet.KBC.STATUS.OUTBUFF_DELAY) {
this.b8042Status |= ChipSet.KBC.STATUS.OUTBUFF_FULL;
this.b8042Status &= ~ChipSet.KBC.STATUS.OUTBUFF_DELAY;
}
return b;
};
/**
* out8042InBuffCmd(port, bOut, addrFrom)
*
* This writes to the 8042's input buffer; using this port (ie, 0x64 instead of 0x60) designates the
* the byte as a "command byte". We immediately set KBC.STATUS.CMD_FLAG, and then see if we can act upon
* the command immediately (some commands requires us to wait for a "data byte").
*
* @this {ChipSet}
* @param {number} port (0x64)
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
*/
ChipSet.prototype.out8042InBuffCmd = function(port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "8042_INBUFF.CMD", Debugger.MESSAGE_8042);
Component.assert(!(this.b8042Status & ChipSet.KBC.STATUS.INBUFF_FULL));
this.b8042InBuff = bOut;
this.b8042Status |= ChipSet.KBC.STATUS.CMD_FLAG;
var bPulseBits = 0;
if (this.b8042InBuff >= ChipSet.KBC.CMD.PULSE_OUTPORT) {
bPulseBits = (this.b8042InBuff ^ 0xf);
/*
* Now that we have isolated the bit(s) to pulse, map all pulse commands to KBC.CMD.PULSE_OUTPORT
*/
this.b8042InBuff = ChipSet.KBC.CMD.PULSE_OUTPORT;
}
switch (this.b8042InBuff) {
case ChipSet.KBC.CMD.WRITE_CMD: // 0x60
case ChipSet.KBC.CMD.WRITE_OUTPORT: // 0xD1
/*
* No further action required for this first group of commands; more data is expected via out8042InBuffData()
*/
break;
case ChipSet.KBC.CMD.READ_INPORT: // 0xC0
this.set8042OutBuff(this.b8042InPort);
break;
case ChipSet.KBC.CMD.DISABLE_KBD: // 0xAD
this.set8042CmdData(this.b8042CmdData | ChipSet.KBC.DATA.CMD.NO_CLOCK);
if (DEBUG) this.messageDebugger("keyboard disabled", Debugger.MESSAGE_KBD);
/*
* NOTE: The MODEL_5170 BIOS calls "KBD_RESET" (F000:17D2) while the keyboard interface is disabled,
* yet we must still deliver the Keyboard's CMDRES.BATSUCCESS response code? Seems like an odd thing for
* a "disabled interface" to do.
*/
break;
case ChipSet.KBC.CMD.ENABLE_KBD: // 0xAE
this.set8042CmdData(this.b8042CmdData & ~ChipSet.KBC.DATA.CMD.NO_CLOCK);
if (DEBUG) this.messageDebugger("keyboard re-enabled", Debugger.MESSAGE_KBD);
break;
case ChipSet.KBC.CMD.SELF_TEST: // 0xAA
if (this.kbd) this.kbd.shiftScanCode(true);
this.set8042CmdData(this.b8042CmdData | ChipSet.KBC.DATA.CMD.NO_CLOCK);
if (DEBUG) this.messageDebugger("keyboard disabled on reset", Debugger.MESSAGE_KBD);
this.set8042OutBuff(ChipSet.KBC.DATA.SELF_TEST.OK);
this.set8042OutPort(ChipSet.KBC.OUTPORT.NO_RESET | ChipSet.KBC.OUTPORT.A20_ON);
break;
case ChipSet.KBC.CMD.READ_TEST: // 0xE0
this.set8042OutBuff((this.b8042CmdData & ChipSet.KBC.DATA.CMD.NO_CLOCK)? 0 : ChipSet.KBC.TESTPORT.KBD_CLOCK);
break;
case ChipSet.KBC.CMD.PULSE_OUTPORT: // 0xF0-0xFF
if (bPulseBits & 0x1) {
/*
* Bit 0 of the 8042's output port is connected to RESET. If it's pulsed, the processor resets.
* We don't want to clear ALL our internal state (eg, cycle counts), so we call cpu.resetRegs() instead
* of cpu.reset().
*/
this.cpu.resetRegs();
}
break;
default:
if (DEBUG && DEBUGGER && this.dbg) {
this.dbg.message("unrecognized 8042 command: " + str.toHexByte(this.b8042InBuff));
this.cpu.haltCPU();
}
break;
}
};
/**
* set8042CmdData(b)
*
* @this {ChipSet}
* @param {number} b
*/
ChipSet.prototype.set8042CmdData = function(b)
{
this.b8042CmdData = b;
Component.assert(ChipSet.KBC.DATA.CMD.SYS_FLAG === ChipSet.KBC.STATUS.SYS_FLAG);
this.b8042Status = (this.b8042Status & ~ChipSet.KBC.STATUS.SYS_FLAG) | (b & ChipSet.KBC.DATA.CMD.SYS_FLAG);
if (this.kbd) {
/*
* This seems to be what the doctor ordered for the MODEL_5170_REV3 BIOS @F000:0A6D, where it
* sends ChipSet.KBC.CMD.WRITE_CMD to port 0x64, followed by 0x4D to port 0x60, which clears NO_CLOCK
* and enables the keyboard. The BIOS then waits for OUTBUFF_FULL to be set, at which point it seems
* to be anticipating an 0xAA response in the output buffer.
*
* And indeed, if we call the original MODEL_5150/MODEL_5160 setEnable() Keyboard interface here,
* and both the data and clock lines have transitioned high (ie, both parameters are true), then it
* will call resetDevice(), generating a Keyboard.CMDRES.BATSUCCESS response.
*
* This agrees with my understanding of what happens when the 8042 toggles the clock line high
* (ie, clears NO_CLOCK): the TechRef's "Basic Assurance Test" section says that when the Keyboard is
* powered on, it performs the BAT, and then when the clock and data lines go high, the keyboard sends
* a completion code (eg, 0xAA for success, or 0xFC or something else for failure).
*/
if (this.kbd.setEnable(!!(b & ChipSet.KBC.DATA.CMD.NO_INHIBIT), !(b & ChipSet.KBC.DATA.CMD.NO_CLOCK))) {
this.set8042OutBuff(this.kbd.readScanCode(true));
}
}
};
/**
* set8042OutBuff(b)
*
* @this {ChipSet}
* @param {number} b
*/
ChipSet.prototype.set8042OutBuff = function(b)
{
if (b >= 0) {
this.b8042OutBuff = b;
this.b8042Status &= ~ChipSet.KBC.STATUS.OUTBUFF_FULL;
this.b8042Status |= ChipSet.KBC.STATUS.OUTBUFF_DELAY;
}
};
/**
* set8042OutPort(b)
*
* @this {ChipSet}
* @param {number} b
*/
ChipSet.prototype.set8042OutPort = function(b)
{
this.b8042OutPort = b;
this.bus.setA20(!!(b & ChipSet.KBC.OUTPORT.A20_ON));
if (!(b & ChipSet.KBC.OUTPORT.NO_RESET)) {
/*
* Bit 0 of the 8042's output port is connected to RESET. Normally, it's "pulsed" with the
* KBC.CMD.PULSE_OUTPORT command, so if a RESET is detected via this command, we should try to
* determine if that's what the caller intended.
*/
if (DEBUG && DEBUGGER && this.dbg) {
this.dbg.message("unexpected 8042 output port reset: " + str.toHexByte(b));
this.cpu.haltCPU();
}
this.cpu.resetRegs();
}
};
/**
* inCMOSAddr(port, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x70)
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inCMOSAddr = function(port, addrFrom)
{
this.messagePort(port, null, addrFrom, "CMOS_ADDR", Debugger.MESSAGE_CMOS, this.bCMOSAddr);
return this.bCMOSAddr;
};
/**
* outCMOSAddr(port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x70)
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
*/
ChipSet.prototype.outCMOSAddr = function(port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "CMOS_ADDR", Debugger.MESSAGE_CMOS);
this.bCMOSAddr = bOut;
this.bNMI = (bOut & ChipSet.CMOS.ADDR.NMI_DISABLE)? ChipSet.NMI.DISABLE : ChipSet.NMI.ENABLE;
};
/**
* inCMOSData(port, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x71)
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inCMOSData = function(port, addrFrom)
{
var bAddr = this.bCMOSAddr & ChipSet.CMOS.ADDR.MASK;
var bIn = (bAddr <= ChipSet.CMOS.ADDR.RTC_STATUSD? this.getRTCByte(bAddr) : this.abCMOSData[bAddr]);
this.messagePort(port, null, addrFrom, "CMOS_DATA[" + str.toHexByte(bAddr) + "]", Debugger.MESSAGE_CMOS, bIn);
return bIn;
};
/**
* outCMOSData(port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x71)
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
*/
ChipSet.prototype.outCMOSData = function(port, bOut, addrFrom)
{
var bAddr = this.bCMOSAddr & ChipSet.CMOS.ADDR.MASK;
this.messagePort(port, bOut, addrFrom, "CMOS_DATA[" + str.toHexByte(bAddr) + "]", Debugger.MESSAGE_CMOS);
this.abCMOSData[bAddr] = (bAddr <= ChipSet.CMOS.ADDR.RTC_STATUSD? this.setRTCByte(bAddr, bOut) : bOut);
};
/**
* inMFGData(port, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x80)
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
* @return {number} simulated port value
*/
ChipSet.prototype.inMFGData = function(port, addrFrom)
{
this.messagePort(port, null, addrFrom, "MFG_DATA", Debugger.MESSAGE_CHIPSET, this.bMFGData);
return this.bMFGData;
};
/**
* outMFGData(port, bOut, addrFrom)
*
* @this {ChipSet}
* @param {number} port (0x80)
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
*/
ChipSet.prototype.outMFGData = function(port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "MFG_DATA", Debugger.MESSAGE_CHIPSET);
this.bMFGData = bOut;
};
/**
* outNMI(port, bOut, addrFrom)
*
* This handler is installed only for models before MODEL_5170.
*
* @this {ChipSet}
* @param {number} port (0xA0)
* @param {number} bOut
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
*/
ChipSet.prototype.outNMI = function(port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "NMI", Debugger.MESSAGE_CHIPSET);
this.bNMI = bOut;
};
/**
* 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(Debugger.MESSAGE_RTC)) {
this.dbg.messageInt(ChipSet.BIOS.INT_RTC, addr);
this.cpu.addIntReturn(addr, function(chipset, nCycles) {
return function onBIOSRTCReturn(nLevel) {
nCycles = chipset.cpu.getCycles() - nCycles;
var sResult;
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.dbg.messageIntReturn(ChipSet.BIOS.INT_RTC, nLevel, nCycles, sResult);
};
}(this, this.cpu.getCycles()));
}
}
return true;
};
/**
* parseSwitches(s, def)
*
* @this {ChipSet}
* @param {string|undefined} s describing switch settings (can't simply use parseInt() with a base of 2, because the bit order is reversed, as well as the bit sense)
* @param {number} def is a default value to use if s is undefined
* @return {number} value representing the switch settings
*/
ChipSet.prototype.parseSwitches = function(s, def)
{
if (s === undefined) return def;
var b = 0, bit = 0x1;
for (var i = 0; i < s.length; i++) {
if (s.charAt(i) == "0") b |= bit;
bit <<= 1;
}
return b;
};
/**
* setSpeaker(fOn)
*
* @this {ChipSet}
* @param {boolean} [fOn] true to turn speaker on, false to turn off, otherwise update as appropriate
*/
ChipSet.prototype.setSpeaker = function(fOn)
{
if (this.contextAudio) {
if (fOn !== undefined) {
this.fSpeaker = fOn;
} else {
fOn = this.fSpeaker && this.cpu && this.cpu.isRunning();
}
var freq = Math.round(ChipSet.TIMER_TICKS_PER_SEC / this.getTimerInit(ChipSet.TIMER2.INDEX));
/*
* Treat frequencies outside the normal hearing range (below 20hz or above 20Khz) as a clever attempt
* to turn sound off; we have to explicitly turn the sound off in those cases, to prevent the Audio API
* from "easing" the audio to the target frequency and creating odd sound effects.
*/
if (freq < 20 || freq > 20000) fOn = false;
if (fOn) {
if (this.sourceAudio) {
this.sourceAudio['frequency']['value'] = freq;
this.messageDebugger("speaker set to " + freq + "hz", Debugger.MESSAGE_SPEAKER);
} else {
this.sourceAudio = this.contextAudio['createOscillator']();
this.sourceAudio['type'] = 1; // 0: sine wave, 1: square wave, 2: sawtooth wave, 3: triangle wave
this.sourceAudio['connect'](this.contextAudio['destination']);
this.sourceAudio['frequency']['value'] = freq;
this.messageDebugger("speaker on at " + freq + "hz", Debugger.MESSAGE_SPEAKER);
this.sourceAudio['noteOn'](0); // aka start()
}
} else {
if (this.sourceAudio) {
this.sourceAudio['noteOff'](0); // aka stop()
this.sourceAudio['disconnect'](); // QUESTION: is this automatic following a stop(), since this particular source cannot be started again?
delete this.sourceAudio; // QUESTION: ditto?
this.messageDebugger("speaker off at " + freq + "hz", Debugger.MESSAGE_SPEAKER);
}
}
} else if (fOn) {
this.messageDebugger("BEEP", Debugger.MESSAGE_SPEAKER);
}
};
/**
* messageDebugger(sMessage, bitsMessage, nIRQ)
*
* @this {ChipSet}
* @param {string} sMessage is any caller-defined message string
* @param {number} [bitsMessage] is one or more Debugger MESSAGE_* category flag(s)
* @param {number|undefined} [nIRQ] if the message is associated with a particular IRQ #
*
* This is a combination of the Debugger's messageEnabled() and message() functions, for convenience.
*
* NOTE: If the caller specifies multiple MESSAGE category flags, then ALL the corresponding message
* categories in the Debugger must be enabled as well, else the message will not be displayed.
*/
ChipSet.prototype.messageDebugger = function(sMessage, bitsMessage, nIRQ)
{
if (DEBUGGER && this.dbg) {
if (bitsMessage == null) bitsMessage = Debugger.MESSAGE_CHIPSET;
if (nIRQ !== undefined) {
bitsMessage |= (nIRQ == ChipSet.IRQ.TIMER0? Debugger.MESSAGE_TIMER : (nIRQ == ChipSet.IRQ.KBD? Debugger.MESSAGE_KBD : (nIRQ == ChipSet.IRQ.FDC? Debugger.MESSAGE_FDC : 0)));
}
if (this.dbg.messageEnabled(bitsMessage)) this.dbg.message(sMessage);
}
};
/**
* messagePort(port, bOut, addrFrom, name, bitsMessage, bIn)
*
* @this {ChipSet}
* @param {number} port
* @param {number|null} bOut if an output operation
* @param {number|null} [addrFrom]
* @param {string|null} [name] of the port, if any
* @param {number|null} [bitsMessage] is one or more a Debugger MESSAGE_* category flag(s)
* @param {number} [bIn] is the input value, if known, on an input operation
*
* This is an internal version of the Debugger's messagePort() function, for convenience.
*
* NOTE: If the caller specifies multiple MESSAGE category flags, then ALL the corresponding message
* categories in the Debugger must be enabled as well, else the message will not be displayed.
*/
ChipSet.prototype.messagePort = function(port, bOut, addrFrom, name, bitsMessage, bIn)
{
if (DEBUGGER && this.dbg) {
if (bitsMessage == null) bitsMessage = Debugger.MESSAGE_CHIPSET;
this.dbg.messagePort(this, port, bOut, addrFrom, name, bitsMessage, bIn);
}
};
/*
* Port input notification tables
*/
ChipSet.aPortInput = {
0x00: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelAddr(ChipSet.DMA0.INDEX, 0, port, addrFrom); },
0x01: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelCount(ChipSet.DMA0.INDEX, 0, port, addrFrom); },
0x02: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelAddr(ChipSet.DMA0.INDEX, 1, port, addrFrom); },
0x03: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelCount(ChipSet.DMA0.INDEX, 1, port, addrFrom); },
0x04: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelAddr(ChipSet.DMA0.INDEX, 2, port, addrFrom); },
0x05: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelCount(ChipSet.DMA0.INDEX, 2, port, addrFrom); },
0x06: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelAddr(ChipSet.DMA0.INDEX, 3, port, addrFrom); },
0x07: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelCount(ChipSet.DMA0.INDEX, 3, port, addrFrom); },
0x08: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAStatus(ChipSet.DMA0.INDEX, port, addrFrom); },
0x20: /** @this {ChipSet} */ function(port, addrFrom) { return this.inPICLo(ChipSet.PIC0.INDEX, addrFrom); },
0x21: /** @this {ChipSet} */ function(port, addrFrom) { return this.inPICHi(ChipSet.PIC0.INDEX, addrFrom); },
0x40: /** @this {ChipSet} */ function(port, addrFrom) { return this.inTimer(ChipSet.TIMER0.INDEX, addrFrom); },
0x41: /** @this {ChipSet} */ function(port, addrFrom) { return this.inTimer(ChipSet.TIMER1.INDEX, addrFrom); },
0x42: /** @this {ChipSet} */ function(port, addrFrom) { return this.inTimer(ChipSet.TIMER2.INDEX, addrFrom); },
0x43: ChipSet.prototype.inTimerCtrl,
0x81: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageReg(ChipSet.DMA0.INDEX, 2, port, addrFrom); },
0x82: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageReg(ChipSet.DMA0.INDEX, 3, port, addrFrom); },
0x83: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageReg(ChipSet.DMA0.INDEX, 1, port, addrFrom); },
0x87: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageReg(ChipSet.DMA0.INDEX, 0, port, addrFrom); }
};
ChipSet.aPortInput5150 = {
0x60: ChipSet.prototype.inPPIA,
0x61: ChipSet.prototype.inPPIB,
0x62: ChipSet.prototype.inPPIC,
0x63: ChipSet.prototype.inPPICtrl // technically, not actually readable, but I want the Debugger to be able to read this
};
ChipSet.aPortInput5170 = {
0x60: ChipSet.prototype.in8042OutBuff,
0x61: ChipSet.prototype.in8042RWReg,
0x64: ChipSet.prototype.in8042Status,
0x70: ChipSet.prototype.inCMOSAddr,
0x71: ChipSet.prototype.inCMOSData,
0x80: ChipSet.prototype.inMFGData,
0x84: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageSpare(0, port, addrFrom); },
0x85: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageSpare(1, port, addrFrom); },
0x86: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageSpare(2, port, addrFrom); },
0x88: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageSpare(3, port, addrFrom); },
0x89: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageReg(ChipSet.DMA1.INDEX, 2, port, addrFrom); },
0x8A: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageReg(ChipSet.DMA1.INDEX, 3, port, addrFrom); },
0x8B: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageReg(ChipSet.DMA1.INDEX, 1, port, addrFrom); },
0x8C: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageSpare(4, port, addrFrom); },
0x8D: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageSpare(5, port, addrFrom); },
0x8E: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageSpare(6, port, addrFrom); },
0x8F: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageReg(ChipSet.DMA1.INDEX, 0, port, addrFrom); },
0xA0: /** @this {ChipSet} */ function(port, addrFrom) { return this.inPICLo(ChipSet.PIC1.INDEX, addrFrom); },
0xA1: /** @this {ChipSet} */ function(port, addrFrom) { return this.inPICHi(ChipSet.PIC1.INDEX, addrFrom); },
0xC0: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelAddr(ChipSet.DMA1.INDEX, 0, port, addrFrom); },
0xC2: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelCount(ChipSet.DMA1.INDEX, 0, port, addrFrom); },
0xC4: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelAddr(ChipSet.DMA1.INDEX, 1, port, addrFrom); },
0xC6: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelCount(ChipSet.DMA1.INDEX, 1, port, addrFrom); },
0xC8: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelAddr(ChipSet.DMA1.INDEX, 2, port, addrFrom); },
0xCA: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelCount(ChipSet.DMA1.INDEX, 2, port, addrFrom); },
0xCC: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelAddr(ChipSet.DMA1.INDEX, 3, port, addrFrom); },
0xCE: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAChannelCount(ChipSet.DMA1.INDEX, 3, port, addrFrom); },
0xD0: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAStatus(ChipSet.DMA1.INDEX, port, addrFrom); }
};
/*
* Port output notification tables
*/
ChipSet.aPortOutput = {
0x00: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelAddr(ChipSet.DMA0.INDEX, 0, port, bOut, addrFrom); },
0x01: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelCount(ChipSet.DMA0.INDEX, 0, port, bOut, addrFrom); },
0x02: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelAddr(ChipSet.DMA0.INDEX, 1, port, bOut, addrFrom); },
0x03: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelCount(ChipSet.DMA0.INDEX, 1, port, bOut, addrFrom); },
0x04: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelAddr(ChipSet.DMA0.INDEX, 2, port, bOut, addrFrom); },
0x05: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelCount(ChipSet.DMA0.INDEX, 2, port, bOut, addrFrom); },
0x06: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelAddr(ChipSet.DMA0.INDEX, 3, port, bOut, addrFrom); },
0x07: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelCount(ChipSet.DMA0.INDEX, 3, port, bOut, addrFrom); },
0x08: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMACmd(ChipSet.DMA0.INDEX, port, bOut, addrFrom); },
0x09: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAReq(ChipSet.DMA0.INDEX, port, bOut, addrFrom); },
0x0A: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAMask(ChipSet.DMA0.INDEX, port, bOut, addrFrom); },
0x0B: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAMode(ChipSet.DMA0.INDEX, port, bOut, addrFrom); },
0x0C: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAIndex(ChipSet.DMA0.INDEX, port, bOut, addrFrom); },
0x0D: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAClear(ChipSet.DMA0.INDEX, port, bOut, addrFrom); },
0x20: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outPICLo(ChipSet.PIC0.INDEX, bOut, addrFrom); },
0x21: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outPICHi(ChipSet.PIC0.INDEX, bOut, addrFrom); },
0x40: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outTimer(ChipSet.TIMER0.INDEX, bOut, addrFrom); },
0x41: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outTimer(ChipSet.TIMER1.INDEX, bOut, addrFrom); },
0x42: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outTimer(ChipSet.TIMER2.INDEX, bOut, addrFrom); },
0x43: ChipSet.prototype.outTimerCtrl,
0x81: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageReg(ChipSet.DMA0.INDEX, 2, port, bOut, addrFrom); },
0x82: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageReg(ChipSet.DMA0.INDEX, 3, port, bOut, addrFrom); },
0x83: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageReg(ChipSet.DMA0.INDEX, 1, port, bOut, addrFrom); },
0x87: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageReg(ChipSet.DMA0.INDEX, 0, port, bOut, addrFrom); }
};
ChipSet.aPortOutput5150 = {
0x60: ChipSet.prototype.outPPIA,
0x61: ChipSet.prototype.outPPIB,
0x62: ChipSet.prototype.outPPIC,
0x63: ChipSet.prototype.outPPICtrl,
0xA0: ChipSet.prototype.outNMI
};
ChipSet.aPortOutput5170 = {
0x60: ChipSet.prototype.out8042InBuffData,
0x61: ChipSet.prototype.out8042RWReg,
0x64: ChipSet.prototype.out8042InBuffCmd,
0x70: ChipSet.prototype.outCMOSAddr,
0x71: ChipSet.prototype.outCMOSData,
0x80: ChipSet.prototype.outMFGData,
0x84: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageSpare(0, port, bOut, addrFrom); },
0x85: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageSpare(1, port, bOut, addrFrom); },
0x86: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageSpare(2, port, bOut, addrFrom); },
0x88: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageSpare(3, port, bOut, addrFrom); },
0x89: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageReg(ChipSet.DMA1.INDEX, 2, port, bOut, addrFrom); },
0x8A: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageReg(ChipSet.DMA1.INDEX, 3, port, bOut, addrFrom); },
0x8B: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageReg(ChipSet.DMA1.INDEX, 1, port, bOut, addrFrom); },
0x8C: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageSpare(4, port, bOut, addrFrom); },
0x8D: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageSpare(5, port, bOut, addrFrom); },
0x8E: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageSpare(6, port, bOut, addrFrom); },
0x8F: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageReg(ChipSet.DMA1.INDEX, 0, port, bOut, addrFrom); },
0xA0: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outPICLo(ChipSet.PIC1.INDEX, bOut, addrFrom); },
0xA1: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outPICHi(ChipSet.PIC1.INDEX, bOut, addrFrom); },
0xC0: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelAddr(ChipSet.DMA1.INDEX, 0, port, bOut, addrFrom); },
0xC2: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelCount(ChipSet.DMA1.INDEX, 0, port, bOut, addrFrom); },
0xC4: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelAddr(ChipSet.DMA1.INDEX, 1, port, bOut, addrFrom); },
0xC6: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelCount(ChipSet.DMA1.INDEX, 1, port, bOut, addrFrom); },
0xC8: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelAddr(ChipSet.DMA1.INDEX, 2, port, bOut, addrFrom); },
0xCA: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelCount(ChipSet.DMA1.INDEX, 2, port, bOut, addrFrom); },
0xCC: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelAddr(ChipSet.DMA1.INDEX, 3, port, bOut, addrFrom); },
0xCE: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAChannelCount(ChipSet.DMA1.INDEX, 3, port, bOut, addrFrom); },
0xD0: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMACmd(ChipSet.DMA1.INDEX, port, bOut, addrFrom); },
0xD2: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAReq(ChipSet.DMA1.INDEX, port, bOut, addrFrom); },
0xD4: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAMask(ChipSet.DMA1.INDEX, port, bOut, addrFrom); },
0xD6: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAMode(ChipSet.DMA1.INDEX, port, bOut, addrFrom); },
0xD8: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAIndex(ChipSet.DMA1.INDEX, port, bOut, addrFrom); },
0xDA: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAClear(ChipSet.DMA1.INDEX, port, bOut, addrFrom); }
};
/**
* ChipSet.init()
*
* This function operates on every element (e) of class "chipset", and initializes
* all the necessary HTML to construct the ChipSet module(s) as spec'ed.
*
* Note that each element (e) of class "chipset" is expected to have a "data-value"
* attribute containing the same JSON-encoded parameters that the ChipSet constructor
* expects.
*/
ChipSet.init = function()
{
var aeChipSet = Component.getElementsByClass(window.document, PCJSCLASS, "chipset");
for (var iChip = 0; iChip < aeChipSet.length; iChip++) {
var eChipSet = aeChipSet[iChip];
var parmsChipSet = Component.getComponentParms(eChipSet);
var chipset = new ChipSet(parmsChipSet);
Component.bindComponentControls(chipset, eChipSet, PCJSCLASS);
chipset.updateSwitchDesc();
}
};
/*
* Initialize every ChipSet module on the page.
*/
web.onInit(ChipSet.init);
if (typeof APP_PCJS !== 'undefined') APP_PCJS.ChipSet = ChipSet;
if (typeof module !== 'undefined') module.exports = ChipSet;