More details on the A20 problem with the DeskPro 386 ROM
This commit is contained in:
parent
1508cd1bf3
commit
10202ccef5
7 changed files with 1340 additions and 1228 deletions
|
|
@ -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
105
blog/2015/04/16/README.md
Normal 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*
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue