More details on the A20 problem with the DeskPro 386 ROM

This commit is contained in:
Jeff Parsons 2015-04-17 18:58:54 -07:00 committed by jeffpar
commit 10202ccef5
7 changed files with 1340 additions and 1228 deletions

View file

@ -1,6 +1,6 @@
Compaq DeskPro 386
---
I finally dumped the [Compaq DeskPro 386-16 ROMs](/devices/pc/bios/compaq/deskpro386/) from the motherboard I bought
I finally dumped the [Compaq DeskPro 386/16 ROMs](/devices/pc/bios/compaq/deskpro386/) from the motherboard I bought
on ebay last year, so I'm ready to begin adding 80386 support to PCjs.
I'd also like to locate a copy of the "Compaq DeskPro 386 Technical Reference Guide, Volumes 1 and 2". It's not hard

105
blog/2015/04/16/README.md Normal file
View file

@ -0,0 +1,105 @@
Compaq DeskPro 386 Update
---
PCjs can now boot the [Compaq DeskPro 386/16 ROM BIOS](/devices/pc/bios/compaq/deskpro386/).
There's still a problem with the hard disk controller, which I haven't looked into yet, but
it can be bypassed. Booting from a floppy works.
While working through issues with this ROM BIOS, I created some lightly-annotated
[source code](/devices/pc/bios/compaq/deskpro386/1988-01-28.nasm) that can be re-assembled
with [NASM](http://www.nasm.us/). The process of creating the initial source code is
explained [here](/devices/pc/bios/compaq/deskpro386/#producing-rom-source-code).
During the debugging process, I also came across a real head-scratcher. Take a look at this code
at F000:BCAF:
;;
;; Relocate the ROM and initialize conventional RAM
;;
call xc825 ; 0000BCAF E8730B
;;
;; This function loads the GDTR with [00FF0730,0047], but since the previous call
;; disabled the A20 line, the first selector load crashes, because physical address
;; %FF0730 requires A20.
;;
call xf480 ; 0000BCB2 E8CB37
The first call (to F000:C825) is to a long, fairly linear function that includes a call to F000:A478,
a small function whose sole purpose is to issue an 8042 Keyboard Controller command that disables the A20
line.
;;
;; Disable A20
;;
call xa478 ; 0000C8B4 E8C1DB
After disabling A20, the function at F000:C825 then performs a series of `rep stosd` to zero all
conventional (below 1Mb) RAM, and then it returns to the code at F000:BCB2, which in turn calls F000:F480.
Here's the problem: the code at F000:F480 almost immediately attempts to enter protected-mode, which
fails because A20 is disabled.
;;
;; When we arrive here, the A20 line has been disabled, so in theory, the GDT is
;; accessible only at the "low" ROM address (%0F0730), not the "high" address (%FF0730).
;; And even if we DID access it from the "low" address, it contains base addresses (eg,
;; for selector 0x28) located at %FFxxxx, so we're still screwed if A20 is disabled.
;;
;; TODO: Determine how this code worked in "real life"
;;
;; FYI, it seems this code doesn't do anything if bits 6 and 7 of the RAM Settings
;; register are set to anything other than 0x40 (ie, it returns to real-mode almost
;; immediately after entering protected-mode).
;;
lgdt [cs:0x077e] ; 0000F498 load [gdtr_hi] into GDTR
mov eax,cr0 ; 0000F49E 0F2000
or ax,0x1 ; 0000F4A1 0D0100
mov cr0,eax ; 0000F4A4 0F2200
jmp 0x28:xf4ac ; 0000F4A7 EAACF42800
I don't see how the above code can work. The JMP instruction will invariably fault, because the
GDTR is pointing to memory that cannot be accessed when A20 is disabled.
The code at F000:F498 is not called from any other place. It can only be reached by running the
preceding code which, among other things, disables A20. And there's only one branch between that
code and the code that enters protected-mode that could possibly make any difference:
test word [cs:si],0x0f00 ; 0000F48A 2EF704000F
jz xf494 ; 0000F48F 7403
But the high byte of the word at [cs:si] (part of Compaq's Built-In Memory table) is initialized
earlier with a value from AL that was first masked with 0xF0:
;;
;; Isolate the base memory settings in bits 5-4 (00=640Kb, 10=512Kb, 11=256Kb)
;;
and al,0xf0 ; 000085D7 24F0 '$.'
;;
;; Update [bim_table_offset]+1 (eg, %FF7FB7) with base memory settings
;;
mov [es:di+0x1],al ; 000085D9 26884501 '&.E.'
and I'm not aware of any intervening code that could have altered that byte, so it's not clear how the
high byte of the word at [cs:si] would ever contain any set bits that coincide with 0x0F.
I've worked around this problem for now by changing how PCjs manages the A20 line. All A20 changes
now go through the CPU component, instead of directly to the Bus component, giving the CPU first crack
at any changes to A20. If the CPU is in real-mode, it simply passes the A20 request on to the Bus.
However, if the CPU is in protected-mode, it maintains the requested "logical" A20 state, but ensures
that the "physical" state of A20 is always enabled. In short, it is no longer possible for the CPU
to be in protected-mode AND for the A20 line to be disabled; when one is enabled, the other is enabled
as well.
That's obviously NOT how the actual hardware works, but it does have the advantage of avoiding strange
bugs involving the A20 line in protected-mode -- including this "bug" in the Compaq DeskPro 386 ROM BIOS.
Here's my Compaq DeskPro 386/16 PCjs test configuration. Set a breakpoint at F000:F498 ("bp f000:f498")
in the Debugger panel and see what *you* think. Use the "rp" command to see all registers, including
the current base and limit values loaded into the selectors.
[Embedded DeskPro 386](/devices/pc/machine/compaq/deskpro386/ega/2048kb/machine.xml "PCjs:deskpro386-ega-2048k::uncompiled:debugger")
*[@jeffpar](http://twitter.com/jeffpar)*
*April 16, 2015*

View file

@ -6628,12 +6628,12 @@ xbc71: call xa3e0 ; 0000BC71 E86CE7 '.l.'
;;
;; Relocate the ROM and initialize conventional RAM
;;
call xc825 ; 0000BCAF E8730B '.s.'
call xc825 ; 0000BCAF E8730B
;;
;; This function loads the GDTR with [00FF0730,0047], but since the previous call
;; turned A20 off, the first selector load crashes, because physical address %FF0730
;; requires A20 on.
;; disabled the A20 line, the first selector load crashes, because physical address
;; %FF0730 requires A20.
;;
call xf480 ; 0000BCB2 E8CB37
@ -8142,7 +8142,7 @@ xc89d: lidt [cs:0xa151] ; 0000C89D 2E0F011E51A1 '....Q.'
;;
;; Disable A20
;;
call xa478 ; 0000C8B4 E8C1DB '...'
call xa478 ; 0000C8B4 E8C1DB
;;
;; Get the (conventional) memory size in Kb from 0x40:0x0013, divide by 64 (0x40) to yield
@ -11574,14 +11574,14 @@ xe770: test dl,0x80 ; 0000E770 F6C280 '...'
jnz xe793 ; 0000E773 751E 'u.'
;;
;; More disk I/O (TODO: Investigate)
;; More diskette I/O (TODO: Investigate)
;;
push bx ; 0000E775 53 'S'
call xd540 ; 0000E776 E8C7ED '...'
pop bx ; 0000E779 5B '['
;;
;; Check the ROM progress byte for 0xBD (value I've seen: 0xB9)
;; Check the ROM status byte for 0xBD (values I've seen: 0xB9)
;;
in al,0x84 ; 0000E77A E484 '..'
cmp al,0xbd ; 0000E77C 3CBD '<.'
@ -11589,14 +11589,15 @@ xe770: test dl,0x80 ; 0000E770 F6C280 '...'
;;
;; This code looks broken: ES:BX points to the boot sector just loaded
;; (0x0000:0x7C00), and apparently it wants to scan the first 9 words of
;; the boot sector, to see if they all match the 1st word; if they do,
;; (0x0000:0x7C00), and apparently the ROM wants to scan the first 9 words
;; of the boot sector, to see if they all match the 1st word; if they do,
;; then it's likely the boot sector is invalid (hence the "boot_error"
;; message). However, it's loading (AX) with the 1st word from DS:BX
;; (0x0040:0x7C00) rather than ES:BX, which increases the likelihood that
;; this code will always see a difference and never trigger the error.
;; message). However, it's loading AX with the 1st word from DS:BX
;; (0x0040:0x7C00) rather than ES:BX, which aside from being the wrong word,
;; also increases the likelihood that this code will always see a difference
;; and never trigger the error.
;;
;; The value in (AX) is typically 0x0000.
;; The value in AX is typically 0x0000.
;;
mov ax,[bx] ; 0000E780 8B07 '..'
mov cx,0x9 ; 0000E782 B90900 '...'
@ -12986,10 +12987,11 @@ xf494: mov al,0x0 ; 0000F494 B000
;; And even if we DID access it from the "low" address, it contains base addresses (eg,
;; for selector 0x28) located at %FFxxxx, so we're still screwed if A20 is disabled.
;;
;; TODO: Determine how this code works in "real life"
;; TODO: Determine how this code worked in "real life"
;;
;; FYI, it seems this code doesn't do anything PROVIDED bits 6 and 7 of the RAM Settings
;; register are set to anything other than 0x40.
;; FYI, it seems this code doesn't do anything if bits 6 and 7 of the RAM Settings
;; register are set to anything other than 0x40 (ie, it returns to real-mode almost
;; immediately after entering protected-mode).
;;
lgdt [cs:0x077e] ; 0000F498 load [gdtr_hi] into GDTR
mov eax,cr0 ; 0000F49E 0F2000

View file

@ -63,7 +63,7 @@ while the /PGM should be connected to INPUT HIGH VOLTAGE. So I wired pin 27 (/P
worked perfectly. The NYC Resistor article implied that every *active low* pin should be connected to GND, but apparently
there are exceptions to that general rule.
Producing BIOS Source Code
Producing ROM Source Code
---
In the current directory, an original ROM can be regenerated from the JSON-encoded file:

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -660,7 +660,7 @@ X86CPU.prototype.initMemory = function(aMemBlocks, blockShift, blockLimit, block
* it's hard to imagine any real-world protected-mode code relying on A20 being off. However, that
* doesn't change the fact that this hack should NOT be necessary.
*
* TODO: Figure out why the DeskPro 386 ROM BIOS misbehaves under emulation, necessitating this hack.
* TODO: Figure out why the DeskPro 386 ROM BIOS misbehaves under emulation, and eliminate this hack.
*
* @this {X86CPU}
* @param {boolean} fEnable is true to enable A20, false to disable