From 851b077957dbb6c272b0a1759846c04024db4620 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Fri, 21 Nov 2014 18:05:53 -0800 Subject: [PATCH] Assorted RTC and Debugger improvements All three types of RTC interrupts (Periodic, Alarm and Update) should be supported now --- devices/pc/bios/5170/1985-11-15.json | 2 +- devices/pc/bios/5170/1985-11-15.map | 14 ++ modules/pcjs/lib/chipset.js | 301 +++++++++++++++++++++------ modules/pcjs/lib/cpu.js | 5 +- modules/pcjs/lib/debugger.js | 60 +++--- modules/pcjs/lib/hdc.js | 2 +- modules/pcjs/lib/x86cpu.js | 36 ++-- modules/shared/lib/component.js | 5 - 8 files changed, 308 insertions(+), 117 deletions(-) diff --git a/devices/pc/bios/5170/1985-11-15.json b/devices/pc/bios/5170/1985-11-15.json index 6e7870369..14990a0ec 100644 --- a/devices/pc/bios/5170/1985-11-15.json +++ b/devices/pc/bios/5170/1985-11-15.json @@ -1023,4 +1023,4 @@ 0,0,0,0,-822083584,4958953,216530944,-1259812051,1156107052,-773207253,53,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,14703594,791753200,942617905,-1979973579] -,"symbols":{"POST1_TEST01":{"o":80,"s":61440,"c":"TEST.01: 286 PROCESSOR TEST (REAL MODE)"},"POST1_TEST02":{"o":391,"s":61440,"c":"TEST.02: ROM CHECKSUM TEST"},"POST1_TEST03":{"o":419,"s":61440,"c":"TEST.03: VERIFY CMOS SHUTDOWN BYTE"},"POST1_TEST04":{"o":457,"s":61440,"c":"TEST.04: 8254 CHECK TIMER 1 (ALL BITS ON)"},"POST1_TEST05":{"o":503,"s":61440,"c":"TEST.05: 8254 CHECK TIMER 1 (ALL BITS OFF)"},"POST1_TEST06":{"o":541,"s":61440,"c":"TEST.06: 8237 DMA 0 INITIALIZATION"},"POST1_TEST07":{"o":607,"s":61440,"c":"TEST.07: 8237 DMA 1 INITIALIZATION"},"POST1_TEST08":{"o":724,"s":61440,"c":"TEST.08: DMA PAGE REGISTER TEST"},"POST1_TEST09":{"o":807,"s":61440,"c":"TEST.09: STORAGE REFRESH TEST"},"POST1_TEST10":{"o":829,"s":61440,"c":"TEST.10: 8042 INTERFACE TEST"},"POST1_TEST11":{"o":951,"s":61440,"c":"TEST.11: BASE 64K R/W STORAGE TEST"},"POST1_TEST11A":{"o":1494,"s":61440,"c":"TEST.11A: VERIFY GDT/IDT INSTRUCTIONS"},"POST1_TEST12":{"o":1726,"s":61440,"c":"TEST.12: VERIFY CMOS CHECKSUM"},"POST1_TEST13":{"o":1920,"s":61440,"c":"TEST.13: PROTECTED MODE TEST"},"POST1_TEST13A":{"o":2158,"s":61440,"c":"TEST.13A: MEMORY SIZE TEST (ABOVE 1024K)"},"POST1_TEST14":{"o":2713,"s":61440,"c":"TEST.14: INITIALIZE CRT CONTROLLER"},"POST1_TEST15":{"o":2829,"s":61440,"c":"TEST.15: VIDEO LINE TEST"},"POST1_TEST16":{"o":2845,"s":61440,"c":"TEST.16: CRT INTERFACE LINES TEST"},"MFG_BOOT":{"o":3109,"s":61440,"c":"MANUFACTURING BOOT TEST CODE ROUTINE"},"POST2":{"o":3220,"s":61440},"POST2_CHK_HFNUM":{"o":5372,"s":61440,"c":"CHECK FOR SECOND FIXED DISK PRESENT BUT NOT DEFINED"},"POST3":{"o":5749,"s":61440},"POST4":{"o":6469,"s":61440},"CMOS_READ":{"o":6469,"s":61440},"CMOS_WRITE":{"o":6495,"s":61440},"DDS":{"o":6521,"s":61440},"E_MSG":{"o":6529,"s":61440},"ERR_BEEP":{"o":6582,"s":61440},"BEEP":{"o":6644,"s":61440},"WAITF":{"o":6714,"s":61440,"c":"FIXED TIME WAIT (CX = COUNT OF 15.085737us INTERVALS TO WAIT)"},"CONFIG_BAD":{"o":6729,"s":61440},"XPC_BYTE":{"o":6749,"s":61440},"PRT_SEG":{"o":6772,"s":61440},"PROT_PRT":{"o":6793,"s":61440},"ROM_CHECKSUM":{"o":6837,"s":61440},"ROM_CHECK":{"o":6849,"s":61440},"KBD_RESET":{"o":6899,"s":61440},"BLINK_INT":{"o":6942,"s":61440},"SET_TOD":{"o":6956,"s":61440},"POST5":{"o":7230,"s":61440},"POST6":{"o":7867,"s":61440},"XMIT_8042":{"o":8167,"s":61440},"BOOT_STRAP_1":{"o":8232,"s":61440},"DISKETTE_IO_1":{"o":8521,"s":61440},"DISK_RESET":{"o":8656,"s":61440},"DISK_STATUS":{"o":8741,"s":61440},"DISK_READ":{"o":8753,"s":61440},"DISK_WRITE":{"o":8765,"s":61440},"DISK_VERF":{"o":8777,"s":61440},"DISK_FORMAT":{"o":8789,"s":61440},"DISK_PARMS":{"o":8891,"s":61440},"DISK_CHANGE":{"o":9110,"s":61440,"c":"RETURNS THE STATE OF THE DISK CHANGE LINE"},"FORMAT_SET":{"o":9153,"s":61440},"DISK_TYPE":{"o":9076,"s":61440},"SET_MEDIA":{"o":9250,"s":61440},"DR_TYPE_CHECK":{"o":9365,"s":61440},"SEND_SPEC":{"o":9397,"s":61440},"XLAT_NEW":{"o":9452,"s":61440},"XLAT_OLD":{"o":9490,"s":61440},"SETUP_END":{"o":10462,"s":61440},"SETUP_DBL":{"o":10488,"s":61440,"c":"CHECK DOUBLE STEP"},"READ_ID":{"o":10588,"s":61440},"CMOS_TYPE":{"o":10609,"s":61440},"MOTOR_ON":{"o":10655,"s":61440},"NEC_OUTPUT":{"o":10877,"s":61440},"SEEK":{"o":10921,"s":61440},"RESULTS":{"o":11118,"s":61440},"READ_DSKCHNG":{"o":11181,"s":61440},"WAIT_INT":{"o":11078,"s":61440},"DISK_INT_1":{"o":11230,"s":61440},"DSKETTE_SETUP":{"o":11253,"s":61440},"DISK_SETUP":{"o":11337,"s":61440},"DISK_IO":{"o":11762,"s":61440},"HDISK_SETUP":{"o":11369,"s":61440},"HD_RESET_1":{"o":11629,"s":61440},"SET_FAIL":{"o":11749,"s":61440},"POD_TCHK":{"o":11763,"s":61440},"HDISK_IO":{"o":11794,"s":61440},"HDISK_IO_CONT":{"o":11918,"s":61440},"HDISK_RESET":{"o":12074,"s":61440},"HDISK_STATUS":{"o":12158,"s":61440},"HDISK_READ":{"o":12167,"s":61440},"HDISK_WRITE":{"o":12174,"s":61440},"HDISK_VERF":{"o":12181,"s":61440},"FMT_TRK":{"o":12199,"s":61440},"READ_DASD":{"o":12220,"s":61440},"GET_PARM":{"o":12284,"s":61440},"INIT_DRV":{"o":12378,"s":61440},"RD_LONG":{"o":12428,"s":61440},"WR_LONG":{"o":12435,"s":61440},"DISK_SEEK":{"o":12442,"s":61440},"TST_RDY":{"o":12472,"s":61440,"c":"TEST HARD DISK READY (AH = 0x10)"},"HDISK_RECAL":{"o":12495,"s":61440},"CTLR_DIAGNOSTIC":{"o":12535,"s":61440},"COMMANDI":{"o":12591,"s":61440},"COMMANDO":{"o":12654,"s":61440},"COMMAND":{"o":12741,"s":61440},"WAIT":{"o":12741,"s":61440},"NOT_BUSY":{"o":12892,"s":61440},"WAIT_DRQ":{"o":12931,"s":61440},"CHECK_STATUS":{"o":12953,"s":61440},"CHECK_ST":{"o":12971,"s":61440},"CHECK_ER":{"o":13023,"s":61440},"CHECK_DMA":{"o":13066,"s":61440},"GET_VEC":{"o":13101,"s":61440},"HD_INT":{"o":13123,"s":61440},"KEYBOARD_IO_1":{"o":13158,"s":61440},"K1S":{"o":13357,"s":61440,"c":"READ THE KEY TO FIGURE OUT WHAT TO DO"},"K2S":{"o":13417,"s":61440,"c":"READ THE KEY TO SEE IF ONE IS PRESENT"},"KIO_E_XLAT":{"o":13451,"s":61440,"c":"TRANSLATE SCAN CODE PAIRS FOR EXTENDED CALLS"},"KIO_S_XLAT":{"o":13462,"s":61440,"c":"TRANSLATE SCAN CODE PAIRS FOR STANDARD CALLS"},"K4":{"o":13518,"s":61440,"c":"INCREMENT BUFFER POINTER ROUTINE"},"KB_INT_1":{"o":13531,"s":61440,"c":"KEYBOARD INTERRUPT ROUTINE"},"KB_INT_01":{"o":13553,"s":61440,"c":"WAIT FOR KEYBOARD DISABLE COMMAND TO BE ACCEPTED"},"KB_INT_02":{"o":13571,"s":61440,"c":"CHECK FOR A RESEND COMMAND TO KEYBOARD"},"KB_INT_4":{"o":13589,"s":61440,"c":"RESEND THE LAST BYTE"},"KB_INT_2":{"o":13598,"s":61440,"c":"UPDATE MODE INDICATORS IF CHANGE IN STATE"},"UP0":{"o":13616,"s":61440,"c":"START OF KEY PROCESSING"},"T_SYS_KEY":{"o":13758,"s":61440,"c":"TEST FOR SYSTEM KEY"},"K16A":{"o":13823,"s":61440,"c":"TEST FOR SHIFT KEYS"},"K17":{"o":13842,"s":61440,"c":"SHIFT KEY FOUND"},"K17C":{"o":13860,"s":61440,"c":"SHIFT KEY FOUND, DETERMINE SET OR TOGGLE"},"K23":{"o":13979,"s":61440,"c":"BREAK SHIFT FOUND"},"K25":{"o":14059,"s":61440,"c":"TEST FOR HOLD STATE"},"K26A":{"o":14084,"s":61440,"c":"INTERRUPT RETURN"},"K57":{"o":14676,"s":61440,"c":"PUT CHARACTER INTO BUFFER"},"SHIP_IT":{"o":14747,"s":61440,"c":"HANDLE TRANSMISSION OF BYTES TO 8042"},"SND_DATA":{"o":14762,"s":61440,"c":"HANDLE TRANSMISSION OF COMMAND AND DATA BYTES"},"SND_LED":{"o":14822,"s":61440,"c":"TURN ON THE MODE INDICATORS"},"MAKE_LED":{"o":14903,"s":61440,"c":"FORM THE DATA BYTE FOR THE MODE INDICATORS"}}} \ No newline at end of file +,"symbols":{"POST1_TEST01":{"o":80,"s":61440,"c":"TEST.01: 286 PROCESSOR TEST (REAL MODE)"},"POST1_TEST02":{"o":391,"s":61440,"c":"TEST.02: ROM CHECKSUM TEST"},"POST1_TEST03":{"o":419,"s":61440,"c":"TEST.03: VERIFY CMOS SHUTDOWN BYTE"},"POST1_TEST04":{"o":457,"s":61440,"c":"TEST.04: 8254 CHECK TIMER 1 (ALL BITS ON)"},"POST1_TEST05":{"o":503,"s":61440,"c":"TEST.05: 8254 CHECK TIMER 1 (ALL BITS OFF)"},"POST1_TEST06":{"o":541,"s":61440,"c":"TEST.06: 8237 DMA 0 INITIALIZATION"},"POST1_TEST07":{"o":607,"s":61440,"c":"TEST.07: 8237 DMA 1 INITIALIZATION"},"POST1_TEST08":{"o":724,"s":61440,"c":"TEST.08: DMA PAGE REGISTER TEST"},"POST1_TEST09":{"o":807,"s":61440,"c":"TEST.09: STORAGE REFRESH TEST"},"POST1_TEST10":{"o":829,"s":61440,"c":"TEST.10: 8042 INTERFACE TEST"},"POST1_TEST11":{"o":951,"s":61440,"c":"TEST.11: BASE 64K R/W STORAGE TEST"},"POST1_TEST11A":{"o":1494,"s":61440,"c":"TEST.11A: VERIFY GDT/IDT INSTRUCTIONS"},"POST1_TEST12":{"o":1726,"s":61440,"c":"TEST.12: VERIFY CMOS CHECKSUM"},"POST1_TEST13":{"o":1920,"s":61440,"c":"TEST.13: PROTECTED MODE TEST"},"POST1_TEST13A":{"o":2158,"s":61440,"c":"TEST.13A: MEMORY SIZE TEST (ABOVE 1024K)"},"POST1_TEST14":{"o":2713,"s":61440,"c":"TEST.14: INITIALIZE CRT CONTROLLER"},"POST1_TEST15":{"o":2829,"s":61440,"c":"TEST.15: VIDEO LINE TEST"},"POST1_TEST16":{"o":2845,"s":61440,"c":"TEST.16: CRT INTERFACE LINES TEST"},"MFG_BOOT":{"o":3109,"s":61440,"c":"MANUFACTURING BOOT TEST CODE ROUTINE"},"POST2":{"o":3220,"s":61440},"POST2_CHK_HFNUM":{"o":5372,"s":61440,"c":"CHECK FOR SECOND FIXED DISK PRESENT BUT NOT DEFINED"},"POST3":{"o":5749,"s":61440},"POST4":{"o":6469,"s":61440},"CMOS_READ":{"o":6469,"s":61440},"CMOS_WRITE":{"o":6495,"s":61440},"DDS":{"o":6521,"s":61440},"E_MSG":{"o":6529,"s":61440},"ERR_BEEP":{"o":6582,"s":61440},"BEEP":{"o":6644,"s":61440},"WAITF":{"o":6714,"s":61440,"c":"FIXED TIME WAIT (CX = COUNT OF 15.085737us INTERVALS TO WAIT)"},"CONFIG_BAD":{"o":6729,"s":61440},"XPC_BYTE":{"o":6749,"s":61440},"PRT_SEG":{"o":6772,"s":61440},"PROT_PRT":{"o":6793,"s":61440},"ROM_CHECKSUM":{"o":6837,"s":61440},"ROM_CHECK":{"o":6849,"s":61440},"KBD_RESET":{"o":6899,"s":61440},"BLINK_INT":{"o":6942,"s":61440},"SET_TOD":{"o":6956,"s":61440},"POST5":{"o":7230,"s":61440},"POST6":{"o":7867,"s":61440},"XMIT_8042":{"o":8167,"s":61440},"BOOT_STRAP_1":{"o":8232,"s":61440},"DISKETTE_IO_1":{"o":8521,"s":61440},"DISK_RESET":{"o":8656,"s":61440},"DISK_STATUS":{"o":8741,"s":61440},"DISK_READ":{"o":8753,"s":61440},"DISK_WRITE":{"o":8765,"s":61440},"DISK_VERF":{"o":8777,"s":61440},"DISK_FORMAT":{"o":8789,"s":61440},"DISK_PARMS":{"o":8891,"s":61440},"DISK_CHANGE":{"o":9110,"s":61440,"c":"RETURNS THE STATE OF THE DISK CHANGE LINE"},"FORMAT_SET":{"o":9153,"s":61440},"DISK_TYPE":{"o":9076,"s":61440},"SET_MEDIA":{"o":9250,"s":61440},"DR_TYPE_CHECK":{"o":9365,"s":61440},"SEND_SPEC":{"o":9397,"s":61440},"XLAT_NEW":{"o":9452,"s":61440},"XLAT_OLD":{"o":9490,"s":61440},"SETUP_END":{"o":10462,"s":61440},"SETUP_DBL":{"o":10488,"s":61440,"c":"CHECK DOUBLE STEP"},"READ_ID":{"o":10588,"s":61440},"CMOS_TYPE":{"o":10609,"s":61440},"MOTOR_ON":{"o":10655,"s":61440},"NEC_OUTPUT":{"o":10877,"s":61440},"SEEK":{"o":10921,"s":61440},"RESULTS":{"o":11118,"s":61440},"READ_DSKCHNG":{"o":11181,"s":61440},"WAIT_INT":{"o":11078,"s":61440},"DISK_INT_1":{"o":11230,"s":61440},"DSKETTE_SETUP":{"o":11253,"s":61440},"DISK_SETUP":{"o":11337,"s":61440},"DISK_IO":{"o":11762,"s":61440},"HDISK_SETUP":{"o":11369,"s":61440},"HD_RESET_1":{"o":11629,"s":61440},"SET_FAIL":{"o":11749,"s":61440},"POD_TCHK":{"o":11763,"s":61440},"HDISK_IO":{"o":11794,"s":61440},"HDISK_IO_CONT":{"o":11918,"s":61440},"HDISK_RESET":{"o":12074,"s":61440},"HDISK_STATUS":{"o":12158,"s":61440},"HDISK_READ":{"o":12167,"s":61440},"HDISK_WRITE":{"o":12174,"s":61440},"HDISK_VERF":{"o":12181,"s":61440},"FMT_TRK":{"o":12199,"s":61440},"READ_DASD":{"o":12220,"s":61440},"GET_PARM":{"o":12284,"s":61440},"INIT_DRV":{"o":12378,"s":61440},"RD_LONG":{"o":12428,"s":61440},"WR_LONG":{"o":12435,"s":61440},"DISK_SEEK":{"o":12442,"s":61440},"TST_RDY":{"o":12472,"s":61440,"c":"TEST HARD DISK READY (AH = 0x10)"},"HDISK_RECAL":{"o":12495,"s":61440},"CTLR_DIAGNOSTIC":{"o":12535,"s":61440},"COMMANDI":{"o":12591,"s":61440},"COMMANDO":{"o":12654,"s":61440},"COMMAND":{"o":12741,"s":61440},"WAIT":{"o":12741,"s":61440},"NOT_BUSY":{"o":12892,"s":61440},"WAIT_DRQ":{"o":12931,"s":61440},"CHECK_STATUS":{"o":12953,"s":61440},"CHECK_ST":{"o":12971,"s":61440},"CHECK_ER":{"o":13023,"s":61440},"CHECK_DMA":{"o":13066,"s":61440},"GET_VEC":{"o":13101,"s":61440},"HD_INT":{"o":13123,"s":61440},"KEYBOARD_IO_1":{"o":13158,"s":61440},"K1S":{"o":13357,"s":61440,"c":"READ THE KEY TO FIGURE OUT WHAT TO DO"},"K2S":{"o":13417,"s":61440,"c":"READ THE KEY TO SEE IF ONE IS PRESENT"},"KIO_E_XLAT":{"o":13451,"s":61440,"c":"TRANSLATE SCAN CODE PAIRS FOR EXTENDED CALLS"},"KIO_S_XLAT":{"o":13462,"s":61440,"c":"TRANSLATE SCAN CODE PAIRS FOR STANDARD CALLS"},"K4":{"o":13518,"s":61440,"c":"INCREMENT BUFFER POINTER ROUTINE"},"KB_INT_1":{"o":13531,"s":61440,"c":"KEYBOARD INTERRUPT ROUTINE"},"KB_INT_01":{"o":13553,"s":61440,"c":"WAIT FOR KEYBOARD DISABLE COMMAND TO BE ACCEPTED"},"KB_INT_02":{"o":13571,"s":61440,"c":"CHECK FOR A RESEND COMMAND TO KEYBOARD"},"KB_INT_4":{"o":13589,"s":61440,"c":"RESEND THE LAST BYTE"},"KB_INT_2":{"o":13598,"s":61440,"c":"UPDATE MODE INDICATORS IF CHANGE IN STATE"},"UP0":{"o":13616,"s":61440,"c":"START OF KEY PROCESSING"},"T_SYS_KEY":{"o":13758,"s":61440,"c":"TEST FOR SYSTEM KEY"},"K16A":{"o":13823,"s":61440,"c":"TEST FOR SHIFT KEYS"},"K17":{"o":13842,"s":61440,"c":"SHIFT KEY FOUND"},"K17C":{"o":13860,"s":61440,"c":"SHIFT KEY FOUND, DETERMINE SET OR TOGGLE"},"K23":{"o":13979,"s":61440,"c":"BREAK SHIFT FOUND"},"K25":{"o":14059,"s":61440,"c":"TEST FOR HOLD STATE"},"K26A":{"o":14084,"s":61440,"c":"INTERRUPT RETURN"},"K57":{"o":14676,"s":61440,"c":"PUT CHARACTER INTO BUFFER"},"SHIP_IT":{"o":14747,"s":61440,"c":"HANDLE TRANSMISSION OF BYTES TO 8042"},"SND_DATA":{"o":14762,"s":61440,"c":"HANDLE TRANSMISSION OF COMMAND AND DATA BYTES"},"SND_LED":{"o":14822,"s":61440,"c":"TURN ON THE MODE INDICATORS"},"MAKE_LED":{"o":14903,"s":61440,"c":"FORM THE DATA BYTE FOR THE MODE INDICATORS"},"CASSETTE_IO_1":{"o":17549,"s":61440,"c":"BIOS1 (11/15/85)"},"INT15_EVENT_WAIT":{"o":17650,"s":61440},"INT15_JOY_STICK":{"o":17757,"s":61440},"INT15_WAIT":{"o":17911,"s":61440},"INT15_BLOCKMOVE":{"o":18007,"s":61440},"SHUT9":{"o":18199,"s":61440,"c":"RETURN FROM SHUTDOWN"},"GATE_A20":{"o":18521,"s":61440},"EMPTY_8042":{"o":18546,"s":61440},"EXT_MEMORY":{"o":18555,"s":61440},"X_VIRTUAL":{"o":18567,"s":61440},"TIME_OF_DAY_1":{"o":18709,"s":61440,"c":"BIOS2 (11/15/85)"},"RTC_INT":{"o":19095,"s":61440,"c":"ALARM INTERRUPT (INT 0x70, IRQ 8)"}}} \ No newline at end of file diff --git a/devices/pc/bios/5170/1985-11-15.map b/devices/pc/bios/5170/1985-11-15.map index c0eeb67c4..4ced11d22 100644 --- a/devices/pc/bios/5170/1985-11-15.map +++ b/devices/pc/bios/5170/1985-11-15.map @@ -155,3 +155,17 @@ F000:0644 @ SND_DATA ; HANDLE TRANSMISSION OF COMMAND AND DATA BYTES F000:0680 @ SND_LED ; TURN ON THE MODE INDICATORS F000:06D1 @ MAKE_LED ; FORM THE DATA BYTE FOR THE MODE INDICATORS + 448D + + F000:0000 @ CASSETTE_IO_1 ; BIOS1 (11/15/85) + F000:0065 @ INT15_EVENT_WAIT + F000:00D0 @ INT15_JOY_STICK + F000:016A @ INT15_WAIT + F000:01CA @ INT15_BLOCKMOVE + F000:028A @ SHUT9 ; RETURN FROM SHUTDOWN + F000:03CC @ GATE_A20 + F000:03E5 @ EMPTY_8042 + F000:03EE @ EXT_MEMORY + F000:03FA @ X_VIRTUAL + 4915 + + F000:0000 @ TIME_OF_DAY_1 ; BIOS2 (11/15/85) + F000:0182 @ RTC_INT ; ALARM INTERRUPT (INT 0x70, IRQ 8) diff --git a/modules/pcjs/lib/chipset.js b/modules/pcjs/lib/chipset.js index 4c804ac23..626b2fbca 100644 --- a/modules/pcjs/lib/chipset.js +++ b/modules/pcjs/lib/chipset.js @@ -503,13 +503,14 @@ ChipSet.IRQ = { SLAVE: 0x02, COM2: 0x03, COM1: 0x04, - XTC: 0x05, // MODEL_5160 uses this for its HDC; MODEL_5170 designates it for LPT2 + XTC: 0x05, // MODEL_5160 uses IRQ 5 for HDC (XTC version) + LPT2: 0x05, // MODEL_5170 uses IRQ 5 for LPT2 FDC: 0x06, LPT1: 0x07, RTC: 0x08, IRQ2: 0x09, COPROC: 0x0D, - ATC: 0x0E // MODEL_5170 uses this for its HDC + ATC: 0x0E // MODEL_5170 uses IRQ 14 for HDC (ATC version) }; /* @@ -759,10 +760,10 @@ ChipSet.CMOS = { RTC_MONTH_DAY: 0x07, RTC_MONTH: 0x08, RTC_YEAR: 0x09, - RTC_STATUSA: 0x0A, - RTC_STATUSB: 0x0B, - RTC_STATUSC: 0x0C, - RTC_STATUSD: 0x0D, + STATUSA: 0x0A, + STATUSB: 0x0B, + STATUSC: 0x0C, + STATUSD: 0x0D, DIAG: 0x0E, SHUTDOWN: 0x0F, FDRIVE: 0x10, @@ -785,29 +786,29 @@ ChipSet.CMOS = { DATA: { // this.abCMOSData PORT: 0x71 }, - STATUSA: { // abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSA] + STATUSA: { // abCMOSData[ChipSet.CMOS.ADDR.STATUSA] UIP: 0x80, // bit 7: 1 indicates Update-In-Progress, 0 indicates date/time ready to read DV: 0x70, // bits 6-4 (DV2-DV0) are programmed to 010 to select a 32.768Khz time base RS: 0x0F // bits 3-0 (RS3-RS0) are programmed to 0110 to select a 976.562us interrupt rate }, - STATUSB: { // abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSB] + STATUSB: { // abCMOSData[ChipSet.CMOS.ADDR.STATUSB] SET: 0x80, // bit 7: 1 to set any/all of the 14 time-bytes PIE: 0x40, // bit 6: 1 for Periodic Interrupt Enable AIE: 0x20, // bit 5: 1 for Alarm Interrupt Enable - UIE: 0x10, // bit 4: 1 for Update-Ended Interrupt Enable + UIE: 0x10, // bit 4: 1 for Update Interrupt Enable SQWE: 0x08, // bit 3: 1 for Square Wave Enabled (as set by the STATUSA rate selection bits) BINARY: 0x04, // bit 2: 1 for binary Date Mode, 0 for BCD Date Mode HOUR24: 0x02, // bit 1: 1 for 24-hour mode, 0 for 12-hour mode DST: 0x01 // bit 0: 1 for Daylight Savings Time enabled }, - STATUSC: { // abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSC] TODO: Does reading this register clear these interrupt conditions? (see F000:01C6 in the MODEL_5170 BIOS) - IRQF: 0x80, // bit 7 + STATUSC: { // abCMOSData[ChipSet.CMOS.ADDR.STATUSC] + IRQF: 0x80, // bit 7: 1 indicates one or more of the following bits (PF, AF, UF) are set PF: 0x40, // bit 6: 1 indicates Periodic Interrupt AF: 0x20, // bit 5: 1 indicates Alarm Interrupt - UF: 0x10, // bit 4: 1 indicates Update-Ended Interrupt + UF: 0x10, // bit 4: 1 indicates Update Interrupt RESERVED: 0x0F }, - STATUSD: { // abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSD] + STATUSD: { // abCMOSData[ChipSet.CMOS.ADDR.STATUSD] VRB: 0x80, // bit 7: 1 indicates Valid RAM Bit (0 implies power was and/or is lost) RESERVED: 0x7F }, @@ -1012,7 +1013,7 @@ ChipSet.prototype.powerDown = function(fSave) * reset(fHard) * * @this {ChipSet} - * @param {boolean} [fHard] true if a machine reset (not just a soft reset) + * @param {boolean} [fHard] true on the initial reset (not a normal "soft" reset) */ ChipSet.prototype.reset = function(fHard) { @@ -1098,9 +1099,11 @@ ChipSet.prototype.reset = function(fHard) * and any later ("soft") resets (eg, from powerUp() calls), and make sure the latter preserves * existing CMOS information. */ - if (fHard) this.abCMOSData = new Array(ChipSet.CMOS.ADDR.TOTAL); + if (fHard) { + this.abCMOSData = new Array(ChipSet.CMOS.ADDR.TOTAL); + } - this.initRTCDate(this.sRTCDate); + this.initRTCTime(this.sRTCDate); /* * initCMOSData() will initialize a variety of "legacy" CMOS bytes, but it will NOT overwrite any memory @@ -1120,7 +1123,7 @@ ChipSet.prototype.reset = function(fHard) }; /** - * initRTCDate(sDate) + * initRTCTime(sDate) * * Initialize the RTC portion of the CMOS registers to match the specified date/time (or if none is specified, * the current date/time). The date/time should be expressed in the ISO 8601 format; eg: "2011-10-10T14:48:00". @@ -1128,7 +1131,7 @@ ChipSet.prototype.reset = function(fHard) * NOTE: There are two approaches we could take here: always store the RTC bytes in binary, and convert them * to/from BCD on-demand (ie, as the simulation reads/writes the CMOS RTC registers); or init/update them in the * format specified by CMOS_STATUSB.BINARY (1 for binary, 0 for BCD). Both approaches require BCD conversion - * functions, but the former seems more efficient, in part because the periodic calls to updateRTCDate() won't + * functions, but the former seems more efficient, in part because the periodic calls to updateRTCTime() won't * require any conversions. * * We take the same approach with the CMOS_STATUSB.HOUR24 setting: internally, we always operate in 24-hour mode, @@ -1140,7 +1143,7 @@ ChipSet.prototype.reset = function(fHard) * @this {ChipSet} * @param {string} [sDate] */ -ChipSet.prototype.initRTCDate = function(sDate) +ChipSet.prototype.initRTCTime = function(sDate) { /* * NOTE: I've already been burned once by a JavaScript library function that did NOT treat an undefined @@ -1185,12 +1188,14 @@ ChipSet.prototype.initRTCDate = function(sDate) this.abCMOSData[ChipSet.CMOS.ADDR.RTC_YEAR] = nYear % 100; var nCentury = (nYear / 100); this.abCMOSData[ChipSet.CMOS.ADDR.CENTURY_DATE] = (nCentury % 10) | ((nCentury / 10) << 4); - this.nCyclesCMOSLastUpdate = -1; - this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSA] = 0x26; // hard-coded default; refer to ChipSet.CMOS.STATUSA.DV and ChipSet.CMOS.STATUSA.RS - this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSB] = ChipSet.CMOS.STATUSB.HOUR24; // default to BCD mode (ChipSet.CMOS.STATUSB.BINARY not set) - this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSC] = 0x00; - this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSD] = ChipSet.CMOS.STATUSD.VRB; + this.abCMOSData[ChipSet.CMOS.ADDR.STATUSA] = 0x26; // hard-coded default; refer to ChipSet.CMOS.STATUSA.DV and ChipSet.CMOS.STATUSA.RS + this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] = ChipSet.CMOS.STATUSB.HOUR24; // default to BCD mode (ChipSet.CMOS.STATUSB.BINARY not set) + this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC] = 0x00; + this.abCMOSData[ChipSet.CMOS.ADDR.STATUSD] = ChipSet.CMOS.STATUSD.VRB; + + this.nRTCCyclesLastUpdate = this.nRTCCyclesNextUpdate = 0; + this.nRTCPeriodsPerSecond = this.nRTCCyclesPerPeriod = null; }; /** @@ -1201,14 +1206,14 @@ ChipSet.prototype.initRTCDate = function(sDate) */ ChipSet.prototype.getRTCByte = function(iRTC) { - if (DEBUG) this.assert(iRTC >= 0 && iRTC <= ChipSet.CMOS.ADDR.RTC_STATUSD); + if (DEBUG) this.assert(iRTC >= 0 && iRTC <= ChipSet.CMOS.ADDR.STATUSD); var b = this.abCMOSData[iRTC]; - if (iRTC < ChipSet.CMOS.ADDR.RTC_STATUSA) { + if (iRTC < ChipSet.CMOS.ADDR.STATUSA) { var f12HourValue = false; if (iRTC == ChipSet.CMOS.ADDR.RTC_HOUR || iRTC == ChipSet.CMOS.ADDR.RTC_HOUR_ALRM) { - if (!(this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSB] & ChipSet.CMOS.STATUSB.HOUR24)) { + if (!(this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.HOUR24)) { if (b < 12) { b = (!b? 12 : b); } else { @@ -1218,7 +1223,7 @@ ChipSet.prototype.getRTCByte = function(iRTC) f12HourValue = true; } } - if (!(this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSB] & ChipSet.CMOS.STATUSB.BINARY)) { + if (!(this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.BINARY)) { /* * We're in BCD mode, so we must convert b from BINARY to BCD. But first: * @@ -1233,7 +1238,7 @@ ChipSet.prototype.getRTCByte = function(iRTC) b = (b % 10) | ((b / 10) << 4); } } else { - if (iRTC == ChipSet.CMOS.ADDR.RTC_STATUSA) { + if (iRTC == ChipSet.CMOS.ADDR.STATUSA) { /* * HACK: Perform a mindless toggling of the "Update-In-Progress" bit, so that it's flipped * on the next read; this makes the MODEL_5170 BIOS ("POST2_RTCUP") happy. @@ -1253,11 +1258,11 @@ ChipSet.prototype.getRTCByte = function(iRTC) */ ChipSet.prototype.setRTCByte = function(iRTC, b) { - if (DEBUG) this.assert(iRTC >= 0 && iRTC <= ChipSet.CMOS.ADDR.RTC_STATUSD); + if (DEBUG) this.assert(iRTC >= 0 && iRTC <= ChipSet.CMOS.ADDR.STATUSD); - if (iRTC < ChipSet.CMOS.ADDR.RTC_STATUSA) { + if (iRTC < ChipSet.CMOS.ADDR.STATUSA) { var fBCD = false; - if (!(this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSB] & ChipSet.CMOS.STATUSB.BINARY)) { + if (!(this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.BINARY)) { /* * We're in BCD mode, so we must convert b from BCD to BINARY (we assume it's valid * BCD; ie, that both nibbles contain only 0-9, not A-F). @@ -1276,7 +1281,7 @@ ChipSet.prototype.setRTCByte = function(iRTC, b) b += 0x30; } } - if (!(this.abCMOSData[ChipSet.CMOS.ADDR.RTC_STATUSB] & ChipSet.CMOS.STATUSB.HOUR24)) { + if (!(this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.HOUR24)) { if (b <= 12) { b = (b == 12? 0 : b); } else { @@ -1290,33 +1295,157 @@ ChipSet.prototype.setRTCByte = function(iRTC, b) }; /** - * updateRTCDate() + * calcRTCCyclePeriod() + * + * This should be called whenever the timings in STATUSA may have changed. + * + * TODO: 1024 is a hard-coded number of periods per second based on the default interrupt rate of 976.562us + * (ie, 1000000 / 976.562). Calculate the actual number based on the values programmed in the STATUSA register. * * @this {ChipSet} */ -ChipSet.prototype.updateRTCDate = function() +ChipSet.prototype.calcRTCCyclePeriod = function() +{ + this.nRTCCyclesLastUpdate = this.cpu.getCycles(this.fScaleTimers); + this.nRTCPeriodsPerSecond = 1024; + this.nRTCCyclesPerPeriod = Math.floor(this.cpu.getCyclesPerSecond() / this.nRTCPeriodsPerSecond); + this.setRTCCycleLimit(); +}; + +/** + * getRTCCycleLimit(nCycles) + * + * This is called by the CPU to determine the maximum number of cycles it can process for the current burst. + * + * @this {ChipSet} + * @param {number} nCycles + * @return {number} maximum number of cycles (<= nCycles) + */ +ChipSet.prototype.getRTCCycleLimit = function(nCycles) +{ + if (this.abCMOSData && this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.PIE) { + var nCyclesUpdate = this.nRTCCyclesNextUpdate - this.cpu.getCycles(this.fScaleTimers); + if (nCyclesUpdate > 0) { + if (nCycles > nCyclesUpdate) { + if (DEBUG) this.messageDebugger("getRTCCycleLimit(" + nCycles + "): reduced to " + nCyclesUpdate + " cycles", Debugger.MESSAGE.RTC); + nCycles = nCyclesUpdate; + } else { + if (DEBUG) this.messageDebugger("getRTCCycleLimit(" + nCycles + "): already less than " + nCyclesUpdate + " cycles", Debugger.MESSAGE.RTC); + } + } else { + if (DEBUG) this.messageDebugger("RTC next update has passed by " + nCyclesUpdate + " cycles", Debugger.MESSAGE.RTC); + } + } + return nCycles; +}; + +/** + * setRTCCycleLimit(nCycles) + * + * This should be called when PIE becomes set in STATUSB (and whenever PF is cleared in STATUSC while PIE is still set). + * + * @this {ChipSet} + * @param {number} [nCycles] + */ +ChipSet.prototype.setRTCCycleLimit = function(nCycles) +{ + if (nCycles === undefined) nCycles = this.nRTCCyclesPerPeriod; + this.nRTCCyclesNextUpdate = this.cpu.getCycles(this.fScaleTimers) + nCycles; + if (this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.PIE) { + this.cpu.setBurstCycles(nCycles); + } +}; + +/** + * updateRTCTime() + * + * @this {ChipSet} + */ +ChipSet.prototype.updateRTCTime = function() { - var nCyclesDelta = 0; var nCyclesPerSecond = this.cpu.getCyclesPerSecond(); var nCyclesUpdate = this.cpu.getCycles(this.fScaleTimers); /* - * If nCyclesCMOSLastUpdate hasn't been set yet (ie, if this is our first updateRTCDate() call), - * then do nothing except initialize nCyclesCMOSLastUpdate. + * We must arrange for the very first calcRTCCyclePeriod() call to occur here, on the very first + * updateRTCTime() call, because this is the first point we can be guaranteed that CPU cycle counts + * are initialized (the CPU is the last component to be powered up/restored). + * + * TODO: A side-effect of this is that it undermines the save/restore code's preservation of last + * and next RTC cycle counts, which may change when the next RTC event is delivered. */ - if (this.nCyclesCMOSLastUpdate >= 0) { - nCyclesDelta = nCyclesUpdate - this.nCyclesCMOSLastUpdate; - if (DEBUG) this.assert(nCyclesDelta >= 0); - var nSecondsDelta = Math.floor(nCyclesDelta / nCyclesPerSecond); - /* - * We trust that updateRTCDate() is being called as part of updateAllTimers(), and is therefore - * being called often enough to ensure that nSecondsDelta will never be greater than one. In fact, - * it would always be LESS than one if it weren't ALSO for the fact that we plow any "unused" cycles - * (nCyclesDelta % nCyclesPerSecond) back into nCyclesCMOSLastUpdate, so that we will eventually - * see a one-second delta. - */ - if (DEBUG) this.assert(nSecondsDelta <= 1); - if (nSecondsDelta) { + if (this.nRTCCyclesPerPeriod == null) this.calcRTCCyclePeriod(); + + /* + * Step 1: Deal with Periodic Interrupts + */ + if (nCyclesUpdate >= this.nRTCCyclesNextUpdate) { + var bPrev = this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC]; + this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC] |= ChipSet.CMOS.STATUSC.PF; + if (this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.PIE) { + /* + * When PIE is set, setBurstCycles() should be getting called as needed to ensure + * that updateRTCTime() is called more frequently, so let's assert that we don't have + * an excess of cycles and thus possibly some missed Periodic Interrupts. + */ + if (DEBUG) { + if (nCyclesUpdate - this.nRTCCyclesNextUpdate > this.nRTCCyclesPerPeriod) { + if (bPrev & ChipSet.CMOS.STATUSC.PF) { + this.messageDebugger("RTC interrupt handler failed to clear STATUSC", Debugger.MESSAGE.RTC | Debugger.MESSAGE.WARN); + } else { + this.messageDebugger("CPU took too long trigger new RTC periodic interrupt", Debugger.MESSAGE.RTC | Debugger.MESSAGE.WARN); + } + } + } + this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC] |= ChipSet.CMOS.STATUSC.IRQF; + this.setIRR(ChipSet.IRQ.RTC); + /* + * We could also call setRTCCycleLimit() at this point, but I don't think there's any + * benefit until the interrupt had been acknowledged and STATUSC has been read, thereby + * clearing the way for another Periodic Interrupt; it seems to me that when STATUSC + * is read, that's the more appropriate time to call setRTCCycleLimit(). + */ + } + this.nRTCCyclesNextUpdate = nCyclesUpdate + this.nRTCCyclesPerPeriod; + } + + /* + * Step 2: Deal with Alarm Interrupts + */ + if (this.abCMOSData[ChipSet.CMOS.ADDR.RTC_SEC] == this.abCMOSData[ChipSet.CMOS.ADDR.RTC_SEC_ALRM]) { + if (this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MIN] == this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MIN_ALRM]) { + if (this.abCMOSData[ChipSet.CMOS.ADDR.RTC_HOUR] == this.abCMOSData[ChipSet.CMOS.ADDR.RTC_HOUR_ALRM]) { + this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC] |= ChipSet.CMOS.STATUSC.AF; + if (this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.AIE) { + this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC] |= ChipSet.CMOS.STATUSC.IRQF; + this.setIRR(ChipSet.IRQ.RTC); + } + } + } + } + + /* + * Step 3: Update the RTC date/time and deal with Update Interrupts + */ + var nCyclesDelta = nCyclesUpdate - this.nRTCCyclesLastUpdate; + if (DEBUG) this.assert(nCyclesDelta >= 0); + var nSecondsDelta = Math.floor(nCyclesDelta / nCyclesPerSecond); + + /* + * We trust that updateRTCTime() is being called as part of updateAllTimers(), and is therefore + * being called often enough to ensure that nSecondsDelta will never be greater than one. In fact, + * it would always be LESS than one if it weren't also for the fact that we plow any "unused" cycles + * (nCyclesDelta % nCyclesPerSecond) back into nRTCCyclesLastUpdate, so that we will eventually + * see a one-second delta. + */ + if (DEBUG) this.assert(nSecondsDelta <= 1); + + /* + * Make sure that CMOS.STATUSB.SET isn't set; if it is, then the once-per-second RTC updates must be + * disabled so that software can write new RTC date/time values without interference. + */ + if (nSecondsDelta && !(this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.SET)) { + while (nSecondsDelta--) { if (++this.abCMOSData[ChipSet.CMOS.ADDR.RTC_SEC] >= 60) { this.abCMOSData[ChipSet.CMOS.ADDR.RTC_SEC] = 0; if (++this.abCMOSData[ChipSet.CMOS.ADDR.RTC_MIN] >= 60) { @@ -1336,8 +1465,14 @@ ChipSet.prototype.updateRTCDate = function() } } } + this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC] |= ChipSet.CMOS.STATUSC.UF; + if (this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.UIE) { + this.abCMOSData[ChipSet.CMOS.ADDR.STATUSC] |= ChipSet.CMOS.STATUSC.IRQF; + this.setIRR(ChipSet.IRQ.RTC); + } } - this.nCyclesCMOSLastUpdate = nCyclesUpdate - (nCyclesDelta % nCyclesPerSecond); + + this.nRTCCyclesLastUpdate = nCyclesUpdate - (nCyclesDelta % nCyclesPerSecond); }; /** @@ -1501,7 +1636,7 @@ ChipSet.prototype.save = function() if (this.model >= ChipSet.MODEL_5170) { state.set(5, [this.b8042Status, this.b8042InBuff, this.b8042CmdData, this.b8042OutBuff, this.b8042InPort, this.b8042OutPort]); - state.set(6, [this.bMFGData, this.abDMAPageSpare, this.bCMOSAddr, this.abCMOSData, this.nCyclesCMOSLastUpdate]); + state.set(6, [this.bMFGData, this.abDMAPageSpare, this.bCMOSAddr, this.abCMOSData, this.nRTCCyclesLastUpdate, this.nRTCCyclesNextUpdate]); } return state.data(); }; @@ -1565,16 +1700,17 @@ ChipSet.prototype.restore = function(data) this.abDMAPageSpare = a[1]; this.bCMOSAddr = a[2]; this.abCMOSData = a[3]; - this.nCyclesCMOSLastUpdate = a[4]; + this.nRTCCyclesLastUpdate = a[4]; + this.nRTCCyclesNextUpdate = a[5]; /* * TODO: Decide whether restore() should faithfully preserve the RTC date/time that save() saved, * or always reinitialize the date/time, or give the user (or the machine configuration) the option. * * For now, we're always reinitializing the RTC date. Alternatively, we could selectively update - * the CMOS bytes above, instead of overwriting them all, in which case this extra call to initRTCDate() + * the CMOS bytes above, instead of overwriting them all, in which case this extra call to initRTCTime() * could be avoided. */ - this.initRTCDate(); + this.initRTCTime(); } return true; }; @@ -2119,7 +2255,7 @@ ChipSet.prototype.dumpCMOS = function() if (DEBUGGER) { var sDump = ""; for (var iCMOS = 0; iCMOS < ChipSet.CMOS.ADDR.TOTAL; iCMOS++) { - var b = (iCMOS <= ChipSet.CMOS.ADDR.RTC_STATUSD? this.getRTCByte(iCMOS) : this.abCMOSData[iCMOS]); + var b = (iCMOS <= ChipSet.CMOS.ADDR.STATUSD? this.getRTCByte(iCMOS) : this.abCMOSData[iCMOS]); if (sDump) sDump += '\n'; sDump += "CMOS[0x" + str.toHexByte(iCMOS) + "]: 0x" + str.toHexByte(b); } @@ -2660,7 +2796,7 @@ ChipSet.prototype.advanceDMA = function(channel, fInit) } } else { - if (DEBUG) this.messageDebugger("advanceDMA(" + iDMAChannel + ") unsupported xfer mode: " + str.toHexWord(channel.xfer), Debugger.MESSAGE.DMA); + if (DEBUG) this.messageDebugger("advanceDMA(" + iDMAChannel + ") unsupported xfer mode: " + str.toHexWord(channel.xfer), Debugger.MESSAGE.DMA | Debugger.MESSAGE.WARN); channel.fError = true; } } @@ -2855,11 +2991,12 @@ ChipSet.prototype.outPICLo = function(iPIC, bOut, addrFrom) pic.bISR &= ~bIREnd; this.checkIRR(iPIC); } else { - if (DEBUG) this.messageDebugger("outPIC" + iPIC + "(" + str.toHexByte(pic.port) + "): unexpected EOI command, IRQ " + nIRQ + " not in service", Debugger.MESSAGE.PIC); + if (DEBUG) this.messageDebugger("outPIC" + iPIC + "(" + str.toHexByte(pic.port) + "): unexpected EOI command, IRQ " + nIRQ + " not in service", Debugger.MESSAGE.PIC | Debugger.MESSAGE.WARN); } /* * TODO: Support EOI commands with automatic rotation (eg, ChipSet.PIC_LO.OCW2_EOI_ROT and ChipSet.PIC_LO.OCW2_EOI_ROTSPEC) */ + if (DEBUG && (bOCW2 & ChipSet.PIC_LO.OCW2_SET_ROTAUTO)) this.messageDebugger("outPIC" + iPIC + "(" + str.toHexByte(pic.port) + "): unsupported OCW2 rotate command: " + str.toHexByte(bOut), Debugger.MESSAGE.PIC | Debugger.MESSAGE.WARN); } else if (bOCW2 == ChipSet.PIC_LO.OCW2_SET_PRI) { /* @@ -2871,7 +3008,7 @@ ChipSet.prototype.outPICLo = function(iPIC, bOut, addrFrom) /* * TODO: Remaining commands to support: ChipSet.PIC_LO.OCW2_SET_ROTAUTO and ChipSet.PIC_LO.OCW2_CLR_ROTAUTO */ - if (DEBUG) this.messageDebugger("outPIC" + iPIC + "(" + str.toHexByte(pic.port) + "): unsupported OCW2 command: " + str.toHexByte(bOut), Debugger.MESSAGE.PIC); + if (DEBUG) this.messageDebugger("outPIC" + iPIC + "(" + str.toHexByte(pic.port) + "): unsupported OCW2 automatic EOI command: " + str.toHexByte(bOut), Debugger.MESSAGE.PIC | Debugger.MESSAGE.WARN); } } else { /* @@ -2881,7 +3018,7 @@ ChipSet.prototype.outPICLo = function(iPIC, bOut, addrFrom) * that's unfortunate, because I don't support them yet. */ if (bOut & (ChipSet.PIC_LO.OCW3_POLL_CMD | ChipSet.PIC_LO.OCW3_SMM_CMD)) { - if (DEBUG) this.messageDebugger("outPIC" + iPIC + "(" + str.toHexByte(pic.port) + "): unsupported OCW3 command: " + str.toHexByte(bOut), Debugger.MESSAGE.PIC); + if (DEBUG) this.messageDebugger("outPIC" + iPIC + "(" + str.toHexByte(pic.port) + "): unsupported OCW3 command: " + str.toHexByte(bOut), Debugger.MESSAGE.PIC | Debugger.MESSAGE.WARN); } pic.bOCW3 = bOut; } @@ -3606,7 +3743,7 @@ ChipSet.prototype.updateAllTimers = function(fCycleReset) for (var iTimer = 0; iTimer < this.aTimers.length; iTimer++) { this.updateTimer(iTimer, fCycleReset); } - if (this.model >= ChipSet.MODEL_5170) this.updateRTCDate(); + if (this.model >= ChipSet.MODEL_5170) this.updateRTCTime(); }; /** @@ -4307,8 +4444,26 @@ ChipSet.prototype.outCMOSAddr = function(port, bOut, addrFrom) ChipSet.prototype.inCMOSData = function(port, addrFrom) { var bAddr = this.bCMOSAddr & ChipSet.CMOS.ADDR.MASK; - var bIn = (bAddr <= ChipSet.CMOS.ADDR.RTC_STATUSD? this.getRTCByte(bAddr) : this.abCMOSData[bAddr]); + var bIn = (bAddr <= ChipSet.CMOS.ADDR.STATUSD? this.getRTCByte(bAddr) : this.abCMOSData[bAddr]); this.messagePort(port, null, addrFrom, "CMOS_DATA[" + str.toHexByte(bAddr) + "]", Debugger.MESSAGE.CMOS, bIn); + if (addrFrom != null) { + if (bAddr == ChipSet.CMOS.ADDR.STATUSC) { + /* + * When software reads the STATUSC port, all interrupt bits (PF, AF, and UF) are automatically + * cleared, which in turn clears the IRQF bit, which in turn clears the IRQ. + */ + this.abCMOSData[bAddr] &= ChipSet.CMOS.STATUSC.RESERVED; + if (bIn & ChipSet.CMOS.STATUSC.IRQF) this.clearIRR(ChipSet.IRQ.RTC); + /* + * If we just cleared PF, and PIE is still set, then we need to make sure the next Periodic Interrupt + * occurs in a timely manner, too. + */ + if ((bIn & ChipSet.CMOS.STATUSC.PF) && (this.abCMOSData[ChipSet.CMOS.ADDR.STATUSB] & ChipSet.CMOS.STATUSB.PIE)) { + if (DEBUG) this.messageDebugger("RTC periodic interrupt cleared", Debugger.MESSAGE.RTC); + this.setRTCCycleLimit(); + } + } + } return bIn; }; @@ -4324,7 +4479,16 @@ ChipSet.prototype.outCMOSData = function(port, bOut, addrFrom) { var bAddr = this.bCMOSAddr & ChipSet.CMOS.ADDR.MASK; this.messagePort(port, bOut, addrFrom, "CMOS_DATA[" + str.toHexByte(bAddr) + "]", Debugger.MESSAGE.CMOS); - this.abCMOSData[bAddr] = (bAddr <= ChipSet.CMOS.ADDR.RTC_STATUSD? this.setRTCByte(bAddr, bOut) : bOut); + var bDelta = bOut ^ this.abCMOSData[bAddr]; + this.abCMOSData[bAddr] = (bAddr <= ChipSet.CMOS.ADDR.STATUSD? this.setRTCByte(bAddr, bOut) : bOut); + if (bAddr == ChipSet.CMOS.ADDR.STATUSB && (bDelta & ChipSet.CMOS.STATUSB.PIE)) { + if (bOut & ChipSet.CMOS.STATUSB.PIE) { + if (DEBUG) this.messageDebugger("RTC periodic interrupts enabled", Debugger.MESSAGE.RTC); + this.setRTCCycleLimit(); + } else { + if (DEBUG) this.messageDebugger("RTC periodic interrupts disabled", Debugger.MESSAGE.RTC); + } + } }; /** @@ -4394,9 +4558,12 @@ ChipSet.prototype.outNMI = function(port, bOut, addrFrom) ChipSet.prototype.intBIOSRTC = function(addr) { if (DEBUGGER) { - var AH = this.cpu.regAX >> 8; - if (this.dbg && this.dbg.messageEnabled(Debugger.MESSAGE.RTC)) { - this.dbg.messageInt(Debugger.INT.RTC, addr); + if (this.dbg && this.dbg.messageEnabled(Debugger.MESSAGE.RTC) && this.dbg.messageInt(Debugger.INT.RTC, addr)) { + /* + * By computing AH now, we get the incoming AH value; if we computed it below, along with + * the rest of the register values, we'd get the outgoing AH value, which is not what we want. + */ + var AH = this.cpu.regAX >> 8; this.cpu.addIntReturn(addr, function(chipset, nCycles) { return function onBIOSRTCReturn(nLevel) { nCycles = chipset.cpu.getCycles() - nCycles; diff --git a/modules/pcjs/lib/cpu.js b/modules/pcjs/lib/cpu.js index 63be0abc5..7a6dfceaa 100644 --- a/modules/pcjs/lib/cpu.js +++ b/modules/pcjs/lib/cpu.js @@ -98,6 +98,7 @@ function CPU(parmsCPU, nCyclesDefault) this.aFlags.fPowered = false; this.aFlags.fRunning = false; + this.aFlags.fStarting = false; this.aFlags.fAutoStart = parmsCPU['autoStart']; /* @@ -955,7 +956,7 @@ CPU.prototype.calcRemainingTime = function() this.aCounts.nCyclesRecalc += this.aCounts.nCyclesThisRun; if (DEBUG && this.dbg && this.dbg.messageEnabled(Debugger.MESSAGE.LOG) && msRemainsThisRun) { - this.dbg.message("at " + this.aCounts.msEndThisRun + "ms, calcRemainingTime returned " + msRemainsThisRun + "ms to sleep"); + this.dbg.message("calcRemainingTime: " + msRemainsThisRun + "ms to sleep after " + this.aCounts.msEndThisRun + "ms"); } this.aCounts.msEndThisRun += msRemainsThisRun; @@ -998,6 +999,7 @@ CPU.prototype.runCPU = function(fOnClick) * nCyclesPerBurst = nCyclesTimer0; * } */ + if (this.chipset) nCyclesPerBurst = this.chipset.getRTCCycleLimit(nCyclesPerBurst); /* * nCyclesPerBurst is how many cycles we WANT to run on each iteration of stepCPU(), but it may run @@ -1065,6 +1067,7 @@ CPU.prototype.startCPU = function(fSetFocus) this.setSpeed(); if (this.cmp) this.cmp.start(this.aCounts.msStartRun, this.getCycles()); this.aFlags.fRunning = true; + this.aFlags.fStarting = true; if (this.chipset) this.chipset.setSpeaker(); var controlRun = this.bindings["run"]; if (controlRun) controlRun.textContent = "Halt"; diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index 7f897d9a7..37b74e3a3 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -81,7 +81,7 @@ function Debugger(parmsDbg) this.cInstructions = -1; /* - * The default numder of hex characters in a physical address; updated by initBus(). + * Default number of hex chars in a physical address (ie, for real-mode); updated by initBus(). */ this.cchAddr = 5; @@ -214,9 +214,11 @@ Debugger.MESSAGE = { STATE: 0x00800000, MOUSE: 0x01000000, COMPUTER: 0x02000000, - LOG: 0x04000000, - DOS: 0x08000000, - HALT: 0x80000000 + DOS: 0x04000000, + OTHER: 0x08000000, + LOG: 0x10000000, + WARN: 0x20000000, + HALT: 0x40000000 }; if (DEBUGGER) { @@ -531,8 +533,10 @@ if (DEBUGGER) { "state": Debugger.MESSAGE.STATE, "mouse": Debugger.MESSAGE.MOUSE, "computer": Debugger.MESSAGE.COMPUTER, - "log": Debugger.MESSAGE.LOG, "dos": Debugger.MESSAGE.DOS, + "other": Debugger.MESSAGE.OTHER, + "log": Debugger.MESSAGE.LOG, + "warn": Debugger.MESSAGE.WARN, /* * Now we turn to message actions rather than message types; for example, setting "halt" * on or off doesn't enable "halt" messages, but rather halts the CPU on any message above. @@ -1265,6 +1269,9 @@ if (DEBUGGER) { var a = dbg.parseCommand(sInput, true); for (var s in a) dbg.doCommand(a[s]); } + else if (event.keyCode == Keyboard.KEYCODE.ESC) { + control.value = sInput = ""; + } else { if (event.keyCode == Keyboard.KEYCODE.UP) { if (dbg.iPrevCmd < dbg.aPrevCmds.length - 1) { @@ -1297,7 +1304,8 @@ if (DEBUGGER) { function onClickDebugEnter(fRepeat) { if (dbg.controlDebug) { var sInput = dbg.controlDebug.value; - var a = dbg.parseCommand(sInput, true, true); + dbg.controlDebug.value = ""; + var a = dbg.parseCommand(sInput, true); for (var s in a) dbg.doCommand(a[s]); return true; } @@ -1537,7 +1545,7 @@ if (DEBUGGER) { Debugger.prototype.initMessages = function(sEnable) { this.afnDumpers = []; - this.bitsMessageEnabled = 0; + this.bitsMessageEnabled = Debugger.MESSAGE.WARN; this.sMessagePrev = null; var aEnable = this.parseCommand(sEnable); if (aEnable.length) { @@ -1575,13 +1583,16 @@ if (DEBUGGER) { * NOTE: If the caller specifies multiple MESSAGE category flags, then ALL the corresponding flags * in the Debugger's bitsMessageEnabled variable must be enabled as well, else the result will be false. * + * One wrinkle is MESSAGE.WARN: if that category is enabled, then ANY value with that bit will return true. + * * @this {Debugger} * @param {number} bitsMessage is one or more Debugger MESSAGE_* category flag(s) * @return {boolean} true if message category is enabled, false if not */ Debugger.prototype.messageEnabled = function(bitsMessage) { - return ((this.bitsMessageEnabled & bitsMessage) === bitsMessage); + var bitsEnabled = this.bitsMessageEnabled & bitsMessage; + return (bitsEnabled === bitsMessage || !!(bitsEnabled & Debugger.MESSAGE.WARN)); }; /** @@ -1620,8 +1631,8 @@ if (DEBUGGER) { * * @this {Debugger} * @param {number} nInt - * @param {number} addr - * @return {boolean} true if message generated, false if not + * @param {number} addr (EIP after the "INT n" instruction has been fetched but not dispatched) + * @return {boolean} true if message generated (which in turn triggers addIntReturn() inside checkIntNotify()), false if not */ Debugger.prototype.messageInt = function(nInt, addr) { @@ -1642,6 +1653,12 @@ if (DEBUGGER) { this.updateRegValues(); sFunc = " " + str.replaceArray(this.aRegValues, sFunc); } + /* + * For purposes of display only, rewind addr to the address of the responsible "INT n" instruction; we + * know it's the two-byte "INT n" instruction because that's the only opcode handler that calls checkIntNotify() + * at the moment. If that changes, then this will have to change as well. + */ + addr -= 2; this.message("INT 0x" + str.toHexByte(nInt) + ": AH=" + str.toHexByte(AH) + " at " + str.toHexAddr(addr - this.cpu.segCS.base, this.cpu.segCS.sel) + sFunc); } return fMessage; @@ -3977,8 +3994,8 @@ if (DEBUGGER) { if (aAddr[0] == null && aAddr[2] == null) return; var addr = this.getAddr(aAddr); - - this.println(sSymbol + ": " + this.hexAddr(aAddr) + " (%" + str.toHex(addr, this.cchAddr) + ")"); + sSymbol = sSymbol? (sSymbol + ": ") : ""; + this.println(sSymbol + this.hexAddr(aAddr) + " (%" + str.toHex(addr, this.cchAddr) + ")"); var aSymbol = this.findSymbolAtAddr(aAddr, true); if (aSymbol.length) { @@ -4121,7 +4138,7 @@ if (DEBUGGER) { if (sCategory !== undefined) { var bitsMessage = 0; if (sCategory == "all") { - bitsMessage = 0xffffffff; + bitsMessage = 0xffffffff & ~Debugger.MESSAGE.HALT; sCategory = null; } else if (sCategory == "on") { fCriteria = true; @@ -4674,15 +4691,14 @@ if (DEBUGGER) { }; /** - * parseCommand(sCmd, fSave, fRepeat) + * parseCommand(sCmd, fSave) * * @this {Debugger} * @param {string|undefined} sCmd * @param {boolean} [fSave] is true to save the command, false if not - * @param {boolean} [fRepeat] is true if the command may be repeated (and therefore iPrevCmd should not be decremented) * @return {Array.} */ - Debugger.prototype.parseCommand = function(sCmd, fSave, fRepeat) + Debugger.prototype.parseCommand = function(sCmd, fSave) { if (fSave) { if (!sCmd) { @@ -4692,7 +4708,7 @@ if (DEBUGGER) { this.aPrevCmds.splice(0, 0, sCmd); this.iPrevCmd = 0; } - if (!fRepeat) this.iPrevCmd--; + this.iPrevCmd--; } } var a = (sCmd? sCmd.split(sCmd.indexOf('|') >= 0? '|' : ';') : ['']); @@ -4743,24 +4759,18 @@ if (DEBUGGER) { * For all other commands, if they lack a space between the command and argument portions, * insert a space before the first non-alpha character, so that split() will have the desired effect. */ - - /* - * These commands work great, except that they won't compile, and in fact, I don't WANT them in the - * compiled version, but putting them inside (!COMPILED) doesn't help, so I must disable them for now. - * if (!COMPILED) { if (sCmd == "debug") { - DEBUG = true; + window.DEBUG = true; this.println("DEBUG checks on"); return true; } else if (sCmd == "nodebug") { - DEBUG = false; + window.DEBUG = false; this.println("DEBUG checks off"); return true; } } - */ var ch, ch0, i; switch (sCmd) { diff --git a/modules/pcjs/lib/hdc.js b/modules/pcjs/lib/hdc.js index 50f3e4381..428392268 100644 --- a/modules/pcjs/lib/hdc.js +++ b/modules/pcjs/lib/hdc.js @@ -1420,7 +1420,7 @@ HDC.prototype.inATCData = function(port, addrFrom) /* * I shouldn't have to set BUSY (or DATA_REQ) again, because it should still be set, no? */ - if (DEBUG) this.assert(!!(hdc.regStatus & HDC.ATC.STATUS.BUSY)); + if (DEBUG) hdc.assert(!!(hdc.regStatus & HDC.ATC.STATUS.BUSY)); } else { /* * TODO: It would be nice to be a bit more specific about the error (if any) that just occurred. diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index 778571ad8..a5006d9f1 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -2423,21 +2423,20 @@ X86CPU.prototype.stepCPU = function(nMinCycles) this.aFlags.fComplete = true; /* - * fDebugCheck is true if we need to "check" every instruction with the Debugger. The Debugger will - * call cpu.stepCPU(n) with n == 0 if it's executing only ONE instruction (ie, the user just clicked the - * "Step" button, or they've issued a "t" or "t1" command). Otherwise, it will call with n == 1 - * (ie, the user is holding the "Step" button, or they've issued a "t#" command where # > 1). - * - * In the first case, we want to ignore (ie, "step over") any breakpoints; otherwise, the Debugger has - * no easy way of moving past a breakpoint (other than clearing it, of course). In the second case, - * we want to honor any breakpoints, which in turn will set fComplete to false and signal the Debugger - * to stop. - * - * Note that as a practical matter, both 0 and 1 are otherwise treated the same when it comes to the - * minimum number of cycles to process: one and only one instruction will execute, since every (valid) - * instruction consumes at least 1 cycle. + * fDebugCheck is true if we need to "check" every instruction with the Debugger. */ - var fDebugCheck = this.aFlags.fDebugCheck = (DEBUGGER && nMinCycles && this.dbg && this.dbg.checksEnabled()); + var fDebugCheck = this.aFlags.fDebugCheck = (DEBUGGER && this.dbg && this.dbg.checksEnabled()); + + /* + * fDebugSkip is checked only when fDebugCheck is true, and its sole purpose is to tell the first call + * to checkInstruction() that it can skip breakpoint checks, and that will be true ONLY when fStarting is + * true OR nMinCycles is zero (the latter means the Debugger is single-stepping). + * + * Once we snap fStarting, we clear it, because technically, we've moved beyond "starting" and have officially + * "started" now. + */ + var fDebugSkip = this.aFlags.fStarting || !nMinCycles; + this.aFlags.fStarting = false; /* * We move the minimum cycle count to nStepCycles (the number of cycles left to step), so that other @@ -2519,9 +2518,12 @@ X86CPU.prototype.stepCPU = function(nMinCycles) } } - if (DEBUGGER && fDebugCheck && this.dbg.checkInstruction(this.regEIP)) { - this.stopCPU(); - break; + if (DEBUGGER && fDebugCheck) { + if (this.dbg.checkInstruction(this.regEIP, fDebugSkip)) { + this.stopCPU(); + break; + } + fDebugSkip = false; } this.opFlags = 0; diff --git a/modules/shared/lib/component.js b/modules/shared/lib/component.js index 25884cc6b..97742e5d3 100644 --- a/modules/shared/lib/component.js +++ b/modules/shared/lib/component.js @@ -728,11 +728,6 @@ Component.prototype = { } catch(e) {} return; } - /* - * If there's no Debugger, or the current component didn't bother saving a reference to the Debugger - * (eg, in its initBus() handler), then this component-level assert() the same as the class-level assert(), - * except for the (slightly) more detailed log() message. - */ this.log(s); throw new Error(s); }