Finally, some progress with the IBM VGA ROM

This commit is contained in:
Jeff Parsons 2015-06-02 14:09:59 -07:00
commit f7e93fbeb3
6 changed files with 574 additions and 88 deletions

227
blog/2015/06/01/README.md Normal file
View file

@ -0,0 +1,227 @@
Debugging the IBM VGA ROM
---
The IBM VGA ("Video Graphics Array") standard was introduced as part of the IBM PS/2 line of computers;
it was not a feature you could purchase or install in older PC, XT or AT-compatible machines. In fact, full VGA
support was not even available in all PS/2 models. Of the first four PS/2 models -- the 8086-based Model 30, the
80286-based Model 50 and Model 60, and the 80386-based Model 80 -- VGA support was available only in the three
higher-end models. The Model 30 came with MCGA ("Multicolor Graphics Array") video hardware that supported a subset
of VGA modes (eg, 640x480 2-color and 320x200 256-color graphics).
It wasn't until October 1987 that IBM finally introduced an 8-bit ISA card that brought VGA capability to older PCs.
The card was called the **IBM PS/2 Display Adapter**. However, I think the name is a bit confusing, since the card
could only be used in PC, XT, and AT-compatible systems. I'll refer to it here simply as the IBM VGA.
The VGA ROM used here is assumed to have come from an original IBM VGA. It's unknown if IBM ever made any
revisions to the VGA ROM. With the introduction of the PS/2 family and the VGA, IBM decided to no longer publish
the source code for its ROMs, so I've created some assemblable source code from the IBM VGA ROM
[here](/devices/pc/video/ibm/vga/ibm-vga.nasm).
I've finally started debugging a machine configuration that uses the IBM VGA ROM. Since the VGA and the 80386 are
contemporaries, I'm using an [80386 machine configuration](/devices/pc/machine/compaq/deskpro386/vga/2048kb/machine.xml).
However, I don't expect the IBM VGA ROM to require any 80386 support or PS/2-specific features.
The first problem I ran into was here:
;
; Initialize the ROM BIOS Video Mode Options byte @40:0087 (default to color and 256Kb of RAM)
;
mov byte [0x487],0x60 ; 0000008D
;
; The x100 subroutine alternately enables port 0x3B? and 0x3D? decoding, verifying that there is
; no response on opposing ports 0x3D? and 0x3B?, respectively; otherwise, it assumes that another
; video card must exist and attempts to select co-existing settings for the VGA. For example, if
; there is an unexpected response on the color ports, the VGA ROM will default to mono operation.
;
call x100 ; 00000092
The Video component installs I/O port handlers for all possible I/O ranges; when a range isn't being used, the
associated I/O operations are redirected to a dummy Card, so that the active Card isn't affected. Here,
however, that was insufficient. If the VGA is the only installed video card, the VGA ROM expects *NO RESPONSE*
on inactive CRTC ports. So I've changed the CRTC I/O handlers to check the Card's fActive flag. This seems
like a safe and logical change, but we'll still have to check for backward-compatibility issues with older ROMs.
The VGA ROM programs the card for the first time here:
EAX=00000000 EBX=00001642 ECX=00000004 EDX=00004AE8
ESP=000000EA EBP=FFFF00F0 ESI=00006000 EDI=000003D4
SS=0030 DS=0000 ES=C000 FS=0000 GS=0304 PS=00000246 V0 D0 I1 T0 S0 Z1 A0 P1 C0
C000:00CB E85910 CALL 1127 ;cycles=5
videoVGA.outPort(0x03C4,SEQ.INDX,0x00) @C000:112F
videoVGA.outPort(0x03C5,SEQ.RESET,0x01) @C000:112F
videoVGA.outPort(0x03C4,SEQ.INDX,0x01) @C000:1204
videoVGA.outPort(0x03C5,SEQ.CLK,0x29) @C000:1204
videoVGA.outPort(0x03C4,SEQ.INDX,0x02) @C000:1204
videoVGA.outPort(0x03C5,SEQ.MAPMASK,0x0F) @C000:1204
videoVGA.outPort(0x03C4,SEQ.INDX,0x03) @C000:1204
videoVGA.outPort(0x03C5,SEQ.CHARMAP,0x00) @C000:1204
videoVGA.outPort(0x03C4,SEQ.INDX,0x04) @C000:1204
videoVGA.outPort(0x03C5,SEQ.MODE,0x06) @C000:1204
videoVGA.outPort(0x03C2,MISC,0x63) @C000:1143
videoVGA.outPort(0x03C4,SEQ.INDX,0x00) @C000:1149
videoVGA.outPort(0x03C5,SEQ.RESET,0x03) @C000:1149
videoVGA.outPort(0x03D4,CRTC.INDX,0x11) @C000:1151
videoVGA.outPort(0x03D5,CRTC.VERT_RETRACE_END,0x00) @C000:1151
videoVGA.outPort(0x03D4,CRTC.INDX,0x00) @C000:1204
videoVGA.outPort(0x03D5,CRTC.HORZ_TOTAL,0x5F) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x01) @C000:1204
videoVGA.outPort(0x03D5,CRTC.HORZ_DISP_END,0x4F) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x02) @C000:1204
videoVGA.outPort(0x03D5,CRTC.HORZ_BLANK_START,0x50) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x03) @C000:1204
videoVGA.outPort(0x03D5,CRTC.HORZ_BLANK_END,0x82) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x04) @C000:1204
videoVGA.outPort(0x03D5,CRTC.HORZ_RETRACE_START,0x55) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x05) @C000:1204
videoVGA.outPort(0x03D5,CRTC.HORZ_RETRACE_END,0x81) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x06) @C000:1204
videoVGA.outPort(0x03D5,CRTC.VERT_TOTAL,0xBF) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x07) @C000:1204
videoVGA.outPort(0x03D5,CRTC.CRTC_OVERFLOW,0x1F) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x08) @C000:1204
videoVGA.outPort(0x03D5,CRTC.PRESET_ROW_SCAN,0x00) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x09) @C000:1204
videoVGA.outPort(0x03D5,CRTC.MAX_SCAN_LINE,0x40) @C000:1204
removeCursor(): removed from 0,0
videoVGA.outPort(0x03D4,CRTC.INDX,0x0A) @C000:1204
videoVGA.outPort(0x03D5,CRTC.CURSOR_START,0x00) @C000:1204
checkCursor(): cursor moved from -1 to 0
videoVGA.outPort(0x03D4,CRTC.INDX,0x0B) @C000:1204
videoVGA.outPort(0x03D5,CRTC.CURSOR_END,0x00) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x0C) @C000:1204
videoVGA.outPort(0x03D5,CRTC.START_ADDR_HI,0x00) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x0D) @C000:1204
videoVGA.outPort(0x03D5,CRTC.START_ADDR_LO,0x00) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x0E) @C000:1204
videoVGA.outPort(0x03D5,CRTC.CURSOR_ADDR_HI,0x00) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x0F) @C000:1204
videoVGA.outPort(0x03D5,CRTC.CURSOR_ADDR_LO,0x00) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x10) @C000:1204
videoVGA.outPort(0x03D5,CRTC.VERT_RETRACE_START,0x9C) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x11) @C000:1204
videoVGA.outPort(0x03D5,CRTC.VERT_RETRACE_END,0x8E) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x12) @C000:1204
videoVGA.outPort(0x03D5,CRTC.VERT_DISP_END,0x8F) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x13) @C000:1204
videoVGA.outPort(0x03D5,CRTC.OFFSET,0x28) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x14) @C000:1204
videoVGA.outPort(0x03D5,CRTC.UNDERLINE,0x1F) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x15) @C000:1204
videoVGA.outPort(0x03D5,CRTC.VERT_BLANK_START,0x96) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x16) @C000:1204
videoVGA.outPort(0x03D5,CRTC.VERT_BLANK_END,0xB9) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x17) @C000:1204
videoVGA.outPort(0x03D5,CRTC.MODE_CTRL,0xE3) @C000:1204
videoVGA.outPort(0x03D4,CRTC.INDX,0x18) @C000:1204
videoVGA.outPort(0x03D5,CRTC.LINE_COMPARE,0xFF) @C000:1204
videoVGA.inPort(0x03DA,STATUS1): 0x00 @C000:1167
videoVGA.outPort(0x03C0,ATC.INDX,0x10) @C000:116C
videoVGA.outPort(0x03C0,ATC.MODE,0x01) @C000:1171
videoVGA.outPort(0x03C0,ATC.INDX,0x12) @C000:1174
videoVGA.outPort(0x03C0,ATC.PLANES,0x0F) @C000:1179
videoVGA.outPort(0x03C0,ATC.INDX,0x13) @C000:117C
videoVGA.outPort(0x03C0,ATC.HORZPAN,0x00) @C000:1181
videoVGA.outPort(0x03CE,GRC.INDX,0x00) @C000:1204
videoVGA.outPort(0x03CF,GRC.SRESET,0x00) @C000:1204
videoVGA.outPort(0x03CE,GRC.INDX,0x01) @C000:1204
videoVGA.outPort(0x03CF,GRC.ESRESET,0x00) @C000:1204
videoVGA.outPort(0x03CE,GRC.INDX,0x02) @C000:1204
videoVGA.outPort(0x03CF,GRC.COLRCMP,0x0F) @C000:1204
videoVGA.outPort(0x03CE,GRC.INDX,0x03) @C000:1204
videoVGA.outPort(0x03CF,GRC.DATAROT,0x00) @C000:1204
videoVGA.outPort(0x03CE,GRC.INDX,0x04) @C000:1204
videoVGA.outPort(0x03CF,GRC.READMAP,0x00) @C000:1204
videoVGA.outPort(0x03CE,GRC.INDX,0x05) @C000:1204
videoVGA.outPort(0x03CF,GRC.MODE,0x08) @C000:1204
setAccess(0x0210)
videoVGA.outPort(0x03CE,GRC.INDX,0x06) @C000:1204
videoVGA.outPort(0x03CF,GRC.MISC,0x05) @C000:1204
setMode(0x0010)
setMode(16): removing 0x00008000 bytes from 0x000B8000
setMode(16): adding 0x00010000 bytes to 0x000A0000
videoVGA.outPort(0x03CE,GRC.INDX,0x07) @C000:1204
videoVGA.outPort(0x03CF,GRC.COLRDC,0x0F) @C000:1204
videoVGA.outPort(0x03CE,GRC.INDX,0x08) @C000:1204
videoVGA.outPort(0x03CF,GRC.BITMASK,0xFF) @C000:1204
EAX=0000FF09 EBX=00001642 ECX=00000000 EDX=000003DA
ESP=000000EA EBP=FFFF00F0 ESI=00006000 EDI=000003D4
SS=0030 DS=0000 ES=C000 FS=0000 GS=0304 PS=00000246 V0 D0 I1 T0 S0 Z1 A0 P1 C0
C000:00CE 32C0 XOR AL,AL ;cycles=1031
Now that the card was programmed for the first time, I used the `d video` Debugger command to dump the video
card's state:
BIOSMODE: 0x10
CRTC[0x00]: HORZ_TOTAL 0x5F
CRTC[0x01]: HORZ_DISP_END 0x4F
CRTC[0x02]: HORZ_BLANK_START 0x50
CRTC[0x03]: HORZ_BLANK_END 0x82
CRTC[0x04]: HORZ_RETRACE_START 0x55
CRTC[0x05]: HORZ_RETRACE_END 0x81
CRTC[0x06]: VERT_TOTAL 0xBF
CRTC[0x07]: CRTC_OVERFLOW 0x1F
CRTC[0x08]: PRESET_ROW_SCAN 0x00
CRTC[0x09]: MAX_SCAN_LINE 0x40
CRTC[0x0A]: CURSOR_START 0x00
CRTC[0x0B]: CURSOR_END 0x00
CRTC[0x0C]: START_ADDR_HI 0x00
CRTC[0x0D]: START_ADDR_LO 0x00
CRTC[0x0E]: CURSOR_ADDR_HI 0x00
CRTC[0x0F]: CURSOR_ADDR_LO 0x00
CRTC[0x10]: VERT_RETRACE_START 0x9C
CRTC[0x11]: VERT_RETRACE_END 0x8E
CRTC[0x12]: VERT_DISP_END 0x8F
CRTC[0x13]: OFFSET 0x28
CRTC[0x14]: UNDERLINE 0x1F
CRTC[0x15]: VERT_BLANK_START 0x96
CRTC[0x16]: VERT_BLANK_END 0xB9
CRTC[0x17]: MODE_CTRL 0xE3
CRTC[0x18]: LINE_COMPARE 0xFF*
STATUS1: 0x00
ATCDATA: false
ATC[0x00]: PAL00 0x??
ATC[0x01]: PAL01 0x??
ATC[0x02]: PAL02 0x??
ATC[0x03]: PAL03 0x??
ATC[0x04]: PAL04 0x??
ATC[0x05]: PAL05 0x??
ATC[0x06]: PAL06 0x??
ATC[0x07]: PAL07 0x??
ATC[0x08]: PAL08 0x??
ATC[0x09]: PAL09 0x??
ATC[0x0A]: PAL0A 0x??
ATC[0x0B]: PAL0B 0x??
ATC[0x0C]: PAL0C 0x??
ATC[0x0D]: PAL0D 0x??
ATC[0x0E]: PAL0E 0x??
ATC[0x0F]: PAL0F 0x??
ATC[0x10]: MODE 0x01
ATC[0x11]: OVRSCAN 0x??
ATC[0x12]: PLANES 0x0F
ATC[0x13]: HORZPAN 0x00*
GRC[0x00]: SRESET 0x00
GRC[0x01]: ESRESET 0x00
GRC[0x02]: COLRCMP 0x0F
GRC[0x03]: DATAROT 0x00
GRC[0x04]: READMAP 0x00
GRC[0x05]: MODE 0x08
GRC[0x06]: MISC 0x05
GRC[0x07]: COLRDC 0x0F
GRC[0x08]: BITMASK 0xFF*
SEQ[0x00]: RESET 0x03*
SEQ[0x01]: CLK 0x29
SEQ[0x02]: MAPMASK 0x0F
SEQ[0x03]: CHARMAP 0x00
SEQ[0x04]: MODE 0x06
FEAT: 0x00
MISC: 0x63
STATUS0: 0x00
LATCHES: 0x00
ACCESS: 0x0210
Use 'dump video buffer' to dump video memory
If you've noticed that none of the ATC palette registers were programmed, well, that's apparently by design.
*[@jeffpar](http://twitter.com/jeffpar)*
*June 1, 2015*

View file

@ -4805,6 +4805,11 @@ xa9e9: cs rep movsw ; 0000A9E9 F32EA5 '...'
mov al,0x3b ; 0000A9F9 B03B '.;'
out 0x84,al ; 0000A9FB E684 '..'
call xc259 ; 0000A9FD E85918 '.Y.'
;
; Set bit 2 (reserved?) and bit 4 (memory size?) of ROM BIOS Video Mode Options byte @40:0087.
; It would appear that Compaq had a different conception of this byte; however, in the case of an
; adapter like the IBM VGA, it doesn't matter, since the IBM VGA ROM will rewrite this byte anyway.
;
or byte [0x87],0x14 ; 0000AA00 800E870014 '.....'
mov al,0x8e ; 0000AA05 B08E '..'
call xb544 ; 0000AA07 E83A0B '.:.'

View file

@ -24,8 +24,31 @@
mov [0x10e],cs ; 00000083 8C0E0E01 '....'
sti ; 00000087 FB '.'
mov byte [0x489],0x11 ; 00000088 C606890411 '.....'
mov byte [0x487],0x60 ; 0000008D C606870460 '....`'
call x100 ; 00000092 E86B00 '.k.'
;
; Initialize the ROM BIOS Video Mode Options byte @40:0087 (default to color and 256Kb of RAM)
;
mov byte [0x487],0x60 ; 0000008D
;
; The x100 subroutine alternately enables port 0x3B? and 0x3D? decoding, verifying that there is
; no response on opposing ports 0x3D? and 0x3B?, respectively; otherwise, it assumes that another
; video card must exist and attempts to select co-existing settings for the VGA. For example, if
; there is an unexpected response on the color ports, the VGA ROM will default to mono operation.
;
call x100 ; 00000092
;
; From the "IBM Personal System/2 Model 50 and 60 Technical Reference: I/O Controllers, Video Subsystem", p.4-29:
;
; When in setup mode (I/O address hex 0094, bit 5 equals 0), the VGA responds to a single option select byte
; at I/O address hex 0102 and treats the LSB (bit 0) of that byte as the VGA sleep bit. When the LSB equals 0,
; the VGA does not respond to commands, addresses, or data, on the data bus. When the LSB equals 1, the VGA
; responds. If the VGA was set up and is generating video output when the LSB is set to 0, the output is still
; generated.
;
; The VGA responds only to address hex 0102 when in the setup mode. No other addresses are valid at that time.
; Conversely, the VGA ignores address hex 0102 when in the enabled mode (I/O address hex 0094, bit 5 equals 1),
; and decodes normal I/O and memory addresses.
;
cli ; 00000095 FA '.'
mov dx,0x46e8 ; 00000096 BAE846 '..F'
mov ax,0x16 ; 00000099 B81600 '...'
@ -42,14 +65,35 @@
sti ; 000000B1 FB '.'
push cs ; 000000B2 0E '.'
pop es ; 000000B3 07 '.'
;
; Default to mono settings
;
mov di,0x3b4 ; 000000B4 BFB403 '...'
mov bx,0x1602 ; 000000B7 BB0216 '...'
;
; Test bit 1 of ROM BIOS Video Mode Options byte @40:0087 (set if mono, clear if color)
;
test byte [0x487],0x2 ; 000000BA F606870402 '.....'
jnz xc7 ; 000000BF 7506 'u.'
;
; Choose color settings
;
mov di,0x3d4 ; 000000C1 BFD403 '...'
mov bx,0x1642 ; 000000C4 BB4216 '.B.'
;
; Set the CRTC base port address (0x3B4 for mono, 0x3D4 for color)
;
xc7: mov [0x463],di ; 000000C7 893E6304 '.>c.'
;
; Program the card for a default mode
;
call x1127 ; 000000CB E85910 '.Y.'
xor al,al ; 000000CE 32C0 '2.'
out dx,al ; 000000D0 EE '.'
call x547 ; 000000D1 E87304 '.s.'
@ -2105,7 +2149,19 @@ x1122: mov dx,0x3c8 ; 00001122 BAC803 '...'
out dx,al ; 00001125 EE '.'
x1126: ret ; 00001126 C3 '.'
x1127: mov dx,0x3c4 ; 00001127 BAC403 '...'
;
; Programs the card using data from [BX]; CRTC port address is in DI (0x3B4 or 0x3D4).
;
; Writes 0x01 to SEQ register 0x00 (SEQ.RESET).
; Writes values starting at [BX+0x05] to SEQ registers 0x01-0x04.
; Writes [BX+0x09] to Miscellaneous Output register (MISC).
; Writes 0x03 to SEQ register 0x00 (SEQ.RESET).
; Writes 0x00 to CRTC register 0x11 (CRTC.VERT_RETRACE_END).
; Writes values starting at [BX+0x0A] to CRTC registers 0x00-0x18.
; Writes values starting at [BX+0x33] to ATC registers 0x10, 0x12 and 0x13
; Writes values starting at [BX+0x37] to GRC registers 0x00-0x08.
;
x1127: mov dx,0x3c4 ; (DX) == SEQ port address (0x3C4)
mov ax,0x100 ; 0000112A B80001 '...'
pushf ; 0000112D 9C '.'
cli ; 0000112E FA '.'
@ -2116,14 +2172,14 @@ x1127: mov dx,0x3c4 ; 00001127 BAC403 '...'
mov al,0x1 ; 00001137 B001 '..'
call x1201 ; 00001139 E8C500 '...'
pop bx ; 0000113C 5B '['
mov dl,0xc2 ; 0000113D B2C2 '..'
mov dl,0xc2 ; (DX) == MISC port address (0x3C2)
mov al,[es:bx+0x9] ; 0000113F 268A4709 '&.G.'
out dx,al ; 00001143 EE '.'
mov dl,0xc4 ; 00001144 B2C4 '..'
mov dl,0xc4 ; (DX) == SEQ port address (0x3C4)
mov ax,0x300 ; 00001146 B80003 '...'
out dx,ax ; 00001149 EF '.'
popf ; 0000114A 9D '.'
mov dx,di ; 0000114B 8BD7 '..'
mov dx,di ; (DX) == CRTC port address (0x3B4 or 0x3D4)
mov al,0x11 ; 0000114D B011 '..'
xor ah,ah ; 0000114F 32E4 '2.'
out dx,ax ; 00001151 EF '.'
@ -2134,12 +2190,12 @@ x1127: mov dx,0x3c4 ; 00001127 BAC403 '...'
call x1201 ; 0000115B E8A300 '...'
pop bx ; 0000115E 5B '['
mov dx,di ; 0000115F 8BD7 '..'
add dl,0x6 ; 00001161 80C206 '...'
add dl,0x6 ; (DX) == STATUS1 port address (0x3BA or 0x3DA)
push dx ; 00001164 52 'R'
pushf ; 00001165 9C '.'
cli ; 00001166 FA '.'
in al,dx ; 00001167 EC '.'
mov dl,0xc0 ; 00001168 B2C0 '..'
mov dl,0xc0 ; (DX) == ATC port address (0x3C0)
mov al,0x10 ; 0000116A B010 '..'
out dx,al ; 0000116C EE '.'
mov al,[es:bx+0x33] ; 0000116D 268A4733 '&.G3'
@ -2154,7 +2210,7 @@ x1127: mov dx,0x3c4 ; 00001127 BAC403 '...'
out dx,al ; 00001181 EE '.'
popf ; 00001182 9D '.'
push bx ; 00001183 53 'S'
mov dl,0xce ; 00001184 B2CE '..'
mov dl,0xce ; (DX) == GRC port address (0x3CE)
add bx,byte +0x37 ; 00001186 83C337 '..7'
mov cx,0x9 ; 00001189 B90900 '...'
xor al,al ; 0000118C 32C0 '2.'

View file

@ -1375,7 +1375,8 @@ Bus.prototype.checkPortInputNotify = function(port, addrLIP)
}
if (aNotify !== undefined) {
if (aNotify[1]) {
bIn = aNotify[1].call(aNotify[0], port, addrLIP);
var b = aNotify[1].call(aNotify[0], port, addrLIP);
if (b !== undefined) bIn = b;
}
if (DEBUGGER && this.dbg && this.fPortInputBreakAll != aNotify[2]) {
this.dbg.checkPortInput(port, bIn);

View file

@ -462,6 +462,42 @@ Video.CARD = {
}
};
/*
* Supported Modes
*
* Although this component is designed to be a video hardware emulation, not a BIOS simulation, we DO
* look for changes to the hardware state that correspond to standard BIOS mode settings, so our internal
* mode setting will normally match the current BIOS mode setting; however, this a debugging convenience,
* not an attempt to monitor or emulate the BIOS.
*
* We do have some BIOS awareness (eg, when loading ROM-based fonts, and some special code to ensure all
* the BIOS diagnostics pass), but for the most part, we treat the BIOS like any other application code.
*
* As we expand support to include more programmable cards like the EGA, it becomes quite easy for the card
* to enter a "mode" that has no BIOS counterpart (eg, non-standard combinations of frame buffer address,
* memory access modes, fonts, display regions, etc). Our hardware emulation routines will cope with those
* situations as best they can (and when they don't, it should be considered a bug if some application is
* broken as a result), but realistically, our hardware emulation is never likely to be 100% accurate.
*/
Video.MODE = {
CGA_40X25_BW: 0,
CGA_40X25: 1,
CGA_80X25_BW: 2,
CGA_80X25: 3,
CGA_320X200: 4,
CGA_320X200_BW: 5,
CGA_640X200: 6,
MDA_80X25: 7,
EGA_320X200: 0x0D, // mapped at A000:0000
EGA_640X200: 0x0E, // mapped at A000:0000
EGA_640X350_MONO: 0x0F, // mapped at A000:0000, monochrome
EGA_640X350: 0x10, // mapped at A000:0000, color
VGA_640X480_MONO: 0x11, // mapped at A000:0000, monochrome
VGA_640X480: 0x12, // mapped at A000:0000, color
VGA_320X200: 0x13, // mapped at A000:0000, color
UNKNOWN: 0xFF
};
/*
* Supported Monitors
*
@ -604,42 +640,6 @@ Video.aEGAMonitorSwitches = {
0x05: [ChipSet.MONITOR.MONO, ChipSet.MONITOR.COLOR, false] // "0101"
};
/*
* Supported Modes
*
* Although this component is designed to be a video hardware emulation, not a BIOS simulation, we DO
* look for changes to the hardware state that correspond to standard BIOS mode settings, so our internal
* mode setting will normally match the current BIOS mode setting; however, this a debugging convenience,
* not an attempt to monitor or emulate the BIOS.
*
* We do have some BIOS awareness (eg, when loading ROM-based fonts, and some special code to ensure all
* the BIOS diagnostics pass), but for the most part, we treat the BIOS like any other application code.
*
* As we expand support to include more programmable cards like the EGA, it becomes quite easy for the card
* to enter a "mode" that has no BIOS counterpart (eg, non-standard combinations of frame buffer address,
* memory access modes, fonts, display regions, etc). Our hardware emulation routines will cope with those
* situations as best they can (and when they don't, it should be considered a bug if some application is
* broken as a result), but realistically, our hardware emulation is never likely to be 100% accurate.
*/
Video.MODE = {
CGA_40X25_BW: 0,
CGA_40X25: 1,
CGA_80X25_BW: 2,
CGA_80X25: 3,
CGA_320X200: 4,
CGA_320X200_BW: 5,
CGA_640X200: 6,
MDA_80X25: 7,
EGA_320X200: 0x0D, // mapped at A000:0000
EGA_640X200: 0x0E, // mapped at A000:0000
EGA_640X350_MONO: 0x0F, // mapped at A000:0000, monochrome
EGA_640X350: 0x10, // mapped at A000:0000, color
VGA_640X480_MONO: 0x11, // mapped at A000:0000, monochrome
VGA_640X480: 0x12, // mapped at A000:0000, color
VGA_320X200: 0x13, // mapped at A000:0000, color
UNKNOWN: 0xFF
};
/**
* @class Font
* @property {number} cxCell
@ -1121,7 +1121,7 @@ Card.CRTC = {
HR: 0x80 // Hardware Reset
},
LINE_COMPARE: 0x18,
TOTAL_REGS: 0x19 // total CRT registers on EGA
TOTAL_REGS: 0x19 // total CRT registers on EGA/VGA
},
ADDR_HI_MASK: 0x3F
};
@ -1134,7 +1134,7 @@ if (DEBUGGER) {
Card.CRTC.EGA_REGS = ["HORZ_TOTAL","HORZ_DISP_END","HORZ_BLANK_START","HORZ_BLANK_END","HORZ_RETRACE_START","HORZ_RETRACE_END",
"VERT_TOTAL","CRTC_OVERFLOW","PRESET_ROW_SCAN","MAX_SCAN_LINE","CURSOR_START","CURSOR_END",
"START_ADDR_HI","START_ADDR_LO","CURSOR_ADDR_HI","CURSOR_ADDR_LO","LIGHT_PEN_HI","LIGHT_PEN_LO",
"START_ADDR_HI","START_ADDR_LO","CURSOR_ADDR_HI","CURSOR_ADDR_LO","VERT_RETRACE_START","VERT_RETRACE_END",
"VERT_DISP_END","OFFSET","UNDERLINE","VERT_BLANK_START","VERT_BLANK_END","MODE_CTRL","LINE_COMPARE"];
}
@ -1352,7 +1352,8 @@ if (DEBUGGER) Card.SEQ.REGS = ["RESET","CLK","MAPMASK","CHARMAP","MODE"];
*/
Card.DAC = {
MASK: {
PORT: 0x3C6 // initialized to 0xFF and should not be changed
PORT: 0x3C6, // initialized to 0xFF and should not be changed
DEFAULT: 0xFF
},
STATE: {
PORT: 0x3C7,
@ -1365,15 +1366,16 @@ Card.DAC = {
},
DATA: {
PORT: 0x3C9
}
},
TOTAL_REGS: 0x100
};
/*
* EGA/VGA Graphics Controller Registers (regGRCIndx and regGRCData)
*/
Card.GRC = {
POS1_PORT: 0x3CC,
POS2_PORT: 0x3CA,
POS1_PORT: 0x3CC, // EGA only, write-only
POS2_PORT: 0x3CA, // EGA only, write-only
INDX: {
PORT: 0x3CE,
MASK: 0x0F
@ -1903,7 +1905,13 @@ Card.prototype.initEGA = function(data, nMonitorType)
/*21*/ 0xffffffff|0,
/*22*/ 0,
/*23*/ 0,
/*24*/ 0
/*24*/ 0,
/*25*/ Card.VGA_ENABLE.ENABLED,
/*26*/ Card.DAC.MASK.DEFAULT,
/*27*/ 0,
/*28*/ 0,
/*29*/ Card.DAC.STATE.MODE_WRITE,
/*30*/ new Array(Card.DAC.TOTAL_REGS)
];
}
@ -1969,7 +1977,15 @@ Card.prototype.initEGA = function(data, nMonitorType)
this.nSetMapBits = data[22];
this.nColorCompare = data[23];
this.nColorDontCare = data[24];
this.regVGAEnable = data[25];
if (this.nCard == Video.CARD.VGA) {
this.regVGAEnable = data[25];
this.regDACMask = data[26];
this.regDACAddr = data[27];
this.regDACShift = data[28];
this.regDACState = data[29];
this.regDACData = data[30];
}
};
/**
@ -2030,7 +2046,15 @@ Card.prototype.saveEGA = function()
data[22] = this.nSetMapBits;
data[23] = this.nColorCompare;
data[24] = this.nColorDontCare;
data[25] = this.regVGAEnable;
if (this.nCard == Video.CARD.VGA) {
data[25] = this.regVGAEnable;
data[26] = this.regDACMask;
data[27] = this.regDACAddr;
data[28] = this.regDACShift;
data[29] = this.regDACState;
data[30] = this.regDACData;
}
return data;
};
@ -4016,7 +4040,16 @@ Video.prototype.checkMode = function(fForce)
// we've already defaulted to 0x0F or 0x10, so determine if it's 0x0D or 0x0E (ie, a 200-row mode)
// and then which one (ie, 320 wide or 640 wide).
//
if (nCRTCVertTotal < 350) nMode = (fSEQDotClock? Video.MODE.EGA_320X200 : Video.MODE.EGA_640X200);
if (nCRTCVertTotal > 480) {
nMode = Video.MODE.VGA_640X480;
}
else if (nCRTCVertTotal < 350) {
nMode = (fSEQDotClock? Video.MODE.EGA_320X200 : Video.MODE.EGA_640X200);
}
if (DEBUG && this.messageEnabled()) {
this.printMessage("checkMode(): nCRTCVertTotal=" + nCRTCVertTotal + ", mode=" + str.toHexByte(nMode));
this.cpu.stopCPU();
}
}
}
}
@ -4074,7 +4107,7 @@ Video.prototype.setMode = function(nMode, fForce)
if (nMode != null && (nMode != this.nMode || fForce)) {
if (DEBUG && this.messageEnabled()) {
this.printMessage("setMode(" + str.toHexWord(nMode) + (fForce? ",force" : "") + ")");
this.printMessage("setMode(" + str.toHexByte(nMode) + (fForce? ",force" : "") + ")");
}
this.cUpdates = 0; // count updateScreen() calls as a means of driving blink updates
@ -4100,7 +4133,7 @@ Video.prototype.setMode = function(nMode, fForce)
if (this.addrBuffer) {
if (DEBUG && this.messageEnabled()) {
this.printMessage("setMode(" + nMode + "): removing " + str.toHexLong(this.sizeBuffer) + " bytes from " + str.toHexLong(this.addrBuffer));
this.printMessage("setMode(" + str.toHexByte(nMode) + "): removing " + str.toHexLong(this.sizeBuffer) + " bytes from " + str.toHexLong(this.addrBuffer));
}
if (!this.bus.removeMemory(this.addrBuffer, this.sizeBuffer)) {
@ -4119,7 +4152,7 @@ Video.prototype.setMode = function(nMode, fForce)
this.sizeBuffer = card.sizeBuffer;
if (DEBUG && this.messageEnabled()) {
this.printMessage("setMode(" + nMode + "): adding " + str.toHexLong(this.sizeBuffer) + " bytes to " + str.toHexLong(this.addrBuffer));
this.printMessage("setMode(" + str.toHexByte(nMode) + "): adding " + str.toHexLong(this.sizeBuffer) + " bytes to " + str.toHexLong(this.addrBuffer));
}
var controller = (card === this.cardEGA? card : null);
@ -4692,11 +4725,11 @@ Video.prototype.updateScreenGraphicsEGA = function(addrScreen, addrScreenLimit)
* @this {Video}
* @param {number} port (0x3B4)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number}
* @return {number|undefined}
*/
Video.prototype.inMDAIndx = function(port, addrFrom)
{
return this.inCRTCIndx(this.cardMono, addrFrom);
return this.inCRTCIndx(this.cardMono, port, addrFrom);
};
/**
@ -4709,7 +4742,7 @@ Video.prototype.inMDAIndx = function(port, addrFrom)
*/
Video.prototype.outMDAIndx = function(port, bOut, addrFrom)
{
this.outCRTCIndx(this.cardMono, bOut, addrFrom);
this.outCRTCIndx(this.cardMono, port, bOut, addrFrom);
};
/**
@ -4722,7 +4755,7 @@ Video.prototype.outMDAIndx = function(port, bOut, addrFrom)
*/
Video.prototype.inMDAData = function(port, addrFrom)
{
return this.inCRTCData(this.cardMono, addrFrom);
return this.inCRTCData(this.cardMono, port, addrFrom);
};
/**
@ -4735,7 +4768,7 @@ Video.prototype.inMDAData = function(port, addrFrom)
*/
Video.prototype.outMDAData = function(port, bOut, addrFrom)
{
this.outCRTCData(this.cardMono, bOut, addrFrom);
this.outCRTCData(this.cardMono, port, bOut, addrFrom);
};
/**
@ -4982,6 +5015,138 @@ Video.prototype.outSEQData = function(port, bOut, addrFrom)
}
};
/**
* inDACMask(port, addrFrom)
*
* @this {Video}
* @param {number} port (0x3C6)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number}
*/
Video.prototype.inDACMask = function(port, addrFrom)
{
var b = this.cardEGA.regDACMask;
if (this.messageEnabled()) {
this.printMessageIO(Card.DAC.MASK.PORT, null, addrFrom, "DAC.MASK", b);
}
return b;
};
/**
* outDACMask(port, bOut, addrFrom)
*
* @this {Video}
* @param {number} port (0x3C6)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
*/
Video.prototype.outDACMask = function(port, bOut, addrFrom)
{
if (Video.TRAPALL || this.cardEGA.regDACMask !== bOut) {
if (this.messageEnabled()) {
this.printMessageIO(Card.DAC.MASK.PORT, bOut, addrFrom, "DAC.MASK");
}
this.cardEGA.regDACMask = bOut;
}
};
/**
* inDACState(port, addrFrom)
*
* @this {Video}
* @param {number} port (0x3C7)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number}
*/
Video.prototype.inDACState = function(port, addrFrom)
{
var b = this.cardEGA.regDACState;
if (this.messageEnabled()) {
this.printMessageIO(Card.DAC.STATE.PORT, null, addrFrom, "DAC.STATE", b);
}
return b;
};
/**
* outDACRead(port, bOut, addrFrom)
*
* @this {Video}
* @param {number} port (0x3C7)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
*/
Video.prototype.outDACRead = function(port, bOut, addrFrom)
{
if (this.messageEnabled()) {
this.printMessageIO(Card.DAC.ADDR.PORT_READ, bOut, addrFrom, "DAC.READ");
}
this.cardEGA.regDACAddr = bOut;
this.cardEGA.regDACState = Card.DAC.STATE.MODE_READ;
this.cardEGA.regDACShift = 0;
};
/**
* outDACWrite(port, bOut, addrFrom)
*
* @this {Video}
* @param {number} port (0x3C8)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
*/
Video.prototype.outDACWrite = function(port, bOut, addrFrom)
{
if (this.messageEnabled()) {
this.printMessageIO(Card.DAC.ADDR.PORT_WRITE, bOut, addrFrom, "DAC.WRITE");
}
this.cardEGA.regDACAddr = bOut;
this.cardEGA.regDACState = Card.DAC.STATE.MODE_WRITE;
this.cardEGA.regDACShift = 0;
};
/**
* inDACData(port, addrFrom)
*
* @this {Video}
* @param {number} port (0x3C9)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number}
*/
Video.prototype.inDACData = function(port, addrFrom)
{
var b = (this.cardEGA.regDACData[this.cardEGA.regDACAddr] >> this.cardEGA.regDACShift) & 0x3f;
if (this.messageEnabled()) {
this.printMessageIO(Card.DAC.DATA.PORT, null, addrFrom, "DAC.DATA[" + str.toHexByte(this.cardEGA.regDACAddr) + "][" + str.toHexByte(this.cardEGA.regDACShift) + "]", b);
}
this.cardEGA.regDACShift += 6;
if (this.cardEGA.regDACShift > 12) {
this.cardEGA.regDACShift = 0;
this.cardEGA.regDACAddr = (this.cardEGA.regDACAddr + 1) & (Card.DAC.TOTAL_REGS-1);
}
return b;
};
/**
* outDACData(port, bOut, addrFrom)
*
* @this {Video}
* @param {number} port (0x3C9)
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
*/
Video.prototype.outDACData = function(port, bOut, addrFrom)
{
var dw = this.cardEGA.regDACData[this.cardEGA.regDACAddr];
if (this.messageEnabled()) {
this.printMessageIO(Card.DAC.DATA.PORT, bOut, addrFrom, "DAC.DATA[" + str.toHexByte(this.cardEGA.regDACAddr) + "][" + str.toHexByte(this.cardEGA.regDACShift) + "]");
}
this.cardEGA.regDACData[this.cardEGA.regDACAddr] = (dw & ~(0x3f << this.cardEGA.regDACShift)) | ((bOut & 0x3f) << this.cardEGA.regDACShift);
this.cardEGA.regDACShift += 6;
if (this.cardEGA.regDACShift > 12) {
this.cardEGA.regDACShift = 0;
this.cardEGA.regDACAddr = (this.cardEGA.regDACAddr + 1) & (Card.DAC.TOTAL_REGS-1);
}
};
/**
* inVGAFeat(port, addrFrom)
*
@ -5154,11 +5319,11 @@ Video.prototype.outGRCData = function(port, bOut, addrFrom)
* @this {Video}
* @param {number} port (0x3D4)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number}
* @return {number|undefined}
*/
Video.prototype.inCGAIndx = function(port, addrFrom)
{
return this.inCRTCIndx(this.cardColor, addrFrom);
return this.inCRTCIndx(this.cardColor, port, addrFrom);
};
/**
@ -5171,7 +5336,7 @@ Video.prototype.inCGAIndx = function(port, addrFrom)
*/
Video.prototype.outCGAIndx = function(port, bOut, addrFrom)
{
this.outCRTCIndx(this.cardColor, bOut, addrFrom);
this.outCRTCIndx(this.cardColor, port, bOut, addrFrom);
};
/**
@ -5184,7 +5349,7 @@ Video.prototype.outCGAIndx = function(port, bOut, addrFrom)
*/
Video.prototype.inCGAData = function(port, addrFrom)
{
return this.inCRTCData(this.cardColor, addrFrom);
return this.inCRTCData(this.cardColor, port, addrFrom);
};
/**
@ -5197,7 +5362,7 @@ Video.prototype.inCGAData = function(port, addrFrom)
*/
Video.prototype.outCGAData = function(port, bOut, addrFrom)
{
this.outCRTCData(this.cardColor, bOut, addrFrom);
this.outCRTCData(this.cardColor, port, bOut, addrFrom);
};
/**
@ -5238,7 +5403,7 @@ Video.prototype.inCGAColor = function(port, addrFrom)
{
var b = this.cardColor.regColor;
if (this.messageEnabled()) {
this.printMessageIO(this.cardColor.port + 5, null, addrFrom, this.cardColor.type + ".COLOR", b);
this.printMessageIO(port /* this.cardColor.port + 5 */, null, addrFrom, this.cardColor.type + ".COLOR", b);
}
return b;
};
@ -5254,7 +5419,7 @@ Video.prototype.inCGAColor = function(port, addrFrom)
Video.prototype.outCGAColor = function(port, bOut, addrFrom)
{
if (this.messageEnabled()) {
this.printMessageIO(this.cardColor.port + 5, bOut, addrFrom, this.cardColor.type + ".COLOR");
this.printMessageIO(port /* this.cardColor.port + 5 */, bOut, addrFrom, this.cardColor.type + ".COLOR");
}
if (this.cardColor.regColor !== bOut) {
this.cardColor.regColor = bOut;
@ -5280,67 +5445,92 @@ Video.prototype.inCGAStatus = function(port, addrFrom)
};
/**
* inCRTCIndx(card, addrFrom)
* inCRTCIndx(card, port, addrFrom)
*
* @this {Video}
* @param {Object} card
* @param {number} port
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number}
* @return {number|undefined}
*/
Video.prototype.inCRTCIndx = function(card, addrFrom)
Video.prototype.inCRTCIndx = function(card, port, addrFrom)
{
var b = card.regCRTIndx;
this.printMessageIO(card.port, null, addrFrom, "CRTC.INDX", b);
var b;
/*
* The IBM VGA ROM makes some hardware determinations based on how the CRTC controller responds when
* the IO_SELECT bit in the Miscellaneous Output Register is cleared; normally, that would mean ports
* 0x3B? are decoded and ports 0x3D? are ignored. We didn't used to bother ignoring them, but the
* VGA ROM's logic requires it, so now we also check fActive. However, we ignore only CTRC reads;
* we retain any writes in case that information proves useful later.
*
* Note that returning an undefined value now signals the Bus component to return whatever default value
* it prefers (normally 0xff).
*/
if (card.fActive) b = card.regCRTIndx;
this.printMessageIO(port, null, addrFrom, "CRTC.INDX", b);
return b;
};
/**
* outCRTCIndx(card, bOut, addrFrom)
* outCRTCIndx(card, port, bOut, addrFrom)
*
* @this {Video}
* @param {Object} card
* @param {number} port
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
*/
Video.prototype.outCRTCIndx = function(card, bOut, addrFrom)
Video.prototype.outCRTCIndx = function(card, port, bOut, addrFrom)
{
card.regCRTPrev = card.regCRTIndx;
card.regCRTIndx = bOut & Card.CGA.CRTC.INDX.MASK;
this.printMessageIO(card.port, bOut, addrFrom, "CRTC.INDX");
this.printMessageIO(port /* card.port */, bOut, addrFrom, "CRTC.INDX");
};
/**
* inCRTCData(card, addrFrom)
* inCRTCData(card, port, addrFrom)
*
* @this {Video}
* @param {Object} card
* @param {number} port
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number|undefined}
*/
Video.prototype.inCRTCData = function(card, addrFrom)
Video.prototype.inCRTCData = function(card, port, addrFrom)
{
var b;
if (card.regCRTIndx < card.nCRTCRegs) b = card.regCRTData[card.regCRTIndx];
/*
* The IBM VGA ROM makes some hardware determinations based on how the CRTC controller responds when
* the IO_SELECT bit in the Miscellaneous Output Register is cleared; normally, that would mean ports
* 0x3B? are decoded and ports 0x3D? are ignored. We didn't used to bother ignoring them, but the
* VGA ROM's logic requires it, so now we also check fActive. However, we ignore only CTRC reads;
* we retain any writes in case that information proves useful later.
*
* Note that returning an undefined value now signals the Bus component to return whatever default value
* it prefers (normally 0xff).
*/
if (card.fActive && card.regCRTIndx < card.nCRTCRegs) b = card.regCRTData[card.regCRTIndx];
if (this.messageEnabled()) {
this.printMessageIO(card.port + 1, null, addrFrom, "CRTC." + card.asCRTCRegs[card.regCRTIndx], b);
this.printMessageIO(port /* card.port + 1 */, null, addrFrom, "CRTC." + card.asCRTCRegs[card.regCRTIndx], b);
}
return b;
};
/**
* outCRTCData(card, bOut, addrFrom)
* outCRTCData(card, port, bOut, addrFrom)
*
* @this {Video}
* @param {Object} card
* @param {number} port
* @param {number} bOut
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
*/
Video.prototype.outCRTCData = function(card, bOut, addrFrom)
Video.prototype.outCRTCData = function(card, port, bOut, addrFrom)
{
if (card.regCRTIndx < card.nCRTCRegs) {
if (Video.TRAPALL || card.regCRTData[card.regCRTIndx] !== bOut) {
if (this.messageEnabled()) {
this.printMessageIO(card.port + 1, bOut, addrFrom, "CRTC." + card.asCRTCRegs[card.regCRTIndx]);
this.printMessageIO(port /* card.port + 1 */, bOut, addrFrom, "CRTC." + card.asCRTCRegs[card.regCRTIndx]);
}
card.regCRTData[card.regCRTIndx] = bOut;
}
@ -5571,12 +5761,19 @@ Video.aEGAPortOutput = {
Video.aVGAPortInput = {
0x3C3: Video.prototype.inVGAEnable,
0x3C6: Video.prototype.inDACMask,
0x3C7: Video.prototype.inDACState,
0x3C9: Video.prototype.inDACData,
0x3CA: Video.prototype.inVGAFeat,
0x3CC: Video.prototype.inVGAMisc
};
Video.aVGAPortOutput = {
0x3C3: Video.prototype.outVGAEnable
0x3C3: Video.prototype.outVGAEnable,
0x3C6: Video.prototype.outDACMask,
0x3C7: Video.prototype.outDACRead,
0x3C8: Video.prototype.outDACWrite,
0x3C9: Video.prototype.outDACData
};
/**

View file

@ -2114,7 +2114,7 @@ X86CPU.prototype.rewindIP = function(dec)
X86CPU.prototype.getSP = function()
{
if (I386) {
this.assert(!((this.regLSP - this.segSS.base) & ~this.segSS.addrMask));
// assert(!((this.regLSP - this.segSS.base) & ~this.segSS.addrMask));
return (this.regESP & ~this.segSS.addrMask) | (this.regLSP - this.segSS.base);
}
return (this.regLSP - this.segSS.base)|0;