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:
parent
093099d980
commit
997db9787b
15 changed files with 1557 additions and 1282 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -2781,6 +2781,7 @@ if (DEBUGGER) {
|
|||
if (iColon < 0) {
|
||||
if (seg != null) {
|
||||
off = this.parseValue(sAddr);
|
||||
addr = null;
|
||||
} else {
|
||||
addr = this.parseValue(sAddr);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue