Fix EGA scrolling for modes 0x0D and 0x0E
This commit is contained in:
parent
aa98f63f47
commit
38f16fcc50
13 changed files with 3322 additions and 3077 deletions
180
blog/2015/06/05/README.md
Normal file
180
blog/2015/06/05/README.md
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
The Strange Case of the EGA Graphics Scroll Bug
|
||||
---
|
||||
|
||||
For reasons I've forgotten, I was playing with different video modes in this
|
||||
[IBM PC AT w/EGA](/devices/pc/machine/5170/ega/640kb/rev1/) machine, and I discovered some odd problems.
|
||||
|
||||
For example, run this code:
|
||||
|
||||
A>b:debug
|
||||
-a
|
||||
0CE0:0100 mov ax,e
|
||||
0CE0:0103 int 10
|
||||
0CE0:0105 int 3
|
||||
0CE0:0106
|
||||
-g
|
||||
|
||||
and the following "text" will appear at the top of the screen, in 640x200 16-color graphics mode 0x0E:
|
||||
|
||||
AX=0B01 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
|
||||
DS=0CE0 ES=0CE0 SS=0CE0 CS=0CE0 IP=0105 NV UP EI PL NZ NA PO NC
|
||||
0CE0:0105 CC INT 3
|
||||
-
|
||||
|
||||
If I typed "q", then "cls" and finally "dir", the screen would properly fill with DOS directory contents.
|
||||
However, as soon as the screen started to scroll, the screen contents became garbled. Other EGA graphics modes,
|
||||
like 640x350 16-color mode 0x10, didn't have this problem.
|
||||
|
||||
To investigate, I set a breakpoint in the IBM EGA ROM where the scrolling starts, at 0xC000:12EA (see p.130 of the
|
||||
"IBM Enhanced Graphics Adapter" Technical Reference document):
|
||||
|
||||
CRANK_A:
|
||||
PUSH CX
|
||||
MOV CL,DL
|
||||
SUB CH,CH
|
||||
PUSH SI
|
||||
PUSH DI
|
||||
REP MOVSB
|
||||
POP DI
|
||||
POP SI
|
||||
ADD SI,BX
|
||||
ADD DI,BX
|
||||
POP CX
|
||||
LOOP CRANK_A
|
||||
|
||||
CX arrives as 0xC0 (192), which is the number of scan-lines to move up, and BX is 0x50 (80), the number
|
||||
of bytes per scan-line.
|
||||
|
||||
When the breakpoint was hit, I dumped the video hardware state, using the Debugger's "*d video*" command.
|
||||
For reference purposes, I've pasted the corresponding video state for mode 0x10 on the right-hand side.
|
||||
|
||||
breakpoint hit: C000:12EA (exec)
|
||||
stopped (175707689 ops, 800060264 cycles, 133470 ms, 5994308 hz)
|
||||
AX=020F BX=0050 CX=00C0 DX=1950 SP=0B58 BP=0511 SI=0280 DI=0000
|
||||
SS=011F DS=A000 ES=A000 PS=0246 V0 D0 I1 T0 S0 Z1 A0 P1 C0
|
||||
C000:12EA 51 PUSH CX
|
||||
|
||||
BIOSMODE: 0x0E BIOSMODE: 0x10
|
||||
CRTC[0x00]: HORZ_TOTAL 0x70 CRTC[0x00]: HORZ_TOTAL 0x5B
|
||||
CRTC[0x01]: HORZ_DISP_END 0x4F CRTC[0x01]: HORZ_DISP_END 0x4F
|
||||
CRTC[0x02]: HORZ_BLANK_START 0x59 CRTC[0x02]: HORZ_BLANK_START 0x53
|
||||
CRTC[0x03]: HORZ_BLANK_END 0x2D CRTC[0x03]: HORZ_BLANK_END 0x37
|
||||
CRTC[0x04]: HORZ_RETRACE_START 0x5E CRTC[0x04]: HORZ_RETRACE_START 0x52
|
||||
CRTC[0x05]: HORZ_RETRACE_END 0x06 CRTC[0x05]: HORZ_RETRACE_END 0x00
|
||||
CRTC[0x06]: VERT_TOTAL 0x04 CRTC[0x06]: VERT_TOTAL 0x6C
|
||||
CRTC[0x07]: OVERFLOW 0x11 CRTC[0x07]: OVERFLOW 0x1F
|
||||
CRTC[0x08]: PRESET_ROW_SCAN 0x00 CRTC[0x08]: PRESET_ROW_SCAN 0x00
|
||||
CRTC[0x09]: MAX_SCAN_LINE 0x00 CRTC[0x09]: MAX_SCAN_LINE 0x00
|
||||
CRTC[0x0A]: CURSOR_START 0x00 CRTC[0x0A]: CURSOR_START 0x00
|
||||
CRTC[0x0B]: CURSOR_END 0x01 CRTC[0x0B]: CURSOR_END 0x01
|
||||
CRTC[0x0C]: START_ADDR_HI 0x00 CRTC[0x0C]: START_ADDR_HI 0x00
|
||||
CRTC[0x0D]: START_ADDR_LO 0x00 CRTC[0x0D]: START_ADDR_LO 0x00
|
||||
CRTC[0x0E]: CURSOR_ADDR_HI 0x07 CRTC[0x0E]: CURSOR_ADDR_HI 0x01
|
||||
CRTC[0x0F]: CURSOR_ADDR_LO 0x80* CRTC[0x0F]: CURSOR_ADDR_LO 0x41*
|
||||
CRTC[0x10]: VERT_RETRACE_START 0xE0 CRTC[0x10]: VERT_RETRACE_START 0x5E
|
||||
CRTC[0x11]: VERT_RETRACE_END 0x23 CRTC[0x11]: VERT_RETRACE_END 0x2B
|
||||
CRTC[0x12]: VERT_DISP_END 0xC7 CRTC[0x12]: VERT_DISP_END 0x5D
|
||||
CRTC[0x13]: OFFSET 0x28 CRTC[0x13]: OFFSET 0x28
|
||||
CRTC[0x14]: UNDERLINE 0x00 CRTC[0x14]: UNDERLINE 0x0F
|
||||
CRTC[0x15]: VERT_BLANK_START 0xDF CRTC[0x15]: VERT_BLANK_START 0x5F
|
||||
CRTC[0x16]: VERT_BLANK_END 0xEF CRTC[0x16]: VERT_BLANK_END 0x0A
|
||||
CRTC[0x17]: MODE_CTRL 0xE3 CRTC[0x17]: MODE_CTRL 0xE3
|
||||
CRTC[0x18]: LINE_COMPARE 0xFF CRTC[0x18]: LINE_COMPARE 0xFF
|
||||
STATUS1: 0x01 STATUS1: 0x01
|
||||
ATCDATA: true ATCDATA: true
|
||||
ATC[0x00]: PAL00 0x00 ATC[0x00]: PAL00 0x00
|
||||
ATC[0x01]: PAL01 0x01 ATC[0x01]: PAL01 0x01
|
||||
ATC[0x02]: PAL02 0x02 ATC[0x02]: PAL02 0x02
|
||||
ATC[0x03]: PAL03 0x03 ATC[0x03]: PAL03 0x03
|
||||
ATC[0x04]: PAL04 0x04 ATC[0x04]: PAL04 0x04
|
||||
ATC[0x05]: PAL05 0x05 ATC[0x05]: PAL05 0x05
|
||||
ATC[0x06]: PAL06 0x06 ATC[0x06]: PAL06 0x14
|
||||
ATC[0x07]: PAL07 0x07 ATC[0x07]: PAL07 0x07
|
||||
ATC[0x08]: PAL08 0x10 ATC[0x08]: PAL08 0x38
|
||||
ATC[0x09]: PAL09 0x11 ATC[0x09]: PAL09 0x39
|
||||
ATC[0x0A]: PAL0A 0x12 ATC[0x0A]: PAL0A 0x3A
|
||||
ATC[0x0B]: PAL0B 0x13 ATC[0x0B]: PAL0B 0x3B
|
||||
ATC[0x0C]: PAL0C 0x14 ATC[0x0C]: PAL0C 0x3C
|
||||
ATC[0x0D]: PAL0D 0x15 ATC[0x0D]: PAL0D 0x3D
|
||||
ATC[0x0E]: PAL0E 0x16 ATC[0x0E]: PAL0E 0x3E
|
||||
ATC[0x0F]: PAL0F 0x17 ATC[0x0F]: PAL0F 0x3F
|
||||
ATC[0x10]: MODE 0x01 ATC[0x10]: MODE 0x01
|
||||
ATC[0x11]: OVERSCAN 0x00 ATC[0x11]: OVERSCAN 0x00
|
||||
ATC[0x12]: PLANES 0x0F ATC[0x12]: PLANES 0x0F
|
||||
ATC[0x13]: HORZPAN 0x00 ATC[0x13]: HORZPAN 0x00
|
||||
GRC[0x00]: SRESET 0x00 GRC[0x00]: SRESET 0x00
|
||||
GRC[0x01]: ESRESET 0x00 GRC[0x01]: ESRESET 0x00
|
||||
GRC[0x02]: COLORCMP 0x00 GRC[0x02]: COLORCMP 0x00
|
||||
GRC[0x03]: DATAROT 0x00 GRC[0x03]: DATAROT 0x00*
|
||||
GRC[0x04]: READMAP 0x00 GRC[0x04]: READMAP 0x00
|
||||
GRC[0x05]: MODE 0x11* GRC[0x05]: MODE 0x00
|
||||
GRC[0x06]: MISC 0x05 GRC[0x06]: MISC 0x05
|
||||
GRC[0x07]: COLORDC 0x0F GRC[0x07]: COLORDC 0x0F
|
||||
GRC[0x08]: BITMASK 0xFF GRC[0x08]: BITMASK 0xFF
|
||||
SEQ[0x00]: RESET 0x03 SEQ[0x00]: RESET 0x03
|
||||
SEQ[0x01]: CLOCKING 0x01 SEQ[0x01]: CLOCKING 0x01
|
||||
SEQ[0x02]: MAPMASK 0x0F* SEQ[0x02]: MAPMASK 0x0F*
|
||||
SEQ[0x03]: CHARMAP 0x00 SEQ[0x03]: CHARMAP 0x00
|
||||
SEQ[0x04]: MEMMODE 0x06 SEQ[0x04]: MEMMODE 0x06
|
||||
FEAT: 0x02 FEAT: 0x02
|
||||
MISC: 0x23 MISC: 0xA7
|
||||
STATUS0: 0x10 STATUS0: 0x10
|
||||
LATCHES: 0x00000000 LATCHES: 0x00000000
|
||||
ACCESS: 0x1411 ACCESS: 0x0400
|
||||
|
||||
One of the apparent oddities is that, for mode 0x0E, GRC[MODE] has been programmed with 0x11, whereas
|
||||
for mode 0x10, it was programmed with 0x00. Why would mode 0x0E want to set the ODDEVEN bit during the scroll,
|
||||
when it hadn't been set during any other writes to the screen?
|
||||
|
||||
At this point, I dumped the instruction history buffer a bit ("*dh 100*"), and noticed this GRC write:
|
||||
|
||||
C000:1582 8BC5 MOV AX,BP ;history=33
|
||||
C000:1584 B603 MOV DH,03 ;history=32
|
||||
C000:1586 B2CE MOV DL,CE ;history=31
|
||||
C000:1588 E88AF7 CALL 0D15 ;history=30
|
||||
C000:0D15 86C4 XCHG AL,AH ;history=29
|
||||
C000:0D17 EE OUT DX,AL ;history=28
|
||||
C000:0D18 42 INC DX ;history=27
|
||||
C000:0D19 86C4 XCHG AL,AH ;history=26
|
||||
C000:0D1B EE OUT DX,AL ;history=25
|
||||
C000:0D1C 4A DEC DX ;history=24
|
||||
|
||||
So I looked back farther and saw where BP was set:
|
||||
|
||||
C000:1522 BA00A0 MOV DX,A000 ;history=97
|
||||
C000:1525 BD1105 MOV BP,0511 ;history=96
|
||||
|
||||
Here's the complete function:
|
||||
|
||||
GR_ST_1:
|
||||
MOV DX,A000
|
||||
MOV BP,0511
|
||||
CMP AH,0F
|
||||
JC 1535
|
||||
CALL 14F7
|
||||
JNC 1535
|
||||
MOV BP,0501
|
||||
RET
|
||||
|
||||
OK, so any (EGA) graphics mode below 0x0F is going to the trigger the use of Write Mode 1 with the ODDEVEN bit set.
|
||||
And sure enough, the scrolling bug also occurs when using 320x200 16-color mode 0x0D.
|
||||
|
||||
It's also worth noting that, whenver the ODDEVEN bit of the GRC Mode Register is set, the SEQUENTIAL bit in the Sequencer
|
||||
Memory Mode Register is supposed to be clear (and vice versa -- those two bits are always supposed to be oppositely set).
|
||||
But here, the IBM EGA BIOS hasn't done that. One wonders if that was a mistake....
|
||||
|
||||
Another bit of trivia: while dumping the frame buffer in a VGA text mode in a different emulator, I discovered that
|
||||
if I turned off the ODDEVEN bit in the GRC Mode Register, odd bytes would still appear from plane 1; it wasn't until I
|
||||
*also* turned off the CHAIN bit in the GRC Miscellaneous Register that the odd bytes would no longer appear. But,
|
||||
that could have just been an idiosyncrasy of that particular emulator.
|
||||
|
||||
As a result of all these observations, and more importantly, to make EGA scrolling work properly in modes 0x0D and 0x0E,
|
||||
I've changed the Video component to use odd/even memory functions *only* when the SEQUENTIAL bit (bit 2) of the
|
||||
Sequencer's Memory Mode Register is clear, instead of relying on the ODDEVEN bit (bit 4) of the Graphics Controller's Mode
|
||||
Register.
|
||||
|
||||
To be continued.... because I've barely scratched the surface of all the side-effects of EGA/VGA odd/even addressing,
|
||||
and I hope to do some testing on real hardware in the near future.
|
||||
|
||||
*[@jeffpar](http://twitter.com/jeffpar)*
|
||||
*June 5, 2015*
|
||||
Loading…
Reference in a new issue