MODEL_5170_REV3 boots without errors now

However, performance will suffer until I can implement RTC interrupts;
also, there may be a problem exposed by the BIOS memory test following a soft reset
This commit is contained in:
Jeff Parsons 2014-10-14 16:07:46 -07:00 committed by jeffpar
commit 997db9787b
15 changed files with 1557 additions and 1282 deletions

View file

@ -53,7 +53,7 @@ if (typeof module !== 'undefined') {
* scaleTimers: true to divide timer cycle counts by the CPU's cycle multiplier (default is false)
* fdrives: 0-4 floppy drives (default is 2 if no sw1 value provided)
* monitor: none|tv|color|mono (default is mono if no sw1 value provided)
* rtcDate: optional RTC date to be used on resets; use the ISO 8601 format; eg: "2011-10-10T14:48:00"
* rtcDate: optional RTC date to be used on resets; use the ISO 8601 format; eg: "2014-10-01T08:00:00-0700"
*
* 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.
@ -78,7 +78,7 @@ if (typeof module !== 'undefined') {
*
* 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
* 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.
@ -247,33 +247,41 @@ function ChipSet(parmsChipSet)
Component.subclass(Component, ChipSet);
/*
* Supported Models
* Supported Models
*
* Unless otherwise noted, all BIOS references refer to the *original* BIOS released with each model
*/
ChipSet.MODEL_5150 = 5150;
ChipSet.MODEL_5160 = 5160;
ChipSet.MODEL_5170 = 5170;
ChipSet.MODEL_5150 = 5150; // used in reference to the 1st 5150 BIOS, dated Apr 24, 1981
ChipSet.MODEL_5160 = 5160; // used in reference to the 1st 5160 BIOS, dated Nov 8, 1982
ChipSet.MODEL_5170 = 5170; // used in reference to the 1st 5170 BIOS, dated Jan 10, 1984
/*
* The following are fake model numbers, used only to document issues/features of note in later BIOS revisions
*/
ChipSet.MODEL_5170_REV2 = 5170.2; // used in reference to the 2nd 5170 BIOS, dated Jun 10, 1985
ChipSet.MODEL_5170_REV3 = 5170.3; // used in reference to the 3rd 5170 BIOS, dated Nov 15, 1985
/*
* Values returned by ChipSet.getSWVideoMonitor()
*/
ChipSet.MONITOR = {};
ChipSet.MONITOR.NONE = 0;
ChipSet.MONITOR.TV = 1; // Composite monitor (lower resolution; no support)
ChipSet.MONITOR.COLOR = 2; // Color Display (5153)
ChipSet.MONITOR.MONO = 3; // Monochrome Display (5151)
ChipSet.MONITOR.EGACOLOR = 4; // Enhanced Color Display (5154) in High-Res Mode
ChipSet.MONITOR.EGAEMULATION = 5; // Enhanced Color Display (5154) in Emulation Mode
ChipSet.MONITOR = {
NONE: 0,
TV: 1, // Composite monitor (lower resolution; no support)
COLOR: 2, // Color Display (5153)
MONO: 3, // Monochrome Display (5151)
EGACOLOR: 4, // Enhanced Color Display (5154) in High-Res Mode
EGAEMULATION: 6 // Enhanced Color Display (5154) in Emulation Mode
};
/*
* Lookup table for converting ChipSet "monitor" parameter into the corresponding SW1 switch bits
* Lookup table for converting ChipSet "monitor" values into the corresponding SW1 switch bits
* (they must be shifted left by ChipSet.PPI_SW.MONITOR.SHIFT before OR'ing them into sw1/sw1Init).
*/
ChipSet.aMonitorSwitches = {
"none": 0x0,
"tv": 0x1,
"color":0x2,
"mono": 0x3,
"ega": 0x0
"none": 0x0,
"tv": 0x1,
"color": 0x2,
"mono": 0x3,
"ega": 0x0
};
/*
@ -289,92 +297,100 @@ ChipSet.aMonitorSwitches = {
* For FDC DMA notes, refer to: http://wiki.osdev.org/ISA_DMA
* For general DMA notes, refer to: http://www.freebsd.org/doc/en/books/developers-handbook/dma.html
*/
ChipSet.DMA0 = {};
ChipSet.DMA0.INDEX = 0;
ChipSet.DMA0.PORT_CH0_ADDR = 0x00; // OUT: starting address IN: current address
ChipSet.DMA0.PORT_CH0_COUNT = 0x01; // OUT: starting word count IN: remaining word count
ChipSet.DMA0.PORT_CH1_ADDR = 0x02; // OUT: starting address IN: current address
ChipSet.DMA0.PORT_CH1_COUNT = 0x03; // OUT: starting word count IN: remaining word count
ChipSet.DMA0.PORT_CH2_ADDR = 0x04; // OUT: starting address IN: current address
ChipSet.DMA0.PORT_CH2_COUNT = 0x05; // OUT: starting word count IN: remaining word count
ChipSet.DMA0.PORT_CH3_ADDR = 0x06; // OUT: starting address IN: current address
ChipSet.DMA0.PORT_CH3_COUNT = 0x07; // OUT: starting word count IN: remaining word count
ChipSet.DMA0.PORT_CMD_STATUS = 0x08; // OUT: command register IN: status register
ChipSet.DMA0.PORT_REQUEST = 0x09;
ChipSet.DMA0.PORT_MASK = 0x0A;
ChipSet.DMA0.PORT_MODE = 0x0B;
ChipSet.DMA0.PORT_CLEAR_FF = 0x0C;
ChipSet.DMA0.PORT_MASTER_CLR = 0x0D;
ChipSet.DMA0.PORT_CLEAR_MASK = 0x0E; // TODO: Provide handlers
ChipSet.DMA0.PORT_ALL_MASK = 0x0F; // TODO: Provide handlers
ChipSet.DMA0.PORT_CH2_PAGE = 0x81; // OUT: DMA channel 2 page register
ChipSet.DMA0.PORT_CH3_PAGE = 0x82; // OUT: DMA channel 3 page register
ChipSet.DMA0.PORT_CH1_PAGE = 0x83; // OUT: DMA channel 1 page register
ChipSet.DMA0.PORT_CH0_PAGE = 0x87; // OUT: DMA channel 0 page register (unusable; See "The Inside Out" book, p.246)
ChipSet.DMA0 = {
INDEX: 0,
PORT: {
CH0_ADDR: 0x00, // OUT: starting address IN: current address
CH0_COUNT: 0x01, // OUT: starting word count IN: remaining word count
CH1_ADDR: 0x02, // OUT: starting address IN: current address
CH1_COUNT: 0x03, // OUT: starting word count IN: remaining word count
CH2_ADDR: 0x04, // OUT: starting address IN: current address
CH2_COUNT: 0x05, // OUT: starting word count IN: remaining word count
CH3_ADDR: 0x06, // OUT: starting address IN: current address
CH3_COUNT: 0x07, // OUT: starting word count IN: remaining word count
CMD_STATUS: 0x08, // OUT: command register IN: status register
REQUEST: 0x09,
MASK: 0x0A,
MODE: 0x0B,
CLEAR_FF: 0x0C,
MASTER_CLR: 0x0D,
CLEAR_MASK: 0x0E, // TODO: Provide handlers
ALL_MASK: 0x0F, // TODO: Provide handlers
CH2_PAGE: 0x81, // OUT: DMA channel 2 page register
CH3_PAGE: 0x82, // OUT: DMA channel 3 page register
CH1_PAGE: 0x83, // OUT: DMA channel 1 page register
CH0_PAGE: 0x87 // OUT: DMA channel 0 page register (unusable; See "The Inside Out" book, p.246)
}
};
ChipSet.DMA1 = {
INDEX: 1,
PORT: {
CH6_PAGE: 0x89, // OUT: DMA channel 6 page register (MODEL_5170)
CH7_PAGE: 0x8A, // OUT: DMA channel 7 page register (MODEL_5170)
CH5_PAGE: 0x8B, // OUT: DMA channel 5 page register (MODEL_5170)
CH4_PAGE: 0x8F, // OUT: DMA channel 4 page register (MODEL_5170; unusable; aka "refresh" page register?)
CH4_ADDR: 0xC0, // OUT: starting address IN: current address
CH4_COUNT: 0xC2, // OUT: starting word count IN: remaining word count
CH5_ADDR: 0xC4, // OUT: starting address IN: current address
CH5_COUNT: 0xC6, // OUT: starting word count IN: remaining word count
CH6_ADDR: 0xC8, // OUT: starting address IN: current address
CH6_COUNT: 0xCA, // OUT: starting word count IN: remaining word count
CH7_ADDR: 0xCC, // OUT: starting address IN: current address
CH7_COUNT: 0xCE, // OUT: starting word count IN: remaining word count
CMD_STATUS: 0xD0, // OUT: command register IN: status register
REQUEST: 0xD2,
MASK: 0xD4,
MODE: 0xD6,
CLEAR_FF: 0xD8,
MASTER_CLR: 0xDA,
CLEAR_MASK: 0xDC, // TODO: Provide handlers
ALL_MASK: 0xDE // TODO: Provide handlers
}
};
ChipSet.DMA1 = {};
ChipSet.DMA1.INDEX = 1;
ChipSet.DMA1.PORT_CH6_PAGE = 0x89; // OUT: DMA channel 6 page register (MODEL_5170)
ChipSet.DMA1.PORT_CH7_PAGE = 0x8A; // OUT: DMA channel 7 page register (MODEL_5170)
ChipSet.DMA1.PORT_CH5_PAGE = 0x8B; // OUT: DMA channel 5 page register (MODEL_5170)
ChipSet.DMA1.PORT_CH4_PAGE = 0x8F; // OUT: DMA channel 4 page register (MODEL_5170; unusable; aka "refresh" page register?)
ChipSet.DMA1.PORT_CH4_ADDR = 0xC0; // OUT: starting address IN: current address
ChipSet.DMA1.PORT_CH4_COUNT = 0xC2; // OUT: starting word count IN: remaining word count
ChipSet.DMA1.PORT_CH5_ADDR = 0xC4; // OUT: starting address IN: current address
ChipSet.DMA1.PORT_CH5_COUNT = 0xC6; // OUT: starting word count IN: remaining word count
ChipSet.DMA1.PORT_CH6_ADDR = 0xC8; // OUT: starting address IN: current address
ChipSet.DMA1.PORT_CH6_COUNT = 0xCA; // OUT: starting word count IN: remaining word count
ChipSet.DMA1.PORT_CH7_ADDR = 0xCC; // OUT: starting address IN: current address
ChipSet.DMA1.PORT_CH7_COUNT = 0xCE; // OUT: starting word count IN: remaining word count
ChipSet.DMA1.PORT_CMD_STATUS = 0xD0; // OUT: command register IN: status register
ChipSet.DMA1.PORT_REQUEST = 0xD2;
ChipSet.DMA1.PORT_MASK = 0xD4;
ChipSet.DMA1.PORT_MODE = 0xD6;
ChipSet.DMA1.PORT_CLEAR_FF = 0xD8;
ChipSet.DMA1.PORT_MASTER_CLR = 0xDA;
ChipSet.DMA1.PORT_CLEAR_MASK = 0xDC; // TODO: Provide handlers
ChipSet.DMA1.PORT_ALL_MASK = 0xDE; // TODO: Provide handlers
ChipSet.DMA_CMD = {
M2M_ENABLE: 0x01,
CH0HOLD_ENABLE: 0x02,
CTRL_DISABLE: 0x04,
COMP_TIMING: 0x08,
ROT_PRIORITY: 0x10,
EXT_WRITE_SEL: 0x20,
DREQ_ACTIVE_LO: 0x40,
DACK_ACTIVE_HI: 0x80
};
ChipSet.DMA_CMD = {};
ChipSet.DMA_CMD.M2M_ENABLE = 0x01;
ChipSet.DMA_CMD.CH0HOLD_ENABLE = 0x02;
ChipSet.DMA_CMD.CTRL_DISABLE = 0x04;
ChipSet.DMA_CMD.COMP_TIMING = 0x08;
ChipSet.DMA_CMD.ROT_PRIORITY = 0x10;
ChipSet.DMA_CMD.EXT_WRITE_SEL = 0x20;
ChipSet.DMA_CMD.DREQ_ACTIVE_LO = 0x40;
ChipSet.DMA_CMD.DACK_ACTIVE_HI = 0x80;
ChipSet.DMA_MASK = {
CHANNEL: 0x03,
CHANNEL_SET: 0x04
};
ChipSet.DMA_MASK = {};
ChipSet.DMA_MASK.CHANNEL = 0x03;
ChipSet.DMA_MASK.CHANNEL_SET = 0x04;
ChipSet.DMA_MODE = {
CHANNEL: 0x03,
XFER: 0x0C,
XFER_VERIFY: 0x00,
XFER_WRITE: 0x04,
XFER_READ: 0x08,
AUTOINIT: 0x10,
DECREMENT: 0x20,
MODE: 0xC0,
MODE_DEMAND: 0x00,
MODE_SINGLE: 0x40,
MODE_BLOCK: 0x80,
MODE_CASCADE: 0xC0
};
ChipSet.DMA_MODE = {};
ChipSet.DMA_MODE.CHANNEL = 0x03;
ChipSet.DMA_MODE.XFER = 0x0C;
ChipSet.DMA_MODE.XFER_VERIFY = 0x00;
ChipSet.DMA_MODE.XFER_WRITE = 0x04;
ChipSet.DMA_MODE.XFER_READ = 0x08;
ChipSet.DMA_MODE.AUTOINIT = 0x10;
ChipSet.DMA_MODE.DECREMENT = 0x20;
ChipSet.DMA_MODE.MODE = 0xC0;
ChipSet.DMA_MODE.MODE_DEMAND = 0x00;
ChipSet.DMA_MODE.MODE_SINGLE = 0x40;
ChipSet.DMA_MODE.MODE_BLOCK = 0x80;
ChipSet.DMA_MODE.MODE_CASCADE = 0xC0;
ChipSet.DMA_FDC = 0x02; // DMA channel assigned to the Floppy Drive Controller (FDC)
ChipSet.DMA_HDC = 0x03; // DMA channel assigned to the Hard Drive Controller (HDC; XTC only)
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.PORT_LO)
* ICW2 Initialization Command Word 2 (sent to port ChipSet.PIC.PORT_HI)
* ICW3 Initialization Command Word 3 (sent to port ChipSet.PIC.PORT_HI)
* ICW4 Initialization Command Word 4 (sent to port ChipSet.PIC.PORT_HI)
* 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
@ -397,56 +413,53 @@ ChipSet.DMA_HDC = 0x03; // DMA channel assigned to the Hard Driv
* 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
ChipSet.PIC0.INDEX = 0;
ChipSet.PIC0.PORT_LO = 0x20;
ChipSet.PIC0.PORT_HI = 0x21;
ChipSet.PIC0 = { // all models: the "master" PIC
INDEX: 0,
PORT_LO: 0x20,
PORT_HI: 0x21
};
ChipSet.PIC1 = {}; // MODEL_5170 and up: the "slave" PIC
ChipSet.PIC1.INDEX = 1;
ChipSet.PIC1.PORT_LO = 0xA0;
ChipSet.PIC1.PORT_HI = 0xA1;
ChipSet.PIC1 = { // MODEL_5170 and up: the "slave" PIC
INDEX: 1,
PORT_LO: 0xA0,
PORT_HI: 0xA1
};
ChipSet.PIC_LO = {};
ChipSet.PIC_LO.ICW1 = 0x10; // set means ICW1
ChipSet.PIC_LO.ICW1_ICW4 = 0x01; // ICW4 needed (otherwise ICW4 must be sent)
ChipSet.PIC_LO.ICW1_SNGL = 0x02; // single PIC (and therefore no ICW3; otherwise there is another "cascaded" PIC)
ChipSet.PIC_LO.ICW1_ADI = 0x04; // call address interval is 4 (otherwise 8; presumably ignored in 8086/8088 mode)
ChipSet.PIC_LO.ICW1_LTIM = 0x08; // level-triggered interrupt mode (otherwise edge-triggered mode, which is what PCs use)
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.PIC_HI.ICW2_VECTOR = 0xF8; // starting vector number (bits 0-2 are effectively treated as zeros in 8086/8088 mode)
ChipSet.PIC_HI.ICW4_8086 = 0x01;
ChipSet.PIC_HI.ICW4_AUTO_EOI = 0x02;
ChipSet.PIC_HI.ICW4_MASTER = 0x04;
ChipSet.PIC_HI.ICW4_BUFFERED = 0x08;
ChipSet.PIC_HI.ICW4_FULLY_NESTED= 0x10;
/*
* Definitions for Operation Command Words (OCW1, OCW2 and OCW3)
*/
ChipSet.PIC_HI.OCW1_IMR = 0xFF;
ChipSet.PIC_LO.OCW2 = 0x00; // bit 3 (PIC_LO.OCW3) and bit 4 (ChipSet.PIC_LO.ICW1) are clear in an OCW2 command byte
ChipSet.PIC_LO.OCW2_IR_LVL = 0x07;
ChipSet.PIC_LO.OCW2_OP_MASK = 0xE0; // of the following valid OCW2 operations, the first 4 are EOI commands (all have ChipSet.PIC_LO.OCW2_EOI set)
ChipSet.PIC_LO.OCW2_EOI = 0x20; // non-specific EOI (end-of-interrupt)
ChipSet.PIC_LO.OCW2_EOI_SPEC = 0x60; // specific EOI
ChipSet.PIC_LO.OCW2_EOI_ROT = 0xA0; // rotate on non-specific EOI
ChipSet.PIC_LO.OCW2_EOI_ROTSPEC = 0xE0; // rotate on specific EOI
ChipSet.PIC_LO.OCW2_SET_ROTAUTO = 0x80; // set rotate in automatic EOI mode
ChipSet.PIC_LO.OCW2_CLR_ROTAUTO = 0x00; // clear rotate in automatic EOI mode
ChipSet.PIC_LO.OCW2_SET_PRI = 0xC0; // bits 0-2 specify the lowest priority interrupt
ChipSet.PIC_LO.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)
ChipSet.PIC_LO.OCW3_READ_IRR = 0x02; // read IRR register
ChipSet.PIC_LO.OCW3_READ_ISR = 0x03; // read ISR register
ChipSet.PIC_LO.OCW3_READ_CMD = 0x03;
ChipSet.PIC_LO.OCW3_POLL_CMD = 0x04; // poll
ChipSet.PIC_LO.OCW3_SMM_RESET = 0x40; // special mask mode: reset
ChipSet.PIC_LO.OCW3_SMM_SET = 0x60; // special mask mode: set
ChipSet.PIC_LO.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.
@ -465,57 +478,62 @@ ChipSet.PIC_LO.OCW3_SMM_CMD = 0x60;
* 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 = {};
ChipSet.IRQ.TIMER0 = 0x00;
ChipSet.IRQ.KBD = 0x01;
ChipSet.IRQ.SLAVE = 0x02;
ChipSet.IRQ.COM2 = 0x03;
ChipSet.IRQ.COM1 = 0x04;
ChipSet.IRQ.XTC = 0x05; // MODEL_5160 uses this for its HDC; MODEL_5170 designates it for LPT2
ChipSet.IRQ.FDC = 0x06;
ChipSet.IRQ.LPT1 = 0x07;
ChipSet.IRQ.RTC = 0x08;
ChipSet.IRQ.IRQ2 = 0x09;
ChipSet.IRQ.COPROC = 0x0D;
ChipSet.IRQ.ATC = 0x0E; // MODEL_5170 uses this for its HDC
ChipSet.IRQ = {
TIMER0: 0x00,
KBD: 0x01,
SLAVE: 0x02,
COM2: 0x03,
COM1: 0x04,
XTC: 0x05, // MODEL_5160 uses this for its HDC; MODEL_5170 designates it for LPT2
FDC: 0x06,
LPT1: 0x07,
RTC: 0x08,
IRQ2: 0x09,
COPROC: 0x0D,
ATC: 0x0E // MODEL_5170 uses this for its HDC
};
/*
* 8253 Programmable Interval Timer (PIT) I/O ports
*/
ChipSet.TIMER0 = {};
ChipSet.TIMER0.INDEX = 0;
ChipSet.TIMER0.PORT = 0x40; // used for time-of-day (prior to MODEL_5170)
ChipSet.TIMER0 = {
INDEX: 0,
PORT: 0x40 // used for time-of-day (prior to MODEL_5170)
};
ChipSet.TIMER1 = {};
ChipSet.TIMER1.INDEX = 1;
ChipSet.TIMER1.PORT = 0x41; // used for memory refresh
ChipSet.TIMER1 = {
INDEX: 1,
PORT: 0x41 // used for memory refresh
};
ChipSet.TIMER2 = {};
ChipSet.TIMER2.INDEX = 2;
ChipSet.TIMER2.PORT = 0x42; // used speaker tone generation
ChipSet.TIMER2 = {
INDEX: 2,
PORT: 0x42 // used speaker tone generation
};
ChipSet.TIMER_CTRL = {};
ChipSet.TIMER_CTRL.PORT = 0x43; // write-only control register (use the Read-Back command to get status)
ChipSet.TIMER_CTRL.BCD = 0x01;
ChipSet.TIMER_CTRL.MODE = 0x0E;
ChipSet.TIMER_CTRL.MODE0 = 0x00; // interrupt on terminal count
ChipSet.TIMER_CTRL.MODE1 = 0x02; // programmable one-shot
ChipSet.TIMER_CTRL.MODE2 = 0x04; // rate generator
ChipSet.TIMER_CTRL.MODE3 = 0x06; // square wave generator
ChipSet.TIMER_CTRL.MODE4 = 0x08; // software-triggered strobe
ChipSet.TIMER_CTRL.MODE5 = 0x0A; // hardware-triggered strobe
ChipSet.TIMER_CTRL.RW = 0x30;
ChipSet.TIMER_CTRL.RW_LATCH = 0x00;
ChipSet.TIMER_CTRL.RW_LSB = 0x10;
ChipSet.TIMER_CTRL.RW_MSB = 0x20;
ChipSet.TIMER_CTRL.RW_BOTH = 0x30;
ChipSet.TIMER_CTRL.SC = 0xC0;
ChipSet.TIMER_CTRL.SC_CTR0 = 0x00;
ChipSet.TIMER_CTRL.SC_CTR1 = 0x40;
ChipSet.TIMER_CTRL.SC_CTR2 = 0x80;
ChipSet.TIMER_CTRL.SC_BACK = 0xC0;
ChipSet.TIMER_CTRL = {
PORT: 0x43, // write-only control register (use the Read-Back command to get status)
BCD: 0x01,
MODE: 0x0E,
MODE0: 0x00, // interrupt on terminal count
MODE1: 0x02, // programmable one-shot
MODE2: 0x04, // rate generator
MODE3: 0x06, // square wave generator
MODE4: 0x08, // software-triggered strobe
MODE5: 0x0A, // hardware-triggered strobe
RW: 0x30,
RW_LATCH: 0x00,
RW_LSB: 0x10,
RW_MSB: 0x20,
RW_BOTH: 0x30,
SC: 0xC0,
SC_CTR0: 0x00,
SC_CTR1: 0x40,
SC_CTR2: 0x80,
SC_BACK: 0xC0
};
ChipSet.TIMER_TICKS_PER_SEC = 1193181;
ChipSet.TIMER_TICKS_PER_SEC = 1193181;
/*
* 8255A Programmable Peripheral Interface (PPI) I/O ports, for Cassette/Speaker/Keyboard/SW1/etc
@ -524,88 +542,95 @@ ChipSet.TIMER_TICKS_PER_SEC = 1193181;
* 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.
* However, I'm guessing that's just some sort of "diagnostic mode" operation, 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.
* 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
ChipSet.PPI_A.PORT = 0x60; // INPUT: keyboard scan code (PPI_B.CLEAR_KBD must be clear)
ChipSet.PPI_A = { // this.bPPIA
PORT: 0x60 // INPUT: keyboard scan code (PPI_B.CLEAR_KBD must be clear)
};
ChipSet.PPI_B = {}; // this.bPPIB
ChipSet.PPI_B.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)
ChipSet.PPI_B.CLK_TIMER2 = 0x01; // ALL: set to enable clock to TIMER2
ChipSet.PPI_B.SPK_TIMER2 = 0x02; // ALL: set to connect output of TIMER2 to speaker (MODEL_5150: clear for cassette)
ChipSet.PPI_B.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)
ChipSet.PPI_B.CASS_MOTOR_OFF = 0x08; // MODEL_5150: cassette motor off
ChipSet.PPI_B.ENABLE_SW_HI = 0x08; // MODEL_5160: clear to read SW1[1-4], set to read SW1[5-8]
ChipSet.PPI_B.DISABLE_RW_MEM = 0x10; // ALL: clear to enable RAM parity check, set to disable
ChipSet.PPI_B.DISABLE_IO_CHK = 0x20; // ALL: clear to enable I/O channel check, set to disable
ChipSet.PPI_B.CLK_KBD = 0x40; // ALL: clear to force keyboard clock low
ChipSet.PPI_B.CLEAR_KBD = 0x80; // ALL: clear to enable keyboard scan codes (MODEL_5150: set to enable SW1 through PPI_A.PORT)
ChipSet.PPI_B = { // this.bPPIB
PORT: 0x61, // OUTPUT (although it has to be treated as INPUT, too: the keyboard interrupt handler reads it, OR's PPI_B.CLEAR_KBD, writes it, and then rewrites the original read value)
CLK_TIMER2: 0x01, // ALL: set to enable clock to TIMER2
SPK_TIMER2: 0x02, // ALL: set to connect output of TIMER2 to speaker (MODEL_5150: clear for cassette)
ENABLE_SW2: 0x04, // MODEL_5150: set to enable SW2[1-4] through PPI_C.PORT, clear to enable SW2[5]; MODEL_5160: unused (there is no SW2 switch block on the MODEL_5160 motherboard)
CASS_MOTOR_OFF: 0x08, // MODEL_5150: cassette motor off
ENABLE_SW_HI: 0x08, // MODEL_5160: clear to read SW1[1-4], set to read SW1[5-8]
DISABLE_RW_MEM: 0x10, // ALL: clear to enable RAM parity check, set to disable
DISABLE_IO_CHK: 0x20, // ALL: clear to enable I/O channel check, set to disable
CLK_KBD: 0x40, // ALL: clear to force keyboard clock low
CLEAR_KBD: 0x80 // ALL: clear to enable keyboard scan codes (MODEL_5150: set to enable SW1 through PPI_A.PORT)
};
ChipSet.PPI_C = {}; // this.bPPIC
ChipSet.PPI_C.PORT = 0x62; // INPUT (see below)
ChipSet.PPI_C.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
ChipSet.PPI_C.CASS_DATA_IN = 0x10;
ChipSet.PPI_C.TIMER2_OUT = 0x20;
ChipSet.PPI_C.IO_CHANNEL_CHK = 0x40; // used by NMI handler to detect I/O channel errors
ChipSet.PPI_C.RW_PARITY_CHK = 0x80; // used by NMI handler to detect R/W memory parity errors
ChipSet.PPI_C = { // this.bPPIC
PORT: 0x62, // INPUT (see below)
SW: 0x0F, // MODEL_5150: SW2[1-4] or SW2[5], depending on whether PPI_B.ENABLE_SW2 is set or clear; MODEL_5160: SW1[1-4] or SW1[5-8], depending on whether PPI_B.ENABLE_SW_HI is clear or set
CASS_DATA_IN: 0x10,
TIMER2_OUT: 0x20,
IO_CHANNEL_CHK: 0x40, // used by NMI handler to detect I/O channel errors
RW_PARITY_CHK: 0x80 // used by NMI handler to detect R/W memory parity errors
};
ChipSet.PPI_CTRL = {}; // this.bPPICtrl
ChipSet.PPI_CTRL.PORT = 0x63; // OUTPUT: initialized to 0x99, defining PPI_A and PPI_C as INPUT and PPI_B as OUTPUT
ChipSet.PPI_CTRL.A_IN = 0x10;
ChipSet.PPI_CTRL.B_IN = 0x02;
ChipSet.PPI_CTRL.C_IN_LO = 0x01;
ChipSet.PPI_CTRL.C_IN_HI = 0x08;
ChipSet.PPI_CTRL.B_MODE = 0x04;
ChipSet.PPI_CTRL.A_MODE = 0x60;
ChipSet.PPI_CTRL = { // this.bPPICtrl
PORT: 0x63, // OUTPUT: initialized to 0x99, defining PPI_A and PPI_C as INPUT and PPI_B as OUTPUT
A_IN: 0x10,
B_IN: 0x02,
C_IN_LO: 0x01,
C_IN_HI: 0x08,
B_MODE: 0x04,
A_MODE: 0x60
};
/*
* On the MODEL_5150, the following PPI_SW bits are exposed through PPI_A.
*
* On the MODEL_5160, either the low or high 4 bits are exposed through PPI_C.SW, if PPI_B.ENABLE_SW_HI is clear or set.
*/
ChipSet.PPI_SW = {};
ChipSet.PPI_SW.FDRIVE = {};
ChipSet.PPI_SW.FDRIVE.IPL = 0x01; // MODEL_5150: IPL ("Initial Program Load") floppy drive attached; MODEL_5160: "Loop on POST"
ChipSet.PPI_SW.COPROC = 0x02; // MODEL_5150: reserved; MODEL_5160: coprocessor installed
ChipSet.PPI_SW.MEMORY = {};
ChipSet.PPI_SW.MEMORY.X1 = 0x00; // MODEL_5150: "X" is 16Kb; MODEL_5160: "X" is 64Kb
ChipSet.PPI_SW.MEMORY.X2 = 0x04;
ChipSet.PPI_SW.MEMORY.X3 = 0x08;
ChipSet.PPI_SW.MEMORY.X4 = 0x0C;
ChipSet.PPI_SW.MEMORY.MASK = 0x0C;
ChipSet.PPI_SW.MEMORY.SHIFT = 2;
ChipSet.PPI_SW.MONITOR = {};
ChipSet.PPI_SW.MONITOR.TV = 0x10;
ChipSet.PPI_SW.MONITOR.COLOR = 0x20;
ChipSet.PPI_SW.MONITOR.MONO = 0x30;
ChipSet.PPI_SW.MONITOR.MASK = 0x30;
ChipSet.PPI_SW.MONITOR.SHIFT = 4;
ChipSet.PPI_SW.FDRIVE.ONE = 0x00; // 1 floppy drive attached (or 0 drives if PPI_SW.FDRIVE_IPL is not set -- MODEL_5150 only)
ChipSet.PPI_SW.FDRIVE.TWO = 0x40; // 2 floppy drives attached
ChipSet.PPI_SW.FDRIVE.THREE = 0x80; // 3 floppy drives attached
ChipSet.PPI_SW.FDRIVE.FOUR = 0xC0; // 4 floppy drives attached
ChipSet.PPI_SW.FDRIVE.MASK = 0xC0;
ChipSet.PPI_SW.FDRIVE.SHIFT = 6;
ChipSet.PPI_SW = {
FDRIVE: {
IPL: 0x01, // MODEL_5150: IPL ("Initial Program Load") floppy drive attached; MODEL_5160: "Loop on POST"
ONE: 0x00, // 1 floppy drive attached (or 0 drives if PPI_SW.FDRIVE_IPL is not set -- MODEL_5150 only)
TWO: 0x40, // 2 floppy drives attached
THREE: 0x80, // 3 floppy drives attached
FOUR: 0xC0, // 4 floppy drives attached
MASK: 0xC0,
SHIFT: 6
},
COPROC: 0x02, // MODEL_5150: reserved; MODEL_5160: coprocessor installed
MEMORY: {
X1: 0x00, // MODEL_5150: "X" is 16Kb; MODEL_5160: "X" is 64Kb
X2: 0x04,
X3: 0x08,
X4: 0x0C,
MASK: 0x0C,
SHIFT: 2
},
MONITOR: {
TV: 0x10,
COLOR: 0x20,
MONO: 0x30,
MASK: 0x30,
SHIFT: 4
}
};
/*
* 8042 Keyboard Controller I/O ports (MODEL_5170)
*
* On the MODEL_5170, port 0x60 is designated KBD_DATA rather than PPI_A, although the BIOS also refers to it
* On the MODEL_5170, port 0x60 is designated KBC.DATA rather than PPI_A, although the BIOS also refers to it
* as "PORT_A: 8042 KEYBOARD SCAN/DIAG OUTPUTS"). This is the 8042's output buffer and should be read only when
* KBD_STATUS.OUTBUFF_FULL is set.
* KBC.STATUS.OUTBUFF_FULL is set.
*
* Similarly, port 0x61 is designated KBD_RWREG rather than PPI_B; the BIOS also refers to it as "PORT_B: 8042
* Similarly, port 0x61 is designated KBC.RWREG rather than PPI_B; the BIOS also refers to it as "PORT_B: 8042
* READ WRITE REGISTER", but it is not otherwise discussed in the MODEL_5170 TechRef's 8042 documentation.
* There are brief references to bits 0 and 1 (KBD_RWREG.CLK_TIMER2 and KBD_RWREG.SPK_TIMER2), and the BIOS sets
* bits 2-7 to "DISABLE PARITY CHECKERS" (principally KBD_RWREG.DISABLE_CHK, which are bits 2 and 3); why the BIOS
* There are brief references to bits 0 and 1 (KBC.RWREG.CLK_TIMER2 and KBC.RWREG.SPK_TIMER2), and the BIOS sets
* bits 2-7 to "DISABLE PARITY CHECKERS" (principally KBC.RWREG.DISABLE_CHK, which are bits 2 and 3); why the BIOS
* also sets bits 4-7 (or if those bits are even settable) is unclear, since it uses 11111100B rather than defined
* constants.
*
* The bottom line: on a MODEL_5170, port 0x61 is still used for speaker control and parity checking, so we use
* the same register (bPPIB) but install different I/O handlers. It's also bi-directional: at one point, the BIOS
* reads KBD_RWREG.REFRESH_BIT (bit 4) to verify that it's alternating.
* reads KBC.RWREG.REFRESH_BIT (bit 4) to verify that it's alternating.
*
* PPI_C and PPI_CTRL don't seem to be documented or used by the MODEL_5170 BIOS, so I'm assuming they're obsolete.
*
@ -618,7 +643,7 @@ ChipSet.PPI_SW.FDRIVE.SHIFT = 6;
ChipSet.KBC = {
DATA: { // this.b8042OutBuff (PPI_A on previous models, still referred to as "PORT A" by the MODEL_5170 BIOS)
PORT: 0x60,
CMD: { // this.b8042CmdData (KBD_DATA.CMD "data bytes" written to port 0x60, after writing a KBD_CMD byte to port 0x64)
CMD: { // this.b8042CmdData (KBC.DATA.CMD "data bytes" written to port 0x60, after writing a KBC.CMD byte to port 0x64)
PC_COMPAT: 0x40, // generate IBM PC-compatible scan codes
PC_MODE: 0x20,
NO_CLOCK: 0x10, // disable keyboard by driving "clock" line low
@ -656,7 +681,7 @@ ChipSet.KBC = {
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 KBD_RWREG)
RWREG: { // this.bPPIB (since CLK_TIMER2 and SPK_TIMER2 are in both PPI_B and KBC.RWREG)
PORT: 0x61,
CLK_TIMER2: 0x01, // set to enable clock to TIMER2
SPK_TIMER2: 0x02, // set to connect output of TIMER2 to speaker
@ -669,15 +694,15 @@ ChipSet.KBC = {
CMD: { // this.b8042InBuff (on write to port 0x64, interpret this as a CMD)
PORT: 0x64,
READ_CMD: 0x20,
WRITE_CMD: 0x60, // followed by a command byte written to KBD_DATA.PORT (see KBD_DATA.CMD)
SELF_TEST: 0xAA, // self-test (KBD_DATA.SELF_TEST_OK is placed in the output buffer if no errors)
WRITE_CMD: 0x60, // followed by a command byte written to KBC.DATA.PORT (see KBC.DATA.CMD)
SELF_TEST: 0xAA, // self-test (KBC.DATA.SELF_TEST.OK is placed in the output buffer if no errors)
INTF_TEST: 0xAB, // interface test
DIAG_DUMP: 0xAC, // diagnostic dump
DISABLE_KBD: 0xAD, // disable keyboard
ENABLE_KBD: 0xAE, // enable keyboard
READ_INPORT: 0xC0, // read input port and place data in output buffer (use only if output buffer empty)
READ_OUTPORT: 0xD0, // read output port and place data in output buffer (use only if output buffer empty)
WRITE_OUTPORT: 0xD1, // next byte written to KBD_DATA.PORT (port 0x60) is placed in the output port (see KBD_DATA.OUTPUT)
WRITE_OUTPORT: 0xD1, // next byte written to KBC.DATA.PORT (port 0x60) is placed in the output port (see KBC.DATA.OUTPUT)
READ_TEST: 0xE0,
PULSE_OUTPORT: 0xF0 // this is the 1st of 16 commands (0xF0-0xFF) that pulse bits 0-3 of the output port
},
@ -686,7 +711,7 @@ ChipSet.KBC = {
OUTBUFF_FULL: 0x01,
INBUFF_FULL: 0x02, // set if the controller has received but not yet read data written to the input buffer (not normally set)
SYS_FLAG: 0x04,
CMD_FLAG: 0x08, // set on write to KBD_CMD (port 0x64), clear on write to KBD_DATA (port 0x60)
CMD_FLAG: 0x08, // set on write to KBC.CMD (port 0x64), clear on write to KBC.DATA (port 0x60)
NO_INHIBIT: 0x10,
XMT_TIMEOUT: 0x20,
RCV_TIMEOUT: 0x40,
@ -1018,8 +1043,8 @@ ChipSet.prototype.reset = function()
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 KBD_CMD.WRITE_CMD "command byte" is written to the input
* buffer, the subsequent command data byte is saved in b8042CmdData. Similarly, for KBD_CMD.WRITE_OUTPORT,
* when written via port 0x60. So, whenever the KBC.CMD.WRITE_CMD "command byte" is written to the input
* buffer, the subsequent command data byte is saved in b8042CmdData. Similarly, for KBC.CMD.WRITE_OUTPORT,
* the subsequent data byte is saved in b8042OutPort.
*
* TODO: Consider a UI for the Keyboard INHIBIT switch. By default, our keyboard is never inhibited
@ -1105,13 +1130,13 @@ ChipSet.prototype.initRTCDate = function(sDate)
/*
* Example of a valid Date string:
*
*
* 2014-10-01T08:00:00-0700
*
*
* Example of an INVALID Date string:
*
*
* 2014-10-01T08:00:00PST
*
*
* In the second example, the Date object is invalid, but it wasn't obvious (to me) how to detect that.
* So here's a test from StackOverflow (http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript).
*/
@ -2895,7 +2920,7 @@ ChipSet.prototype.getIRRVector = function(iPIC)
*
* 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.
* whereas here we must search across all PICs.
*/
var nIRL = pic.bIRLow + 1;
while (true) {
@ -3307,7 +3332,7 @@ ChipSet.prototype.updateTimer = function(iTimer, fCycleReset)
* 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 on the base CPU speed returned by cpu.getCyclesPerSecond().
* calculates using the base CPU speed returned by cpu.getCyclesPerSecond().
*/
var ticks = ((nCycles - timer.nStartCycles) / this.nTicksDivisor) | 0;
@ -3646,8 +3671,8 @@ ChipSet.prototype.in8042OutBuff = function(port, addrFrom)
* 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 KBD_DATA.CMD "data byte". Before clearing KBD_STATUS.CMD_FLAG, however, we see if it's set,
* and then based on the previous KBD_CMD "command byte", we do whatever needs to be done with this "data byte".
* the byte as a KBC.DATA.CMD "data byte". Before clearing KBC.STATUS.CMD_FLAG, however, we see if it's set,
* and then based on the previous KBC.CMD "command byte", we do whatever needs to be done with this "data byte".
*
* @this {ChipSet}
* @param {number} port (0x60)
@ -3662,9 +3687,7 @@ ChipSet.prototype.out8042InBuffData = function(port, bOut, addrFrom)
switch (this.b8042InBuff) {
case ChipSet.KBC.CMD.WRITE_CMD:
this.b8042CmdData = bOut;
Component.assert(ChipSet.KBC.DATA.CMD.SYS_FLAG === ChipSet.KBC.STATUS.SYS_FLAG);
this.b8042Status = (this.b8042Status & ~ChipSet.KBC.STATUS.SYS_FLAG) | (bOut & ChipSet.KBC.DATA.CMD.SYS_FLAG);
this.set8042CmdData(bOut);
break;
case ChipSet.KBC.CMD.WRITE_OUTPORT:
@ -3736,7 +3759,7 @@ ChipSet.prototype.out8042InBuffData = function(port, bOut, addrFrom)
* error, but "TEST.21" assumes that it is.
*/
default:
this.b8042CmdData &= ~ChipSet.KBC.DATA.CMD.NO_CLOCK;
this.set8042CmdData(this.b8042CmdData & ~ChipSet.KBC.DATA.CMD.NO_CLOCK);
if (this.kbd) this.set8042OutBuff(this.kbd.sendCmd(bOut));
break;
}
@ -3757,14 +3780,39 @@ 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 (KBD_RWREG.PARITY_ERR), because we never want to report a parity error.
* two upper-most bits (KBC.RWREG.PARITY_ERR), as those are output-only bits used to signal
* parity errors.
*
* Also, "TEST.09" of the MODEL_5170 BIOS expects the REFRESH_BIT to alternate, so we used to
* do this:
*
* this.bPPIB ^= ChipSet.KBC.RWREG.REFRESH_BIT;
*
* However, the MODEL_5170_REV3 BIOS not only checks REFRESH_BIT in "TEST.09", but includes
* an additional test right before "TEST.11A", which requires the bit change "a bit less"
* frequently.
*
* QUESTION: Did IBM throw in this additional REFRESH_BIT test in an attempt to either tie
* their revised BIOS to their own hardware OR to insure that the processor was running at a
* "condoned" speed? Note that 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.
*
* So now we tie the state of the REFRESH_BIT to bit 6 of the current CPU cycle count,
* effectively toggling the bit after every 64 cycles, or roughly every 4th read, and yielding
* a count of 0xF815 in CX, safely within the required range. I also confirmed that using
* the next highest bit (bit 7) created too much of a delay (CX was 0xF015).
*
* NOTE: the "WAITF" function @F000:1A3A relies on REFRESH_BIT to achieve a "FIXED TIME WAIT",
* where CX is a "COUNT OF 15.085737us INTERVALS TO WAIT". By toggling REFRESH_BIT every 64
* cycles, on an 8Mhz CPU that can do 8 cycles in 1us, 64 cycles represents 8us, so this might
* be 7us too fast? But I think we're close enough.
*/
var b = this.bPPIB & ~ChipSet.KBC.RWREG.PARITY_ERR;
this.messagePort(port, null, addrFrom, "8042_RWREG", ChipSet.MESSAGE_CHIPSET, b);
var b = this.bPPIB & ~(ChipSet.KBC.RWREG.PARITY_ERR | ChipSet.KBC.RWREG.REFRESH_BIT) | ((this.cpu.getCycles() & 0x40)? ChipSet.KBC.RWREG.REFRESH_BIT : 0);
/*
* "TEST.09" of the MODEL_5170 BIOS expects the following bit ("REFRESH_BIT") to alternate, so we oblige.
* Thanks to the WAITF function, this has become a very "busy" port, so let's not generate messages
* unless both MESSAGE_CHIPSET *and* MESSAGE_LOG are set.
*/
this.bPPIB ^= ChipSet.KBC.RWREG.REFRESH_BIT;
this.messagePort(port, null, addrFrom, "8042_RWREG", ChipSet.MESSAGE_CHIPSET | ChipSet.MESSAGE_LOG, b);
return b;
};
@ -3796,14 +3844,14 @@ ChipSet.prototype.in8042Status = function(port, addrFrom)
var b = this.b8042Status & 0xff;
/*
* There's code in the 5170 BIOS (F000:03BF) that writes an 8042 command (0xAA), waits for
* KBD_STATUS.INBUFF_FULL to go clear (which it always is, because we always accept commands
* immediately), then checks KBD_STATUS.OUTBUFF_FULL and performs a "flush" on port 0x60 if
* it's set, then waits for KBD_STATUS.OUTBUFF_FULL *again*. Unfortunately, the "flush" throws
* KBC.STATUS.INBUFF_FULL to go clear (which it always is, because we always accept commands
* immediately), then checks KBC.STATUS.OUTBUFF_FULL and performs a "flush" on port 0x60 if
* it's set, then waits for KBC.STATUS.OUTBUFF_FULL *again*. Unfortunately, the "flush" throws
* away our response if we respond immediately.
*
* So now when out8042InBuffCmd() has a response, it sets KBD_STATUS.OUTBUFF_DELAY instead
* (which is outside the 0xff range of bits we return); when we see KBD_STATUS.OUTBUFF_DELAY,
* we clear it and set KBD_STATUS.OUTBUFF_FULL, which will be returned on the next read.
* So now when out8042InBuffCmd() has a response, it sets KBC.STATUS.OUTBUFF_DELAY instead
* (which is outside the 0xff range of bits we return); when we see KBC.STATUS.OUTBUFF_DELAY,
* we clear it and set KBC.STATUS.OUTBUFF_FULL, which will be returned on the next read.
*
* This provides a single poll delay, so that the aforementioned "flush" won't toss our response.
* If longer delays are needed down the road, we may need to set a delay count in the upper (hidden)
@ -3820,7 +3868,7 @@ ChipSet.prototype.in8042Status = function(port, addrFrom)
* 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 KBD_STATUS.CMD_FLAG, and then see if we can act upon
* the byte as a "command byte". We immediately set KBC.STATUS.CMD_FLAG, and then see if we can act upon
* the command immediately (some commands requires us to wait for a "data byte").
*
* @this {ChipSet}
@ -3840,7 +3888,7 @@ ChipSet.prototype.out8042InBuffCmd = function(port, bOut, addrFrom)
if (this.b8042InBuff >= ChipSet.KBC.CMD.PULSE_OUTPORT) {
bPulseBits = (this.b8042InBuff ^ 0xf);
/*
* Now that we have isolated the bit(s) to pulse, map all pulse commands to KBD_CMD.PULSE_OUTPORT
* Now that we have isolated the bit(s) to pulse, map all pulse commands to KBC.CMD.PULSE_OUTPORT
*/
this.b8042InBuff = ChipSet.KBC.CMD.PULSE_OUTPORT;
}
@ -3858,22 +3906,23 @@ ChipSet.prototype.out8042InBuffCmd = function(port, bOut, addrFrom)
break;
case ChipSet.KBC.CMD.DISABLE_KBD: // 0xAD
this.b8042CmdData |= ChipSet.KBC.DATA.CMD.NO_CLOCK;
this.set8042CmdData(this.b8042CmdData | ChipSet.KBC.DATA.CMD.NO_CLOCK);
if (DEBUG) this.messageDebugger("keyboard disabled", ChipSet.MESSAGE_KBD);
/*
* TODO: Determine where to honor KBD_DATA.CMD.NO_CLOCK; note that the MODEL_5170 BIOS calls "KBD_RESET" (F000:17D2)
* while the keyboard interface is disabled, yet we must still deliver the Keyboard's CMDRES.BATSUCCESS response code.
* NOTE: The MODEL_5170 BIOS calls "KBD_RESET" (F000:17D2) while the keyboard interface is disabled,
* yet we must still deliver the Keyboard's CMDRES.BATSUCCESS response code? Seems like an odd thing for
* a "disabled interface" to do.
*/
break;
case ChipSet.KBC.CMD.ENABLE_KBD: // 0xAE
this.b8042CmdData &= ~ChipSet.KBC.DATA.CMD.NO_CLOCK;
this.set8042CmdData(this.b8042CmdData & ~ChipSet.KBC.DATA.CMD.NO_CLOCK);
if (DEBUG) this.messageDebugger("keyboard re-enabled", ChipSet.MESSAGE_KBD);
break;
case ChipSet.KBC.CMD.SELF_TEST: // 0xAA
if (this.kbd) this.kbd.shiftScanCode(true);
this.b8042CmdData |= ChipSet.KBC.DATA.CMD.NO_CLOCK;
this.set8042CmdData(this.b8042CmdData | ChipSet.KBC.DATA.CMD.NO_CLOCK);
if (DEBUG) this.messageDebugger("keyboard disabled on reset", ChipSet.MESSAGE_KBD);
this.set8042OutBuff(ChipSet.KBC.DATA.SELF_TEST.OK);
this.set8042OutPort(ChipSet.KBC.OUTPORT.NO_RESET | ChipSet.KBC.OUTPORT.A20_ON);
@ -3903,6 +3952,39 @@ ChipSet.prototype.out8042InBuffCmd = function(port, bOut, addrFrom)
}
};
/**
* set8042CmdData(b)
*
* @this {ChipSet}
* @param {number} b
*/
ChipSet.prototype.set8042CmdData = function(b)
{
this.b8042CmdData = b;
Component.assert(ChipSet.KBC.DATA.CMD.SYS_FLAG === ChipSet.KBC.STATUS.SYS_FLAG);
this.b8042Status = (this.b8042Status & ~ChipSet.KBC.STATUS.SYS_FLAG) | (b & ChipSet.KBC.DATA.CMD.SYS_FLAG);
if (this.kbd) {
/*
* This seems to be what the doctor ordered for the MODEL_5170_REV3 BIOS @F000:0A6D, where it
* sends ChipSet.KBC.CMD.WRITE_CMD to port 0x64, followed by 0x4D to port 0x60, which clears NO_CLOCK
* and enables the keyboard. The BIOS then waits for OUTBUFF_FULL to be set, at which point it seems
* to be anticipating an 0xAA response in the output buffer.
*
* And indeed, if we call the original MODEL_5150/MODEL_5160 setEnable() Keyboard interface here,
* and both the data and clock lines have transitioned high (ie, both parameters are true), then it
* will call resetDevice(), generating a Keyboard.CMDRES.BATSUCCESS response.
*
* This agrees with my understanding of what happens when the 8042 toggles the clock line high
* (ie, clears NO_CLOCK): the TechRef's "Basic Assurance Test" section says that when the Keyboard is
* powered on, it performs the BAT, and then when the clock and data lines go high, the keyboard sends
* a completion code (eg, 0xAA for success, or 0xFC or something else for failure).
*/
if (this.kbd.setEnable(!!(b & ChipSet.KBC.DATA.CMD.NO_INHIBIT), !(b & ChipSet.KBC.DATA.CMD.NO_CLOCK))) {
this.set8042OutBuff(this.kbd.readScanCode(true));
}
}
};
/**
* set8042OutBuff(b)
*
@ -3931,7 +4013,7 @@ ChipSet.prototype.set8042OutPort = function(b)
if (!(b & ChipSet.KBC.OUTPORT.NO_RESET)) {
/*
* Bit 0 of the 8042's output port is connected to RESET. Normally, it's "pulsed" with the
* KBD_CMD.PULSE_OUTPORT command, so if a RESET is detected via this command, we should try to
* KBC.CMD.PULSE_OUTPORT command, so if a RESET is detected via this command, we should try to
* determine if that's what the caller intended.
*/
if (DEBUG && DEBUGGER && this.dbg) {

View file

@ -2781,6 +2781,7 @@ if (DEBUGGER) {
if (iColon < 0) {
if (seg != null) {
off = this.parseValue(sAddr);
addr = null;
} else {
addr = this.parseValue(sAddr);
}

View file

@ -45,7 +45,7 @@ if (typeof module !== 'undefined') {
}
/*
* FDC Terms
* FDC Terms (see FDC.TERMS)
*
* C Cylinder Number the current or selected cylinder number
*
@ -78,7 +78,7 @@ if (typeof module !== 'undefined') {
*
* N Number the number of data bytes written in a sector
*
* NCN New Cylinder the new cylinder number for a Seek operation
* NCN New Cylinder Number the new cylinder number for a Seek operation
*
* ND Non-Data Mode indicates an operation in the non-data mode
*
@ -185,6 +185,31 @@ Component.subclass(Component, FDC);
FDC.DEFAULT_DRIVE_NAME = "Floppy Drive";
if (DEBUG) {
FDC.TERMS = {
C: "C", // Cylinder Number
D: "D", // Data (eg, pattern to be written to a sector)
H: "H", // Head Address
R: "R", // Record (ie, sector number to be read or written)
N: "N", // Number (ie, number of data bytes to write)
DS: "DS", // Drive Select
SC: "SC", // Sectors per Cylinder
DTL: "DTL", // Data Length
EOT: "EOT", // End of Track
GPL: "GPL", // Gap Length
HLT: "HLT", // Head Load Time
NCN: "NCN", // New Cylinder Number
PCN: "PCN", // Present Cylinder Number
SRT: "SRT", // Stepping Rate
ST0: "ST0", // Status Register 0
ST1: "ST1", // Status Register 1
ST2: "ST2", // Status Register 2
ST3: "ST3" // Status Register 3
}
} else {
FDC.TERMS = {};
}
/*
* FDC Digital Output Register (DOR) (0x3F2, write-only)
*
@ -279,11 +304,11 @@ FDC.REG_CONTROL.RATEUNUSED = 0x03;
FDC.REG_DATA.CMD = {};
FDC.REG_DATA.CMD.READ_TRACK = 0x02;
FDC.REG_DATA.CMD.SPECIFY = 0x03;
FDC.REG_DATA.CMD.DRIVE_STATUS = 0x04;
FDC.REG_DATA.CMD.SENSE_DRIVE = 0x04;
FDC.REG_DATA.CMD.WRITE_DATA = 0x05;
FDC.REG_DATA.CMD.READ_DATA = 0x06;
FDC.REG_DATA.CMD.RECALIBRATE = 0x07;
FDC.REG_DATA.CMD.INT_STATUS = 0x08; // this command is used to clear the FDC interrupt following the clearing/setting of FDC.REG_OUTPUT.ENABLE
FDC.REG_DATA.CMD.SENSE_INT = 0x08; // this command is used to clear the FDC interrupt following the clearing/setting of FDC.REG_OUTPUT.ENABLE
FDC.REG_DATA.CMD.WRITE_DEL_DATA = 0x09;
FDC.REG_DATA.CMD.READ_ID = 0x0A;
FDC.REG_DATA.CMD.READ_DEL_DATA = 0x0C;
@ -338,18 +363,35 @@ FDC.REG_DATA.RES.ST3 = 0xFF000000;
/*
* FDC Command Sequences
*
* For each command, cbWrite indicates the total number of bytes in the command request sequence,
* including the first (command) byte; cbRead indicates total number of bytes in the response sequence.
* For each command, cbReq indicates the total number of bytes in the command request sequence,
* including the first (command) byte; cbRes indicates total number of bytes in the response sequence.
*/
FDC.aCmdSeqs = {
0x03: {cbWrite: 3, cbRead: 0, name: "SPECIFY"},
0x04: {cbWrite: 2, cbRead: 1, name: "DRIVE_STATUS"},
0x05: {cbWrite: 9, cbRead: 7, name: "WRITE_DATA"},
0x06: {cbWrite: 9, cbRead: 7, name: "READ_DATA"},
0x07: {cbWrite: 2, cbRead: 0, name: "RECALIBRATE"},
0x08: {cbWrite: 1, cbRead: 2, name: "INT_STATUS"},
0x0D: {cbWrite: 6, cbRead: 7, name: "FORMAT"},
0x0F: {cbWrite: 3, cbRead: 0, name: "SEEK"}
if (DEBUG) {
FDC.CMDS = {
SPECIFY: "SPECIFY",
SENSE_DRIVE: "SENSE DRIVE",
WRITE_DATA: "WRITE DATA",
READ_DATA: "READ DATA",
RECALIBRATE: "RECALIBRATE",
SENSE_INT: "SENSE INTERRUPT",
READ_ID: "READ ID",
FORMAT: "FORMAT",
SEEK: "SEEK"
}
} else {
FDC.CMDS = {};
}
FDC.aCmdInfo = {
0x03: {cbReq: 3, cbRes: 0, name: FDC.CMDS.SPECIFY},
0x04: {cbReq: 2, cbRes: 1, name: FDC.CMDS.SENSE_DRIVE},
0x05: {cbReq: 9, cbRes: 7, name: FDC.CMDS.WRITE_DATA},
0x06: {cbReq: 9, cbRes: 7, name: FDC.CMDS.READ_DATA},
0x07: {cbReq: 2, cbRes: 0, name: FDC.CMDS.RECALIBRATE},
0x08: {cbReq: 1, cbRes: 2, name: FDC.CMDS.SENSE_INT},
0x0A: {cbReq: 2, cbRes: 7, name: FDC.CMDS.READ_ID},
0x0D: {cbReq: 6, cbRes: 7, name: FDC.CMDS.FORMAT},
0x0F: {cbReq: 3, cbRes: 0, name: FDC.CMDS.SEEK}
};
/*
@ -649,7 +691,7 @@ FDC.prototype.initController = function(data)
}
/*
* Selected drive (from reOutput), which can only be selected if its motor is on (see regOutput).
* Selected drive (from regOutput), which can only be selected if its motor is on (see regOutput).
*/
this.iDrive = data[i++];
i++; // unused slot (if reused, bias by +4, since it was formerly a unit #)
@ -806,7 +848,7 @@ FDC.prototype.initDrive = function(drive, iDrive, data)
* The next group of properties are set by various FDC command sequences.
*
* We initialize this.iDrive (above) and drive.bHead and drive.bCylinder (below) to zero, but leave the rest undefined,
* awaiting their first FDC command. We do this because the initial INT_STATUS command returns a PCN, which will also
* awaiting their first FDC command. We do this because the initial SENSE_INT command returns a PCN, which will also
* be undefined unless we have at least zeroed both the current drive and the "present" cylinder on that drive.
*
* Alternatively, I could make PCN a global FDC variable. That may be closer to how the actual hardware operates,
@ -819,7 +861,7 @@ FDC.prototype.initDrive = function(drive, iDrive, data)
*
* Here's how it works: the BIOS seeks to track 48 (which is fine on an 80-track 1.2Mb high-capacity drive, but 9 tracks
* too far on a 40-track 360Kb double-density drive), then seeks to track 10, and then seeks in single-track increments
* up to 10 more times until the DRIVE_STATUS command returns ST3 with the TRACK0 bit set.
* up to 10 more times until the SENSE_DRIVE command returns ST3 with the TRACK0 bit set.
*
* This implies that SEEK isn't really seeking to a specified cylinder, but rather it is calculating a delta from
* the previous cylinder to the specified cylinder, and stepping over that number of tracks. Which means that SEEK
@ -1363,8 +1405,9 @@ FDC.prototype.outFDCOutput = function(port, bOut, addrFrom)
* still updating this.iDrive below based on the "drive select" bits in regOutput, we would want
* to make sure those bits now match what initController() set. But since we no longer do that
* (see below), this is no longer needed either.
*
* bOut = (bOut & ~FDC.REG_OUTPUT.DS) | this.iDrive;
*/
// bOut = (bOut & ~FDC.REG_OUTPUT.DS) | this.iDrive;
}
else if (!(this.regOutput & FDC.REG_OUTPUT.ENABLE)) {
/*
@ -1385,14 +1428,14 @@ FDC.prototype.outFDCOutput = function(port, bOut, addrFrom)
* with FDC.REG_OUTPUT.ENABLE clear, and then with it set. However, both times, it ALSO loads the last selected
* drive number into regOutput's "drive select" bits.
*
* If we switched our selected drive to match regOutput, then the ST0 value we returned on an INT_STATUS command
* If we switched our selected drive to match regOutput, then the ST0 value we returned on an SENSE_INT command
* following the regOutput reset operation would indicate drive 1 instead of drive 0. But the BIOS requires
* the ST0 result from the INT_STATUS command ALWAYS be 0xC0 (not 0xC1), so the controller must not be propagating
* the ST0 result from the SENSE_INT command ALWAYS be 0xC0 (not 0xC1), so the controller must not be propagating
* regOutput's "drive select" bits in the way I originally assumed.
*
* var iDrive = bOut & FDC.REG_OUTPUT.DS;
* if (bOut & (FDC.REG_OUTPUT.MOTOR_D0 << iDrive)) this.iDrive = iDrive;
*/
// var iDrive = bOut & FDC.REG_OUTPUT.DS;
// if (bOut & (FDC.REG_OUTPUT.MOTOR_D0 << iDrive)) this.iDrive = iDrive;
this.regOutput = bOut;
};
@ -1455,13 +1498,16 @@ FDC.prototype.outFDCData = function(port, bOut, addrFrom)
}
var bCmd = this.regDataArray[0];
var bCmdMasked = bCmd & FDC.REG_DATA.CMD.MASK;
if (FDC.aCmdSeqs[bCmdMasked] !== undefined) {
if (this.regDataTotal >= FDC.aCmdSeqs[bCmdMasked].cbWrite) {
if (FDC.aCmdInfo[bCmdMasked] !== undefined) {
if (this.regDataTotal >= FDC.aCmdInfo[bCmdMasked].cbReq) {
this.doCmd();
}
return;
}
if (DEBUG) this.messageDebugger("unsupported FDC command: " + str.toHexByte(bCmd));
if (DEBUG) {
this.messageDebugger("unsupported FDC command: " + str.toHexByte(bCmd));
if (DEBUGGER) this.cpu.haltCPU();
}
};
/**
@ -1524,35 +1570,40 @@ FDC.prototype.doCmd = function()
var bCmdMasked = bCmd & FDC.REG_DATA.CMD.MASK;
switch (bCmdMasked) {
case FDC.REG_DATA.CMD.SPECIFY: // 0x03
this.popSRT(); // SRT and HUT (encodings?)
this.popHLT(); // HLT and ND (encodings?)
this.beginResult(); // no results are provided by this command, and fIRQ should remain false
case FDC.REG_DATA.CMD.SPECIFY: // 0x03
this.popSRT(); // SRT and HUT (encodings?)
this.popHLT(); // HLT and ND (encodings?)
this.beginResult();
/*
* No results are provided by this command, and fIRQ should remain false
*/
break;
case FDC.REG_DATA.CMD.DRIVE_STATUS: // 0x04 (SENSE DRIVE STATUS)
bDrive = this.popCmd("DS");
case FDC.REG_DATA.CMD.SENSE_DRIVE: // 0x04
bDrive = this.popCmd(FDC.TERMS.DS);
bHeadSelect = (bDrive >> 2) & 0x1;
this.iDrive = (bDrive & 0x3);
drive = this.aDrives[this.iDrive];
this.beginResult();
this.pushST3(drive);
break;
case FDC.REG_DATA.CMD.WRITE_DATA: // 0x05
case FDC.REG_DATA.CMD.READ_DATA: // 0x06
bDrive = this.popCmd("DS");
case FDC.REG_DATA.CMD.WRITE_DATA: // 0x05
case FDC.REG_DATA.CMD.READ_DATA: // 0x06
bDrive = this.popCmd(FDC.TERMS.DS);
bHeadSelect = (bDrive >> 2) & 0x1;
this.iDrive = (bDrive & 0x3);
drive = this.aDrives[this.iDrive];
drive.bHead = bHeadSelect;
drive.bCylinder = this.popCmd("C"); // C
bHead = this.popCmd("H"); // H
drive.bCylinder = this.popCmd(FDC.TERMS.C); // C
bHead = this.popCmd(FDC.TERMS.H); // H
Component.assert(bHead == bHeadSelect);
drive.bSector = this.popCmd("R"); // R
n = this.popCmd("N"); // N
drive.nBytes = 128 << n; // 0 => 128, 1 => 256, 2 => 512, 3 => 1024
drive.bSectorEnd = this.popCmd("EOT"); // EOT (final sector number on a cylinder)
this.popCmd("GPL"); // GPL (spacing between sectors, excluding VCO Sync Field; 3)
this.popCmd("DTL"); // DTL (when N is 0, DTL stands for the data length to read out or write into the sector)
drive.bSector = this.popCmd(FDC.TERMS.R); // R
n = this.popCmd(FDC.TERMS.N); // N
drive.nBytes = 128 << n; // 0 => 128, 1 => 256, 2 => 512, 3 => 1024
drive.bSectorEnd = this.popCmd(FDC.TERMS.EOT); // EOT (final sector number on a cylinder)
this.popCmd(FDC.TERMS.GPL); // GPL (spacing between sectors, excluding VCO Sync Field; 3)
this.popCmd(FDC.TERMS.DTL); // DTL (when N is 0, DTL stands for the data length to read out or write into the sector)
if (bCmdMasked == FDC.REG_DATA.CMD.READ_DATA)
this.doRead(drive);
else
@ -1561,52 +1612,108 @@ FDC.prototype.doCmd = function()
this.pushST0(drive);
this.pushST1(drive);
this.pushST2(drive);
this.pushResult(drive.bCylinder, "C");
this.pushResult(drive.bHead, "H");
this.pushResult(drive.bSector, "R");
this.pushResult(n, "N");
this.pushResult(drive.bCylinder, FDC.TERMS.C);
this.pushResult(drive.bHead, FDC.TERMS.H);
this.pushResult(drive.bSector, FDC.TERMS.R);
this.pushResult(n, FDC.TERMS.N);
fIRQ = true;
break;
case FDC.REG_DATA.CMD.RECALIBRATE: // 0x07
bDrive = this.popCmd("DS");
case FDC.REG_DATA.CMD.RECALIBRATE: // 0x07
bDrive = this.popCmd(FDC.TERMS.DS);
this.iDrive = (bDrive & 0x3);
drive = this.aDrives[this.iDrive];
drive.bCylinder = drive.bCylinderSeek = 0;
drive.resCode = FDC.REG_DATA.RES.SEEK_END | FDC.REG_DATA.RES.TRACK0;
this.beginResult(); // no results provided; this command is typically followed by FDC.REG_DATA.CMD.INT_STATUS
this.beginResult(); // no results provided; this command is typically followed by FDC.REG_DATA.CMD.SENSE_INT
fIRQ = true;
break;
case FDC.REG_DATA.CMD.INT_STATUS: // 0x08 (SENSE INTERRUPT STATUS)
case FDC.REG_DATA.CMD.SENSE_INT: // 0x08
drive = this.aDrives[this.iDrive];
drive.bHead = 0; // this command is documented as ALWAYS returning a head address of 0 in ST0; see pushST0()
drive.bHead = 0; // this command is documented as ALWAYS returning a head address of 0 in ST0; see pushST0()
this.beginResult();
this.pushST0(drive);
this.pushResult(drive.bCylinder, "PCN");// no interrupt is generated by this command, so fIRQ should remain false
this.pushResult(drive.bCylinder, FDC.TERMS.PCN);
/*
* For some strange reason, the "DISK_RESET" function in the MODEL_5170_REV3 BIOS resets the
* adapter and then issues FOUR -- that's right, not ONE but FOUR -- SENSE INTERRUPT STATUS commands
* in a row, and expects ST0 to contain a different drive number after each command (first 0, then 1,
* then 2, and finally 3). What makes this doubly weird is SENSE INTERRUPT STATUS (unlike SENSE
* DRIVE STATUS) is a drive-agnostic command.
*
* Didn't the original PC AT "HFCOMBO" controller limit support to TWO diskette drives max?
* And even if the PC AT supported other FDC controllers that DID support up to FOUR diskette drives,
* why should "DISK_RESET" hard-code a 4-drive loop?
*
* Well, whatever. All this head-scratching doesn't change the fact that I apparently have to
* "auto-increment" the internal drive number (this.iDrive) after each SENSE INTERRUPT STATUS command.
*/
this.iDrive = (this.iDrive + 1) & 0x3;
/*
* No interrupt is generated by this command, so fIRQ should remain false.
*/
break;
case FDC.REG_DATA.CMD.FORMAT_TRACK: // 0x0D
bDrive = this.popCmd("DS");
case FDC.REG_DATA.CMD.READ_ID: // 0x0A
/*
* This command is used by "SETUP_DBL" in the MODEL_5170_REV3 BIOS to determine if a double-density
* (40-track) diskette has been inserted in a high-density (80-track) drive; ie, whether "double stepping"
* is required, since only 40 of the 80 possible "steps" are valid for a double-density diskette.
*
* To start, we'll focus on making this work in the normal case (80-track diskette in 80-track drive).
*/
bDrive = this.popCmd(FDC.TERMS.DS);
bHeadSelect = (bDrive >> 2) & 0x1;
this.iDrive = (bDrive & 0x3);
drive = this.aDrives[this.iDrive];
drive.bHead = bHeadSelect;
n = this.popCmd("N"); // N
drive.nBytes = 128 << n; // 0 => 128, 1 => 256, 2 => 512, 3 => 1024 (bytes/sector)
drive.bSectorEnd = this.popCmd("SC"); // SC (sectors/track)
this.popCmd("GPL"); // GPL (spacing between sectors, excluding VCO Sync Field; 3)
drive.bFiller = this.popCmd("D"); // D (filler byte)
drive.bSector = 1;
n = 0;
drive.resCode = FDC.REG_DATA.RES.NONE;
if (drive.disk && (drive.sector = drive.disk.seek(drive.bCylinder, drive.bHead, drive.bSector))) {
n = drive.sector.length;
} else {
/*
* TODO: Determine the appropriate response code(s) for the possible errors that can occur here.
*/
drive.resCode = FDC.REG_DATA.RES.NO_DATA | FDC.REG_DATA.RES.INCOMPLETE;
}
this.pushST0(drive);
this.pushST1(drive);
this.pushST2(drive);
this.pushResult(drive.bCylinder, FDC.TERMS.C);
this.pushResult(drive.bHead, FDC.TERMS.H);
this.pushResult(drive.bSector, FDC.TERMS.R);
this.pushResult(n, FDC.TERMS.N);
fIRQ = true;
break;
case FDC.REG_DATA.CMD.FORMAT_TRACK: // 0x0D
bDrive = this.popCmd(FDC.TERMS.DS);
bHeadSelect = (bDrive >> 2) & 0x1;
this.iDrive = (bDrive & 0x3);
drive = this.aDrives[this.iDrive];
drive.bHead = bHeadSelect;
n = this.popCmd(FDC.TERMS.N); // N
drive.nBytes = 128 << n; // 0 => 128, 1 => 256, 2 => 512, 3 => 1024 (bytes/sector)
drive.bSectorEnd = this.popCmd(FDC.TERMS.SC); // SC (sectors/track)
this.popCmd(FDC.TERMS.GPL); // GPL (spacing between sectors, excluding VCO Sync Field; 3)
drive.bFiller = this.popCmd(FDC.TERMS.D); // D (filler byte)
this.doFormat(drive);
this.beginResult();
this.pushST0(drive);
this.pushST1(drive);
this.pushST2(drive);
this.pushResult(drive.bCylinder, "C");
this.pushResult(drive.bHead, "H");
this.pushResult(drive.bSector, "R");
this.pushResult(n, "N");
this.pushResult(drive.bCylinder, FDC.TERMS.C);
this.pushResult(drive.bHead, FDC.TERMS.H);
this.pushResult(drive.bSector, FDC.TERMS.R);
this.pushResult(n, FDC.TERMS.N);
fIRQ = true;
break;
case FDC.REG_DATA.CMD.SEEK: // 0x0F
bDrive = this.popCmd("DS");
case FDC.REG_DATA.CMD.SEEK: // 0x0F
bDrive = this.popCmd(FDC.TERMS.DS);
bHeadSelect = (bDrive >> 2) & 0x1;
this.iDrive = (bDrive & 0x3);
drive = this.aDrives[this.iDrive];
@ -1620,7 +1727,7 @@ FDC.prototype.doCmd = function()
* allowed to exceed the physical boundaries of the drive (ie, never lower than 0, and never greater
* than or equal to nCylinders).
*/
bCylinder = this.popCmd("NCN");
bCylinder = this.popCmd(FDC.TERMS.NCN);
drive.bCylinder += bCylinder - drive.bCylinderSeek;
if (drive.bCylinder < 0) drive.bCylinder = 0;
if (drive.bCylinder >= drive.nCylinders) drive.bCylinder = drive.nCylinders - 1;
@ -1634,11 +1741,15 @@ FDC.prototype.doCmd = function()
if (drive.bCylinder == 0) {
drive.resCode |= FDC.REG_DATA.RES.TRACK0;
}
this.beginResult(); // like FDC.REG_DATA.CMD.RECALIBRATE, no results are provided
this.beginResult(); // like FDC.REG_DATA.CMD.RECALIBRATE, no results are provided
fIRQ = true;
break;
default:
if (DEBUG) this.messageDebugger("FDC operation unsupported (command=0x: " + str.toHexByte(bCmd) + ")");
if (DEBUG) {
this.messageDebugger("FDC operation unsupported (command=0x: " + str.toHexByte(bCmd) + ")");
if (DEBUGGER) this.cpu.haltCPU();
}
break;
}
@ -1649,7 +1760,7 @@ FDC.prototype.doCmd = function()
* an interrupt is supposed to occur, signaling the beginning of the Result Phase. Once the first byte of the
* result has been read, the interrupt is cleared (see inFDCData).
*
* TODO: Technically, interrupt request status should be cleared by the FDC.REG_DATA.CMD.INT_STATUS command; in fact,
* TODO: Technically, interrupt request status should be cleared by the FDC.REG_DATA.CMD.SENSE_INT command; in fact,
* if that command is issued and no interrupt was pending, then FDC.REG_DATA.RES.INVALID should be returned (via ST0).
*/
if (this.regOutput & FDC.REG_OUTPUT.INT_ENABLE) {
@ -1672,7 +1783,7 @@ FDC.prototype.popCmd = function(name)
var bCmd = this.regDataArray[this.regDataIndex];
if (DEBUG && DEBUGGER && this.dbg && this.dbg.messageEnabled((this.regDataIndex > 0? this.dbg.MESSAGE_PORT : 0) | this.dbg.MESSAGE_FDC)) {
var bCmdMasked = bCmd & FDC.REG_DATA.CMD.MASK;
if (!name && !this.regDataIndex && FDC.aCmdSeqs[bCmdMasked]) name = FDC.aCmdSeqs[bCmdMasked].name;
if (!name && !this.regDataIndex && FDC.aCmdInfo[bCmdMasked]) name = FDC.aCmdInfo[bCmdMasked].name;
this.dbg.message("FDC.CMD[" + (name || this.regDataIndex) + "]: 0x" + str.toHexByte(bCmd));
}
this.regDataIndex++;
@ -1688,8 +1799,8 @@ FDC.prototype.popCmd = function(name)
*/
FDC.prototype.popHLT = function()
{
this.popCmd("HLT");
// this.nHLT = this.popCmd("HLT");
this.popCmd(FDC.TERMS.HLT);
// this.nHLT = this.popCmd(FDC.TERMS.HLT);
};
/**
@ -1701,8 +1812,8 @@ FDC.prototype.popHLT = function()
*/
FDC.prototype.popSRT = function()
{
this.popCmd("SRT");
// this.nSRT = this.popCmd("SRT");
this.popCmd(FDC.TERMS.SRT);
// this.nSRT = this.popCmd(FDC.TERMS.SRT);
};
/**
@ -1736,7 +1847,7 @@ FDC.prototype.pushResult = function(bResult, name)
*/
FDC.prototype.pushST0 = function(drive)
{
this.pushResult(drive.iDrive | (drive.bHead << 2) | (drive.resCode & FDC.REG_DATA.RES.ST0), "ST0");
this.pushResult(drive.iDrive | (drive.bHead << 2) | (drive.resCode & FDC.REG_DATA.RES.ST0), FDC.TERMS.ST0);
};
/**
@ -1747,7 +1858,7 @@ FDC.prototype.pushST0 = function(drive)
*/
FDC.prototype.pushST1 = function(drive)
{
this.pushResult((drive.resCode & FDC.REG_DATA.RES.ST1) >>> 8, "ST1");
this.pushResult((drive.resCode & FDC.REG_DATA.RES.ST1) >>> 8, FDC.TERMS.ST1);
};
/**
@ -1758,7 +1869,7 @@ FDC.prototype.pushST1 = function(drive)
*/
FDC.prototype.pushST2 = function(drive)
{
this.pushResult((drive.resCode & FDC.REG_DATA.RES.ST2) >>> 16, "ST2");
this.pushResult((drive.resCode & FDC.REG_DATA.RES.ST2) >>> 16, FDC.TERMS.ST2);
};
/**
@ -1769,7 +1880,7 @@ FDC.prototype.pushST2 = function(drive)
*/
FDC.prototype.pushST3 = function(drive)
{
this.pushResult((drive.resCode & FDC.REG_DATA.RES.ST3) >>> 24, "ST3");
this.pushResult((drive.resCode & FDC.REG_DATA.RES.ST3) >>> 24, FDC.TERMS.ST3);
};
/**
@ -2120,13 +2231,11 @@ FDC.prototype.intBIOSDiskette = function(addr)
var DL = this.cpu.regDX & 0xff;
var DH = this.cpu.regDX >> 8;
if (this.dbg && this.dbg.messageEnabled(this.dbg.MESSAGE_FDC) && DL < 0x80) {
this.dbg.message("\nFDC.intBIOS(AH=" + str.toHexByte(AH) + ",D=" + str.toHexByte(DL) + ",C=" + str.toHexByte(CH) + ",H=" + str.toHexByte(DH) + ",S=" + str.toHexByte(CL) + ",N=" + str.toHexByte(AL) + ") at " + str.toHexAddr(addr - this.cpu.segCS.base, this.cpu.segCS.sel));
// this.cpu.haltCPU();
this.dbg.message("\nFDC.intBIOS(AH=" + str.toHexByte(AH) + ",drv=" + str.toHexByte(DL) + ",cyl=" + str.toHexByte(CH) + ",hd=" + str.toHexByte(DH) + ",sec=" + str.toHexByte(CL) + ",num=" + str.toHexByte(AL) + ") at " + str.toHexAddr(addr - this.cpu.segCS.base, this.cpu.segCS.sel));
this.cpu.addInterruptReturn(addr, function(fdc, nCycles) {
return function onBIOSDisketteReturn(nLevel) {
nCycles = fdc.cpu.getCycles() - nCycles;
fdc.messageDebugger("FDC.intBIOS(" + nLevel + "): C=" + (fdc.cpu.getCF()? 1 : 0) + " (cycles=" + nCycles + ")");
// if (DEBUG && nCycles > 10000) fdc.cpu.haltCPU();
};
}(this, this.cpu.getCycles()));
}

View file

@ -1642,6 +1642,27 @@ HDC.prototype.outATCDrvHd = function(port, bOut, addrFrom)
{
this.messagePort(port, bOut, addrFrom, "DRVHD");
this.regDrvHd = bOut;
/*
* The MODEL_5170_REV3 BIOS (see "POST2_CHK_HF2" @F000:14FC) probes for a 2nd hard drive when the number
* of configured hard drives is something other than 2, using INT 0x13/AH=0x10. This in turn calls the
* BIOS "TST_RDY" function, which selects the drive in this register (see DRIVE_MASK), and then immediately
* expects regStatus to reflect success or failure.
*
* We were always returning success, because no ATC command was actually issued, and so the user would
* always get a spurious CMOS configuration error: "System Options Not Set-(Run SETUP)".
*
* So now we update regStatus here. I'm not sure which status bits are normally set to indicate failure,
* but it should be sufficient to set or clear the READY bit according to whether the drive exists or not.
*
* TODO: Dig into the ATC documentation some more, and determine what other situations, if any, regStatus
* needs to be updated.
*/
var iDrive = (this.regDrvHd & HDC.ATC.DRVHD.DRIVE_MASK? 1 : 0);
if (this.aDrives[iDrive]) {
this.regStatus |= HDC.ATC.STATUS.READY;
} else {
this.regStatus &= ~HDC.ATC.STATUS.READY;
}
};
/**
@ -2558,12 +2579,10 @@ HDC.prototype.intBIOSDisk = function(addr)
if (DEBUGGER) {
if (this.dbg && this.dbg.messageEnabled(this.dbg.MESSAGE_HDC) && DL >= 0x80) {
this.dbg.message("HDC.intBIOSDisk(AX=" + str.toHexWord(this.cpu.regAX) + ",DL=" + str.toHexByte(DL) + ") at " + str.toHexAddr(addr - this.cpu.segCS.base, this.cpu.segCS.sel));
// this.cpu.haltCPU();
this.cpu.addInterruptReturn(addr, function (hdc, nCycles) {
return function onBIOSDiskReturn(nLevel) {
nCycles = hdc.cpu.getCycles() - nCycles;
hdc.messageDebugger("HDC.intBIOSDisk(" + nLevel + "): C=" + (hdc.cpu.getCF()? 1 : 0) + " (cycles=" + nCycles + ")");
// if (DEBUG && nCycles > 10000) hdc.cpu.haltCPU();
};
}(this, this.cpu.getCycles()));
}

View file

@ -117,14 +117,15 @@ Keyboard.CMD.ECHO = 0xEE;
Keyboard.CMD.SETLEDS = 0xED;
Keyboard.CMDRES = {};
Keyboard.CMDRES.RESEND = 0xFE;
Keyboard.CMDRES.ACK = 0xFA;
Keyboard.CMDRES.OVERRUN = 0x00;
Keyboard.CMDRES.DIAGFAIL = 0xFD;
Keyboard.CMDRES.BREAKPREFIX = 0xF0;
Keyboard.CMDRES.LOADTEST = 0x65; // this is an undocumented "LOAD MANUFACTURING TEST REQUEST" response code
Keyboard.CMDRES.BATSUCCESS = 0xAA; // Basic Assurance Test (BAT) completed successfully
Keyboard.CMDRES.BATFAIL = 0xFC; // Basic Assurance Test (BAT) failed
Keyboard.CMDRES.ECHO = 0xEE;
Keyboard.CMDRES.BREAKPREFIX = 0xF0;
Keyboard.CMDRES.ACK = 0xFA;
Keyboard.CMDRES.BATFAIL = 0xFC; // Basic Assurance Test (BAT) failed
Keyboard.CMDRES.DIAGFAIL = 0xFD;
Keyboard.CMDRES.RESEND = 0xFE;
/*
* Keyboard keyCodes I must pay particular attention to...
@ -587,37 +588,46 @@ Keyboard.prototype.resetDevice = function()
};
/**
* setEnable(fEnable, fClock)
* setEnable(fData, fClock)
*
* This is the ChipSet's primary interface for controlling "Model F" keyboards (ie, those used with
* MODEL_5150 and MODEL_5160 machines). This function is called from the ChipSet's PPI_B output handler.
* This is the ChipSet's primary interface for toggling keyboard "data" and "clock" lines.
* For MODEL_5150 and MODEL_5160 machines, this function is called from the ChipSet's PPI_B
* output handler. For MODEL_5170 machines, this function is called when selected KBC.CMD
* "data bytes" have been written.
*
* @this {Keyboard}
* @param {boolean} fEnable is true if the keyboard interface should be enabled
* @param {boolean} fClock is true if the keyboard's simulated clock line should go "high"
* @param {boolean} fData is true if the keyboard simulated data line should be enabled
* @param {boolean} fClock is true if the keyboard's simulated clock line should be enabled
* @return {boolean} true if keyboard was re-enabled, false if not (or no change)
*/
Keyboard.prototype.setEnable = function(fEnable, fClock)
Keyboard.prototype.setEnable = function(fData, fClock)
{
var fReset = false;
if (this.fClock !== fClock) {
if (DEBUG) this.messageDebugger("keyboard clock changing to " + fClock, true);
if (DEBUG) this.messageDebugger("keyboard clock line changing to " + fClock, true);
/*
* Toggling the clock line low and then high signals a "reset", which we acknowledge when enabled
* Toggling the clock line low and then high signals a "reset", which we acknowledge once the
* data line is high as well.
*/
this.fClock = this.fResetOnEnable = fClock;
}
if (this.fEnable !== fEnable) {
if (DEBUG) this.messageDebugger("keyboard enable changing to " + fEnable, true);
this.fEnable = fEnable;
if (fEnable) {
if (this.fResetOnEnable) {
this.resetDevice();
this.fResetOnEnable = false;
}
else {
this.shiftScanCode();
}
if (this.fData !== fData) {
if (DEBUG) this.messageDebugger("keyboard data line changing to " + fData, true);
this.fData = fData;
/*
* TODO: Review this code; it was added during the early days of MODEL_5150 testing and may not be
* *exactly* what's called for here.
*/
if (fData && !this.fResetOnEnable) {
this.shiftScanCode();
}
}
if (this.fData && this.fResetOnEnable) {
this.resetDevice();
this.fResetOnEnable = false;
fReset = true;
}
return fReset;
};
/**
@ -625,7 +635,7 @@ Keyboard.prototype.setEnable = function(fEnable, fClock)
*
* This is the ChipSet's primary interface for controlling "Model M" keyboards (ie, those used
* with MODEL_5170 machines). Commands are delivered through the ChipSet's 8042 Keyboard Controller.
*
*
* @this {Keyboard}
* @param {number} bCmd should be one of the Keyboard.CMD.* command codes (Model M keyboards only)
* @return {number} response should be one of the Keyboard.CMDRES.* response codes, or -1 if unrecognized
@ -708,7 +718,7 @@ Keyboard.prototype.powerUp = function(data, fRepower)
if (!fRepower) {
/*
* TODO: Save/restore support for Keyboard is the barest minimum. In fact, originally, I wasn't
* saving/restoring anything, and that was OK, but if we don't at least re-initialize fClock/fEnable,
* saving/restoring anything, and that was OK, but if we don't at least re-initialize fClock/fData,
* we can get a spurious reset following a restore. In an ideal world, we might choose to save/restore
* abScanBuffer as well, but realistically, I think it's going to be safer to always start with an
* empty buffer--and who's going to notice anyway?
@ -828,7 +838,7 @@ Keyboard.prototype.initState = function(data)
var i = 0;
if (data === undefined) data = [];
this.fClock = data[i++];
this.fEnable = data[i];
this.fData = data[i];
return true;
};
@ -843,7 +853,7 @@ Keyboard.prototype.saveState = function()
var i = 0;
var data = [];
data[i++] = this.fClock;
data[i] = this.fEnable;
data[i] = this.fData;
return data;
};

View file

@ -1968,10 +1968,10 @@ Video.prototype.intBIOSVideo = function(addr)
if (DEBUGGER) {
if (this.dbg && this.dbg.messageEnabled(this.dbg.MESSAGE_VIDEO)) {
this.dbg.message("Video.intBIOS(AX=" + str.toHexWord(this.cpu.regAX) + ") at " + str.toHexAddr(addr - this.cpu.segCS.base, this.cpu.segCS.sel));
// this.cpu.haltCPU();
this.cpu.addInterruptReturn(addr, function (video, nCycles) {
return function onBIOSVideoReturn(nLevel) {
video.intBIOSVideoReturn(nCycles, nLevel);
nCycles = video.cpu.getCycles() - nCycles;
video.messageDebugger("Video.intBIOSReturn(" + nLevel + ") (cycles=" + nCycles + ")");
};
}(this, this.cpu.getCycles()));
}
@ -1979,22 +1979,6 @@ Video.prototype.intBIOSVideo = function(addr)
return true;
};
/**
* intBIOSVideoReturn(nCycles, nLevel)
*
* @this {Video}
* @param {number} nCycles
* @param {number} nLevel
*/
Video.prototype.intBIOSVideoReturn = function(nCycles, nLevel)
{
if (DEBUGGER) {
nCycles = this.cpu.getCycles() - nCycles;
this.messageDebugger("Video.intBIOSReturn(" + nLevel + ") (cycles=" + nCycles + ")");
// if (DEBUG && nCycles > 10000) this.cpu.haltCPU();
}
};
/**
* setBinding(sHTMLClass, sHTMLType, sBinding, control)
*

View file

@ -558,7 +558,9 @@ X86CPU.prototype.initProcessor = function()
if (this.model >= X86.MODEL_80186) {
/*
* TODO: I don't go out of my way to make 80186/80188 cycle times accurate, since no IBM PC models used
* those processors; beyond this point, my real priority is the 80286. But we may revisit the 80186 someday.
* those processors; beyond this point, my real priority is the 80286. But we may revisit the 80186 someday;
* instruction handlers that contain "hard-coded" 80286 cycle times include: opINSb, opINSw, opOUTSb,
* opOUTSw, opENTER, and opLEAVE.
*/
this.nShiftCountMask = 0x1f; // on newer processors, all shift counts are MOD 32
@ -582,7 +584,7 @@ X86CPU.prototype.initProcessor = function()
this.aOps[0xC0] = X86OpXX.opGRP2ab;
this.aOps[0xC1] = X86OpXX.opGRP2aw;
this.aOps[X86.OPCODE.ENTER] = X86OpXX.opENTER;
this.aOps[X86.OPCODE.ENTER] = X86OpXX.opLEAVE;
this.aOps[X86.OPCODE.LEAVE] = X86OpXX.opLEAVE;
this.aOps[0xF1] = X86OpXX.opINT1;
X86Grps.aOpGRP4b[0x07] = X86Grps.opGrpInvalid;
X86Grps.aOpGRP4w[0x07] = X86Grps.opGrpInvalid;

View file

@ -1198,7 +1198,7 @@ var X86OpXX = {
/*
* NOTE: 5 + 4n is the cycle time for the 80286; the 80186/80188 has different values: 14 cycles for
* an unrepeated INS, and 8 + 8n for a repeated INS. However, accurate cycle times for the 80186/80188 is
* low priority. TODO: Fix this someday.
* low priority.
*/
var nCycles = 5;
@ -1244,7 +1244,7 @@ var X86OpXX = {
/*
* NOTE: 5 + 4n is the cycle time for the 80286; the 80186/80188 has different values: 14 cycles for
* an unrepeated INS, and 8 + 8n for a repeated INS. However, accurate cycle times for the 80186/80188 is
* low priority. TODO: Fix this someday.
* low priority.
*/
var nCycles = 5;
@ -1288,7 +1288,7 @@ var X86OpXX = {
/*
* NOTE: 5 + 4n is the cycle time for the 80286; the 80186/80188 has different values: 14 cycles for
* an unrepeated INS, and 8 + 8n for a repeated INS. However, accurate cycle times for the 80186/80188 is
* low priority. TODO: Fix this someday.
* low priority.
*/
var nCycles = 5;
@ -1331,7 +1331,7 @@ var X86OpXX = {
/*
* NOTE: 5 + 4n is the cycle time for the 80286; the 80186/80188 has different values: 14 cycles for
* an unrepeated INS, and 8 + 8n for a repeated INS. However, accurate cycle times for the 80186/80188 is
* low priority. TODO: Fix this someday.
* low priority.
*/
var nCycles = 5;
@ -2753,7 +2753,7 @@ var X86OpXX = {
/*
* NOTE: 11 is the minimum cycle time for the 80286; the 80186/80188 has different cycle times: 15, 25 and
* 22 + 16 * (bLevel - 1) for bLevel 0, 1 and > 1, respectively. However, accurate cycle times for the 80186/80188
* is low priority. TODO: Fix this someday.
* is low priority.
*/
this.nStepCycles -= 11;
this.pushWord(this.regBP);
@ -2781,7 +2781,7 @@ var X86OpXX = {
this.regBP = this.popWord();
/*
* NOTE: 5 is the cycle time for the 80286; the 80186/80188 has a cycle time of 8. However, accurate cycle
* counts for the 80186/80188 is low priority. TODO: Fix this someday.
* counts for the 80186/80188 is low priority.
*/
this.nStepCycles -= 5;
},