5999 lines
250 KiB
JavaScript
5999 lines
250 KiB
JavaScript
/**
|
||
* @fileoverview Implements the PCx86 ChipSet component.
|
||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||
* @copyright © Jeff Parsons 2012-2016
|
||
*
|
||
* This file is part of PCjs, a computer emulation software project at <http://pcjs.org/>.
|
||
*
|
||
* 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 <http://www.gnu.org/licenses/gpl.html>.
|
||
*
|
||
* You are required to include the above copyright notice in every modified copy of this work
|
||
* and to display that copyright notice when the software starts running; see COPYRIGHT in
|
||
* <http://pcjs.org/modules/shared/lib/defines.js>.
|
||
*
|
||
* 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 PCjs
|
||
* for purposes of the GNU General Public License, and the author does not claim any copyright
|
||
* as to their contents.
|
||
*/
|
||
|
||
"use strict";
|
||
|
||
if (NODE) {
|
||
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("../../shared/lib/state");
|
||
var PCX86 = require("./defines");
|
||
var Interrupts = require("./interrupts");
|
||
var Messages = require("./messages");
|
||
var X86 = require("./x86");
|
||
}
|
||
|
||
/**
|
||
* ChipSet(parmsChipSet)
|
||
*
|
||
* The ChipSet component has the following component-specific (parmsChipSet) properties:
|
||
*
|
||
* model: eg, "5150", "5160", "5170", "deskpro386" (should be a member of ChipSet.MODELS)
|
||
* sw1: 8-character binary string representing the SW1 DIP switches (SW1[1-8]); see Switches Overview
|
||
* 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|ega|vga (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"
|
||
*
|
||
* As support for IBM-compatible machines grows, we should refrain from adding new model strings (eg, "att6300")
|
||
* and corresponding model checks, and instead add more ChipSet configuration properties, such as:
|
||
*
|
||
* pit1port: 0x48 to enable PIT1 at base port 0x48 (as used by COMPAQ_DESKPRO386); default to undefined
|
||
* kbdchip: 8041 to select 8041 emulation (eg, for ATT_6300); default to 8255 for MODEL_5150/MODEL_5160, 8042 for MODEL_5170
|
||
*
|
||
* @constructor
|
||
* @extends Component
|
||
* @param {Object} parmsChipSet
|
||
*/
|
||
function ChipSet(parmsChipSet)
|
||
{
|
||
Component.call(this, "ChipSet", parmsChipSet, ChipSet, Messages.CHIPSET);
|
||
|
||
var model = parmsChipSet['model'];
|
||
|
||
/*
|
||
* this.model is a numeric version of the 'model' string; when comparing this.model to standard IBM
|
||
* model numbers, you should generally compare (this.model|0) to the target value, which truncates it.
|
||
*/
|
||
if (model && !ChipSet.MODELS[model]) {
|
||
Component.notice("Unrecognized ChipSet model: " + model);
|
||
}
|
||
|
||
this.model = ChipSet.MODELS[model] || ChipSet.MODEL_5150_OTHER;
|
||
|
||
var bSwitches;
|
||
this.aDIPSwitches = [];
|
||
|
||
/*
|
||
* 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.
|
||
*/
|
||
bSwitches = this.parseDIPSwitches(parmsChipSet[ChipSet.CONTROLS.SW1]);
|
||
this.aDIPSwitches[0] = [bSwitches, bSwitches];
|
||
|
||
if (bSwitches == null) {
|
||
this.aFloppyDrives = [360, 360];
|
||
var aFloppyDrives = parmsChipSet['floppies'];
|
||
if (aFloppyDrives && aFloppyDrives.length) this.aFloppyDrives = aFloppyDrives;
|
||
this.setDIPSwitches(ChipSet.SWITCH_TYPE.FLOPNUM, this.aFloppyDrives.length);
|
||
|
||
var sMonitor = parmsChipSet['monitor'] || (this.model < ChipSet.MODEL_5170? "mono" : "ega");
|
||
this.setDIPSwitches(ChipSet.SWITCH_TYPE.MONITOR, sMonitor);
|
||
}
|
||
|
||
/*
|
||
* 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. So with a 16Kb-64Kb motherboard, the MODEL_5150 ROM BIOS could support a grand
|
||
* total of 544Kb. With the 64Kb-256Kb motherboard revision, a 5150 could use the first FIVE SW2 switches,
|
||
* allowing for a grand total as high as 640Kb.
|
||
*
|
||
* For MODEL_5160 (PC XT) and up, memory expansion cards had their own 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.
|
||
*/
|
||
bSwitches = this.parseDIPSwitches(parmsChipSet[ChipSet.CONTROLS.SW2]);
|
||
this.aDIPSwitches[1] = [bSwitches, bSwitches];
|
||
|
||
this.sCellClass = PCX86.CSSCLASS + "-bitCell";
|
||
|
||
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 seems 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']) {
|
||
this.classAudio = this.contextAudio = null;
|
||
if (window) {
|
||
this.classAudio = window['AudioContext'] || window['webkitAudioContext'];
|
||
}
|
||
if (this.classAudio) {
|
||
this.contextAudio = new this.classAudio();
|
||
} else {
|
||
if (DEBUG) this.log("AudioContext 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(ChipSet);
|
||
|
||
/*
|
||
* Ports Overview
|
||
* --------------
|
||
*
|
||
* This module 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 Technical Reference.
|
||
*
|
||
* 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
|
||
* (FPU) 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 FPU Coprocessor Clear Busy (output 0x00)
|
||
* 0F1 FPU 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
|
||
* FPU coprocessor interrupt line is no longer tied to NMI (it uses IRQ 13).
|
||
*/
|
||
|
||
/*
|
||
* Supported model numbers
|
||
*
|
||
* In general, when comparing this.model to "base" model numbers (ie, non-REV numbers), you should use
|
||
* (this.model|0), which truncates the current model number.
|
||
*
|
||
* Note that there were two 5150 motherboard revisions: the "REV A" 16Kb-64Kb motherboard and the
|
||
* "REV B" 64Kb-256Kb motherboard. There may have been a manufacturing correlation between motherboard
|
||
* revisions ("REV A" and "REV B") and the ROM BIOS revisions shown below, but in general, we can't assume
|
||
* any correlation, because newer ROMs could be installed with either motherboard.
|
||
*
|
||
* I do know that, for "REV A" motherboards, the Apr 1984 5150 TechRef says that "To expand the memory
|
||
* of your system beyond 544K requires your IBM Personal Computer System Unit to have a BIOS ROM module
|
||
* dated 10/27/82 or later." Which suggests that SW2[5] was not used until the REV3 5150 ROM BIOS.
|
||
*
|
||
* For now, we treat all our MODEL_5150 systems as 16Kb-64Kb motherboards; if you want a 64Kb-256Kb motherboard,
|
||
* then step up to a MODEL_5160 system. We use a multiplier of 16 for 5150 LOWMEM values, and a multiplier
|
||
* of 64 for 5160 LOWMEM values.
|
||
*/
|
||
ChipSet.MODEL_5150 = 5150; // used in reference to the 1st 5150 ROM BIOS, dated Apr 24, 1981
|
||
ChipSet.MODEL_5150_REV2 = 5150.2; // used in reference to the 2nd 5150 ROM BIOS, dated Oct 19, 1981
|
||
ChipSet.MODEL_5150_REV3 = 5150.3; // used in reference to the 3rd 5150 ROM BIOS, dated Oct 27, 1982
|
||
ChipSet.MODEL_5150_OTHER = 5150.9;
|
||
|
||
ChipSet.MODEL_5160 = 5160; // used in reference to the 1st 5160 ROM BIOS, dated Nov 08, 1982
|
||
ChipSet.MODEL_5160_REV2 = 5160.2; // used in reference to the 1st 5160 ROM BIOS, dated Jan 10, 1986
|
||
ChipSet.MODEL_5160_REV3 = 5160.3; // used in reference to the 1st 5160 ROM BIOS, dated May 09, 1986
|
||
ChipSet.MODEL_5160_OTHER = 5160.9;
|
||
|
||
ChipSet.MODEL_5170 = 5170; // used in reference to the 1st 5170 ROM BIOS, dated Jan 10, 1984
|
||
ChipSet.MODEL_5170_REV2 = 5170.2; // used in reference to the 2nd 5170 ROM BIOS, dated Jun 10, 1985
|
||
ChipSet.MODEL_5170_REV3 = 5170.3; // used in reference to the 3rd 5170 ROM BIOS, dated Nov 15, 1985
|
||
ChipSet.MODEL_5170_OTHER = 5170.9;
|
||
|
||
/*
|
||
* Assorted non-IBM models (we don't put "IBM" in the IBM models, but non-IBM models should include the company name).
|
||
*/
|
||
ChipSet.MODEL_CDP_MPC1600 = 5150.101; // Columbia Data Products MPC 1600 ("Copyright Columbia Data Products 1983, ROM/BIOS Ver 4.34")
|
||
ChipSet.MODEL_COMPAQ_PORTABLE = 5150.102; // COMPAQ Portable (COMPAQ's first PC)
|
||
|
||
ChipSet.MODEL_ATT_6300 = 5160.101; // AT&T Personal Computer 6300/Olivetti M24 ("COPYRIGHT (C) OLIVETTI 1984","04/03/86",v1.43)
|
||
ChipSet.MODEL_ZENITH_Z150 = 5160.150; // Zenith Data Systems Z-150 ("08/11/88 (C)ZDS CORP")
|
||
|
||
ChipSet.MODEL_COMPAQ_DESKPRO386 = 5180; // COMPAQ DeskPro 386 (COMPAQ's first 80386-based PC); should be > MODEL_5170
|
||
|
||
/*
|
||
* Last but not least, a complete list of supported model strings, and corresponding internal model numbers.
|
||
*/
|
||
ChipSet.MODELS = {
|
||
"5150": ChipSet.MODEL_5150,
|
||
"5160": ChipSet.MODEL_5160,
|
||
"5170": ChipSet.MODEL_5170,
|
||
"att6300": ChipSet.MODEL_ATT_6300,
|
||
"mpc1600": ChipSet.MODEL_CDP_MPC1600,
|
||
"z150": ChipSet.MODEL_ZENITH_Z150,
|
||
"compaq": ChipSet.MODEL_COMPAQ_PORTABLE,
|
||
"other": ChipSet.MODEL_5150_OTHER
|
||
};
|
||
|
||
if (DESKPRO386) {
|
||
ChipSet.MODELS["deskpro386"] = ChipSet.MODEL_COMPAQ_DESKPRO386;
|
||
}
|
||
|
||
ChipSet.CONTROLS = {
|
||
SW1: "sw1",
|
||
SW2: "sw2",
|
||
SWDESC: "swdesc"
|
||
};
|
||
|
||
/*
|
||
* Values returned by ChipSet.getDIPVideoMonitor()
|
||
*/
|
||
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
|
||
VGACOLOR: 7 // VGA Color Display
|
||
};
|
||
|
||
/*
|
||
* 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).
|
||
*
|
||
* DMA0 refers to the original DMA controller found on all models, and DMA1 refers to the additional
|
||
* controller found on MODEL_5170 and up; channel 4 on DMA1 is used to "cascade" channels 0-3 from DMA0,
|
||
* so only channels 5-7 are available on DMA1.
|
||
*
|
||
* 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
|
||
*
|
||
* TODO: Determine why the MODEL_5150 ROM BIOS sets the DMA channel 1 page register (port 0x83) to zero.
|
||
*/
|
||
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,
|
||
RESET_FF: 0x0C, // reset flip-flop
|
||
MASTER_CLEAR: 0x0D, // OUT: master clear IN: temporary register
|
||
MASK_CLEAR: 0x0E, // TODO: Provide handlers
|
||
MASK_ALL: 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,
|
||
RESET_FF: 0xD8, // reset flip-flop
|
||
MASTER_CLEAR: 0xDA, // master clear
|
||
MASK_CLEAR: 0xDC, // TODO: Provide handlers
|
||
MASK_ALL: 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_STATUS = {
|
||
CH0_TC: 0x01, // Channel 0 has reached Terminal Count (TC)
|
||
CH1_TC: 0x02, // Channel 1 has reached Terminal Count (TC)
|
||
CH2_TC: 0x04, // Channel 2 has reached Terminal Count (TC)
|
||
CH3_TC: 0x08, // Channel 3 has reached Terminal Count (TC)
|
||
ALL_TC: 0x0f, // all TC bits are cleared whenever DMA_STATUS is read
|
||
CH0_REQ: 0x10, // Channel 0 DMA requested
|
||
CH1_REQ: 0x20, // Channel 1 DMA requested
|
||
CH2_REQ: 0x40, // Channel 2 DMA requested
|
||
CH3_REQ: 0x80 // Channel 3 DMA requested
|
||
};
|
||
|
||
ChipSet.DMA_MASK = {
|
||
CHANNEL: 0x03,
|
||
CHANNEL_SET: 0x04
|
||
};
|
||
|
||
ChipSet.DMA_MODE = {
|
||
CHANNEL: 0x03, // bits 0-1 select 1 of 4 possible channels
|
||
TYPE: 0x0C, // bits 2-3 select 1 of 3 valid (4 possible) transfer types
|
||
TYPE_VERIFY: 0x00, // pseudo transfer (generates addresses, responds to EOP, but nothing is moved)
|
||
TYPE_WRITE: 0x04, // write to memory (move data FROM an I/O device; eg, reading a sector from a disk)
|
||
TYPE_READ: 0x08, // read from memory (move data TO an I/O device; eg, writing a sector to a disk)
|
||
AUTOINIT: 0x10,
|
||
DECREMENT: 0x20, // clear for INCREMENT
|
||
MODE: 0xC0, // bits 6-7 select 1 of 4 possible transfer modes
|
||
MODE_DEMAND: 0x00,
|
||
MODE_SINGLE: 0x40,
|
||
MODE_BLOCK: 0x80,
|
||
MODE_CASCADE: 0xC0
|
||
};
|
||
|
||
ChipSet.DMA_REFRESH = 0x00; // DMA channel assigned to memory refresh
|
||
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.
|
||
*
|
||
* TODO: Determine why the original ROM BIOS chose 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.FPU) 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, // MODEL_5170
|
||
COM2: 0x03,
|
||
COM1: 0x04,
|
||
XTC: 0x05, // MODEL_5160 uses IRQ 5 for HDC (XTC version)
|
||
LPT2: 0x05, // MODEL_5170 uses IRQ 5 for LPT2
|
||
FDC: 0x06,
|
||
LPT1: 0x07,
|
||
RTC: 0x08, // MODEL_5170
|
||
IRQ2: 0x09, // MODEL_5170
|
||
FPU: 0x0D, // MODEL_5170
|
||
ATC: 0x0E // MODEL_5170 uses IRQ 14 for HDC (ATC version)
|
||
};
|
||
|
||
/*
|
||
* 8253 Programmable Interval Timer (PIT) I/O ports
|
||
*
|
||
* Although technically, a PIT provides 3 "counters" rather than 3 "timers", we have
|
||
* adopted IBM's TechRef nomenclature, which refers to the PIT's counters as TIMER0,
|
||
* TIMER1, and TIMER2. For machines with a second PIT (eg, the DeskPro 386), we refer
|
||
* to those additional counters as TIMER3, TIMER4, and TIMER5.
|
||
*
|
||
* In addition, if there's a need to refer to a specific PIT, use PIT0 for the first PIT
|
||
* and PIT1 for the second. This mirrors how we refer to multiple DMA controllers
|
||
* (eg, DMA0 and DMA1) and multiple PICs (eg, PIC0 and PIC1).
|
||
*
|
||
* This differs from COMPAQ's nomenclature, which used "Timer 1" to refer to the first
|
||
* PIT, and "Timer 2" for the second PIT, and then referred to "Counter 0", "Counter 1",
|
||
* and "Counter 2" within each PIT.
|
||
*/
|
||
ChipSet.PIT0 = {
|
||
PORT: 0x40,
|
||
INDEX: 0,
|
||
TIMER0: 0, // used for time-of-day (prior to MODEL_5170)
|
||
TIMER1: 1, // used for memory refresh
|
||
TIMER2: 2 // used for speaker tone generation
|
||
};
|
||
|
||
ChipSet.PIT1 = {
|
||
PORT: 0x48, // MODEL_COMPAQ_DESKPRO386 only
|
||
INDEX: 1,
|
||
TIMER3: 0, // used for fail-safe clock
|
||
TIMER4: 1, // N/A
|
||
TIMER5: 2 // used for refresher request extend/speed control
|
||
};
|
||
|
||
ChipSet.PIT_CTRL = {
|
||
PORT1: 0x43, // write-only control register (use the Read-Back command to get status)
|
||
PORT2: 0x4B, // write-only control register (use the Read-Back command to get status)
|
||
BCD: 0x01,
|
||
MODE: 0x0E,
|
||
MODE0: 0x00, // interrupt on Terminal Count (TC)
|
||
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,
|
||
SC_SHIFT: 6,
|
||
RB_CTR0: 0x02,
|
||
RB_CTR1: 0x04,
|
||
RB_CTR2: 0x08,
|
||
RB_STATUS: 0x10, // if this bit is CLEAR, then latch the current status of the selected counter(s)
|
||
RB_COUNTS: 0x20, // if this bit is CLEAR, then latch the current count(s) of the selected counter(s)
|
||
RB_NULL: 0x40, // bit set in Read-Back status byte if the counter has not been "fully loaded" yet
|
||
RB_OUT: 0x80 // bit set in Read-Back status byte if fOUT is true
|
||
};
|
||
|
||
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)
|
||
PORT: 0x60 // INPUT: keyboard scan code (PPI_B.CLEAR_KBD must be clear)
|
||
};
|
||
|
||
ChipSet.PPI_B = { // this.bPPIB (port 0x61)
|
||
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)
|
||
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)
|
||
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
|
||
};
|
||
|
||
/*
|
||
* Switches Overview
|
||
* -----------------
|
||
*
|
||
* 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 on the 5150; OFF (1) if FPU installed in a 5160
|
||
* SW1[3,4] (bits 3-2) "xx11xxxx" (00): 16Kb, "xx01xxxx" (10): 32Kb, "xx10xxxx" (01): 48Kb, "xx00xxxx" (11): 64Kb
|
||
* SW1[5,6] (bits 5-4) "xxxx11xx" (00): none, "xxxx01xx" (10): tv, "xxxx10xx" (01): color, "xxxx00xx" (11): mono
|
||
* SW1[7,8] (bits 7-6) "xxxxxx11" (00): 1 FD, "xxxxxx01" (10): 2 FD, "xxxxxx10" (01): 3 FD, "xxxxxx00" (11): 4 FD
|
||
*
|
||
* Note: FD refers to floppy drive, and IPL refers to an "Initial Program Load" floppy drive.
|
||
*
|
||
* SW2[1-5] (bits 4-0) "NNNxxxxx": number of 32Kb blocks of I/O expansion RAM present
|
||
*
|
||
* 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.
|
||
*
|
||
* MODEL 5150 Switches
|
||
* -------------------
|
||
*
|
||
* PPI_SW bits are exposed via port PPI_A.
|
||
*
|
||
* MODEL 5160 Switches
|
||
* ------------------------
|
||
*
|
||
* PPI_SW bits 0-3 are exposed via PPI_C.SW if PPI_B.ENABLE_SW_HI is clear; bits 4-7 if PPI_B.ENABLE_SW_HI is set.
|
||
*
|
||
* AT&T 6300 Switches
|
||
* ------------------
|
||
*
|
||
* Based on ATT_PC_6300_Service_Manual.pdf, there are two 8-switch blocks, DIPSW-0 and DIPSW-1, where:
|
||
*
|
||
* DIPSW-0[1-4] Total RAM
|
||
* DIPSW-0[5] - ON if 8087 not installed, OFF if installed
|
||
* DIPSW-0[6] - ON if 8250 ACE serial interface present, OFF if Z-8530 SCC interface present
|
||
* DIPSW-0[7] - Not used
|
||
* DIPSW-0[8] - Type of EPROM chip for ROM 1.21 or lower, or presence of RAM in bank 1 for ROM 1.43 or higher
|
||
*
|
||
* and:
|
||
*
|
||
* DIPSW-1[1] - Floppy Type (ON for 48TPI, OFF for 96TPI)
|
||
* DIPSW-1[2] - Floppy Speed (ON for slow startup, OFF for fast startup)
|
||
* DIPSW-1[3] - HDU ROM (ON for indigenous, OFF for external)
|
||
* DIPSW-1[4] - Not used (ROM 1.21 or lower) or Scroll Speed (ROM 1.43 or higher: ON for fast, OFF for slow)
|
||
* DIPSW-1[5-6] - Display Type (11=EGA or none, 01=color 40x25, 10=color 80x25, 00=monochrome 80x25)
|
||
* DIPSW-1[7-8] - Number of Floppy Drives (11=one, 01=two, 10=three, 00=four)
|
||
*
|
||
* For AT&T 6300 ROM 1.43 and up, DIPSW-0 supports the following RAM combinations:
|
||
*
|
||
* 0111xxx1: 128Kb on motherboard
|
||
* 1011xxx0: 256Kb on motherboard
|
||
* 1101xxx0: 256Kb on motherboard, 256Kb on expansion board (512Kb total)
|
||
* 1110xxx1: 512Kb on motherboard
|
||
* 0101xxx0: 256Kb on motherboard, 384Kb on expansion board (640Kb total)
|
||
* 0100xxx0: 640Kb on motherboard (128Kb bank 0, 512Kb bank 1)
|
||
* 0110xxx0: 640Kb on motherboard (512Kb bank 0, 128Kb bank 1)
|
||
*
|
||
* Inspection of the AT&T 6300 Plus ROM BIOS reveals that DIPSW-0[1-8] are obtained from bits 0-7
|
||
* of port 0x66 ("sys_conf_a") and DIPSW-1[1-8] are obtained from bits 0-7 of port 0x67 ("sys_conf_b").
|
||
*/
|
||
|
||
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
|
||
},
|
||
FPU: 0x02, // MODEL_5150: reserved; MODEL_5160: FPU 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
|
||
}
|
||
};
|
||
|
||
/*
|
||
* Some models have completely different DIP switch implementations from the MODEL_5150, which, being
|
||
* the first IBM PC, was the model that we, um, modeled our DIP switch support on. So, to support other
|
||
* implementations, we now get and set DIP switch values according to SWITCH_TYPE, and rely on the
|
||
* tables that follow to define which DIP switch(es) correspond to each SWITCH_TYPE.
|
||
*
|
||
* Not every model needs its own tables. The getDIPSwitches() and setDIPSwitches() functions look first
|
||
* for an *exact* model match, then a "truncated" model match, and failing that, they fall back to the
|
||
* MODEL_5150 switch definitions.
|
||
*/
|
||
ChipSet.SWITCH_TYPE = {
|
||
FLOPNUM: 1,
|
||
FLOPTYPE: 2,
|
||
FPU: 3,
|
||
MONITOR: 4,
|
||
LOWMEM: 5,
|
||
EXPMEM: 6
|
||
};
|
||
|
||
ChipSet.DIPSW = {};
|
||
ChipSet.DIPSW[ChipSet.MODEL_5150] = [{},{}];
|
||
ChipSet.DIPSW[ChipSet.MODEL_5150][0][ChipSet.SWITCH_TYPE.FLOPNUM] = {
|
||
MASK: 0xC0,
|
||
VALUES: {
|
||
1: 0x00,
|
||
2: 0x40,
|
||
3: 0x80,
|
||
4: 0xC0
|
||
},
|
||
LABEL: "Number of Floppy Drives"
|
||
};
|
||
/*
|
||
* NOTE: Both the Aug 1981 and the Apr 1984 IBM 5150 Technical Reference Manuals list SW1[2] as "RESERVED",
|
||
* but the Aug 1981 edition (p. 2-28) also says SW1[2] "MUST BE ON (RESERVED FOR CO-PROCESSOR)". Contemporary
|
||
* articles discussing 8087 support in early PCs all indicate that switch SW1[2] must OFF if a coprocessor
|
||
* is installed, and the 1984 5150 Guide to Operations (p. 5-34) confirms it.
|
||
*
|
||
* The Aug 1981 5150 TechRef makes no further mention of coprocessor support, whereas the Apr 1984 5150 TechRef
|
||
* discusses it in a fair bit of detail, including the fact that 8087 exceptions generate an NMI, despite Intel's
|
||
* warning in their iAPX 86,88 User's Manual, p. S-27, that "[t]he 8087 should not be tied to the CPU's NMI
|
||
* (non-maskable interrupt) line.")
|
||
*/
|
||
ChipSet.DIPSW[ChipSet.MODEL_5150][0][ChipSet.SWITCH_TYPE.FPU] = {
|
||
MASK: 0x02,
|
||
VALUES: {
|
||
0: 0x00, // 0 means an FPU is NOT installed
|
||
1: 0x02 // 1 means an FPU is installed
|
||
},
|
||
LABEL: "FPU"
|
||
};
|
||
ChipSet.DIPSW[ChipSet.MODEL_5150][0][ChipSet.SWITCH_TYPE.MONITOR] = {
|
||
MASK: 0x30,
|
||
VALUES: {
|
||
0: 0x00,
|
||
1: 0x10,
|
||
2: 0x20,
|
||
3: 0x30,
|
||
"none": 0x00,
|
||
"tv": 0x10, // aka composite
|
||
"color":0x20,
|
||
"cga": 0x20, // alias for color
|
||
"mda": 0x30, // alias for mono
|
||
"mono": 0x30,
|
||
"ega": 0x00,
|
||
"vga": 0x00
|
||
},
|
||
LABEL: "Monitor Type"
|
||
};
|
||
ChipSet.DIPSW[ChipSet.MODEL_5150][0][ChipSet.SWITCH_TYPE.LOWMEM] = {
|
||
MASK: 0x0C,
|
||
VALUES: {
|
||
16: 0x00,
|
||
32: 0x04,
|
||
48: 0x08,
|
||
64: 0x0C
|
||
},
|
||
LABEL: "Base Memory (16Kb Increments)"
|
||
};
|
||
ChipSet.DIPSW[ChipSet.MODEL_5150][1][ChipSet.SWITCH_TYPE.EXPMEM] = {
|
||
MASK: 0x1F, // technically, this mask should be 0x0F for ROM revisions prior to 5150_REV3, and 0x1F on 5150_REV3
|
||
VALUES: {
|
||
0: 0x00,
|
||
32: 0x01,
|
||
64: 0x02,
|
||
96: 0x03,
|
||
128: 0x04,
|
||
160: 0x05,
|
||
192: 0x06,
|
||
224: 0x07,
|
||
256: 0x08,
|
||
288: 0x09,
|
||
320: 0x0A,
|
||
352: 0x0B,
|
||
384: 0x0C,
|
||
416: 0x0D,
|
||
448: 0x0E,
|
||
480: 0x0F,
|
||
512: 0x10,
|
||
544: 0x11,
|
||
576: 0x12
|
||
/*
|
||
* Obviously, more bit combinations are possible here (up to 0x1F), but assuming a minimum of 64Kb already on
|
||
* the motherboard, any amount of expansion memory above 576Kb would break the 640Kb barrier. Yes, if you used
|
||
* only MDA or CGA video cards, you could go as high as 704Kb in a real system. But in our happy little world,
|
||
* this is where we stop.
|
||
*
|
||
* TODO: A value larger than 0x12 usually comes from a misconfigured machine (ie, it forgot to leave SW2[5] ON).
|
||
* To compensate, when getDIPMemorySize() gets null back from its EXPMEM request, perhaps it should try truncating
|
||
* the DIP switch value. However, that would introduce a machine-specific hack into a function that's supposed
|
||
* be machine-independent now.
|
||
*/
|
||
},
|
||
LABEL: "Expansion Memory (32Kb Increments)"
|
||
};
|
||
|
||
ChipSet.DIPSW[ChipSet.MODEL_5160] = [{},{}];
|
||
ChipSet.DIPSW[ChipSet.MODEL_5160][0][ChipSet.SWITCH_TYPE.FLOPNUM] = ChipSet.DIPSW[ChipSet.MODEL_5150][0][ChipSet.SWITCH_TYPE.FLOPNUM];
|
||
ChipSet.DIPSW[ChipSet.MODEL_5160][0][ChipSet.SWITCH_TYPE.FPU] = ChipSet.DIPSW[ChipSet.MODEL_5150][0][ChipSet.SWITCH_TYPE.FPU];
|
||
ChipSet.DIPSW[ChipSet.MODEL_5160][0][ChipSet.SWITCH_TYPE.MONITOR] = ChipSet.DIPSW[ChipSet.MODEL_5150][0][ChipSet.SWITCH_TYPE.MONITOR];
|
||
ChipSet.DIPSW[ChipSet.MODEL_5160][0][ChipSet.SWITCH_TYPE.LOWMEM] = {
|
||
MASK: 0x0C,
|
||
VALUES: {
|
||
64: 0x00,
|
||
128: 0x04,
|
||
192: 0x08,
|
||
256: 0x0C
|
||
},
|
||
LABEL: "Base Memory (64Kb Increments)"
|
||
};
|
||
ChipSet.DIPSW[ChipSet.MODEL_5160][1][ChipSet.SWITCH_TYPE.EXPMEM] = ChipSet.DIPSW[ChipSet.MODEL_5150][1][ChipSet.SWITCH_TYPE.EXPMEM];
|
||
|
||
ChipSet.DIPSW[ChipSet.MODEL_ATT_6300] = [{},{}];
|
||
ChipSet.DIPSW[ChipSet.MODEL_ATT_6300][0][ChipSet.SWITCH_TYPE.LOWMEM] = {
|
||
MASK: 0x8F,
|
||
VALUES: {
|
||
128: 0x01, // "0111xxx1"
|
||
256: 0x82, // "1011xxx0"
|
||
512: 0x08, // "1110xxx1"
|
||
640: 0x8D // "0100xxx0"
|
||
},
|
||
LABEL: "Base Memory (128Kb Increments)"
|
||
};
|
||
ChipSet.DIPSW[ChipSet.MODEL_ATT_6300][0][ChipSet.SWITCH_TYPE.FPU] = {
|
||
MASK: 0x10,
|
||
VALUES: {
|
||
0: 0x00,
|
||
1: 0x10
|
||
},
|
||
LABEL: "FPU"
|
||
};
|
||
ChipSet.DIPSW[ChipSet.MODEL_ATT_6300][1][ChipSet.SWITCH_TYPE.FLOPTYPE] = {
|
||
MASK: 0x01,
|
||
VALUES: {
|
||
0: 0x00,
|
||
1: 0x01
|
||
},
|
||
LABEL: "Floppy Type"
|
||
};
|
||
ChipSet.DIPSW[ChipSet.MODEL_ATT_6300][1][ChipSet.SWITCH_TYPE.FLOPNUM] = ChipSet.DIPSW[ChipSet.MODEL_5150][0][ChipSet.SWITCH_TYPE.FLOPNUM];
|
||
ChipSet.DIPSW[ChipSet.MODEL_ATT_6300][1][ChipSet.SWITCH_TYPE.MONITOR] = ChipSet.DIPSW[ChipSet.MODEL_5150][0][ChipSet.SWITCH_TYPE.MONITOR];
|
||
|
||
/*
|
||
* 8041 Keyboard Controller I/O ports (MODEL_ATT_6300)
|
||
*
|
||
* The AT&T 6300 uses an 8041 for its Keyboard Controller, which has the following ports:
|
||
*
|
||
* Port Description
|
||
* ---- -----------
|
||
* 0x60 Keyboard Scan Code (input)
|
||
* 0x61 Keyboard Control Port (output)
|
||
* 0x64 Keyboard Status Port (input)
|
||
*
|
||
* And the Keyboard Control Port (0x61) has the following bit definitions:
|
||
*
|
||
* 0x01 Speaker gate to 8253 (counter 2)
|
||
* 0x02 Speaker data
|
||
* 0x0C Not used
|
||
* 0x10 RAM Parity (NMI) Enable
|
||
* 0x20 I/O Channel (NMI) Enable
|
||
* 0x40 Keyboard Clock Reset
|
||
* 0x80 Reset Interrupt Pending
|
||
*/
|
||
|
||
/*
|
||
* 8042 Keyboard Controller I/O ports (MODEL_5170)
|
||
*
|
||
* On the MODEL_5170, port 0x60 is designated KC8042.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
|
||
* KC8042.STATUS.OUTBUFF_FULL is set.
|
||
*
|
||
* Similarly, port 0x61 is designated KC8042.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 (KC8042.RWREG.CLK_TIMER2 and KC8042.RWREG.SPK_TIMER2), and the BIOS sets
|
||
* bits 2-7 to "DISABLE PARITY CHECKERS" (principally KC8042.RWREG.DISABLE_NMI, 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 KC8042.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/ibm-pcat-8042-keyboard-controller-commands/
|
||
*/
|
||
ChipSet.KC8042 = {
|
||
DATA: { // this.b8042OutBuff (PPI_A on previous models, still referred to as "PORT A" by the MODEL_5170 BIOS)
|
||
PORT: 0x60,
|
||
CMD: { // this.b8042CmdData (KC8042.DATA.CMD "data bytes" written to port 0x60, after writing a KC8042.CMD byte to port 0x64)
|
||
INT_ENABLE: 0x01, // generate an interrupt when the controller places data in the output buffer
|
||
SYS_FLAG: 0x04, // this value is propagated to ChipSet.KC8042.STATUS.SYS_FLAG
|
||
NO_INHIBIT: 0x08, // disable inhibit function
|
||
NO_CLOCK: 0x10, // disable keyboard by driving "clock" line low
|
||
PC_MODE: 0x20,
|
||
PC_COMPAT: 0x40 // generate IBM PC-compatible scan codes
|
||
},
|
||
SELF_TEST: { // result of ChipSet.KC8042.CMD.SELF_TEST command (0xAA)
|
||
OK: 0x55
|
||
},
|
||
INTF_TEST: { // result of ChipSet.KC8042.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
|
||
COMPAQ_50MHZ: 0x01, // 50Mhz system clock enabled (0=48Mhz); see COMPAQ 386/25 TechRef p2-106
|
||
UNDEFINED: 0x02, // undefined
|
||
COMPAQ_NO80387: 0x04, // 80387 coprocessor NOT installed; see COMPAQ 386/25 TechRef p2-106
|
||
COMPAQ_NOWEITEK:0x08, // Weitek coprocessor NOT installed; see COMPAQ 386/25 TechRef p2-106
|
||
ENABLE_256KB: 0x10, // enable 2nd 256Kb of system board RAM
|
||
COMPAQ_HISPEED: 0x10, // high-speed enabled (0=auto); see COMPAQ 386/25 TechRef p2-106
|
||
MFG_OFF: 0x20, // manufacturing jumper not installed
|
||
COMPAQ_DIP5OFF: 0x20, // system board DIP switch #5 OFF (0=ON); see COMPAQ 386/25 TechRef p2-106
|
||
MONO: 0x40, // monochrome monitor is primary display
|
||
COMPAQ_NONDUAL: 0x40, // COMPAQ Dual-Mode monitor NOT installed; see COMPAQ 386/25 TechRef p2-106
|
||
KBD_UNLOCKED: 0x80 // keyboard not inhibited (in COMPAQ parlance: security lock is unlocked)
|
||
},
|
||
OUTPORT: { // this.b8042OutPort
|
||
NO_RESET: 0x01, // set by default
|
||
A20_ON: 0x02, // set by default
|
||
COMPAQ_SLOWD: 0x08, // SL0WD* NOT asserted (refer to timer 2, counter 2); see COMPAQ 386/25 TechRef p2-105
|
||
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 RWREG)
|
||
PORT: 0x61,
|
||
CLK_TIMER2: 0x01, // set to enable clock to TIMER2 (R/W)
|
||
SPK_TIMER2: 0x02, // set to connect output of TIMER2 to speaker (R/W)
|
||
COMPAQ_FSNMI: 0x04, // set to disable RAM/FS NMI (R/W, DESKPRO386)
|
||
COMPAQ_IONMI: 0x08, // set to disable IOCHK NMI (R/W, DESKPRO386)
|
||
DISABLE_NMI: 0x0C, // set to disable IOCHK and RAM/FS NMI, clear to enable (R/W)
|
||
REFRESH_BIT: 0x10, // 0 if RAM refresh occurring, 1 if RAM not in refresh cycle (R/O)
|
||
OUT_TIMER2: 0x20, // state of TIMER2 output signal (R/O, DESKPRO386)
|
||
IOCHK_NMI: 0x40, // IOCHK NMI (R/O); to reset, pulse bit 3 (0x08)
|
||
RAMFS_NMI: 0x80, // RAM/FS (parity or fail-safe) NMI (R/O); to reset, pulse bit 2 (0x04)
|
||
NMI_ERROR: 0xC0
|
||
},
|
||
CMD: { // this.b8042InBuff (on write to port 0x64, interpret this as a CMD)
|
||
PORT: 0x64,
|
||
READ_CMD: 0x20, // sends the current CMD byte (this.b8042CmdData) to KC8042.DATA.PORT
|
||
WRITE_CMD: 0x60, // followed by a command byte written to KC8042.DATA.PORT (see KC8042.DATA.CMD)
|
||
COMPAQ_SLOWD: 0xA3, // enable system slow down; see COMPAQ 386/25 TechRef p2-111
|
||
COMPAQ_TOGGLE: 0xA4, // toggle speed-control bit; see COMPAQ 386/25 TechRef p2-111
|
||
COMPAQ_SPCREAD: 0xA5, // special read of "port 2"; see COMPAQ 386/25 TechRef p2-111
|
||
SELF_TEST: 0xAA, // self-test (KC8042.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 KC8042.DATA.PORT (port 0x60) is placed in the output port (see KC8042.OUTPORT)
|
||
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 from the input buffer (not normally set)
|
||
SYS_FLAG: 0x04,
|
||
CMD_FLAG: 0x08, // set on write to KC8042.CMD (port 0x64), clear on write to KC8042.DATA (port 0x60)
|
||
NO_INHIBIT: 0x10, // (in COMPAQ parlance: security lock not engaged)
|
||
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,
|
||
STATUSA: 0x0A,
|
||
STATUSB: 0x0B,
|
||
STATUSC: 0x0C,
|
||
STATUSD: 0x0D,
|
||
DIAG: 0x0E,
|
||
SHUTDOWN: 0x0F,
|
||
FDRIVE: 0x10,
|
||
HDRIVE: 0x12, // bits 4-7 contain type of drive 0, bits 0-3 contain type of drive 1 (type 0 means none)
|
||
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)
|
||
EXTHDRIVE0: 0x19, // if bits 4-7 of HDRIVE contains 15, then the type of drive 0 is stored here (16-255)
|
||
EXTHDRIVE1: 0x1A, // if bits 0-3 of HDRIVE contains 15, then the type of drive 1 is stored here (16-255)
|
||
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.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.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 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.STATUSC]
|
||
IRQF: 0x80, // bit 7: 1 indicates one or more of the following bits (PF, AF, UF) are set
|
||
PF: 0x40, // bit 6: 1 indicates Periodic Interrupt
|
||
AF: 0x20, // bit 5: 1 indicates Alarm Interrupt
|
||
UF: 0x10, // bit 4: 1 indicates Update Interrupt
|
||
RESERVED: 0x0F
|
||
},
|
||
STATUSD: { // abCMOSData[ChipSet.CMOS.ADDR.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 getDIPFloppyDriveType(), 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
|
||
FPU: ChipSet.PPI_SW.FPU, // PPI_SW.FPU == 0x02
|
||
FDRIVE: ChipSet.PPI_SW.FDRIVE // PPI_SW.FDRIVE.IPL == 0x01 and PPI_SW.FDRIVE.MASK = 0xC0
|
||
}
|
||
};
|
||
|
||
/*
|
||
* DMA Page Registers
|
||
*
|
||
* The MODEL_5170 TechRef lists 0x80-0x9F as the range for DMA page registers, but that may be a bit
|
||
* overbroad. There are a total of 8 (7 usable) DMA channels on the MODEL_5170, each of which has the
|
||
* following assigned DMA page registers:
|
||
*
|
||
* Channel # Page Reg
|
||
* --------- --------
|
||
* 0 0x87
|
||
* 1 0x83
|
||
* 2 0x81
|
||
* 3 0x82
|
||
* 4 0x8F (not usable; the 5170 TechRef refers to this as the "Refresh" page register)
|
||
* 5 0x8B
|
||
* 6 0x89
|
||
* 7 0x8A
|
||
*
|
||
* That leaves 0x80, 0x84, 0x85, 0x86, 0x88, 0x8C, 0x8D and 0x8E unaccounted for in the range 0x80-0x8F.
|
||
* (I'm saving the question of what, if anything, is available in the range 0x90-0x9F for another day.)
|
||
*
|
||
* As for port 0x80, the TechRef 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 (74LS612)."
|
||
*
|
||
* so I used to have dedicated handlers and storage (bMFGData) for the register at port 0x80, but I've since
|
||
* appended it to abDMAPageSpare, an 8-element array that captures all I/O to the 8 unassigned (aka "spare")
|
||
* DMA page registers. The 5170 BIOS uses 0x80 as a "checkpoint" register, and the DESKPRO386 uses 0x84 in a
|
||
* similar fashion. The 5170 also contains "MFG_TST" code that uses other unassigned DMA page registers as
|
||
* scratch registers, which come in handy when RAM hasn't been tested/initialized yet.
|
||
*
|
||
* Here's our mapping of entries in the abDMAPageSpare array to the unassigned ("spare") DMA page registers:
|
||
*
|
||
* Index # Page Reg
|
||
* -------- --------
|
||
* 0 0x84
|
||
* 1 0x85
|
||
* 2 0x86
|
||
* 3 0x88
|
||
* 4 0x8C
|
||
* 5 0x8D
|
||
* 6 0x8E
|
||
* 7 0x80
|
||
*
|
||
* The only reason port 0x80 is out of sequence (ie, at the end of the array, at index 7 instead of index 0) is
|
||
* because it was added the array later, and the entire array gets written to our save/restore data structures, so
|
||
* reordering the elements would be a bad idea.
|
||
*/
|
||
|
||
/*
|
||
* NMI Mask Register (MODEL_5150 and MODEL_5160 only)
|
||
*/
|
||
ChipSet.NMI = { // this.bNMI
|
||
PORT: 0xA0,
|
||
ENABLE: 0x80,
|
||
DISABLE: 0x00
|
||
};
|
||
|
||
/*
|
||
* FPU Coprocessor Control Registers (MODEL_5170)
|
||
*/
|
||
ChipSet.FPU = { // TODO: Define a variable for this?
|
||
PORT_CLEAR: 0xF0, // clear the FPU's "busy" state
|
||
PORT_RESET: 0xF1 // reset the FPU
|
||
};
|
||
|
||
/**
|
||
* setBinding(sHTMLType, sBinding, control, sValue)
|
||
*
|
||
* @this {ChipSet}
|
||
* @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)
|
||
* @param {string} [sValue] optional data value
|
||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||
*/
|
||
ChipSet.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
|
||
{
|
||
switch (sBinding) {
|
||
|
||
case ChipSet.CONTROLS.SW1:
|
||
this.bindings[sBinding] = control;
|
||
this.addDIPSwitches(0, sBinding);
|
||
return true;
|
||
|
||
case ChipSet.CONTROLS.SW2:
|
||
if ((this.model|0) == ChipSet.MODEL_5150 || this.model == ChipSet.MODEL_ATT_6300) {
|
||
this.bindings[sBinding] = control;
|
||
this.addDIPSwitches(1, sBinding);
|
||
return true;
|
||
}
|
||
break;
|
||
|
||
case ChipSet.CONTROLS.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 {DebuggerX86} dbg
|
||
*/
|
||
ChipSet.prototype.initBus = function(cmp, bus, cpu, dbg)
|
||
{
|
||
this.bus = bus;
|
||
this.cpu = cpu;
|
||
this.dbg = dbg;
|
||
this.cmp = cmp;
|
||
|
||
this.fpu = cmp.getMachineComponent("FPU");
|
||
this.setDIPSwitches(ChipSet.SWITCH_TYPE.FPU, this.fpu?1:0, true);
|
||
|
||
this.kbd = cmp.getMachineComponent("Keyboard");
|
||
|
||
/*
|
||
* This divisor is invariant, so we calculate it as soon as we're able to query the CPU's base speed.
|
||
*/
|
||
this.nTicksDivisor = (cpu.getCyclesPerSecond() / ChipSet.TIMER_TICKS_PER_SEC);
|
||
|
||
bus.addPortInputTable(this, ChipSet.aPortInput);
|
||
bus.addPortOutputTable(this, ChipSet.aPortOutput);
|
||
if (this.model < ChipSet.MODEL_5170) {
|
||
if (this.model != ChipSet.MODEL_ATT_6300) {
|
||
bus.addPortInputTable(this, ChipSet.aPortInput5150);
|
||
bus.addPortOutputTable(this, ChipSet.aPortOutput5150);
|
||
} else {
|
||
bus.addPortInputTable(this, ChipSet.aPortInput6300);
|
||
bus.addPortOutputTable(this, ChipSet.aPortOutput6300);
|
||
}
|
||
} else {
|
||
bus.addPortInputTable(this, ChipSet.aPortInput5170);
|
||
bus.addPortOutputTable(this, ChipSet.aPortOutput5170);
|
||
if (DESKPRO386 && (this.model|0) == ChipSet.MODEL_COMPAQ_DESKPRO386) {
|
||
bus.addPortInputTable(this, ChipSet.aPortInputDeskPro386);
|
||
bus.addPortOutputTable(this, ChipSet.aPortOutputDeskPro386);
|
||
}
|
||
}
|
||
if (DEBUGGER) {
|
||
if (dbg) {
|
||
var chipset = this;
|
||
/*
|
||
* TODO: Add more "dumpers" (eg, for DMA, RTC, 8042, etc)
|
||
*/
|
||
dbg.messageDump(Messages.PIC, function onDumpPIC() {
|
||
chipset.dumpPIC();
|
||
});
|
||
dbg.messageDump(Messages.TIMER, function onDumpTimer(asArgs) {
|
||
chipset.dumpTimer(asArgs);
|
||
});
|
||
dbg.messageDump(Messages.CMOS, function onDumpCMOS() {
|
||
chipset.dumpCMOS();
|
||
});
|
||
}
|
||
cpu.addIntNotify(Interrupts.RTC, this.intBIOSRTC.bind(this));
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 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, fShutdown)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {boolean} [fSave]
|
||
* @param {boolean} [fShutdown]
|
||
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
|
||
*/
|
||
ChipSet.prototype.powerDown = function(fSave, fShutdown)
|
||
{
|
||
return fSave? this.save() : true;
|
||
};
|
||
|
||
/**
|
||
* reset(fHard)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {boolean} [fHard] true on the initial reset (not a normal "soft" reset)
|
||
*/
|
||
ChipSet.prototype.reset = function(fHard)
|
||
{
|
||
/*
|
||
* We propagate the initial DIP switch values to the current DIP switch values on reset;
|
||
* the user is only allowed to tweak the initial values, which require a reset to take effect.
|
||
*/
|
||
var i;
|
||
this.updateDIPSwitches();
|
||
|
||
/*
|
||
* DMA (Direct Memory Access) Controller initialization
|
||
*/
|
||
this.aDMACs = new Array(this.cDMACs);
|
||
for (i = 0; i < this.cDMACs; i++) {
|
||
this.initDMAController(i);
|
||
}
|
||
|
||
/*
|
||
* PIC (Programmable Interupt Controller) 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);
|
||
}
|
||
|
||
/*
|
||
* PIT (Programmable Interval Timer) initialization
|
||
*
|
||
* Although the DeskPro 386 refers to the timers in the first PIT as "Timer 1, Counter 0",
|
||
* "Timer 1, Counter 1" and "Timer 1, Counter 2", we're sticking with IBM's nomenclature:
|
||
* TIMER0, TIMER1 and TIMER2. Which means that we refer to the "counters" in the second PIT
|
||
* as TIMER3, TIMER4 and TIMER5; that numbering also matches their indexes in the aTimers array.
|
||
*/
|
||
this.bPIT0Ctrl = null; // tracks writes to port 0x43
|
||
this.bPIT1Ctrl = null; // tracks writes to port 0x4B (MODEL_COMPAQ_DESKPRO386 only)
|
||
this.aTimers = new Array((this.model|0) == ChipSet.MODEL_COMPAQ_DESKPRO386? 6 : 3);
|
||
for (i = 0; i < this.aTimers.length; i++) {
|
||
this.initTimer(i);
|
||
}
|
||
|
||
/*
|
||
* PPI and other misc ports
|
||
*/
|
||
this.bPPIA = null; // tracks writes to port 0x60, in case PPI_CTRL.A_IN is not set
|
||
this.bPPIB = null; // tracks writes to port 0x61, in case PPI_CTRL.B_IN is not set
|
||
this.bPPIC = null; // tracks writes to port 0x62, in case PPI_CTRL.C_IN_LO or PPI_CTRL.C_IN_HI is not set
|
||
this.bPPICtrl = null; // tracks writes to port 0x63 (eg, 0x99); read-only
|
||
this.bNMI = ChipSet.NMI.DISABLE;// tracks writes to the NMI Mask Register
|
||
|
||
if (this.model == ChipSet.MODEL_ATT_6300) {
|
||
this.b8041Status = 0; // similar to b8042Status (but apparently only bits 0 and 1 are used)
|
||
}
|
||
|
||
/*
|
||
* 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 KC8042.CMD.WRITE_CMD "command byte" is written to the input
|
||
* buffer, the subsequent command data byte is saved in b8042CmdData. Similarly, for KC8042.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.KC8042.STATUS.NO_INHIBIT;
|
||
this.b8042InBuff = 0;
|
||
this.b8042CmdData = ChipSet.KC8042.DATA.CMD.NO_CLOCK;
|
||
this.b8042OutBuff = 0;
|
||
|
||
/*
|
||
* TODO: Provide more control over these 8042 "Input Port" bits (eg, the keyboard lock)
|
||
*/
|
||
this.b8042InPort = ChipSet.KC8042.INPORT.MFG_OFF | ChipSet.KC8042.INPORT.KBD_UNLOCKED;
|
||
|
||
if (this.getDIPMemorySize() >= 512) {
|
||
this.b8042InPort |= ChipSet.KC8042.INPORT.ENABLE_256KB;
|
||
}
|
||
|
||
if (this.getDIPVideoMonitor() == ChipSet.MONITOR.MONO) {
|
||
this.b8042InPort |= ChipSet.KC8042.INPORT.MONO;
|
||
}
|
||
|
||
if (DESKPRO386 && (this.model|0) == ChipSet.MODEL_COMPAQ_DESKPRO386) {
|
||
this.b8042InPort |= ChipSet.KC8042.INPORT.COMPAQ_NO80387 | ChipSet.KC8042.INPORT.COMPAQ_NOWEITEK;
|
||
}
|
||
|
||
this.b8042OutPort = ChipSet.KC8042.OUTPORT.NO_RESET | ChipSet.KC8042.OUTPORT.A20_ON;
|
||
|
||
this.abDMAPageSpare = new Array(8);
|
||
|
||
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.initRTCTime(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 = [];
|
||
}
|
||
};
|
||
|
||
/**
|
||
* initRTCTime(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 updateRTCTime() 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.initRTCTime = 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.abCMOSData[ChipSet.CMOS.ADDR.STATUSA] = 0x26; // hard-coded default; refer to ChipSet.CMOS.STATUSA.DV and ChipSet.CMOS.STATUSA.RS
|
||
this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] = ChipSet.CMOS.STATUSB.HOUR24; // default to BCD mode (ChipSet.CMOS.STATUSB.BINARY not set)
|
||
this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC] = 0x00;
|
||
this.abCMOSData[ChipSet.CMOS.ADDR.STATUSD] = ChipSet.CMOS.STATUSD.VRB;
|
||
|
||
this.nRTCCyclesLastUpdate = this.nRTCCyclesNextUpdate = 0;
|
||
this.nRTCPeriodsPerSecond = this.nRTCCyclesPerPeriod = null;
|
||
};
|
||
|
||
/**
|
||
* getRTCByte(iRTC)
|
||
*
|
||
* @param {number} iRTC
|
||
* @return {number} b
|
||
*/
|
||
ChipSet.prototype.getRTCByte = function(iRTC)
|
||
{
|
||
this.assert(iRTC >= 0 && iRTC <= ChipSet.CMOS.ADDR.STATUSD);
|
||
|
||
var b = this.abCMOSData[iRTC];
|
||
|
||
if (iRTC < ChipSet.CMOS.ADDR.STATUSA) {
|
||
var f12HourValue = false;
|
||
if (iRTC == ChipSet.CMOS.ADDR.RTC_HOUR || iRTC == ChipSet.CMOS.ADDR.RTC_HOUR_ALRM) {
|
||
if (!(this.abCMOSData[ChipSet.CMOS.ADDR.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.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.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)
|
||
{
|
||
this.assert(iRTC >= 0 && iRTC <= ChipSet.CMOS.ADDR.STATUSD);
|
||
|
||
if (iRTC < ChipSet.CMOS.ADDR.STATUSA) {
|
||
var fBCD = false;
|
||
if (!(this.abCMOSData[ChipSet.CMOS.ADDR.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 > 23) {
|
||
this.assert(b >= 0x51 && b <= 0x5c);
|
||
b += 0x30;
|
||
}
|
||
}
|
||
if (!(this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.HOUR24)) {
|
||
if (b <= 12) {
|
||
b = (b == 12? 0 : b);
|
||
} else {
|
||
b -= (0x80 - 12);
|
||
b = (b == 24? 12 : b);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* calcRTCCyclePeriod()
|
||
*
|
||
* This should be called whenever the timings in STATUSA may have changed.
|
||
*
|
||
* TODO: 1024 is a hard-coded number of periods per second based on the default interrupt rate of 976.562us
|
||
* (ie, 1000000 / 976.562). Calculate the actual number based on the values programmed in the STATUSA register.
|
||
*
|
||
* @this {ChipSet}
|
||
*/
|
||
ChipSet.prototype.calcRTCCyclePeriod = function()
|
||
{
|
||
this.nRTCCyclesLastUpdate = this.cpu.getCycles(this.fScaleTimers);
|
||
this.nRTCPeriodsPerSecond = 1024;
|
||
this.nRTCCyclesPerPeriod = Math.floor(this.cpu.getCyclesPerSecond() / this.nRTCPeriodsPerSecond);
|
||
this.setRTCCycleLimit();
|
||
};
|
||
|
||
/**
|
||
* getRTCCycleLimit(nCycles)
|
||
*
|
||
* This is called by the CPU to determine the maximum number of cycles it can process for the current burst.
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} nCycles desired
|
||
* @return {number} maximum number of cycles (<= nCycles)
|
||
*/
|
||
ChipSet.prototype.getRTCCycleLimit = function(nCycles)
|
||
{
|
||
if (this.abCMOSData && this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.PIE) {
|
||
var nCyclesUpdate = this.nRTCCyclesNextUpdate - this.cpu.getCycles(this.fScaleTimers);
|
||
if (nCyclesUpdate > 0) {
|
||
if (nCycles > nCyclesUpdate) {
|
||
if (DEBUG && this.messageEnabled(Messages.RTC)) {
|
||
this.printMessage("getRTCCycleLimit(" + nCycles + "): reduced to " + nCyclesUpdate + " cycles", true);
|
||
}
|
||
nCycles = nCyclesUpdate;
|
||
} else {
|
||
if (DEBUG && this.messageEnabled(Messages.RTC)) {
|
||
this.printMessage("getRTCCycleLimit(" + nCycles + "): already less than " + nCyclesUpdate + " cycles", true);
|
||
}
|
||
}
|
||
} else {
|
||
if (DEBUG && this.messageEnabled(Messages.RTC)) {
|
||
this.printMessage("RTC next update has passed by " + nCyclesUpdate + " cycles", true);
|
||
}
|
||
}
|
||
}
|
||
return nCycles;
|
||
};
|
||
|
||
/**
|
||
* setRTCCycleLimit(nCycles)
|
||
*
|
||
* This should be called when PIE becomes set in STATUSB (and whenever PF is cleared in STATUSC while PIE is still set).
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} [nCycles]
|
||
*/
|
||
ChipSet.prototype.setRTCCycleLimit = function(nCycles)
|
||
{
|
||
if (nCycles === undefined) nCycles = this.nRTCCyclesPerPeriod;
|
||
this.nRTCCyclesNextUpdate = this.cpu.getCycles(this.fScaleTimers) + nCycles;
|
||
if (this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.PIE) {
|
||
this.cpu.setBurstCycles(nCycles);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* updateRTCTime()
|
||
*
|
||
* @this {ChipSet}
|
||
*/
|
||
ChipSet.prototype.updateRTCTime = function()
|
||
{
|
||
var nCyclesPerSecond = this.cpu.getCyclesPerSecond();
|
||
var nCyclesUpdate = this.cpu.getCycles(this.fScaleTimers);
|
||
|
||
/*
|
||
* We must arrange for the very first calcRTCCyclePeriod() call to occur here, on the very first
|
||
* updateRTCTime() call, because this is the first point we can be guaranteed that CPU cycle counts
|
||
* are initialized (the CPU is the last component to be powered up/restored).
|
||
*
|
||
* TODO: A side-effect of this is that it undermines the save/restore code's preservation of last
|
||
* and next RTC cycle counts, which may affect when the next RTC event is delivered.
|
||
*/
|
||
if (this.nRTCCyclesPerPeriod == null) this.calcRTCCyclePeriod();
|
||
|
||
/*
|
||
* Step 1: Deal with Periodic Interrupts
|
||
*/
|
||
if (nCyclesUpdate >= this.nRTCCyclesNextUpdate) {
|
||
var bPrev = this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC];
|
||
this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC] |= ChipSet.CMOS.STATUSC.PF;
|
||
if (this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.PIE) {
|
||
/*
|
||
* When PIE is set, setBurstCycles() should be getting called as needed to ensure
|
||
* that updateRTCTime() is called more frequently, so let's assert that we don't have
|
||
* an excess of cycles and thus possibly some missed Periodic Interrupts.
|
||
*/
|
||
if (DEBUG) {
|
||
if (nCyclesUpdate - this.nRTCCyclesNextUpdate > this.nRTCCyclesPerPeriod) {
|
||
if (bPrev & ChipSet.CMOS.STATUSC.PF) {
|
||
this.printMessage("RTC interrupt handler failed to clear STATUSC", Messages.RTC);
|
||
} else {
|
||
this.printMessage("CPU took too long trigger new RTC periodic interrupt", Messages.RTC);
|
||
}
|
||
}
|
||
}
|
||
this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC] |= ChipSet.CMOS.STATUSC.IRQF;
|
||
this.setIRR(ChipSet.IRQ.RTC);
|
||
/*
|
||
* We could also call setRTCCycleLimit() at this point, but I don't think there's any
|
||
* benefit until the interrupt had been acknowledged and STATUSC has been read, thereby
|
||
* clearing the way for another Periodic Interrupt; it seems to me that when STATUSC
|
||
* is read, that's the more appropriate time to call setRTCCycleLimit().
|
||
*/
|
||
}
|
||
this.nRTCCyclesNextUpdate = nCyclesUpdate + this.nRTCCyclesPerPeriod;
|
||
}
|
||
|
||
/*
|
||
* Step 2: Deal with Alarm Interrupts
|
||
*/
|
||
if (this.abCMOSData[ChipSet.CMOS.ADDR.RTC_SEC] == this.abCMOSData[ChipSet.CMOS.ADDR.RTC_SEC_ALRM]) {
|
||
if (this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MIN] == this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MIN_ALRM]) {
|
||
if (this.abCMOSData[ChipSet.CMOS.ADDR.RTC_HOUR] == this.abCMOSData[ChipSet.CMOS.ADDR.RTC_HOUR_ALRM]) {
|
||
this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC] |= ChipSet.CMOS.STATUSC.AF;
|
||
if (this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.AIE) {
|
||
this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC] |= ChipSet.CMOS.STATUSC.IRQF;
|
||
this.setIRR(ChipSet.IRQ.RTC);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Step 3: Update the RTC date/time and deal with Update Interrupts
|
||
*/
|
||
var nCyclesDelta = nCyclesUpdate - this.nRTCCyclesLastUpdate;
|
||
// DEBUG: this.assert(nCyclesDelta >= 0);
|
||
var nSecondsDelta = Math.floor(nCyclesDelta / nCyclesPerSecond);
|
||
|
||
/*
|
||
* We trust that updateRTCTime() 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 nRTCCyclesLastUpdate, so that we will eventually
|
||
* see a one-second delta.
|
||
*/
|
||
// DEBUG: this.assert(nSecondsDelta <= 1);
|
||
|
||
/*
|
||
* Make sure that CMOS.STATUSB.SET isn't set; if it is, then the once-per-second RTC updates must be
|
||
* disabled so that software can write new RTC date/time values without interference.
|
||
*/
|
||
if (nSecondsDelta && !(this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.SET)) {
|
||
while (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.abCMOSData[ChipSet.CMOS.ADDR.STATUSC] |= ChipSet.CMOS.STATUSC.UF;
|
||
if (this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.UIE) {
|
||
this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC] |= ChipSet.CMOS.STATUSC.IRQF;
|
||
this.setIRR(ChipSet.IRQ.RTC);
|
||
}
|
||
}
|
||
|
||
this.nRTCCyclesLastUpdate = 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.FPU, CMOS.EQUIP.MONITOR.CGA80, etc).
|
||
*/
|
||
this.abCMOSData[ChipSet.CMOS.ADDR.EQUIP] = this.getDIPLegacyBits(0);
|
||
this.abCMOSData[ChipSet.CMOS.ADDR.FDRIVE] = (this.getDIPFloppyDriveType(0) << 4) | this.getDIPFloppyDriveType(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) {
|
||
this.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 (0 or 1)
|
||
* @param {number} bType (0 for none, 1-14 for original drive type, 16-255 for extended drive type; 15 reserved)
|
||
* @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 bExt = null, iExt;
|
||
var bOrig = this.abCMOSData[ChipSet.CMOS.ADDR.HDRIVE];
|
||
if (bType > 15) {
|
||
bExt = bType; bType = 15;
|
||
}
|
||
if (iDrive) {
|
||
bOrig = (bOrig & ChipSet.CMOS.HDRIVE.D0_MASK) | bType;
|
||
iExt = ChipSet.CMOS.ADDR.EXTHDRIVE1;
|
||
} else {
|
||
bOrig = (bOrig & ChipSet.CMOS.HDRIVE.D1_MASK) | (bType << 4);
|
||
iExt = ChipSet.CMOS.ADDR.EXTHDRIVE0;
|
||
}
|
||
this.setCMOSByte(ChipSet.CMOS.ADDR.HDRIVE, bOrig);
|
||
if (bExt != null) this.setCMOSByte(iExt, bExt);
|
||
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 implements save support for the ChipSet component.
|
||
*
|
||
* @this {ChipSet}
|
||
* @return {Object}
|
||
*/
|
||
ChipSet.prototype.save = function()
|
||
{
|
||
var state = new State(this);
|
||
state.set(0, [this.aDIPSwitches]);
|
||
state.set(1, [this.saveDMAControllers()]);
|
||
state.set(2, [this.savePICs()]);
|
||
state.set(3, [this.bPIT0Ctrl, this.saveTimers(), this.bPIT1Ctrl]);
|
||
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.abDMAPageSpare[7], this.abDMAPageSpare, this.bCMOSAddr, this.abCMOSData, this.nRTCCyclesLastUpdate, this.nRTCCyclesNextUpdate]);
|
||
}
|
||
return state.data();
|
||
};
|
||
|
||
/**
|
||
* restore(data)
|
||
*
|
||
* This implements restore support for the ChipSet component.
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {Object} data
|
||
* @return {boolean} true if successful, false if failure
|
||
*/
|
||
ChipSet.prototype.restore = function(data)
|
||
{
|
||
var a, i;
|
||
a = data[0];
|
||
|
||
if (Array.isArray(a[0])) {
|
||
this.aDIPSwitches = a[0];
|
||
} else {
|
||
this.aDIPSwitches[0][0] = a[0];
|
||
this.aDIPSwitches[1][0] = a[1] & 0x0F; // we do honor SW2[5] now, but it was erroneously set on some machines
|
||
this.aDIPSwitches[0][1] = a[2];
|
||
this.aDIPSwitches[1][1] = a[3] & 0x0F; // we do honor SW2[5] now, but it was erroneously set on some machines
|
||
}
|
||
this.updateDIPSwitches();
|
||
|
||
a = data[1];
|
||
for (i = 0; i < this.cDMACs; i++) {
|
||
this.initDMAController(i, a.length == 1? a[0][i] : a);
|
||
}
|
||
|
||
a = data[2];
|
||
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.bPIT0Ctrl = a[0];
|
||
this.bPIT1Ctrl = a[2];
|
||
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) {
|
||
this.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) {
|
||
this.assert(this.model >= ChipSet.MODEL_5170);
|
||
this.abDMAPageSpare = a[1];
|
||
this.abDMAPageSpare[7] = a[0]; // formerly bMFGData
|
||
this.bCMOSAddr = a[2];
|
||
this.abCMOSData = a[3];
|
||
this.nRTCCyclesLastUpdate = a[4];
|
||
this.nRTCCyclesNextUpdate = a[5];
|
||
/*
|
||
* 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 initRTCTime()
|
||
* could be avoided.
|
||
*/
|
||
this.initRTCTime();
|
||
}
|
||
return true;
|
||
};
|
||
|
||
/**
|
||
* start()
|
||
*
|
||
* Notification from the CPU that it's starting.
|
||
*
|
||
* @this {ChipSet}
|
||
*/
|
||
ChipSet.prototype.start = function()
|
||
{
|
||
/*
|
||
* Currently, all we do with this notification is allow the speaker to make noise.
|
||
*/
|
||
this.setSpeaker();
|
||
};
|
||
|
||
/**
|
||
* stop()
|
||
*
|
||
* Notification from the CPU that it's stopping.
|
||
*
|
||
* @this {ChipSet}
|
||
*/
|
||
ChipSet.prototype.stop = function()
|
||
{
|
||
/*
|
||
* Currently, all we do with this notification is prevent the speaker from making noise.
|
||
*/
|
||
this.setSpeaker();
|
||
};
|
||
|
||
ChipSet.aDMAControllerInit = [0, null, null, 0, new Array(4), 0];
|
||
|
||
/**
|
||
* initDMAController(iDMAC, aState)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDMAC
|
||
* @param {Array} [aState]
|
||
*/
|
||
ChipSet.prototype.initDMAController = function(iDMAC, aState)
|
||
{
|
||
var controller = this.aDMACs[iDMAC];
|
||
if (!controller) {
|
||
this.assert(!aState);
|
||
controller = {
|
||
aChannels: new Array(4)
|
||
};
|
||
}
|
||
var a = aState && aState.length >= 5? aState : ChipSet.aDMAControllerInit;
|
||
controller.bStatus = a[0];
|
||
controller.bCmd = a[1];
|
||
controller.bReq = a[2];
|
||
controller.bIndex = a[3];
|
||
controller.nChannelBase = iDMAC << 2;
|
||
for (var iChannel = 0; iChannel < controller.aChannels.length; iChannel++) {
|
||
this.initDMAChannel(controller, iChannel, a[4][iChannel]);
|
||
}
|
||
controller.bTemp = a[5] || 0; // not present in older states
|
||
this.aDMACs[iDMAC] = controller;
|
||
};
|
||
|
||
ChipSet.aDMAChannelInit = [true, [0,0], [0,0], [0,0], [0,0]];
|
||
|
||
/**
|
||
* 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 (!channel) {
|
||
this.assert(!aState);
|
||
channel = {
|
||
addrInit: [0,0],
|
||
countInit: [0,0],
|
||
addrCurrent: [0,0],
|
||
countCurrent: [0,0]
|
||
};
|
||
}
|
||
var a = aState && aState.length == 8? aState : ChipSet.aDMAChannelInit;
|
||
channel.masked = a[0];
|
||
channel.addrInit[0] = a[1][0]; channel.addrInit[1] = a[1][1];
|
||
channel.countInit[0] = a[2][0]; channel.countInit[1] = a[2][1];
|
||
channel.addrCurrent[0] = a[3][0]; channel.addrCurrent[1] = a[3][1];
|
||
channel.countCurrent[0] = a[4][0]; channel.countCurrent[1] = a[4][1];
|
||
channel.mode = a[5];
|
||
channel.bPage = a[6];
|
||
// a[7] is deprecated
|
||
channel.controller = controller;
|
||
channel.iChannel = iChannel;
|
||
this.initDMAFunction(channel, a[8], a[9]);
|
||
controller.aChannels[iChannel] = channel;
|
||
};
|
||
|
||
/**
|
||
* 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 controller = this.aDMACs[iDMAC];
|
||
data[iDMAC] = [
|
||
controller.bStatus,
|
||
controller.bCmd,
|
||
controller.bReq,
|
||
controller.bIndex,
|
||
this.saveDMAChannels(controller),
|
||
controller.bTemp
|
||
];
|
||
}
|
||
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 channel = controller.aChannels[iChannel];
|
||
data[iChannel] = [
|
||
channel.masked,
|
||
channel.addrInit,
|
||
channel.countInit,
|
||
channel.addrCurrent,
|
||
channel.countCurrent,
|
||
channel.mode,
|
||
channel.bPage,
|
||
channel.sDevice,
|
||
channel.sFunction
|
||
];
|
||
}
|
||
return data;
|
||
};
|
||
|
||
ChipSet.aPICInit = [0, new Array(4)];
|
||
|
||
/**
|
||
* initPIC(iPIC, port, 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 (!pic) {
|
||
pic = {
|
||
aICW: [null,null,null,null]
|
||
};
|
||
}
|
||
var a = aState && aState.length == 8? aState : ChipSet.aPICInit;
|
||
pic.port = port;
|
||
pic.nIRQBase = iPIC << 3;
|
||
pic.nDelay = a[0];
|
||
pic.aICW[0] = a[1][0]; pic.aICW[1] = a[1][1]; pic.aICW[2] = a[1][2]; pic.aICW[3] = a[1][3];
|
||
pic.nICW = a[2];
|
||
pic.bIMR = a[3];
|
||
pic.bIRR = a[4];
|
||
pic.bISR = a[5];
|
||
pic.bIRLow = a[6];
|
||
pic.bOCW3 = a[7];
|
||
this.aPICs[iPIC] = pic;
|
||
};
|
||
|
||
/**
|
||
* savePICs()
|
||
*
|
||
* @this {ChipSet}
|
||
* @return {Array}
|
||
*/
|
||
ChipSet.prototype.savePICs = function()
|
||
{
|
||
var data = [];
|
||
for (var iPIC = 0; iPIC < this.aPICs.length; iPIC++) {
|
||
var pic = this.aPICs[iPIC];
|
||
data[iPIC] = [
|
||
pic.nDelay,
|
||
pic.aICW,
|
||
pic.nICW,
|
||
pic.bIMR,
|
||
pic.bIRR,
|
||
pic.bISR,
|
||
pic.bIRLow,
|
||
pic.bOCW3
|
||
];
|
||
}
|
||
return data;
|
||
};
|
||
|
||
ChipSet.aTimerInit = [[0,0], [0,0], [0,0], [0,0]];
|
||
|
||
/**
|
||
* initTimer(iTimer, aState)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iTimer
|
||
* @param {Array} [aState]
|
||
*/
|
||
ChipSet.prototype.initTimer = function(iTimer, aState)
|
||
{
|
||
var timer = this.aTimers[iTimer];
|
||
if (!timer) {
|
||
timer = {
|
||
countInit: [0,0],
|
||
countStart: [0,0],
|
||
countCurrent: [0,0],
|
||
countLatched: [0,0]
|
||
};
|
||
}
|
||
var a = aState && aState.length >= 13? aState : ChipSet.aTimerInit;
|
||
timer.countInit[0] = a[0][0]; timer.countInit[1] = a[0][1];
|
||
timer.countStart[0] = a[1][0]; timer.countStart[1] = a[1][1];
|
||
timer.countCurrent[0] = a[2][0]; timer.countCurrent[1] = a[2][1];
|
||
timer.countLatched[0] = a[3][0]; timer.countLatched[1] = a[3][1];
|
||
timer.bcd = a[4];
|
||
timer.mode = a[5];
|
||
timer.rw = a[6];
|
||
timer.countIndex = a[7];
|
||
timer.countBytes = a[8];
|
||
timer.fOUT = a[9];
|
||
timer.fCountLatched = a[10];
|
||
timer.fCounting = a[11];
|
||
timer.nCyclesStart = a[12];
|
||
timer.bStatus = a[13] || 0;
|
||
timer.fStatusLatched = a[14] || false;
|
||
this.aTimers[iTimer] = timer;
|
||
};
|
||
|
||
/**
|
||
* saveTimers()
|
||
*
|
||
* @this {ChipSet}
|
||
* @return {Array}
|
||
*/
|
||
ChipSet.prototype.saveTimers = function()
|
||
{
|
||
var data = [];
|
||
for (var iTimer = 0; iTimer < this.aTimers.length; iTimer++) {
|
||
var timer = this.aTimers[iTimer];
|
||
data[iTimer] = [
|
||
timer.countInit,
|
||
timer.countStart,
|
||
timer.countCurrent,
|
||
timer.countLatched,
|
||
timer.bcd,
|
||
timer.mode,
|
||
timer.rw,
|
||
timer.countIndex,
|
||
timer.countBytes,
|
||
timer.fOUT,
|
||
timer.fCountLatched,
|
||
timer.fCounting,
|
||
timer.nCyclesStart,
|
||
timer.bStatus,
|
||
timer.fStatusLatched
|
||
];
|
||
}
|
||
return data;
|
||
};
|
||
|
||
/**
|
||
* addDIPSwitches(iDIP, sBinding)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDIP (0 or 1)
|
||
* @param {string} sBinding is the name of the control
|
||
*/
|
||
ChipSet.prototype.addDIPSwitches = function(iDIP, sBinding)
|
||
{
|
||
var sHTML = "";
|
||
var control = this.bindings[sBinding];
|
||
for (var i = 1; i <= 8; i++) {
|
||
var sCellClasses = this.sCellClass;
|
||
if (!i) sCellClasses += " " + this.sCellClass + "Left";
|
||
var sCellID = sBinding + "-" + i;
|
||
sHTML += "<div id=\"" + sCellID + "\" class=\"" + sCellClasses + "\" data-value=\"0\">" + i + "</div>\n";
|
||
}
|
||
control.innerHTML = sHTML;
|
||
this.updateDIPSwitchControls(iDIP, sBinding, true);
|
||
};
|
||
|
||
/**
|
||
* findDIPSwitch(iDIP, iSwitch)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDIP
|
||
* @param {number} iSwitch
|
||
* @return {Object|null} DIPSW switchGroup containing the DIP switch's MASK, VALUES, and LABEL, or null if none
|
||
*/
|
||
ChipSet.prototype.findDIPSwitch = function(iDIP, iSwitch)
|
||
{
|
||
var switchDIPs = ChipSet.DIPSW[this.model|0];
|
||
var switchTypes = switchDIPs && switchDIPs[iDIP];
|
||
if (switchTypes) {
|
||
for (var iType in switchTypes) {
|
||
var switchGroup = switchTypes[iType];
|
||
if (switchGroup.MASK & (1 << iSwitch)) {
|
||
return switchGroup;
|
||
}
|
||
}
|
||
}
|
||
return null;
|
||
};
|
||
|
||
/**
|
||
* getDIPLegacyBits(iDIP)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDIP
|
||
* @return {number|undefined}
|
||
*/
|
||
ChipSet.prototype.getDIPLegacyBits = function(iDIP)
|
||
{
|
||
var b;
|
||
if (!iDIP) {
|
||
b = 0;
|
||
b |= (this.getDIPVideoMonitor() << ChipSet.PPI_SW.MONITOR.SHIFT) & ChipSet.PPI_SW.MONITOR.MASK;
|
||
b |= (this.getDIPCoprocessor()? ChipSet.PPI_SW.FPU : 0);
|
||
var nDrives = this.getDIPFloppyDrives();
|
||
b |= (nDrives? ((((nDrives - 1) << ChipSet.PPI_SW.FDRIVE.SHIFT) & ChipSet.PPI_SW.FDRIVE.MASK) | ChipSet.PPI_SW.FDRIVE.IPL) : 0);
|
||
}
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* getDIPSwitches(iType, fInit)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iType
|
||
* @param {boolean} [fInit] is true for initial switch value, current value otherwise
|
||
* @return {*|null}
|
||
*/
|
||
ChipSet.prototype.getDIPSwitches = function(iType, fInit)
|
||
{
|
||
var value = null;
|
||
var switchDIPs = ChipSet.DIPSW[this.model] || ChipSet.DIPSW[this.model|0] || ChipSet.DIPSW[ChipSet.MODEL_5150];
|
||
for (var iDIP = 0; iDIP < switchDIPs.length; iDIP++) {
|
||
var switchTypes = switchDIPs[iDIP];
|
||
if (switchTypes) {
|
||
var switchGroup = switchTypes[iType];
|
||
if (switchGroup) {
|
||
var bits = this.aDIPSwitches[iDIP][fInit?0:1] & switchGroup.MASK;
|
||
for (var v in switchGroup.VALUES) {
|
||
if (switchGroup.VALUES[v] == bits) {
|
||
value = v;
|
||
/*
|
||
* We prefer numeric properties, and all switch definitions must provide them
|
||
* if their helper functions (eg, getDIPVideoMonitor()) expect numeric properties.
|
||
*/
|
||
if (typeof +value == 'number') break;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return value;
|
||
};
|
||
|
||
/**
|
||
* getDIPCoprocessor(fInit)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {boolean} [fInit] is true for init switch value(s) only, current value(s) otherwise
|
||
* @return {number} 1 if installed, 0 if not
|
||
*/
|
||
ChipSet.prototype.getDIPCoprocessor = function(fInit)
|
||
{
|
||
var n = /** @type {number} */ (this.getDIPSwitches(ChipSet.SWITCH_TYPE.FPU, fInit));
|
||
return +n;
|
||
};
|
||
|
||
/**
|
||
* getDIPFloppyDrives(fInit)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {boolean} [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.getDIPFloppyDrives = function(fInit)
|
||
{
|
||
var n = /** @type {number} */ (this.getDIPSwitches(ChipSet.SWITCH_TYPE.FLOPNUM, fInit));
|
||
return +n;
|
||
};
|
||
|
||
/**
|
||
* getDIPFloppyDriveType(iDrive)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDrive (0-based)
|
||
* @return {number} one of the ChipSet.CMOS.FDRIVE.FD* values (FD360, FD1200, etc)
|
||
*/
|
||
ChipSet.prototype.getDIPFloppyDriveType = function(iDrive)
|
||
{
|
||
if (iDrive < this.getDIPFloppyDrives()) {
|
||
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;
|
||
}
|
||
}
|
||
this.assert(false); // we should never get here (else something is out of out sync)
|
||
}
|
||
return ChipSet.CMOS.FDRIVE.NONE;
|
||
};
|
||
|
||
/**
|
||
* getDIPFloppyDriveSize(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.getDIPFloppyDriveSize = function(iDrive)
|
||
{
|
||
if (iDrive < this.getDIPFloppyDrives()) {
|
||
if (!this.aFloppyDrives) {
|
||
return 360;
|
||
}
|
||
if (iDrive < this.aFloppyDrives.length) {
|
||
return this.aFloppyDrives[iDrive];
|
||
}
|
||
this.assert(false); // we should never get here (else something is out of out sync)
|
||
}
|
||
return 0;
|
||
};
|
||
|
||
/**
|
||
* getDIPMemorySize(fInit)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {boolean} [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.getDIPMemorySize = function(fInit)
|
||
{
|
||
var nKBLowMem = /** @type {number} */ (this.getDIPSwitches(ChipSet.SWITCH_TYPE.LOWMEM, fInit));
|
||
var nKBExpMem = /** @type {number} */ (this.getDIPSwitches(ChipSet.SWITCH_TYPE.EXPMEM, fInit));
|
||
return +nKBLowMem + +nKBExpMem;
|
||
};
|
||
|
||
/**
|
||
* getDIPVideoMonitor(fInit)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {boolean} [fInit] is true for init switch value(s) only, current value(s) otherwise
|
||
* @return {number} one of ChipSet.MONITOR.*
|
||
*/
|
||
ChipSet.prototype.getDIPVideoMonitor = function(fInit)
|
||
{
|
||
var n = /** @type {number} */ (this.getDIPSwitches(ChipSet.SWITCH_TYPE.MONITOR, fInit));
|
||
return +n;
|
||
};
|
||
|
||
/**
|
||
* parseDIPSwitches(sBits, bDefault)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {string} sBits describing switch settings
|
||
* @param {number} [bDefault]
|
||
* @return {number|undefined}
|
||
*/
|
||
ChipSet.prototype.parseDIPSwitches = function(sBits, bDefault)
|
||
{
|
||
var b = bDefault;
|
||
if (sBits) {
|
||
/*
|
||
* NOTE: We can't use parseInt() with a base of 2, because both bit order and bit sense are reversed.
|
||
*/
|
||
b = 0;
|
||
var bit = 0x1;
|
||
for (var i = 0; i < sBits.length; i++) {
|
||
if (sBits.charAt(i) == "0") b |= bit;
|
||
bit <<= 1;
|
||
}
|
||
}
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* setDIPSwitches(iType, value, fInit)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iType
|
||
* @param {*} value
|
||
* @param {boolean} [fInit]
|
||
* @return {boolean} true if successful, false if unrecognized type and/or value
|
||
*/
|
||
ChipSet.prototype.setDIPSwitches = function(iType, value, fInit)
|
||
{
|
||
var switchDIPs = ChipSet.DIPSW[this.model] || ChipSet.DIPSW[this.model|0] || ChipSet.DIPSW[ChipSet.MODEL_5150];
|
||
for (var iDIP = 0; iDIP < switchDIPs.length; iDIP++) {
|
||
var switchTypes = switchDIPs[iDIP];
|
||
if (switchTypes) {
|
||
var switchGroup = switchTypes[iType];
|
||
if (switchGroup) {
|
||
for (var v in switchGroup.VALUES) {
|
||
if (v == value) {
|
||
this.aDIPSwitches[iDIP][fInit?0:1] &= ~switchGroup.MASK;
|
||
this.aDIPSwitches[iDIP][fInit?0:1] |= switchGroup.VALUES[v];
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
|
||
/**
|
||
* getDIPSwitchControl(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.getDIPSwitchControl = function(control)
|
||
{
|
||
return control.getAttribute("data-value") == "1";
|
||
};
|
||
|
||
/**
|
||
* setDIPSwitchControl(control, f)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {Object} control is an HTML control DOM object
|
||
* @param {boolean} f is true if the switch represented by control should be "on", false if "off"
|
||
*/
|
||
ChipSet.prototype.setDIPSwitchControl = function(control, f)
|
||
{
|
||
control.setAttribute("data-value", f? "1" : "0");
|
||
control.style.color = (f? "#ffffff" : "#000000");
|
||
control.style.backgroundColor = (f? "#000000" : "#ffffff");
|
||
};
|
||
|
||
/**
|
||
* toggleDIPSwitchControl(control)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {Object} control is an HTML control DOM object
|
||
*/
|
||
ChipSet.prototype.toggleDIPSwitchControl = function(control)
|
||
{
|
||
var f = !this.getDIPSwitchControl(control);
|
||
this.setDIPSwitchControl(control, f);
|
||
var sID = control.getAttribute("id");
|
||
var asParts = sID.split("-");
|
||
var b = (0x1 << (+asParts[1] - 1));
|
||
switch (asParts[0]) {
|
||
case ChipSet.CONTROLS.SW1:
|
||
this.aDIPSwitches[0][0] = (this.aDIPSwitches[0][0] & ~b) | (f? 0 : b);
|
||
break;
|
||
case ChipSet.CONTROLS.SW2:
|
||
this.aDIPSwitches[1][0] = (this.aDIPSwitches[1][0] & ~b) | (f? 0 : b);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
this.updateDIPSwitchDescriptions();
|
||
};
|
||
|
||
/**
|
||
* updateDIPSwitches()
|
||
*
|
||
* @this {ChipSet}
|
||
*/
|
||
ChipSet.prototype.updateDIPSwitches = function()
|
||
{
|
||
this.updateDIPSwitchControls(0, ChipSet.CONTROLS.SW1);
|
||
this.updateDIPSwitchControls(1, ChipSet.CONTROLS.SW2);
|
||
this.updateDIPSwitchDescriptions();
|
||
};
|
||
|
||
/**
|
||
* updateDIPSwitchControls(iDIP, sBinding, fInit)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDIP (0 or 1)
|
||
* @param {string} sBinding is the name of the control
|
||
* @param {boolean} [fInit]
|
||
*/
|
||
ChipSet.prototype.updateDIPSwitchControls = function(iDIP, sBinding, fInit)
|
||
{
|
||
var control = this.bindings[sBinding];
|
||
if (control) {
|
||
var v;
|
||
if (fInit) {
|
||
v = this.aDIPSwitches[iDIP][0];
|
||
} else {
|
||
v = this.aDIPSwitches[iDIP][1] = this.aDIPSwitches[iDIP][0];
|
||
}
|
||
var aeCells = Component.getElementsByClass(control, this.sCellClass);
|
||
for (var i = 0; i < aeCells.length; i++) {
|
||
var switchGroup = this.findDIPSwitch(iDIP, i);
|
||
var sLabel = switchGroup && switchGroup.LABEL || "Reserved";
|
||
aeCells[i].setAttribute("title", sLabel);
|
||
this.setDIPSwitchControl(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.toggleDIPSwitchControl(eSwitch);
|
||
};
|
||
}(this, aeCells[i]);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* updateDIPSwitchDescriptions()
|
||
*
|
||
* @this {ChipSet}
|
||
*/
|
||
ChipSet.prototype.updateDIPSwitchDescriptions = function()
|
||
{
|
||
var controlDesc = this.bindings[ChipSet.CONTROLS.SWDESC];
|
||
if (controlDesc != null) {
|
||
var sText = "";
|
||
/*
|
||
* TODO: Monitor type 0 used to be "None" (ie, "No Monitor"), which was correct in a pre-EGA world,
|
||
* but in the post-EGA world, it depends. We should ask the Video component for a definitive answer.
|
||
*/
|
||
var asMonitorTypes = {
|
||
0: "Enhanced Color",
|
||
1: "TV",
|
||
2: "Color",
|
||
3: "Monochrome"
|
||
};
|
||
sText += this.getDIPMemorySize(true) + "Kb";
|
||
sText += ", " + (+this.getDIPCoprocessor(true)? "" : "No ") + "FPU";
|
||
sText += ", " + asMonitorTypes[this.getDIPVideoMonitor(true)] + " Monitor";
|
||
sText += ", " + this.getDIPFloppyDrives(true) + " Floppy Drives";
|
||
if (this.aDIPSwitches[0][1] != null && this.aDIPSwitches[0][1] != this.aDIPSwitches[0][0] ||
|
||
this.aDIPSwitches[1][1] != null && this.aDIPSwitches[1][1] != this.aDIPSwitches[1][0]) {
|
||
sText += " (Reset required)";
|
||
}
|
||
controlDesc.textContent = sText;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 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) + " DELAY=" + pic.nDelay;
|
||
this.dbg.println(sDump);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* dumpTimer(asArgs)
|
||
*
|
||
* Use "d timer" to dump all timers, or "d timer n" to dump only timer n.
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {Array.<string>} asArgs
|
||
*/
|
||
ChipSet.prototype.dumpTimer = function(asArgs)
|
||
{
|
||
if (DEBUGGER) {
|
||
var sParm = asArgs[0];
|
||
var nTimer = (sParm? +sParm : null);
|
||
for (var iTimer = 0; iTimer < this.aTimers.length; iTimer++) {
|
||
if (nTimer != null && iTimer != nTimer) continue;
|
||
this.updateTimer(iTimer);
|
||
var timer = this.aTimers[iTimer];
|
||
var sDump = "TIMER" + iTimer + ":";
|
||
var count = 0;
|
||
if (timer.countBytes != null) {
|
||
for (var i = 0; i <= timer.countBytes; i++) {
|
||
count |= (timer.countCurrent[i] << (i * 8));
|
||
}
|
||
}
|
||
sDump += " mode=" + (timer.mode >> 1) + " bytes=" + timer.countBytes + " count=" + str.toHexWord(count);
|
||
this.dbg.println(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.STATUSD? this.getRTCByte(iCMOS) : this.abCMOSData[iCMOS]);
|
||
if (sDump) sDump += '\n';
|
||
sDump += "CMOS[" + str.toHexByte(iCMOS) + "]: " + str.toHexByte(b);
|
||
}
|
||
this.dbg.println(sDump);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* inDMAChannelAddr(iDMAC, iChannel, port, addrFrom)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDMAC
|
||
* @param {number} iChannel
|
||
* @param {number} port (0x00, 0x02, 0x04, 0x06 for DMAC 0, 0xC0, 0xC4, 0xC8, 0xCC for DMAC 1)
|
||
* @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];
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, null, addrFrom, "DMA" + iDMAC + ".CHANNEL" + iChannel + ".ADDR[" + controller.bIndex + "]", b, true);
|
||
}
|
||
controller.bIndex ^= 0x1;
|
||
/*
|
||
* Technically, aTimers[1].fOut is what drives DMA requests for DMA channel 0 (ChipSet.DMA_REFRESH),
|
||
* every 15us, once the BIOS has initialized the channel's "mode" with MODE_SINGLE, INCREMENT, AUTOINIT,
|
||
* and TYPE_READ (0x58) and initialized TIMER1 appropriately.
|
||
*
|
||
* However, we don't need to be that particular. Simply simulate an ever-increasing address after every
|
||
* read of the full DMA channel 0 address.
|
||
*/
|
||
if (!iDMAC && iChannel == ChipSet.DMA_REFRESH && !controller.bIndex) {
|
||
channel.addrCurrent[0]++;
|
||
if (channel.addrCurrent[0] > 0xff) {
|
||
channel.addrCurrent[0] = 0;
|
||
channel.addrCurrent[1]++;
|
||
if (channel.addrCurrent[1] > 0xff) {
|
||
channel.addrCurrent[1] = 0;
|
||
}
|
||
}
|
||
}
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* outDMAChannelAddr(iDMAC, iChannel, port, bOut, addrFrom)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDMAC
|
||
* @param {number} iChannel
|
||
* @param {number} port (0x00, 0x02, 0x04, 0x06 for DMAC 0, 0xC0, 0xC4, 0xC8, 0xCC for DMAC 1)
|
||
* @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];
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, bOut, addrFrom, "DMA" + iDMAC + ".CHANNEL" + iChannel + ".ADDR[" + controller.bIndex + "]", null, true);
|
||
}
|
||
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 (0x01, 0x03, 0x05, 0x07 for DMAC 0, 0xC2, 0xC6, 0xCA, 0xCE for DMAC 1)
|
||
* @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];
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, null, addrFrom, "DMA" + iDMAC + ".CHANNEL" + iChannel + ".COUNT[" + controller.bIndex + "]", b, true);
|
||
}
|
||
controller.bIndex ^= 0x1;
|
||
/*
|
||
* Technically, aTimers[1].fOut is what drives DMA requests for DMA channel 0 (ChipSet.DMA_REFRESH),
|
||
* every 15us, once the BIOS has initialized the channel's "mode" with MODE_SINGLE, INCREMENT, AUTOINIT,
|
||
* and TYPE_READ (0x58) and initialized TIMER1 appropriately.
|
||
*
|
||
* However, we don't need to be that particular. Simply simulate an ever-decreasing count after every
|
||
* read of the full DMA channel 0 count.
|
||
*/
|
||
if (!iDMAC && iChannel == ChipSet.DMA_REFRESH && !controller.bIndex) {
|
||
channel.countCurrent[0]--;
|
||
if (channel.countCurrent[0] < 0) {
|
||
channel.countCurrent[0] = 0xff;
|
||
channel.countCurrent[1]--;
|
||
if (channel.countCurrent[1] < 0) {
|
||
channel.countCurrent[1] = 0xff;
|
||
/*
|
||
* This is the logical point to indicate Terminal Count (TC), but again, there's no need to be
|
||
* so particular; inDMAStatus() has its own logic for periodically signalling TC.
|
||
*/
|
||
}
|
||
}
|
||
}
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* outDMAChannelCount(iDMAC, iChannel, port, bOut, addrFrom)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDMAC
|
||
* @param {number} iChannel (ports 0x01, 0x03, 0x05, 0x07)
|
||
* @param {number} port (0x01, 0x03, 0x05, 0x07 for DMAC 0, 0xC2, 0xC6, 0xCA, 0xCE for DMAC 1)
|
||
* @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];
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, bOut, addrFrom, "DMA" + iDMAC + ".CHANNEL" + iChannel + ".COUNT[" + controller.bIndex + "]", null, true);
|
||
}
|
||
var channel = controller.aChannels[iChannel];
|
||
channel.countCurrent[controller.bIndex] = channel.countInit[controller.bIndex] = bOut;
|
||
controller.bIndex ^= 0x1;
|
||
};
|
||
|
||
/**
|
||
* inDMAStatus(iDMAC, port, addrFrom)
|
||
*
|
||
* 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 Terminal Count (TC) 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.
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDMAC
|
||
* @param {number} port (0x08 for DMAC 0, 0xD0 for DMAC 1)
|
||
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
|
||
* @return {number} simulated port value
|
||
*/
|
||
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 | ChipSet.DMA_STATUS.CH0_TC;
|
||
controller.bStatus &= ~ChipSet.DMA_STATUS.ALL_TC;
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, null, addrFrom, "DMA" + iDMAC + ".STATUS", b, true);
|
||
}
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* outDMACmd(iDMAC, port, bOut, addrFrom)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDMAC
|
||
* @param {number} port (0x08 for DMAC 0, 0xD0 for DMAC 1)
|
||
* @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)
|
||
{
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, bOut, addrFrom, "DMA" + iDMAC + ".CMD", null, true);
|
||
}
|
||
this.aDMACs[iDMAC].bCmd = bOut;
|
||
};
|
||
|
||
/**
|
||
* outDMAReq(iDMAC, port, bOut, addrFrom)
|
||
*
|
||
* 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."
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDMAC
|
||
* @param {number} port (0x09 for DMAC 0, 0xD2 for DMAC 1)
|
||
* @param {number} bOut
|
||
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
|
||
*/
|
||
ChipSet.prototype.outDMAReq = function(iDMAC, port, bOut, addrFrom)
|
||
{
|
||
var controller = this.aDMACs[iDMAC];
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, bOut, addrFrom, "DMA" + iDMAC + ".REQ", null, true);
|
||
}
|
||
/*
|
||
* 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 (0x0A for DMAC 0, 0xD4 for DMAC 1)
|
||
* @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];
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, bOut, addrFrom, "DMA" + iDMAC + ".MASK", null, true);
|
||
}
|
||
var iChannel = bOut & ChipSet.DMA_MASK.CHANNEL;
|
||
var channel = controller.aChannels[iChannel];
|
||
channel.masked = !!(bOut & ChipSet.DMA_MASK.CHANNEL_SET);
|
||
if (!channel.masked) this.requestDMA(controller.nChannelBase + iChannel);
|
||
};
|
||
|
||
/**
|
||
* outDMAMode(iDMAC, port, bOut, addrFrom)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDMAC
|
||
* @param {number} port (0x0B for DMAC 0, 0xD6 for DMAC 1)
|
||
* @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)
|
||
{
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, bOut, addrFrom, "DMA" + iDMAC + ".MODE", null, true);
|
||
}
|
||
var iChannel = bOut & ChipSet.DMA_MODE.CHANNEL;
|
||
this.aDMACs[iDMAC].aChannels[iChannel].mode = bOut;
|
||
};
|
||
|
||
/**
|
||
* outDMAResetFF(iDMAC, port, bOut, addrFrom)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDMAC
|
||
* @param {number} port (0x0C for DMAC 0, 0xD8 for DMAC 1)
|
||
* @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.outDMAResetFF = function(iDMAC, port, bOut, addrFrom)
|
||
{
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, bOut, addrFrom, "DMA" + iDMAC + ".RESET_FF", null, true);
|
||
}
|
||
this.aDMACs[iDMAC].bIndex = 0;
|
||
};
|
||
|
||
/**
|
||
* inDMATemp(iDMAC, port, addrFrom)
|
||
*
|
||
* From the 8237A spec:
|
||
*
|
||
* "The Temporary register is used to hold data during memory-to-memory transfers Following the
|
||
* completion of the transfers, the last word moved can be read by the microprocessor in the Program Condition.
|
||
* The Temporary register always contains the last byte transferred in the previous memory-to-memory operation,
|
||
* unless cleared by a Reset."
|
||
*
|
||
* TRIVIA: This hook wasn't installed when I was testing with ANY of the IBM ROMs, but it's required
|
||
* by the AT&T 6300 (aka Olivetti M24) ROM.
|
||
*
|
||
* TODO: When support is added for memory-to-memory transfers, bTemp needs to be updated according to spec.
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDMAC
|
||
* @param {number} port (0x0D for DMAC 0, 0xDA for DMAC 1)
|
||
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
|
||
* @return {number} simulated port value
|
||
*/
|
||
ChipSet.prototype.inDMATemp = function(iDMAC, port, addrFrom)
|
||
{
|
||
var controller = this.aDMACs[iDMAC];
|
||
var b = controller.bTemp;
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, null, addrFrom, "DMA" + iDMAC + ".TEMP", b, true);
|
||
}
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* outDMAMasterClear(iDMAC, port, bOut, addrFrom)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDMAC
|
||
* @param {number} port (0x0D for DMAC 0, 0xDA for DMAC 1)
|
||
* @param {number} bOut
|
||
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
|
||
*/
|
||
ChipSet.prototype.outDMAMasterClear = function(iDMAC, port, bOut, addrFrom)
|
||
{
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, bOut, addrFrom, "DMA" + iDMAC + ".MASTER_CLEAR", null, true);
|
||
}
|
||
/*
|
||
* The value written to this port doesn't matter; any write triggers a "master clear" operation
|
||
*
|
||
* TODO: Can't we just call initDMAController(), which would also take care of clearing controller.bStatus?
|
||
*/
|
||
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;
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, null, addrFrom, "DMA" + iDMAC + ".CHANNEL" + iChannel + ".PAGE", bIn, true);
|
||
}
|
||
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)
|
||
{
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, bOut, addrFrom, "DMA" + iDMAC + ".CHANNEL" + iChannel + ".PAGE", null, true);
|
||
}
|
||
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];
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT)) {
|
||
this.printMessageIO(port, null, addrFrom, "DMA.SPARE" + iSpare + ".PAGE", bIn, true);
|
||
}
|
||
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)
|
||
{
|
||
/*
|
||
* TODO: Remove this DEBUG-only DESKPRO386 code once we're done debugging DeskPro 386 ROMs;
|
||
* it enables logging of all DeskPro 386 ROM checkpoint I/O to port 0x84.
|
||
*/
|
||
if (this.messageEnabled(Messages.DMA | Messages.PORT) || DEBUG && (this.model|0) == ChipSet.MODEL_COMPAQ_DESKPRO386 && port == 0x84) {
|
||
this.printMessageIO(port, bOut, addrFrom, "DMA.SPARE" + iSpare + ".PAGE", null, true);
|
||
}
|
||
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.TYPE_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.TYPE_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.messageEnabled(Messages.DMA | Messages.DATA)) {
|
||
this.printMessage("requestDMA(" + iDMAChannel + "): not connected to a component", true);
|
||
}
|
||
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.messageEnabled(Messages.DMA | Messages.DATA)) {
|
||
this.printMessage("requestDMA(" + iDMAChannel + "): channel masked, request queued", true);
|
||
}
|
||
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)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {Object} channel
|
||
* @param {boolean} [fInit]
|
||
*/
|
||
ChipSet.prototype.advanceDMA = function(channel, fInit)
|
||
{
|
||
if (fInit) {
|
||
channel.count = (channel.countCurrent[1] << 8) | channel.countCurrent[0];
|
||
channel.type = (channel.mode & ChipSet.DMA_MODE.TYPE);
|
||
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 bto = null;
|
||
var chipset = 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.messageEnabled(this.messageBitsDMA(iDMAChannel)) && channel.type != ChipSet.DMA_MODE.TYPE_WRITE) {
|
||
this.printMessage("advanceDMA(" + iDMAChannel + ") transferring " + channel.cbDebug + " bytes from " + channel.sAddrDebug, true);
|
||
this.dbg.doDump(["db", channel.sAddrDebug, 'l', channel.cbDebug]);
|
||
}
|
||
}
|
||
if (channel.type == ChipSet.DMA_MODE.TYPE_WRITE) {
|
||
fAsyncRequest = true;
|
||
(function advanceDMAWrite(addrCur) {
|
||
channel.fnTransfer.call(channel.component, channel.obj, -1, function onTransferDMA(b, fAsync, obj, off) {
|
||
if (b < 0) {
|
||
if (!channel.fWarning) {
|
||
if (DEBUG && chipset.messageEnabled(Messages.DMA)) {
|
||
chipset.printMessage("advanceDMA(" + iDMAChannel + ") ran out of data, assuming 0xff", true);
|
||
}
|
||
channel.fWarning = true;
|
||
}
|
||
/*
|
||
* TODO: Determine whether to abort, as we do for DMA_MODE.TYPE_READ.
|
||
*/
|
||
b = 0xff;
|
||
}
|
||
if (!channel.masked) {
|
||
chipset.bus.setByte(addrCur, b);
|
||
/*
|
||
* WARNING: Do NOT assume that obj is valid; if the sector data was not found, there will be no obj.
|
||
*/
|
||
if (BACKTRACK && obj) {
|
||
if (!off && obj.file) {
|
||
if (chipset.messageEnabled(Messages.DISK)) {
|
||
chipset.printMessage("loading " + obj.file.sPath + '[' + obj.offFile + "] at %" + str.toHex(addrCur), true);
|
||
}
|
||
/*
|
||
if (obj.file.sPath == "\\SYSBAS.EXE" && obj.offFile == 512) {
|
||
chipset.cpu.stopCPU();
|
||
}
|
||
*/
|
||
}
|
||
bto = chipset.bus.addBackTrackObject(obj, bto, off);
|
||
chipset.bus.writeBackTrackObject(addrCur, bto, off);
|
||
}
|
||
}
|
||
fAsyncRequest = fAsync;
|
||
if (fAsync) {
|
||
setTimeout(function() {
|
||
if (!chipset.updateDMA(channel)) chipset.advanceDMA(channel);
|
||
}, 0);
|
||
}
|
||
});
|
||
}(addr));
|
||
}
|
||
else if (channel.type == ChipSet.DMA_MODE.TYPE_READ) {
|
||
/*
|
||
* TODO: Determine whether we should support async dmaWrite() functions (currently not required)
|
||
*/
|
||
b = chipset.bus.getByte(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 (channel.type == ChipSet.DMA_MODE.TYPE_VERIFY) {
|
||
/*
|
||
* Nothing to read or write; just call updateDMA()
|
||
*/
|
||
}
|
||
else {
|
||
if (DEBUG && this.messageEnabled(Messages.DMA | Messages.WARN)) {
|
||
this.printMessage("advanceDMA(" + iDMAChannel + ") unsupported transfer type: " + str.toHexWord(channel.type), true);
|
||
}
|
||
channel.fError = true;
|
||
}
|
||
}
|
||
if (fAsyncRequest || this.updateDMA(channel)) break;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* updateDMA(channel)
|
||
*
|
||
* @this {ChipSet}
|
||
* @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 && this.messageEnabled(this.messageBitsDMA(iDMAChannel)) && channel.type == ChipSet.DMA_MODE.TYPE_WRITE && channel.sAddrDebug) {
|
||
this.printMessage("updateDMA(" + iDMAChannel + ") transferred " + channel.cbDebug + " bytes to " + channel.sAddrDebug, true);
|
||
this.dbg.doDump(["db", channel.sAddrDebug, 'l', channel.cbDebug]);
|
||
}
|
||
|
||
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 != null) {
|
||
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;
|
||
}
|
||
}
|
||
if (this.messageEnabled(Messages.PIC | Messages.PORT | Messages.CHIPSET)) {
|
||
this.printMessageIO(pic.port, null, addrFrom, "PIC" + iPIC, b, true);
|
||
}
|
||
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];
|
||
if (this.messageEnabled(Messages.PIC | Messages.PORT | Messages.CHIPSET)) {
|
||
this.printMessageIO(pic.port, bOut, addrFrom, "PIC" + iPIC, null, true);
|
||
}
|
||
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.messageEnabled(this.messageBitsIRQ(nIRQ))) {
|
||
this.printMessage("outPIC" + iPIC + '(' + str.toHexByte(pic.port) + "): IRQ " + nIRQ + " ending @" + this.dbg.toHexOffset(this.cpu.getIP(), this.cpu.getCS()) + " stack=" + this.dbg.toHexOffset(this.cpu.getSP(), this.cpu.getSS()), true);
|
||
}
|
||
pic.bISR &= ~bIREnd;
|
||
this.checkIRR();
|
||
} else {
|
||
if (DEBUG && this.messageEnabled(Messages.PIC | Messages.WARN)) {
|
||
this.printMessage("outPIC" + iPIC + '(' + str.toHexByte(pic.port) + "): unexpected EOI command, IRQ " + nIRQ + " not in service", true, true);
|
||
if (MAXDEBUG) this.dbg.stopCPU();
|
||
}
|
||
}
|
||
/*
|
||
* TODO: Support EOI commands with automatic rotation (eg, ChipSet.PIC_LO.OCW2_EOI_ROT and ChipSet.PIC_LO.OCW2_EOI_ROTSPEC)
|
||
*/
|
||
if (bOCW2 & ChipSet.PIC_LO.OCW2_SET_ROTAUTO) {
|
||
if (this.messageEnabled(/*Messages.PIC | */Messages.WARN)) {
|
||
this.printMessage("PIC" + iPIC + '(' + str.toHexByte(pic.port) + "): unsupported OCW2 rotate command " + str.toHexByte(bOut), true, true);
|
||
}
|
||
}
|
||
}
|
||
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 (this.messageEnabled(/*Messages.PIC | */Messages.WARN)) {
|
||
this.printMessage("PIC" + iPIC + '(' + str.toHexByte(pic.port) + "): unsupported OCW2 automatic EOI command " + str.toHexByte(bOut), true, true);
|
||
}
|
||
}
|
||
} 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 (this.messageEnabled(/*Messages.PIC | */Messages.WARN)) {
|
||
this.printMessage("PIC" + iPIC + '(' + str.toHexByte(pic.port) + "): unsupported OCW3 command " + str.toHexByte(bOut), true, true);
|
||
}
|
||
}
|
||
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;
|
||
if (this.messageEnabled(Messages.PIC | Messages.PORT | Messages.CHIPSET)) {
|
||
this.printMessageIO(pic.port+1, null, addrFrom, "PIC" + iPIC, b, true);
|
||
}
|
||
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];
|
||
if (this.messageEnabled(Messages.PIC | Messages.PORT | Messages.CHIPSET)) {
|
||
this.printMessageIO(pic.port+1, bOut, addrFrom, "PIC" + iPIC, null, true);
|
||
}
|
||
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 && bOut == 0xFD? 6 : 0);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 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;
|
||
};
|
||
|
||
/**
|
||
* 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];
|
||
var bIRR = (1 << nIRL);
|
||
if (!(pic.bIRR & bIRR)) {
|
||
pic.bIRR |= bIRR;
|
||
if (DEBUG && this.messageEnabled(this.messageBitsIRQ(nIRQ) | Messages.CHIPSET)) {
|
||
this.printMessage("setIRR(" + nIRQ + ")", true);
|
||
}
|
||
pic.nDelay = nDelay || 0;
|
||
this.checkIRR();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* clearIRR(nIRQ)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} nIRQ (IRQ 0-7 implies iPIC 0, and IRQ 8-15 implies iPIC 1)
|
||
*/
|
||
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.messageEnabled(this.messageBitsIRQ(nIRQ) | Messages.CHIPSET)) {
|
||
this.printMessage("clearIRR(" + nIRQ + ")", true);
|
||
}
|
||
this.checkIRR();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* checkIRR(nDelay)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} [nDelay] is an optional number of instructions to delay acknowledgment of a pending interrupt
|
||
*/
|
||
ChipSet.prototype.checkIRR = function(nDelay)
|
||
{
|
||
/*
|
||
* Look for any IRR bits that aren't masked and aren't already in service; in theory, all we'd have to
|
||
* check is the master PIC (which is the *only* PIC on pre-5170 models), because when any IRQs are set or
|
||
* cleared on the slave, that would automatically be reflected in IRQ.SLAVE on the master; that's what
|
||
* setIRR() and clearIRR() used to do.
|
||
*
|
||
* Unfortunately, despite setIRR() and clearIRR()'s efforts, whenever a slave interrupt is acknowledged,
|
||
* getIRRVector() ends up clearing the IRR bits for BOTH the slave's IRQ and the master's IRQ.SLAVE.
|
||
* So if another lower-priority slave IRQ is waiting to be dispatched, that fact is no longer reflected
|
||
* in IRQ.SLAVE.
|
||
*
|
||
* Since checkIRR() is called on every EOI, we can resolve that problem here, by first checking the slave
|
||
* PIC for any unmasked, unserviced interrupts and updating the master's IRQ.SLAVE.
|
||
*
|
||
* And since this is ALSO called by both setIRR() and clearIRR(), those functions no longer need to perform
|
||
* their own IRQ.SLAVE updates. This function consolidates the propagation of slave interrupts to the master.
|
||
*/
|
||
var pic;
|
||
var bIR = -1;
|
||
|
||
if (this.cPICs > 1) {
|
||
pic = this.aPICs[1];
|
||
bIR = ~(pic.bISR | pic.bIMR) & pic.bIRR;
|
||
}
|
||
|
||
pic = this.aPICs[0];
|
||
|
||
if (bIR >= 0) {
|
||
if (bIR) {
|
||
pic.bIRR |= (1 << ChipSet.IRQ.SLAVE);
|
||
} else {
|
||
pic.bIRR &= ~(1 << ChipSet.IRQ.SLAVE);
|
||
}
|
||
}
|
||
|
||
bIR = ~(pic.bISR | pic.bIMR) & pic.bIRR;
|
||
|
||
this.cpu.updateINTR(!!bIR);
|
||
|
||
if (bIR && nDelay) pic.nDelay = nDelay;
|
||
};
|
||
|
||
/**
|
||
* 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 we encounter an interrupt that's still in-service BEFORE we encounter a requested interrupt,
|
||
* then we're done; we must allow a higher priority in-service interrupt to finish before acknowledging
|
||
* any lower priority interrupts.
|
||
*/
|
||
if (pic.bISR & bIRNext) break;
|
||
|
||
if (bIR & bIRNext) {
|
||
|
||
if (!iPIC && nIRL == ChipSet.IRQ.SLAVE) {
|
||
/*
|
||
* 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;
|
||
|
||
/*
|
||
* Setting the ISR implies clearing the IRR, but clearIRR() has side-effects we don't want
|
||
* (eg, clearing the slave IRQ, notifying the CPU, etc), so we clear the IRR ourselves.
|
||
*/
|
||
pic.bIRR &= ~bIRNext;
|
||
|
||
var nIRQ = pic.nIRQBase + nIRL;
|
||
if (DEBUG && this.messageEnabled(this.messageBitsIRQ(nIRQ))) {
|
||
this.printMessage("getIRRVector(): IRQ " + nIRQ + " interrupting stack " + this.dbg.toHexOffset(this.cpu.getSP(), this.cpu.getSS()), true, true);
|
||
}
|
||
if (MAXDEBUG && DEBUGGER) {
|
||
this.acInterrupts[nIRQ]++;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
if (nIRL++ == pic.bIRLow) break;
|
||
}
|
||
} else {
|
||
nIDT = -2;
|
||
pic.nDelay--;
|
||
}
|
||
return nIDT;
|
||
};
|
||
|
||
/**
|
||
* setFPUInterrupt()
|
||
*
|
||
* @this {ChipSet}
|
||
*/
|
||
ChipSet.prototype.setFPUInterrupt = function()
|
||
{
|
||
if (this.model >= ChipSet.MODEL_5170) {
|
||
this.setIRR(ChipSet.IRQ.FPU);
|
||
} else {
|
||
/*
|
||
* TODO: Determine whether we need to maintain an "Active NMI" state; ie, if NMI.DISABLE is cleared
|
||
* later, and the FPU coprocessor is still indicating an error condition, should we then generate an NMI?
|
||
*/
|
||
if (!(this.bNMI & ChipSet.NMI.DISABLE)) {
|
||
X86.helpInterrupt.call(this.cpu, X86.EXCEPTION.NMI);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* clearFPUInterrupt(fSet)
|
||
*
|
||
* @this {ChipSet}
|
||
*/
|
||
ChipSet.prototype.clearFPUInterrupt = function()
|
||
{
|
||
if (this.model >= ChipSet.MODEL_5170) {
|
||
this.clearIRR(ChipSet.IRQ.FPU);
|
||
} else {
|
||
/*
|
||
* TODO: If we maintain an "Active NMI" state, then we will need code here to clear that state, as well
|
||
* as code in outNMI() to clear that state and generate an NMI as needed.
|
||
*/
|
||
}
|
||
};
|
||
|
||
/**
|
||
* inTimer(iPIT, iPITTimer, addrFrom)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iPIT (0 or 1)
|
||
* @param {number} iPITTimer (0, 1, or 2)
|
||
* @param {number} port (0x40, 0x41, 0x42, etc)
|
||
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
|
||
* @return {number} simulated port value
|
||
*/
|
||
ChipSet.prototype.inTimer = function(iPIT, iPITTimer, port, addrFrom)
|
||
{
|
||
var b;
|
||
var iBaseTimer = (iPIT? 3 : 0);
|
||
var timer = this.aTimers[iBaseTimer + iPITTimer];
|
||
|
||
if (timer.fStatusLatched) {
|
||
b = timer.bStatus;
|
||
timer.fStatusLatched = false;
|
||
}
|
||
else {
|
||
if (timer.countIndex == timer.countBytes) {
|
||
this.resetTimerIndex(iBaseTimer + iPITTimer);
|
||
}
|
||
if (timer.fCountLatched) {
|
||
b = timer.countLatched[timer.countIndex++];
|
||
if (timer.countIndex == timer.countBytes) {
|
||
timer.fCountLatched = false
|
||
}
|
||
}
|
||
else {
|
||
this.updateTimer(iBaseTimer + iPITTimer);
|
||
b = timer.countCurrent[timer.countIndex++];
|
||
}
|
||
}
|
||
if (this.messageEnabled(Messages.TIMER | Messages.PORT)) {
|
||
this.printMessageIO(port, null, addrFrom, "PIT" + iPIT + ".TIMER" + iPITTimer, b, true);
|
||
}
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* outTimer(iPIT, iPITTimer, port, bOut, addrFrom)
|
||
*
|
||
* We now rely EXCLUSIVELY on setBurstCycles() to address situations where quick timer interrupt turn-around
|
||
* is expected; eg, by the ROM BIOS POST 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 must reduce the current
|
||
* burst cycle count, so that the current instruction burst will end at 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 may have the effect of *lengthening* the current burst instead of shortening it, but stepCPU() should be
|
||
* OK with that.
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iPIT (0 or 1)
|
||
* @param {number} iPITTimer (0, 1, or 2)
|
||
* @param {number} port (0x40, 0x41, 0x42, etc)
|
||
* @param {number} bOut
|
||
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
|
||
*/
|
||
ChipSet.prototype.outTimer = function(iPIT, iPITTimer, port, bOut, addrFrom)
|
||
{
|
||
if (this.messageEnabled(Messages.TIMER | Messages.PORT)) {
|
||
this.printMessageIO(port, bOut, addrFrom, "PIT" + iPIT + ".TIMER" + iPITTimer, null, true);
|
||
}
|
||
|
||
var iBaseTimer = (iPIT? 3 : 0);
|
||
var timer = this.aTimers[iBaseTimer + iPITTimer];
|
||
|
||
if (timer.countIndex == timer.countBytes) {
|
||
this.resetTimerIndex(iBaseTimer + iPITTimer);
|
||
}
|
||
|
||
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.PIT_CTRL.MODE0 || timer.mode == ChipSet.PIT_CTRL.MODE4) {
|
||
timer.fCountLatched = false;
|
||
timer.countCurrent[0] = timer.countStart[0] = timer.countInit[0];
|
||
timer.countCurrent[1] = timer.countStart[1] = timer.countInit[1];
|
||
timer.nCyclesStart = 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.PIT_CTRL.MODE0);
|
||
|
||
if (iPIT == ChipSet.PIT0.INDEX && iPITTimer == ChipSet.PIT0.TIMER0) {
|
||
/*
|
||
* TODO: Determine if there are situations/modes where I should NOT automatically clear IRQ0 on behalf of TIMER0.
|
||
*/
|
||
this.clearIRR(ChipSet.IRQ.TIMER0);
|
||
var countInit = this.getTimerInit(ChipSet.PIT0.TIMER0);
|
||
var nCyclesRemain = (countInit * this.nTicksDivisor) | 0;
|
||
if (timer.mode == ChipSet.PIT_CTRL.MODE3) nCyclesRemain >>= 1;
|
||
this.cpu.setBurstCycles(nCyclesRemain);
|
||
}
|
||
}
|
||
|
||
if (iPIT == ChipSet.PIT0.INDEX && iPITTimer == ChipSet.PIT0.TIMER2) this.setSpeaker();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* inTimerCtrl(iPIT, port, addrFrom)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iPIT (0 or 1)
|
||
* @param {number} port (0x43 or 0x4B)
|
||
* @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(iPIT, port, addrFrom)
|
||
{
|
||
this.printMessageIO(port, null, addrFrom, "PIT" + iPIT + ".CTRL", null, Messages.TIMER);
|
||
/*
|
||
* NOTE: Even though reads to port 0x43 are undefined (I think), I'm going to "define" it
|
||
* as returning the last value written, purely for the Debugger's benefit.
|
||
*/
|
||
return iPIT? this.bPIT1Ctrl : this.bPIT0Ctrl;
|
||
};
|
||
|
||
/**
|
||
* outTimerCtrl(iPIT, port, bOut, addrFrom)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iPIT (0 or 1)
|
||
* @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(iPIT, port, bOut, addrFrom)
|
||
{
|
||
this.printMessageIO(port, bOut, addrFrom, "PIT" + iPIT + ".CTRL", null, Messages.TIMER);
|
||
|
||
/*
|
||
* Extract the SC (Select Counter) bits.
|
||
*/
|
||
var iBaseTimer = 0;
|
||
var iPITTimer = (bOut & ChipSet.PIT_CTRL.SC);
|
||
if (!iPIT) {
|
||
this.bPIT0Ctrl = bOut;
|
||
} else {
|
||
iBaseTimer = 3;
|
||
this.bPIT1Ctrl = bOut;
|
||
}
|
||
|
||
/*
|
||
* Check for the Read-Back command and process as needed.
|
||
*/
|
||
if (iPITTimer == ChipSet.PIT_CTRL.SC_BACK) {
|
||
if (!(bOut & ChipSet.PIT_CTRL.RB_STATUS)) {
|
||
for (iPITTimer = 0; iPITTimer <= 2; iPITTimer++) {
|
||
if (bOut & (ChipSet.PIT_CTRL.RB_CTR0 << iPITTimer)) {
|
||
this.latchTimerStatus(iBaseTimer + iPITTimer);
|
||
}
|
||
}
|
||
}
|
||
if (!(bOut & ChipSet.PIT_CTRL.RB_COUNTS)) {
|
||
for (iPITTimer = 0; iPITTimer <= 2; iPITTimer++) {
|
||
if (bOut & (ChipSet.PIT_CTRL.RB_CTR0 << iPITTimer)) {
|
||
this.latchTimerCount(iBaseTimer + iPITTimer);
|
||
}
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* Convert the SC (Select Counter) bits into an iPITTimer index (0-2).
|
||
*/
|
||
iPITTimer >>= ChipSet.PIT_CTRL.SC_SHIFT;
|
||
|
||
/*
|
||
* Extract BCD (bit 0), MODE (bits 1-3), and RW (bits 4-5), which we simply store as-is (see setTimerMode).
|
||
*/
|
||
var bcd = (bOut & ChipSet.PIT_CTRL.BCD);
|
||
var mode = (bOut & ChipSet.PIT_CTRL.MODE);
|
||
var rw = (bOut & ChipSet.PIT_CTRL.RW);
|
||
|
||
if (rw == ChipSet.PIT_CTRL.RW_LATCH) {
|
||
/*
|
||
* Of all the RW bit combinations, this is the only one that "countermands" normal control register
|
||
* processing (the BCD and MODE bits are "don't care").
|
||
*/
|
||
this.latchTimerCount(iBaseTimer + iPITTimer);
|
||
}
|
||
else {
|
||
this.setTimerMode(iBaseTimer + iPITTimer, 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 (iPIT == ChipSet.PIT0.INDEX && iPITTimer == ChipSet.PIT0.TIMER0) 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 (iPIT == ChipSet.PIT0.INDEX && iPITTimer == ChipSet.PIT0.TIMER2) {
|
||
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.nCyclesStart = this.cpu.getCycles(this.fScaleTimers);
|
||
if (DEBUG && this.messageEnabled(Messages.TIMER)) {
|
||
this.printMessage("PIT0.TIMER0 count reset @" + timer.nCyclesStart + " cycles", true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 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, nCycles)
|
||
*
|
||
* This is called by the CPU to determine the maximum number of cycles it can process for the current burst.
|
||
* It's presumed that no instructions have been executed since the last updateTimer(iTimer) call.
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iTimer
|
||
* @param {number} nCycles desired
|
||
* @return {number} maximum number of cycles remaining for the specified timer (<= nCycles)
|
||
*/
|
||
ChipSet.prototype.getTimerCycleLimit = function(iTimer, nCycles)
|
||
{
|
||
var timer = this.aTimers[iTimer];
|
||
if (timer.fCounting) {
|
||
var nCyclesUpdate = this.cpu.getCycles(this.fScaleTimers);
|
||
var ticksElapsed = ((nCyclesUpdate - timer.nCyclesStart) / this.nTicksDivisor) | 0;
|
||
// DEBUG: this.assert(ticksElapsed >= 0);
|
||
var countStart = this.getTimerStart(iTimer);
|
||
var count = countStart - ticksElapsed;
|
||
if (timer.mode == ChipSet.PIT_CTRL.MODE3) count -= ticksElapsed;
|
||
// DEBUG: this.assert(count > 0);
|
||
var nCyclesRemain = (count * this.nTicksDivisor) | 0;
|
||
if (timer.mode == ChipSet.PIT_CTRL.MODE3) nCyclesRemain >>= 1;
|
||
if (nCycles > nCyclesRemain) nCycles = nCyclesRemain;
|
||
}
|
||
return nCycles;
|
||
};
|
||
|
||
/**
|
||
* latchTimerCount(iTimer)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iTimer
|
||
*/
|
||
ChipSet.prototype.latchTimerCount = 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.fCountLatched = true;
|
||
|
||
/*
|
||
* VERIFY: That a latch request resets the timer index.
|
||
*/
|
||
this.resetTimerIndex(iTimer);
|
||
};
|
||
|
||
/**
|
||
* latchTimerStatus(iTimer)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iTimer
|
||
*/
|
||
ChipSet.prototype.latchTimerStatus = function(iTimer)
|
||
{
|
||
var timer = this.aTimers[iTimer];
|
||
if (!timer.fStatusLatched) {
|
||
this.updateTimer(iTimer);
|
||
timer.bStatus = timer.bcd | timer.mode | timer.rw | (timer.countIndex < timer.countBytes? ChipSet.PIT_CTRL.RB_NULL : 0) | (timer.fOUT? ChipSet.PIT_CTRL.RB_OUT : 0);
|
||
timer.fStatusLatched = true;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 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.fCountLatched = false;
|
||
timer.fCounting = false;
|
||
timer.fStatusLatched = 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.PIT_CTRL.RW_MSB? 1 : 0);
|
||
timer.countBytes = (timer.rw == ChipSet.PIT_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 on a 4.77Mhz PC, 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} [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.PIT0.TIMER2 || (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 ticksElapsed = ((nCycles - timer.nCyclesStart) / this.nTicksDivisor) | 0;
|
||
|
||
if (ticksElapsed < 0) {
|
||
if (DEBUG && this.messageEnabled(Messages.TIMER)) {
|
||
this.printMessage("updateTimer(" + iTimer + "): negative tick count (" + ticksElapsed + ")", true);
|
||
}
|
||
timer.nCyclesStart = nCycles;
|
||
ticksElapsed = 0;
|
||
}
|
||
|
||
var countInit = this.getTimerInit(iTimer);
|
||
var countStart = this.getTimerStart(iTimer);
|
||
|
||
var fFired = false;
|
||
var count = countStart - ticksElapsed;
|
||
|
||
/*
|
||
* 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.PIT_CTRL.MODE0) {
|
||
if (count <= 0) count = 0;
|
||
if (DEBUG && this.messageEnabled(Messages.TIMER)) {
|
||
this.printMessage("updateTimer(" + iTimer + "): MODE0 timer count=" + count, true);
|
||
}
|
||
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.PIT_CTRL.MODE2) {
|
||
timer.fOUT = (count != 1); // yes, this line does seem rather pointless....
|
||
if (count <= 0) {
|
||
count = countInit + count;
|
||
if (count <= 0) {
|
||
/*
|
||
* TODO: Consider whether we ever care about TIMER1 or TIMER2 underflow
|
||
*/
|
||
if (DEBUG && this.messageEnabled(Messages.TIMER) && !iTimer) {
|
||
this.printMessage("updateTimer(" + iTimer + "): mode=2, underflow=" + count, true);
|
||
}
|
||
count = countInit;
|
||
}
|
||
timer.countStart[0] = count & 0xff;
|
||
timer.countStart[1] = (count >> 8) & 0xff;
|
||
timer.nCyclesStart = 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.PIT_CTRL.MODE3) {
|
||
count -= ticksElapsed;
|
||
if (count <= 0) {
|
||
timer.fOUT = !timer.fOUT;
|
||
count = countInit + count;
|
||
if (count <= 0) {
|
||
/*
|
||
* TODO: Consider whether we ever care about TIMER1 or TIMER2 underflow
|
||
*/
|
||
if (DEBUG && this.messageEnabled(Messages.TIMER) && !iTimer) {
|
||
this.printMessage("updateTimer(" + iTimer + "): mode=3, underflow=" + count, true);
|
||
}
|
||
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) & 0xff;
|
||
timer.nCyclesStart = nCycles;
|
||
if (!iTimer && timer.fOUT) {
|
||
fFired = true;
|
||
this.setIRR(ChipSet.IRQ.TIMER0);
|
||
if (MAXDEBUG && DEBUGGER) this.acTimersFired[iTimer]++;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (DEBUG && this.messageEnabled(Messages.TIMER | Messages.LOG)) {
|
||
this.log("TIMER" + iTimer + " count: " + count + ", ticks: " + ticksElapsed + ", fired: " + (fFired? "true" : "false"));
|
||
}
|
||
|
||
timer.countCurrent[0] = count & 0xff;
|
||
timer.countCurrent[1] = (count >> 8) & 0xff;
|
||
if (fCycleReset) this.nCyclesStart = 0;
|
||
}
|
||
return timer;
|
||
};
|
||
|
||
/**
|
||
* updateAllTimers(fCycleReset)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {boolean} [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.updateRTCTime();
|
||
};
|
||
|
||
/**
|
||
* 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.aDIPSwitches[0][1];
|
||
}
|
||
else if (this.kbd) {
|
||
b = this.kbd.readScanCode();
|
||
}
|
||
}
|
||
this.printMessageIO(port, null, addrFrom, "PPI_A", 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.printMessageIO(port, bOut, addrFrom, "PPI_A");
|
||
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.printMessageIO(port, null, addrFrom, "PPI_B", b);
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* outPPIB(port, bOut, addrFrom)
|
||
*
|
||
* This is the original (MODEL_5150 and MODEL_5160) handler for port 0x61. Functionality common
|
||
* to all models must be placed in updatePPIB().
|
||
*
|
||
* @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.printMessageIO(port, bOut, addrFrom, "PPI_B");
|
||
this.updatePPIB(bOut);
|
||
};
|
||
|
||
/**
|
||
* 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.
|
||
*
|
||
* UPDATE: The WOLF3D keyboard interrupt handler toggles the CLEAR_KBD bit of port 0x61 (ie, it sets and then
|
||
* clears the bit) after reading the scan code from port 0x60; assuming that they use the same interrupt handler
|
||
* for all machine models (which I haven't verified), the clear implication is that updatePPIB() also needs to
|
||
* support CLEAR_KBD and CLK_KBD, so I've moved that code from outPPIB() to updatePPIB().
|
||
*
|
||
* @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 (this.kbd) this.kbd.setEnabled(!(bOut & ChipSet.PPI_B.CLEAR_KBD), !!(bOut & ChipSet.PPI_B.CLK_KBD));
|
||
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|0) == ChipSet.MODEL_5150) {
|
||
if (this.bPPIB & ChipSet.PPI_B.ENABLE_SW2) {
|
||
b |= this.aDIPSwitches[1][1] & ChipSet.PPI_C.SW;
|
||
} else {
|
||
b |= (this.aDIPSwitches[1][1] >> 4) & 0x1;
|
||
}
|
||
} else {
|
||
if (this.bPPIB & ChipSet.PPI_B.ENABLE_SW_HI) {
|
||
b |= this.aDIPSwitches[0][1] >> 4;
|
||
} else {
|
||
b |= this.aDIPSwitches[0][1] & 0xf;
|
||
}
|
||
}
|
||
|
||
if (this.bPPIB & ChipSet.PPI_B.CLK_TIMER2) {
|
||
var timer = this.updateTimer(ChipSet.PIT0.TIMER2);
|
||
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 Messages.MEM.
|
||
*/
|
||
this.printMessageIO(port, null, addrFrom, "PPI_C", b, Messages.CHIPSET | Messages.MEM);
|
||
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.printMessageIO(port, bOut, addrFrom, "PPI_C");
|
||
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.printMessageIO(port, null, addrFrom, "PPI_CTRL", 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.printMessageIO(port, bOut, addrFrom, "PPI_CTRL");
|
||
this.bPPICtrl = bOut;
|
||
};
|
||
|
||
/**
|
||
* in8041Kbd(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.in8041Kbd = function(port, addrFrom)
|
||
{
|
||
var b = this.kbd? this.kbd.readScanCode() : 0;
|
||
this.printMessageIO(port, null, addrFrom, "8041_KBD", b);
|
||
this.b8041Status &= ~ChipSet.KC8042.STATUS.OUTBUFF_FULL;
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* out8041Kbd(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.out8041Kbd = function(port, bOut, addrFrom)
|
||
{
|
||
this.printMessageIO(port, bOut, addrFrom, "8041_KBD");
|
||
// if (this.kbd) this.kbd.sendCmd(bOut);
|
||
};
|
||
|
||
/**
|
||
* in8041Ctrl(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.in8041Ctrl = function(port, addrFrom)
|
||
{
|
||
var b = this.bPPIB;
|
||
this.printMessageIO(port, null, addrFrom, "8041_CTRL", b);
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* out8041Ctrl(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.out8041Ctrl = function(port, bOut, addrFrom)
|
||
{
|
||
this.printMessageIO(port, bOut, addrFrom, "8041_CTRL");
|
||
this.updatePPIB(bOut);
|
||
};
|
||
|
||
/**
|
||
* in8041Status(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.in8041Status = function(port, addrFrom)
|
||
{
|
||
var b = this.b8041Status;
|
||
this.printMessageIO(port, null, addrFrom, "8041_STATUS", b);
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* in8042OutBuff(port, addrFrom)
|
||
*
|
||
* Return the contents of the OUTBUFF register and clear the OUTBUFF_FULL status bit.
|
||
*
|
||
* This function then calls kbd.checkScanCode(), on the theory that the next buffered scan
|
||
* code, if any, can now be delivered to OUTBUFF. However, there are applications like
|
||
* BASICA that install a keyboard interrupt handler that reads OUTBUFF, do some scan code
|
||
* preprocessing, and then pass control on to the ROM's interrupt handler. As a result,
|
||
* OUTBUFF is read multiple times during a single interrupt, so filling it with new data
|
||
* after every read would result in lost scan codes.
|
||
*
|
||
* To avoid that problem, kbd.checkScanCode() also requires that kbd.setEnabled() be called
|
||
* before it supplies any more data via notifyKbdData(). That will happen as soon as the
|
||
* ROM re-enables the controller, and is why KC8042.CMD.ENABLE_KBD processing also ends with a
|
||
* call to kbd.checkScanCode().
|
||
*
|
||
* Note that, the foregoing notwithstanding, I still clear the OUTBUFF_FULL bit here (as I
|
||
* believe I should); fortunately, none of the interrupt handlers rely on OUTBUFF_FULL as a
|
||
* prerequisite for reading OUTBUFF (not the BASICA handler, and not the ROM). The assumption
|
||
* seems to be that if an interrupt occurred, OUTBUFF must contain data, regardless of the
|
||
* state of OUTBUFF_FULL.
|
||
*
|
||
* @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.printMessageIO(port, null, addrFrom, "8042_OUTBUFF", b, Messages.C8042);
|
||
this.b8042Status &= ~(ChipSet.KC8042.STATUS.OUTBUFF_FULL | ChipSet.KC8042.STATUS.OUTBUFF_DELAY);
|
||
if (this.kbd) this.kbd.checkScanCode();
|
||
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 KC8042.DATA.CMD "data byte". Before clearing KC8042.STATUS.CMD_FLAG, however, we see if it's set,
|
||
* and then based on the previous KC8042.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.printMessageIO(port, bOut, addrFrom, "8042_INBUF.DATA", null, Messages.C8042);
|
||
|
||
if (this.b8042Status & ChipSet.KC8042.STATUS.CMD_FLAG) {
|
||
|
||
switch (this.b8042InBuff) {
|
||
|
||
case ChipSet.KC8042.CMD.WRITE_CMD:
|
||
this.set8042CmdData(bOut);
|
||
break;
|
||
|
||
case ChipSet.KC8042.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.KC8042.DATA.CMD.NO_CLOCK);
|
||
if (this.kbd) this.set8042OutBuff(this.kbd.sendCmd(bOut));
|
||
break;
|
||
}
|
||
}
|
||
this.b8042InBuff = bOut;
|
||
this.b8042Status &= ~ChipSet.KC8042.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 (KC8042.RWREG.NMI_ERROR), 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.KC8042.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.KC8042.RWREG.NMI_ERROR | ChipSet.KC8042.RWREG.REFRESH_BIT) | ((this.cpu.getCycles() & 0x40)? ChipSet.KC8042.RWREG.REFRESH_BIT : 0);
|
||
/*
|
||
* Thanks to the WAITF function, this has become a very "busy" port, so if this generates too
|
||
* many messages, try adding Messages.LOG to the criteria.
|
||
*/
|
||
this.printMessageIO(port, null, addrFrom, "8042_RWREG", b, Messages.C8042);
|
||
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.printMessageIO(port, bOut, addrFrom, "8042_RWREG", null, Messages.C8042);
|
||
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.printMessageIO(port, null, addrFrom, "8042_STATUS", this.b8042Status, Messages.C8042);
|
||
var b = this.b8042Status & 0xff;
|
||
/*
|
||
* There's code in the 5170 BIOS (F000:03BF) that writes an 8042 command (0xAA), waits for
|
||
* KC8042.STATUS.INBUFF_FULL to go clear (which it always is, because we always accept commands
|
||
* immediately), then checks KC8042.STATUS.OUTBUFF_FULL and performs a "flush" on port 0x60 if
|
||
* it's set, then waits for KC8042.STATUS.OUTBUFF_FULL *again*. Unfortunately, the "flush" throws
|
||
* away our response if we respond immediately.
|
||
*
|
||
* So now when out8042InBuffCmd() has a response, it sets KC8042.STATUS.OUTBUFF_DELAY instead
|
||
* (which is outside the 0xff range of bits we return); when we see KC8042.STATUS.OUTBUFF_DELAY,
|
||
* we clear it and set KC8042.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 delay bit.
|
||
*/
|
||
if (this.b8042Status & ChipSet.KC8042.STATUS.OUTBUFF_DELAY) {
|
||
this.b8042Status |= ChipSet.KC8042.STATUS.OUTBUFF_FULL;
|
||
this.b8042Status &= ~ChipSet.KC8042.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 KC8042.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.printMessageIO(port, bOut, addrFrom, "8042_INBUFF.CMD", null, Messages.C8042);
|
||
this.assert(!(this.b8042Status & ChipSet.KC8042.STATUS.INBUFF_FULL));
|
||
this.b8042InBuff = bOut;
|
||
|
||
this.b8042Status |= ChipSet.KC8042.STATUS.CMD_FLAG;
|
||
|
||
var bPulseBits = 0;
|
||
if (this.b8042InBuff >= ChipSet.KC8042.CMD.PULSE_OUTPORT) {
|
||
bPulseBits = (this.b8042InBuff ^ 0xf);
|
||
/*
|
||
* Now that we have isolated the bit(s) to pulse, map all pulse commands to KC8042.CMD.PULSE_OUTPORT
|
||
*/
|
||
this.b8042InBuff = ChipSet.KC8042.CMD.PULSE_OUTPORT;
|
||
}
|
||
|
||
switch (this.b8042InBuff) {
|
||
case ChipSet.KC8042.CMD.READ_CMD: // 0x20
|
||
this.set8042OutBuff(this.b8042CmdData);
|
||
break;
|
||
|
||
case ChipSet.KC8042.CMD.WRITE_CMD: // 0x60
|
||
/*
|
||
* No further action required for this command; more data is expected via out8042InBuffData()
|
||
*/
|
||
break;
|
||
|
||
case ChipSet.KC8042.CMD.DISABLE_KBD: // 0xAD
|
||
this.set8042CmdData(this.b8042CmdData | ChipSet.KC8042.DATA.CMD.NO_CLOCK);
|
||
if (DEBUG) this.printMessage("keyboard disabled", Messages.KEYBOARD | Messages.PORT);
|
||
/*
|
||
* 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.BAT_OK response code? Seems like an odd thing for
|
||
* a "disabled interface" to do.
|
||
*/
|
||
break;
|
||
|
||
case ChipSet.KC8042.CMD.ENABLE_KBD: // 0xAE
|
||
this.set8042CmdData(this.b8042CmdData & ~ChipSet.KC8042.DATA.CMD.NO_CLOCK);
|
||
if (DEBUG) this.printMessage("keyboard re-enabled", Messages.KEYBOARD | Messages.PORT);
|
||
if (this.kbd) this.kbd.checkScanCode();
|
||
break;
|
||
|
||
case ChipSet.KC8042.CMD.SELF_TEST: // 0xAA
|
||
if (this.kbd) this.kbd.flushScanCode();
|
||
this.set8042CmdData(this.b8042CmdData | ChipSet.KC8042.DATA.CMD.NO_CLOCK);
|
||
if (DEBUG) this.printMessage("keyboard disabled on reset", Messages.KEYBOARD | Messages.PORT);
|
||
this.set8042OutBuff(ChipSet.KC8042.DATA.SELF_TEST.OK);
|
||
this.set8042OutPort(ChipSet.KC8042.OUTPORT.NO_RESET | ChipSet.KC8042.OUTPORT.A20_ON);
|
||
break;
|
||
|
||
case ChipSet.KC8042.CMD.INTF_TEST: // 0xAB
|
||
/*
|
||
* TODO: Determine all the side-effects of the Interface Test, if any.
|
||
*/
|
||
this.set8042OutBuff(ChipSet.KC8042.DATA.INTF_TEST.OK);
|
||
break;
|
||
|
||
case ChipSet.KC8042.CMD.READ_INPORT: // 0xC0
|
||
this.set8042OutBuff(this.b8042InPort);
|
||
break;
|
||
|
||
case ChipSet.KC8042.CMD.READ_OUTPORT: // 0xD0
|
||
this.set8042OutBuff(this.b8042OutPort);
|
||
break;
|
||
|
||
case ChipSet.KC8042.CMD.WRITE_OUTPORT: // 0xD1
|
||
/*
|
||
* No further action required for this command; more data is expected via out8042InBuffData()
|
||
*/
|
||
break;
|
||
|
||
case ChipSet.KC8042.CMD.READ_TEST: // 0xE0
|
||
this.set8042OutBuff((this.b8042CmdData & ChipSet.KC8042.DATA.CMD.NO_CLOCK)? 0 : ChipSet.KC8042.TESTPORT.KBD_CLOCK);
|
||
break;
|
||
|
||
case ChipSet.KC8042.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* CPU state (eg, cycle counts), so we call cpu.resetRegs() instead
|
||
* of cpu.reset().
|
||
*/
|
||
this.cpu.resetRegs();
|
||
}
|
||
break;
|
||
|
||
default:
|
||
if (DEBUG && this.messageEnabled(Messages.C8042)) {
|
||
this.printMessage("unrecognized 8042 command: " + str.toHexByte(this.b8042InBuff), true);
|
||
this.dbg.stopCPU();
|
||
}
|
||
break;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* set8042CmdData(b)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} b
|
||
*/
|
||
ChipSet.prototype.set8042CmdData = function(b)
|
||
{
|
||
this.b8042CmdData = b;
|
||
this.assert(ChipSet.KC8042.DATA.CMD.SYS_FLAG === ChipSet.KC8042.STATUS.SYS_FLAG);
|
||
this.b8042Status = (this.b8042Status & ~ChipSet.KC8042.STATUS.SYS_FLAG) | (b & ChipSet.KC8042.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.KC8042.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 setEnabled() 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.BAT_OK 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).
|
||
*/
|
||
this.kbd.setEnabled(!!(b & ChipSet.KC8042.DATA.CMD.NO_INHIBIT), !(b & ChipSet.KC8042.DATA.CMD.NO_CLOCK));
|
||
}
|
||
};
|
||
|
||
/**
|
||
* set8042OutBuff(b, fNoDelay)
|
||
*
|
||
* The 5170 ROM BIOS assumed there would be a slight delay after certain 8042 commands, like SELF_TEST
|
||
* (0xAA), before there was an OUTBUFF response; in fact, there is BIOS code that will fail without such
|
||
* a delay. This is discussed in greater detail in in8042Status().
|
||
*
|
||
* So we default to a "single poll" delay, setting OUTBUFF_DELAY instead of OUTBUFF_FULL, unless the caller
|
||
* explicitly asks for no delay. The fNoDelay parameter was added later, so that notifyKbdData() could
|
||
* request immediate delivery of keyboard scan codes, because some operating systems (eg, Microport's 1986
|
||
* version of Unix for PC AT machines) poll the status port only once, immediately giving up if no data is
|
||
* available.
|
||
*
|
||
* TODO: Determine if we should invert the fNoDelay default (from false to true) and delay only in specific
|
||
* cases; ie, perhaps only the SELF_TEST command required a delay.
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} b
|
||
* @param {boolean} [fNoDelay]
|
||
*/
|
||
ChipSet.prototype.set8042OutBuff = function(b, fNoDelay)
|
||
{
|
||
if (b >= 0) {
|
||
this.b8042OutBuff = b;
|
||
if (fNoDelay) {
|
||
this.b8042Status |= ChipSet.KC8042.STATUS.OUTBUFF_FULL;
|
||
} else {
|
||
this.b8042Status &= ~ChipSet.KC8042.STATUS.OUTBUFF_FULL;
|
||
this.b8042Status |= ChipSet.KC8042.STATUS.OUTBUFF_DELAY;
|
||
}
|
||
if (DEBUG && this.messageEnabled(Messages.KEYBOARD | Messages.PORT)) {
|
||
this.printMessage("set8042OutBuff(" + str.toHexByte(b) + ',' + (fNoDelay? "no" : "") + "delay)", true);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* set8042OutPort(b)
|
||
*
|
||
* When ChipSet.KC8042.CMD.WRITE_OUTPORT (0xD1) is written to port 0x64, the next byte written to port 0x60 comes here,
|
||
* to the KBC's OUTPORT. One of the most important bits in the OUTPORT is the A20_ON bit (0x02): set it to turn A20 on,
|
||
* clear it to turn A20 off.
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} b
|
||
*/
|
||
ChipSet.prototype.set8042OutPort = function(b)
|
||
{
|
||
this.b8042OutPort = b;
|
||
|
||
this.bus.setA20(!!(b & ChipSet.KC8042.OUTPORT.A20_ON));
|
||
|
||
if (!(b & ChipSet.KC8042.OUTPORT.NO_RESET)) {
|
||
/*
|
||
* Bit 0 of the 8042's output port is connected to RESET. Normally, it's "pulsed" with the
|
||
* KC8042.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 && this.messageEnabled(Messages.C8042)) {
|
||
this.printMessage("unexpected 8042 output port reset: " + str.toHexByte(b), true);
|
||
this.dbg.stopCPU();
|
||
}
|
||
this.cpu.resetRegs();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* notifyKbdData(b)
|
||
*
|
||
* In the old days of PCx86, the Keyboard component would simply call setIRR() when it had some data for the
|
||
* keyboard controller. However, the Keyboard's sole responsibility is to emulate an actual keyboard and call
|
||
* notifyKbdData() whenever it has some data; it's not allowed to mess with IRQ lines.
|
||
*
|
||
* If there's an 8042, we check (this.b8042CmdData & ChipSet.KC8042.DATA.CMD.NO_CLOCK); if NO_CLOCK is clear,
|
||
* we can raise the IRQ immediately. Well, not quite immediately....
|
||
*
|
||
* Notes regarding the MODEL_5170 (eg, /devices/pc/machine/5170/ega/1152kb/rev3/machine.xml):
|
||
*
|
||
* The "Rev3" BIOS, dated 11-Nov-1985, contains the following code in the keyboard interrupt handler at K26A:
|
||
*
|
||
* F000:3704 FA CLI
|
||
* F000:3705 B020 MOV AL,20
|
||
* F000:3707 E620 OUT 20,AL
|
||
* F000:3709 B0AE MOV AL,AE
|
||
* F000:370B E88D02 CALL SHIP_IT
|
||
* F000:370E FA CLI <-- window of opportunity
|
||
* F000:370F 07 POP ES
|
||
* F000:3710 1F POP DS
|
||
* F000:3711 5F POP DI
|
||
* F000:3712 5E POP SI
|
||
* F000:3713 5A POP DX
|
||
* F000:3714 59 POP CX
|
||
* F000:3715 5B POP BX
|
||
* F000:3716 58 POP AX
|
||
* F000:3717 5D POP BP
|
||
* F000:3718 CF IRET
|
||
*
|
||
* and SHIP_IT looks like this:
|
||
*
|
||
* F000:399B 50 PUSH AX
|
||
* F000:399C FA CLI
|
||
* F000:399D 2BC9 SUB CX,CX
|
||
* F000:399F E464 IN AL,64
|
||
* F000:39A1 A802 TEST AL,02
|
||
* F000:39A3 E0FA LOOPNZ 399F
|
||
* F000:39A5 58 POP AX
|
||
* F000:39A6 E664 OUT 64,AL
|
||
* F000:39A8 FB STI
|
||
* F000:39A9 C3 RET
|
||
*
|
||
* This code *appears* to be trying to ensure that another keyboard interrupt won't occur until after the IRET,
|
||
* but sadly, it looks to me like the CLI following the call to SHIP_IT is too late. SHIP_IT should have been
|
||
* written with PUSHF/CLI and POPF intro/outro sequences, thereby honoring the first CLI at the top of K26A and
|
||
* eliminating the need for the second CLI (@F000:370E).
|
||
*
|
||
* Of course, in "real life", this was probably never a problem, because the 8042 probably wasn't fast enough to
|
||
* generate another interrupt so soon after receiving the ChipSet.KC8042.CMD.ENABLE_KBD command. In my case, I ran
|
||
* into this problem by 1) turning on "kbd" Debugger messages and 2) rapidly typing lots of keys. The Debugger
|
||
* messages bogged the machine down enough for me to hit the "window of opportunity", generating this message in
|
||
* PC-DOS 3.20:
|
||
*
|
||
* "FATAL: Internal Stack Failure, System Halted."
|
||
*
|
||
* and halting the system @0070:0923 (JMP 0923).
|
||
*
|
||
* That wasn't the only spot in the BIOS where I hit this problem; here's another "window of opportunity":
|
||
*
|
||
* F000:3975 FA CLI
|
||
* F000:3976 B020 MOV AL,20
|
||
* F000:3978 E620 OUT 20,AL
|
||
* F000:397A B0AE MOV AL,AE
|
||
* F000:397C E81C00 CALL SHIP_IT
|
||
* F000:397F B80291 MOV AX,9102 <-- window of opportunity
|
||
* F000:3982 CD15 INT 15
|
||
* F000:3984 80269600FC AND [0096],FC
|
||
* F000:3989 E982FD JMP 370E
|
||
*
|
||
* In this second, lengthier, example, I counted about 60 instructions being executed from the EOI @F000:3978 to
|
||
* the final IRET @F000:3718, most of them in the INT 0x15 handler. So, I'm going to double that count to 120
|
||
* instructions, just to be safe, and pass that along to every setIRR() call we make here.
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} b
|
||
*/
|
||
ChipSet.prototype.notifyKbdData = function(b)
|
||
{
|
||
if (DEBUG && this.messageEnabled(Messages.KEYBOARD | Messages.PORT)) {
|
||
this.printMessage("notifyKbdData(" + str.toHexByte(b) + ')', true);
|
||
}
|
||
if (this.model < ChipSet.MODEL_5170) {
|
||
/*
|
||
* TODO: Should we be checking bPPI for PPI_B.CLK_KBD on these older machines, before calling setIRR()?
|
||
*/
|
||
this.setIRR(ChipSet.IRQ.KBD, 4);
|
||
this.b8041Status |= ChipSet.KC8042.STATUS.OUTBUFF_FULL;
|
||
}
|
||
else {
|
||
if (!(this.b8042CmdData & ChipSet.KC8042.DATA.CMD.NO_CLOCK)) {
|
||
/*
|
||
* The next read of b8042OutBuff will clear both of these bits and call kbd.checkScanCode(),
|
||
* which will call notifyKbdData() again if there's still keyboard data to process.
|
||
*/
|
||
if (!(this.b8042Status & (ChipSet.KC8042.STATUS.OUTBUFF_FULL | ChipSet.KC8042.STATUS.OUTBUFF_DELAY))) {
|
||
this.set8042OutBuff(b, true);
|
||
this.kbd.shiftScanCode();
|
||
/*
|
||
* A delay of 4 instructions was originally requested as part of the the Keyboard's resetDevice()
|
||
* response, but a larger delay (120) is now needed for MODEL_5170 machines, per the discussion above.
|
||
*/
|
||
this.setIRR(ChipSet.IRQ.KBD, 120);
|
||
}
|
||
else {
|
||
if (DEBUG && this.messageEnabled(Messages.KEYBOARD | Messages.PORT)) {
|
||
this.printMessage("notifyKbdData(" + str.toHexByte(b) + "): output buffer full", true);
|
||
}
|
||
}
|
||
} else {
|
||
if (DEBUG && this.messageEnabled(Messages.KEYBOARD | Messages.PORT)) {
|
||
this.printMessage("notifyKbdData(" + str.toHexByte(b) + "): disabled", true);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* in6300DIPSwitches(iDIP, port, addrFrom)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} iDIP (0 or 1)
|
||
* @param {number} port (0x66 or 0x67)
|
||
* @param {number} [addrFrom] (not defined if the Debugger is trying to read the specified port)
|
||
* @return {number} simulated port value
|
||
*/
|
||
ChipSet.prototype.in6300DIPSwitches = function(iDIP, port, addrFrom)
|
||
{
|
||
var b = this.aDIPSwitches[iDIP][1];
|
||
this.printMessageIO(port, null, addrFrom, "DIPSW-" + iDIP, b, Messages.CHIPSET);
|
||
return b;
|
||
};
|
||
|
||
/**
|
||
* 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.printMessageIO(port, null, addrFrom, "CMOS.ADDR", this.bCMOSAddr, Messages.CMOS);
|
||
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.printMessageIO(port, bOut, addrFrom, "CMOS.ADDR", null, Messages.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.STATUSD? this.getRTCByte(bAddr) : this.abCMOSData[bAddr]);
|
||
if (this.messageEnabled(Messages.CMOS | Messages.PORT)) {
|
||
this.printMessageIO(port, null, addrFrom, "CMOS.DATA[" + str.toHexByte(bAddr) + "]", bIn, true);
|
||
}
|
||
if (addrFrom != null) {
|
||
if (bAddr == ChipSet.CMOS.ADDR.STATUSC) {
|
||
/*
|
||
* When software reads the STATUSC port, all interrupt bits (PF, AF, and UF) are automatically
|
||
* cleared, which in turn clears the IRQF bit, which in turn clears the IRQ.
|
||
*/
|
||
this.abCMOSData[bAddr] &= ChipSet.CMOS.STATUSC.RESERVED;
|
||
if (bIn & ChipSet.CMOS.STATUSC.IRQF) this.clearIRR(ChipSet.IRQ.RTC);
|
||
/*
|
||
* If we just cleared PF, and PIE is still set, then we need to make sure the next Periodic Interrupt
|
||
* occurs in a timely manner, too.
|
||
*/
|
||
if ((bIn & ChipSet.CMOS.STATUSC.PF) && (this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.PIE)) {
|
||
if (DEBUG) this.printMessage("RTC periodic interrupt cleared", Messages.RTC);
|
||
this.setRTCCycleLimit();
|
||
}
|
||
}
|
||
}
|
||
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;
|
||
if (this.messageEnabled(Messages.CMOS | Messages.PORT)) {
|
||
this.printMessageIO(port, bOut, addrFrom, "CMOS.DATA[" + str.toHexByte(bAddr) + "]", null, true);
|
||
}
|
||
var bDelta = bOut ^ this.abCMOSData[bAddr];
|
||
this.abCMOSData[bAddr] = (bAddr <= ChipSet.CMOS.ADDR.STATUSD? this.setRTCByte(bAddr, bOut) : bOut);
|
||
if (bAddr == ChipSet.CMOS.ADDR.STATUSB && (bDelta & ChipSet.CMOS.STATUSB.PIE)) {
|
||
if (bOut & ChipSet.CMOS.STATUSB.PIE) {
|
||
if (DEBUG) this.printMessage("RTC periodic interrupts enabled", Messages.RTC);
|
||
this.setRTCCycleLimit();
|
||
} else {
|
||
if (DEBUG) this.printMessage("RTC periodic interrupts disabled", Messages.RTC);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 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.printMessageIO(port, bOut, addrFrom, "NMI");
|
||
this.bNMI = bOut;
|
||
};
|
||
|
||
/**
|
||
* outFPUClear(port, bOut, addrFrom)
|
||
*
|
||
* This handler is installed only for MODEL_5170.
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} port (0xF0)
|
||
* @param {number} bOut (0x00 is the only expected output)
|
||
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
|
||
*/
|
||
ChipSet.prototype.outFPUClear = function(port, bOut, addrFrom)
|
||
{
|
||
this.printMessageIO(port, bOut, addrFrom, "FPU.CLEAR");
|
||
this.assert(!bOut);
|
||
if (this.fpu) this.fpu.clearBusy();
|
||
};
|
||
|
||
/**
|
||
* outFPUReset(port, bOut, addrFrom)
|
||
*
|
||
* This handler is installed only for MODEL_5170.
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} port (0xF1)
|
||
* @param {number} bOut (0x00 is the only expected output)
|
||
* @param {number} [addrFrom] (not defined if the Debugger is trying to write the specified port)
|
||
*/
|
||
ChipSet.prototype.outFPUReset = function(port, bOut, addrFrom)
|
||
{
|
||
this.printMessageIO(port, bOut, addrFrom, "FPU.RESET");
|
||
this.assert(!bOut);
|
||
if (this.fpu) this.fpu.resetFPU();
|
||
};
|
||
|
||
/**
|
||
* 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) {
|
||
if (this.messageEnabled(Messages.INT) && this.dbg.messageInt(Interrupts.RTC, addr)) {
|
||
/*
|
||
* By computing AH now, we get the incoming AH value; if we computed it below, along with
|
||
* the rest of the register values, we'd get the outgoing AH value, which is not what we want.
|
||
*/
|
||
var AH = this.cpu.regEAX >> 8;
|
||
this.cpu.addIntReturn(addr, function(chipset, nCycles) {
|
||
return function onBIOSRTCReturn(nLevel) {
|
||
nCycles = chipset.cpu.getCycles() - nCycles;
|
||
var sResult;
|
||
var CL = chipset.cpu.regEDX & 0xff;
|
||
var CH = chipset.cpu.regEDX >> 8;
|
||
var DL = chipset.cpu.regEDX & 0xff;
|
||
var DH = chipset.cpu.regEDX >> 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.regECX) + " DH(month)=" + str.toHexByte(DH) + " DL(day)=" + str.toHexByte(DL);
|
||
}
|
||
chipset.dbg.messageIntReturn(Interrupts.RTC, nLevel, nCycles, sResult);
|
||
};
|
||
}(this, this.cpu.getCycles()));
|
||
}
|
||
}
|
||
return true;
|
||
};
|
||
|
||
/**
|
||
* 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) {
|
||
try {
|
||
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.PIT0.TIMER2));
|
||
/*
|
||
* 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;
|
||
if (this.messageEnabled(Messages.SPEAKER)) this.printMessage("speaker set to " + freq + "hz", true);
|
||
} else {
|
||
this.sourceAudio = this.contextAudio['createOscillator']();
|
||
if (this.sourceAudio) {
|
||
if (typeof this.sourceAudio['type'] == "number") {
|
||
this.sourceAudio['type'] = 1; // deprecated: 0: "sine", 1: "square", 2: "sawtooth", 3: "triangle"
|
||
} else {
|
||
this.sourceAudio['type'] = "square";
|
||
}
|
||
this.sourceAudio['connect'](this.contextAudio['destination']);
|
||
this.sourceAudio['frequency']['value'] = freq;
|
||
if ('start' in this.sourceAudio) {
|
||
this.sourceAudio['start'](0);
|
||
} else {
|
||
this.sourceAudio['noteOn'](0); // deprecated: this.sourceAudio['noteOn'](0)
|
||
}
|
||
if (this.messageEnabled(Messages.SPEAKER)) this.printMessage("speaker on at " + freq + "hz", true);
|
||
}
|
||
}
|
||
} else {
|
||
if (this.sourceAudio) {
|
||
if ('stop' in this.sourceAudio) {
|
||
this.sourceAudio['stop'](0);
|
||
} else {
|
||
this.sourceAudio['noteOff'](0); // deprecated: this.sourceAudio['noteOff'](0)
|
||
}
|
||
this.sourceAudio['disconnect'](); // QUESTION: is this automatic following a stop(), since this particular source cannot be started again?
|
||
delete this.sourceAudio; // QUESTION: ditto?
|
||
if (this.messageEnabled(Messages.SPEAKER)) this.printMessage("speaker off at " + freq + "hz", true);
|
||
}
|
||
}
|
||
} catch(e) {
|
||
this.notice("AudioContext exception: " + e.message);
|
||
this.contextAudio = null;
|
||
}
|
||
} else if (fOn) {
|
||
this.printMessage("BEEP", Messages.SPEAKER);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* messageBitsDMA(iChannel)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number} [iChannel] if the message is associated with a particular IRQ #
|
||
* @return {number}
|
||
*/
|
||
ChipSet.prototype.messageBitsDMA = function(iChannel)
|
||
{
|
||
var bitsMessage = 0;
|
||
if (DEBUG) {
|
||
bitsMessage = Messages.DATA;
|
||
if (iChannel == ChipSet.DMA_FDC) {
|
||
bitsMessage |= Messages.FDC;
|
||
} else if (iChannel == ChipSet.DMA_HDC) {
|
||
bitsMessage |= Messages.HDC;
|
||
}
|
||
}
|
||
return bitsMessage;
|
||
};
|
||
|
||
/**
|
||
* messageBitsIRQ(nIRQ)
|
||
*
|
||
* @this {ChipSet}
|
||
* @param {number|undefined} [nIRQ] if the message is associated with a particular IRQ #
|
||
* @return {number}
|
||
*/
|
||
ChipSet.prototype.messageBitsIRQ = function(nIRQ)
|
||
{
|
||
var bitsMessage = 0;
|
||
if (DEBUG) {
|
||
bitsMessage = Messages.PIC;
|
||
if (nIRQ == ChipSet.IRQ.TIMER0) { // IRQ 0
|
||
bitsMessage |= Messages.TIMER;
|
||
} else if (nIRQ == ChipSet.IRQ.KBD) { // IRQ 1
|
||
bitsMessage |= Messages.KEYBOARD;
|
||
} else if (nIRQ == ChipSet.IRQ.SLAVE) { // IRQ 2 (MODEL_5170 and up)
|
||
bitsMessage |= Messages.CHIPSET;
|
||
} else if (nIRQ == ChipSet.IRQ.COM1 || nIRQ == ChipSet.IRQ.COM2) {
|
||
bitsMessage |= Messages.SERIAL;
|
||
} else if (nIRQ == ChipSet.IRQ.XTC) { // IRQ 5 (MODEL_5160)
|
||
bitsMessage |= Messages.HDC;
|
||
} else if (nIRQ == ChipSet.IRQ.FDC) { // IRQ 6
|
||
bitsMessage |= Messages.FDC;
|
||
} else if (nIRQ == ChipSet.IRQ.RTC) { // IRQ 8 (MODEL_5170 and up)
|
||
bitsMessage |= Messages.RTC;
|
||
} else if (nIRQ == ChipSet.IRQ.ATC) { // IRQ 14 (MODEL_5170 and up)
|
||
bitsMessage |= Messages.HDC;
|
||
}
|
||
}
|
||
return bitsMessage;
|
||
};
|
||
|
||
/*
|
||
* 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); },
|
||
0x0D: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMATemp(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.PIT0.INDEX, ChipSet.PIT0.TIMER0, port, addrFrom); },
|
||
0x41: /** @this {ChipSet} */ function(port, addrFrom) { return this.inTimer(ChipSet.PIT0.INDEX, ChipSet.PIT0.TIMER1, port, addrFrom); },
|
||
0x42: /** @this {ChipSet} */ function(port, addrFrom) { return this.inTimer(ChipSet.PIT0.INDEX, ChipSet.PIT0.TIMER2, port, addrFrom); },
|
||
0x43: /** @this {ChipSet} */ function(port, addrFrom) { return this.inTimerCtrl(ChipSet.PIT0.INDEX, port, addrFrom); },
|
||
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 it
|
||
};
|
||
|
||
ChipSet.aPortInput5170 = {
|
||
0x60: ChipSet.prototype.in8042OutBuff,
|
||
0x61: ChipSet.prototype.in8042RWReg,
|
||
0x64: ChipSet.prototype.in8042Status,
|
||
0x70: ChipSet.prototype.inCMOSAddr,
|
||
0x71: ChipSet.prototype.inCMOSData,
|
||
0x80: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMAPageSpare(7, port, addrFrom); },
|
||
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); },
|
||
0xDA: /** @this {ChipSet} */ function(port, addrFrom) { return this.inDMATemp(ChipSet.DMA1.INDEX, port, addrFrom); }
|
||
};
|
||
|
||
ChipSet.aPortInput6300 = {
|
||
0x60: ChipSet.prototype.in8041Kbd,
|
||
0x61: ChipSet.prototype.in8041Ctrl,
|
||
0x64: ChipSet.prototype.in8041Status,
|
||
0x66: /** @this {ChipSet} */ function(port, addrFrom) { return this.in6300DIPSwitches(0, port, addrFrom); },
|
||
0x67: /** @this {ChipSet} */ function(port, addrFrom) { return this.in6300DIPSwitches(1, port, addrFrom); }
|
||
};
|
||
|
||
if (DESKPRO386) {
|
||
ChipSet.aPortInputDeskPro386 = {
|
||
0x48: /** @this {ChipSet} */ function(port, addrFrom) { return this.inTimer(ChipSet.PIT1.INDEX, ChipSet.PIT1.TIMER3, port, addrFrom); },
|
||
0x49: /** @this {ChipSet} */ function(port, addrFrom) { return this.inTimer(ChipSet.PIT1.INDEX, ChipSet.PIT1.TIMER4, port, addrFrom); },
|
||
0x4A: /** @this {ChipSet} */ function(port, addrFrom) { return this.inTimer(ChipSet.PIT1.INDEX, ChipSet.PIT1.TIMER5, port, addrFrom); },
|
||
0x4B: /** @this {ChipSet} */ function(port, addrFrom) { return this.inTimerCtrl(ChipSet.PIT1.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.outDMAResetFF(ChipSet.DMA0.INDEX, port, bOut, addrFrom); },
|
||
0x0D: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAMasterClear(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.PIT0.INDEX, ChipSet.PIT0.TIMER0, port, bOut, addrFrom); },
|
||
0x41: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outTimer(ChipSet.PIT0.INDEX, ChipSet.PIT0.TIMER1, port, bOut, addrFrom); },
|
||
0x42: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outTimer(ChipSet.PIT0.INDEX, ChipSet.PIT0.TIMER2, port, bOut, addrFrom); },
|
||
0x43: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outTimerCtrl(ChipSet.PIT0.INDEX, port, bOut, addrFrom); },
|
||
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: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAPageSpare(7, port, bOut, addrFrom); },
|
||
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.outDMAResetFF(ChipSet.DMA1.INDEX, port, bOut, addrFrom); },
|
||
0xDA: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outDMAMasterClear(ChipSet.DMA1.INDEX, port, bOut, addrFrom); },
|
||
0xF0: ChipSet.prototype.outFPUClear,
|
||
0xF1: ChipSet.prototype.outFPUReset
|
||
};
|
||
|
||
ChipSet.aPortOutput6300 = {
|
||
0x60: ChipSet.prototype.out8041Kbd,
|
||
0x61: ChipSet.prototype.out8041Ctrl,
|
||
0xA0: ChipSet.prototype.outNMI
|
||
};
|
||
|
||
if (DESKPRO386) {
|
||
ChipSet.aPortOutputDeskPro386 = {
|
||
0x48: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outTimer(ChipSet.PIT1.INDEX, ChipSet.PIT1.TIMER3, port, bOut, addrFrom); },
|
||
0x49: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outTimer(ChipSet.PIT1.INDEX, ChipSet.PIT1.TIMER4, port, bOut, addrFrom); },
|
||
0x4A: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outTimer(ChipSet.PIT1.INDEX, ChipSet.PIT1.TIMER5, port, bOut, addrFrom); },
|
||
0x4B: /** @this {ChipSet} */ function(port, bOut, addrFrom) { this.outTimerCtrl(ChipSet.PIT1.INDEX, port, bOut, addrFrom); }
|
||
};
|
||
}
|
||
|
||
/**
|
||
* ChipSet.init()
|
||
*
|
||
* This function operates on every HTML element of class "chipset", extracting the
|
||
* JSON-encoded parameters for the ChipSet constructor from the element's "data-value"
|
||
* attribute, invoking the constructor to create a ChipSet component, and then binding
|
||
* any associated HTML controls to the new component.
|
||
*/
|
||
ChipSet.init = function()
|
||
{
|
||
var aeChipSet = Component.getElementsByClass(document, PCX86.APPCLASS, "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, PCX86.APPCLASS);
|
||
chipset.updateDIPSwitchDescriptions();
|
||
}
|
||
};
|
||
|
||
/*
|
||
* Initialize every ChipSet module on the page.
|
||
*/
|
||
web.onInit(ChipSet.init);
|
||
|
||
if (NODE) module.exports = ChipSet;
|