diff --git a/devices/pc/bios/compaq/deskpro386/1988-01-28.nasm b/devices/pc/bios/compaq/deskpro386/1988-01-28.nasm index cec034ad8..e623e585c 100644 --- a/devices/pc/bios/compaq/deskpro386/1988-01-28.nasm +++ b/devices/pc/bios/compaq/deskpro386/1988-01-28.nasm @@ -215,6 +215,13 @@ x80b3: xor dx,dx ; 000080B3 33D2 '3.' pop bx ; 000080E0 5B '[' ret ; 000080E1 C3 '.' + ;; + ;; Use INT 0x15, (AH) == 0x89, to enter protected-mode + ;; + ;; Returns ZF set if successful + ;; + ;; See Compaq 386/25 TechRef, p.4-98 for details + ;; x80e2: push bx ; 000080E2 53 'S' push cx ; 000080E3 51 'Q' push dx ; 000080E4 52 'R' @@ -236,6 +243,11 @@ x80e2: push bx ; 000080E2 53 'S' pop bx ; 00008100 5B '[' ret ; 00008101 C3 '.' + ;; + ;; Descriptor table initialization + ;; + ;; Used by x80e2 to prepare for an INT 0x15 call to enter protected-mode + ;; x8102: pusha ; 00008102 60 '`' push ds ; 00008103 1E '.' push es ; 00008104 06 '.' @@ -314,6 +326,11 @@ x8102: pusha ; 00008102 60 '`' popa ; 000081C9 61 'a' ret ; 000081CA C3 '.' + ;; + ;; Descriptor initializer + ;; + ;; Used by x8102 to initialize a descriptor table + ;; x81cb: mov word [si],0xffff ; 000081CB C704FFFF '....' mov bh,ah ; 000081CF 8AFC '..' shl ax,0x4 ; 000081D1 C1E004 '...' @@ -4160,6 +4177,7 @@ xa43c: mov ax,0x40 ; 0000A43C B84000 '.@.' mov ds,ax ; 0000A43F 8ED8 '..' mov ss,[0x69] ; 0000A441 8E166900 '..i.' mov sp,[0x67] ; 0000A445 8B266700 '.&g.' + ;; ;; Disable A20 ;; @@ -8054,6 +8072,10 @@ xc825: pusha ; 0000C825 60 '`' mov [0x69],ss ; 0000C835 8C166900 '..i.' mov al,0xd1 ; 0000C839 B0D1 '..' out 0x84,al ; 0000C83B E684 '..' + + ;; + ;; Enter protected-mode + ;; call x80e2 ; 0000C83D E8A2B8 '...' jz xc845 ; 0000C840 7403 't.' jmp xc8ef ; 0000C842 E9AA00 '...' @@ -8071,7 +8093,13 @@ xc845: mov al,0xd2 ; 0000C845 B0D2 '..' mov bh,al ; 0000C85C 8AF8 '..' mov bl,0x10 ; 0000C85E B310 '..' call xc8fc ; 0000C860 E89900 '...' + + ;; + ;; The next function writes 0xFF to 0x80C00000, presumably to ensure that + ;; Compaq Built-in Memory at 0xFE0000 is NOT mapped to 0x0E000 at the moment + ;; call x84a5 ; 0000C863 E83FBC '.?.' + mov ax,[0x8d] ; 0000C866 A18D00 '...' add ax,0x80 ; 0000C869 058000 '...' xor dx,dx ; 0000C86C 33D2 '3.' @@ -8084,7 +8112,14 @@ xc845: mov al,0xd2 ; 0000C845 B0D2 '..' ;; ;; Relocate the ROM ;; + ;; On return, we should now be running in RAM (ie, a copy of the ROM in 128Kb of RAM + ;; now mapped to %0E0000 through %0FFFFF) + ;; call x853c ; 0000C87C E8BDBC '...' + + ;; + ;; Return to real-mode + ;; mov al,0xd3 ; 0000C87F B0D3 '..' out 0x84,al ; 0000C881 E684 '..' mov ax,0x28 ; 0000C883 B82800 '.(.' @@ -9691,9 +9726,13 @@ xd509: mov [0x41],ah ; 0000D509 88264100 '.&A.' pop ax ; 0000D50D 58 'X' ret ; 0000D50E C3 '.' + ;; + ;; Check the FDC's ST0 response + ;; xd50f: mov al,[0x42] ; 0000D50F A04200 '.B.' test al,0xc0 ; 0000D512 A8C0 '..' - jz xd53d ; 0000D514 7427 't',0x27 + jz xd53d ; 0000D514 No problems + test al,0x8 ; 0000D516 A808 '..' jz xd51e ; 0000D518 7404 't.' mov ah,0x80 ; 0000D51A B480 '..' @@ -11479,8 +11518,16 @@ xe714: mov al,0xb9 ; 0000E714 B0B9 '..' out 0x84,al ; 0000E716 E684 '..' mov cx,0x3 ; 0000E718 B90300 '...' xe71b: push cx ; 0000E71B 51 'Q' + + ;; + ;; Reset the disk controller + ;; mov ah,0x0 ; 0000E71C B400 '..' int 0x13 ; 0000E71E CD13 '..' + + ;; + ;; Read the boot sector + ;; mov bx,0x7c00 ; 0000E720 BB007C '..|' mov cx,0x1 ; 0000E723 B90100 '...' mov ax,0x201 ; 0000E726 B80102 '...' @@ -11520,14 +11567,37 @@ xe73c: loop xe71b ; 0000E73C E2DD '..' ;; xe76e: int 0x18 ; 0000E76E CD18 '..' + ;; + ;; Boot sector successfully read + ;; xe770: test dl,0x80 ; 0000E770 F6C280 '...' jnz xe793 ; 0000E773 751E 'u.' + + ;; + ;; More disk 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) + ;; in al,0x84 ; 0000E77A E484 '..' cmp al,0xbd ; 0000E77C 3CBD '<.' jz xe714 ; 0000E77E 7494 't.' + + ;; + ;; 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, + ;; 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. + ;; + ;; The value in (AX) is typically 0x0000. + ;; mov ax,[bx] ; 0000E780 8B07 '..' mov cx,0x9 ; 0000E782 B90900 '...' mov di,bx ; 0000E785 8BFB '..' @@ -11539,6 +11609,9 @@ xe770: test dl,0x80 ; 0000E770 F6C280 '...' ;; Hang xe791: jmp short xe791 ; 0000E791 EBFE '..' + ;; + ;; Hard disk sector validation (as opposed to the preceding floppy disk sector validation) + ;; xe793: mov al,0x33 ; 0000E793 B033 '.3' mov ah,al ; 0000E795 8AE0 '..' call xb544 ; 0000E797 E8AACD '...' @@ -11547,10 +11620,11 @@ xe793: mov al,0x33 ; 0000E793 B033 '.3' call xb549 ; 0000E79E E8A8CD '...' ;; - ;; Verify the boot sector signature (0xAA55) + ;; Verify the hard disk boot sector signature (0xAA55) ;; cmp word [bx+0x1fe],0xaa55 ; 0000E7A1 81BFFE0155AA '....U.' jnz xe76e ; 0000E7A7 75C5 'u.' + xe7a9: in al,0x86 ; 0000E7A9 E486 '..' test al,0x80 ; 0000E7AB A880 '..' jnz xe7b7 ; 0000E7AD 7508 'u.' @@ -12105,24 +12179,13 @@ xec32: in al,0x64 ; 0000EC32 E464 '.d' pop cx ; 0000EC38 59 'Y' ret ; 0000EC39 C3 '.' - times 4 db 0xFF ; 0000EC3A - 0000EC3D + db 0xFF,0xFF,0xFF,0xFF,0xFF,0x34,0xC9,0x45,0xC9,0xC0,0xEC,0xC0,0xEC,0xC0,0xEC,0xE1 + db 0xED,0xA6,0xEC,0xA6,0xEC,0x48,0x9F,0x63,0x91,0x7C,0x8F,0x10,0x90,0x38,0xA0 - push word [si] ; 0000EC3E FF34 '.4' - leave ; 0000EC40 C9 '.' - inc bp ; 0000EC41 45 'E' - leave ; 0000EC42 C9 '.' - shr ah,0xc0 ; 0000EC43 C0ECC0 '...' - in al,dx ; 0000EC46 EC '.' - shr ah,0xe1 ; 0000EC47 C0ECE1 '...' - in ax,dx ; 0000EC4A ED '.' - cmpsb ; 0000EC4B A6 '.' - in al,dx ; 0000EC4C EC '.' - cmpsb ; 0000EC4D A6 '.' - in al,dx ; 0000EC4E EC '.' - dec ax ; 0000EC4F 48 'H' - lahf ; 0000EC50 9F '.' - arpl [bx+di+0x8f7c],dx ; 0000EC51 63917C8F 'c.|.' - adc [bx+si+0xa038],dl ; 0000EC55 109038A0 '..8.' + ;; + ;; This address (F000:EC59) is stored in the IDT vector for INT 0x40 when a hard disk controls INT 0x13 + ;; +int13_diskette: sti ; 0000EC59 FB '.' push bp ; 0000EC5A 55 'U' push ds ; 0000EC5B 1E '.' @@ -12161,6 +12224,7 @@ xec95: mov bl,al ; 0000EC95 8AD8 '..' xeca2: cmp al,0x1 ; 0000ECA2 3C01 '<.' jna xec95 ; 0000ECA4 76EF 'v.' xeca6: call xd3e8 ; 0000ECA6 E83FE7 '.?.' + xeca9: mov bl,[es:si+0x2] ; 0000ECA9 268A5C02 '&.\.' mov [0x40],bl ; 0000ECAD 881E4000 '..@.' or word [bp+0x16],0x200 ; 0000ECB1 814E160002 '.N...' @@ -12179,9 +12243,15 @@ xeca9: mov bl,[es:si+0x2] ; 0000ECA9 268A5C02 '&.\.' jnz xecd4 ; 0000ECC3 750F 'u.' call x9061 ; 0000ECC5 E899A3 '...' jc xecd4 ; 0000ECC8 720A 'r',0x0A + + ;; + ;; Perform FDC operation (eg, read); ZF set on success + ;; call xecf0 ; 0000ECCA E82300 '.#.' jz xecd4 ; 0000ECCD 7405 't.' + or word [bp+0x16],0x1 ; 0000ECCF 814E160100 '.N...' + xecd4: ret ; 0000ECD4 C3 '.' xecd5: mov al,0x6 ; 0000ECD5 B006 '..' @@ -12283,8 +12353,13 @@ xedb8: sub si,byte +0x5 ; 0000EDB8 83EE05 '...' xedbb: call xef85 ; 0000EDBB E8C701 '...' jnz xedd7 ; 0000EDBE 7517 'u.' + + ;; + ;; Read 7 FDC response bytes, starting with ST0, storing them at 0x40:0x42 + ;; mov cx,0x7 ; 0000EDC0 B90700 '...' mov di,0x42 ; 0000EDC3 BF4200 '.B.' + xedc6: call xc9bf ; 0000EDC6 E8F6DB '...' jz xedc6 ; 0000EDC9 74FB 't.' call x919a ; 0000EDCB E8CCA3 '...' @@ -12292,9 +12367,19 @@ xedc6: call xc9bf ; 0000EDC6 E8F6DB '...' mov [di],al ; 0000EDCF 8805 '..' inc di ; 0000EDD1 47 'G' loop xedc6 ; 0000EDD2 E2F2 '..' + + ;; + ;; If the FDC's response in ST0 is good, this will return (AH) == 0x00 + ;; call xd50f ; 0000EDD4 E838E7 '.8.' + xedd7: mov [0x41],ah ; 0000EDD7 88264100 '.&A.' + + ;; + ;; This appears to validate the rest of the FDC response bytes; (AX) should be zero on success + ;; call xecd5 ; 0000EDDB E8F7FE '...' + or ah,ah ; 0000EDDE 0AE4 0x0A,'.' ret ; 0000EDE0 C3 '.' @@ -12896,12 +12981,14 @@ xf494: mov al,0x0 ; 0000F494 B000 out 0x80,al ; 0000F496 E680 ;; - ;; When we arrive here, the A20 line has been disabled, so in theory, the GDT-in-ROM - ;; is accessible only at the "lo" ROM address (%0F0730), not the "hi" address (%FF0730). - ;; Which means the following JMP through selector 0x28 (indeed, any selector access) - ;; should immediately fail. TODO: Determine how this code works in "real life" + ;; 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. ;; - ;; It seems this code doesn't really do much PROVIDED bits 6 and 7 of the RAM Settings + ;; TODO: Determine how this code works 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. ;; lgdt [cs:0x077e] ; 0000F498 load [gdtr_hi] into GDTR diff --git a/devices/pc/bios/compaq/deskpro386/notes.md b/devices/pc/bios/compaq/deskpro386/notes.md index 5e569a23b..bad6edbb8 100644 --- a/devices/pc/bios/compaq/deskpro386/notes.md +++ b/devices/pc/bios/compaq/deskpro386/notes.md @@ -75,113 +75,5 @@ and then gets stuck loading CS:IP of 0x28:0xF4AC: CR0=0000FFF1 CR2=00000000 CR3=00000000 PS=00000082 V0 D0 I0 T0 S1 Z0 A0 P0 C0 F000:F4A7 EAACF42800 JMP 0028:F4AC -Here's the code leading up to it: - - F000:F494 B000 MOV AL,00 ;history=8 - F000:F496 E680 OUT 80,AL ;history=7 - F000:F498 2E CS: ;history=6 - F000:F499 0F01167E07 LGDT [077E] ;history=5 - F000:F49E 0F2000 MOV EAX,CR0 ;history=4 - F000:F4A1 0D0100 OR AX,0001 ;history=3 - F000:F4A4 0F2200 MOV CR0,EAX ;history=2 - F000:F4A7 EAACF42800 JMP 0028:F4AC ;history=1 - - %00FF073C 92C0 8000 FFFF 0000 - 9200 0000 FFFF 0000 ................ - %00FF074C 9A0F 0000 FFFF 0000 - 9200 C000 FFFF 0000 ................ - - id physaddr blkaddr used size type id physaddr blkaddr used size type - -------- --------- -------- ------ ------ ---- -------- --------- -------- ------ ------ ---- - 00040006 %00000000: 00000000 0x8000 0x8000 RAM 00040006 %00000000: 00000000 0x8000 0x8000 RAM - 00040008 %00008000: 00008000 0x8000 0x8000 RAM 00040008 %00008000: 00008000 0x8000 0x8000 RAM - 0004000A %00010000: 00010000 0x8000 0x8000 RAM 0004000A %00010000: 00010000 0x8000 0x8000 RAM - 0004000C %00018000: 00018000 0x8000 0x8000 RAM 0004000C %00018000: 00018000 0x8000 0x8000 RAM - 0004000E %00020000: 00020000 0x8000 0x8000 RAM 0004000E %00020000: 00020000 0x8000 0x8000 RAM - 00040010 %00028000: 00028000 0x8000 0x8000 RAM 00040010 %00028000: 00028000 0x8000 0x8000 RAM - 00040012 %00030000: 00030000 0x8000 0x8000 RAM 00040012 %00030000: 00030000 0x8000 0x8000 RAM - 00040014 %00038000: 00038000 0x8000 0x8000 RAM 00040014 %00038000: 00038000 0x8000 0x8000 RAM - 00040016 %00040000: 00040000 0x8000 0x8000 RAM 00040016 %00040000: 00040000 0x8000 0x8000 RAM - 00040018 %00048000: 00048000 0x8000 0x8000 RAM 00040018 %00048000: 00048000 0x8000 0x8000 RAM - 0004001A %00050000: 00050000 0x8000 0x8000 RAM 0004001A %00050000: 00050000 0x8000 0x8000 RAM - 0004001C %00058000: 00058000 0x8000 0x8000 RAM 0004001C %00058000: 00058000 0x8000 0x8000 RAM - 0004001E %00060000: 00060000 0x8000 0x8000 RAM 0004001E %00060000: 00060000 0x8000 0x8000 RAM - 00040020 %00068000: 00068000 0x8000 0x8000 RAM 00040020 %00068000: 00068000 0x8000 0x8000 RAM - 00040022 %00070000: 00070000 0x8000 0x8000 RAM 00040022 %00070000: 00070000 0x8000 0x8000 RAM - 00040024 %00078000: 00078000 0x8000 0x8000 RAM 00040024 %00078000: 00078000 0x8000 0x8000 RAM - 00040026 %00080000: 00080000 0x8000 0x8000 RAM 00040026 %00080000: 00080000 0x8000 0x8000 RAM - 00040028 %00088000: 00088000 0x8000 0x8000 RAM 00040028 %00088000: 00088000 0x8000 0x8000 RAM - 0004002A %00090000: 00090000 0x8000 0x8000 RAM 0004002A %00090000: 00090000 0x8000 0x8000 RAM - 0004002C %00098000: 00098000 0x8000 0x8000 RAM 0004002C %00098000: 00098000 0x8000 0x8000 RAM - 00040088 %000B8000: 000B8000 0x8000 0x8000 VIDEO 000400A0 %000B8000: 000B8000 0x8000 0x8000 VIDEO - 00040002 %000C0000: 000C0000 0x4000 0x8000 ROM 00040002 %000C0000: 000C0000 0x4000 0x8000 ROM - 0004003F %000E0000: 000E0000 0x???? 0x8000 ROM - 00040041 %000E8000: 000E0000 0x???? 0x8000 ROM - 00040004 %000F0000: 000F8000 0x8000 0x8000 ROM 00040043 %000F0000: 000E0000 0x???? 0x8000 ROM - 00040004 %000F8000: 000F8000 0x8000 0x8000 ROM 00040045 %000F8000: 000E0000 0x???? 0x8000 ROM - 00040048 %00100000: 00100000 0x8000 0x8000 RAM 00040048 %00100000: 00100000 0x8000 0x8000 RAM - 0004004A %00108000: 00108000 0x8000 0x8000 RAM 0004004A %00108000: 00108000 0x8000 0x8000 RAM - 0004004C %00110000: 00110000 0x8000 0x8000 RAM 0004004C %00110000: 00110000 0x8000 0x8000 RAM - 0004004E %00118000: 00118000 0x8000 0x8000 RAM 0004004E %00118000: 00118000 0x8000 0x8000 RAM - 00040050 %00120000: 00120000 0x8000 0x8000 RAM 00040050 %00120000: 00120000 0x8000 0x8000 RAM - 00040052 %00128000: 00128000 0x8000 0x8000 RAM 00040052 %00128000: 00128000 0x8000 0x8000 RAM - 00040054 %00130000: 00130000 0x8000 0x8000 RAM 00040054 %00130000: 00130000 0x8000 0x8000 RAM - 00040056 %00138000: 00138000 0x8000 0x8000 RAM 00040056 %00138000: 00138000 0x8000 0x8000 RAM - 00040058 %00140000: 00140000 0x8000 0x8000 RAM 00040058 %00140000: 00140000 0x8000 0x8000 RAM - 0004005A %00148000: 00148000 0x8000 0x8000 RAM 0004005A %00148000: 00148000 0x8000 0x8000 RAM - 0004005C %00150000: 00150000 0x8000 0x8000 RAM 0004005C %00150000: 00150000 0x8000 0x8000 RAM - 0004005E %00158000: 00158000 0x8000 0x8000 RAM 0004005E %00158000: 00158000 0x8000 0x8000 RAM - 00040060 %00160000: 00160000 0x8000 0x8000 RAM 00040060 %00160000: 00160000 0x8000 0x8000 RAM - 00040062 %00168000: 00168000 0x8000 0x8000 RAM 00040062 %00168000: 00168000 0x8000 0x8000 RAM - 00040064 %00170000: 00170000 0x8000 0x8000 RAM 00040064 %00170000: 00170000 0x8000 0x8000 RAM - 00040066 %00178000: 00178000 0x8000 0x8000 RAM 00040066 %00178000: 00178000 0x8000 0x8000 RAM - 00040068 %00180000: 00180000 0x8000 0x8000 RAM 00040068 %00180000: 00180000 0x8000 0x8000 RAM - 0004006A %00188000: 00188000 0x8000 0x8000 RAM 0004006A %00188000: 00188000 0x8000 0x8000 RAM - 0004006C %00190000: 00190000 0x8000 0x8000 RAM 0004006C %00190000: 00190000 0x8000 0x8000 RAM - 0004006E %00198000: 00198000 0x8000 0x8000 RAM 0004006E %00198000: 00198000 0x8000 0x8000 RAM - 00040070 %001A0000: 001A0000 0x8000 0x8000 RAM 00040070 %001A0000: 001A0000 0x8000 0x8000 RAM - 00040072 %001A8000: 001A8000 0x8000 0x8000 RAM 00040072 %001A8000: 001A8000 0x8000 0x8000 RAM - 00040074 %001B0000: 001B0000 0x8000 0x8000 RAM 00040074 %001B0000: 001B0000 0x8000 0x8000 RAM - 00040076 %001B8000: 001B8000 0x8000 0x8000 RAM 00040076 %001B8000: 001B8000 0x8000 0x8000 RAM - 00040078 %001C0000: 001C0000 0x8000 0x8000 RAM 00040078 %001C0000: 001C0000 0x8000 0x8000 RAM - 0004007A %001C8000: 001C8000 0x8000 0x8000 RAM 0004007A %001C8000: 001C8000 0x8000 0x8000 RAM - 0004007C %001D0000: 001D0000 0x8000 0x8000 RAM 0004007C %001D0000: 001D0000 0x8000 0x8000 RAM - 0004007E %001D8000: 001D8000 0x8000 0x8000 RAM 0004007E %001D8000: 001D8000 0x8000 0x8000 RAM - 00040080 %001E0000: 001E0000 0x8000 0x8000 RAM 00040080 %001E0000: 001E0000 0x8000 0x8000 RAM - 00040082 %001E8000: 001E8000 0x8000 0x8000 RAM 00040082 %001E8000: 001E8000 0x8000 0x8000 RAM - 00040084 %001F0000: 001F0000 0x8000 0x8000 RAM 00040084 %001F0000: 001F0000 0x8000 0x8000 RAM - 00040086 %001F8000: 001F8000 0x8000 0x8000 RAM 00040086 %001F8000: 001F8000 0x8000 0x8000 RAM - 0004002E %00FA0000: 00FA0000 0x8000 0x8000 RAM 0004002E %00FA0000: 00FA0000 0x8000 0x8000 RAM - 00040030 %00FA8000: 00FA8000 0x8000 0x8000 RAM 00040030 %00FA8000: 00FA8000 0x8000 0x8000 RAM - 00040032 %00FB0000: 00FB0000 0x8000 0x8000 RAM 00040032 %00FB0000: 00FB0000 0x8000 0x8000 RAM - 00040034 %00FB8000: 00FB8000 0x8000 0x8000 RAM 00040034 %00FB8000: 00FB8000 0x8000 0x8000 RAM - 00040036 %00FC0000: 00FC0000 0x8000 0x8000 RAM 00040036 %00FC0000: 00FC0000 0x8000 0x8000 RAM - 00040038 %00FC8000: 00FC8000 0x8000 0x8000 RAM 00040038 %00FC8000: 00FC8000 0x8000 0x8000 RAM - 0004003A %00FD0000: 00FD0000 0x8000 0x8000 RAM 0004003A %00FD0000: 00FD0000 0x8000 0x8000 RAM - 0004003C %00FD8000: 00FD8000 0x8000 0x8000 RAM 0004003C %00FD8000: 00FD8000 0x8000 0x8000 RAM - 0004003E %00FE0000: 00FE0000 0x8000 0x8000 RAM 0004003E %00FE0000: 00FE0000 0x8000 0x8000 RAM - 00040040 %00FE8000: 00FE8000 0x8000 0x8000 RAM 00040040 %00FE8000: 00FE8000 0x8000 0x8000 RAM - 00040042 %00FF0000: 00FF0000 0x8000 0x8000 RAM 00040042 %00FF0000: 00FF0000 0x8000 0x8000 RAM - 00040044 %00FF8000: 00FF8000 0x8000 0x8000 RAM 00040044 %00FF8000: 00FF8000 0x8000 0x8000 RAM - 00040046 %80C00000: 80C00000 0x0001 0x8000 H/W 00040046 %80C00000: 80C00000 0x0001 0x8000 H/W - 00040004 %FFFF0000: 000F8000 0x8000 0x8000 ROM 00040004 %FFFF0000: 000F8000 0x8000 0x8000 ROM - 00040004 %FFFF8000: 000F8000 0x8000 0x8000 ROM 00040004 %FFFF8000: 000F8000 0x8000 0x8000 ROM - -But when we stopped here next, it was no longer good: - - breakpoint hit: F000:F498 (exec) - stopped (27572162 ops, 443166 cycles, 143 ms, 3099063 hz) - EAX=0000FF00 EBX=00000080 ECX=0000270F EDX=0000004A - ESP=000000FE EBP=00000000 ESI=00007FB6 EDI=00008000 - SS=0030 DS=0040 ES=0000 FS=0000 GS=0304 PS=00000046 V0 D0 I0 T0 S0 Z1 A0 P1 C0 - F000:F498 2E CS: - F000:F499 0F01167E07 LGDT [077E] - - F000:077E 0047 0730 00FF FFFF - 0000 0000 87AA 0018 G.0............. - F000:078E 87F8 0018 8828 0018 - 0F2E 1601 0778 200F ....(.......x.. - - %00FF0730 0000 0000 0000 0000 - 0000 0000 0000 0000 ................ - %00FF0740 0000 0000 0000 0000 - 0000 0000 0000 0000 ................ - -Turns out this is because the A20 line has been disabled. - -------------------------------------------------------------------------------------------------- - +This code will fail if the A20 line is off. Until I can figure out why this code is being run +with A20 off, I've worked around it by enabling A20 internally whenever protected-mode is active. diff --git a/modules/pcjs/lib/bus.js b/modules/pcjs/lib/bus.js index 14277a7fc..db0357690 100644 --- a/modules/pcjs/lib/bus.js +++ b/modules/pcjs/lib/bus.js @@ -476,13 +476,6 @@ Bus.prototype.getA20 = function() */ Bus.prototype.setA20 = function(fEnable) { - /* - * TODO: Until I determine why the Compaq DeskPro 386 ROM is switching to protected-mode - * with the A20 line disabled, resulting in an almost immediate crash, I'm ignoring its requests - * to turn A20 off. - */ - if (I386 && COMPAQ386) fEnable = true; - if (this.nBusWidth > 20) { var addrMask = (this.busMask & ~0x100000) | (fEnable? 0x100000 : 0); if (addrMask != this.busMask) { diff --git a/modules/pcjs/lib/chipset.js b/modules/pcjs/lib/chipset.js index 0d4f7aef9..02aac33ab 100644 --- a/modules/pcjs/lib/chipset.js +++ b/modules/pcjs/lib/chipset.js @@ -267,7 +267,7 @@ ChipSet.MODEL_5160 = 5160; // used in reference to the 1st 5160 BIO ChipSet.MODEL_5170 = 5170; // used in reference to the 1st 5170 BIOS, dated Jan 10, 1984 /* - * The following are fake model numbers, used only to document issues/features of note in later IBM PC AT BIOS revisions. + * The following are fake model numbers, used only to document issues/features in later IBM PC AT BIOS revisions. */ ChipSet.MODEL_5170_REV2 = 5170.2; // used in reference to the 2nd 5170 BIOS, dated Jun 10, 1985 ChipSet.MODEL_5170_REV3 = 5170.3; // used in reference to the 3rd 5170 BIOS, dated Nov 15, 1985 @@ -408,14 +408,14 @@ ChipSet.DMA_MASK = { }; ChipSet.DMA_MODE = { - CHANNEL: 0x03, - XFER: 0x0C, - XFER_VERIFY: 0x00, - XFER_WRITE: 0x04, - XFER_READ: 0x08, + CHANNEL: 0x03, // bits 0-1 select 1 of 4 possible channels + TYPE: 0x0C, // bits 2-3 select 1 of 3 valid (4 possible) transfer types + TYPE_VERIFY: 0x00, // pseudo transfer (generates addresses, responds to EOP, but nothing is moved) + TYPE_WRITE: 0x04, // write to memory (move data FROM an I/O device; eg, reading a sector from a disk) + TYPE_READ: 0x08, // read from memory (move data TO an I/O device; eg, writing a sector to a disk) AUTOINIT: 0x10, DECREMENT: 0x20, // clear for INCREMENT - MODE: 0xC0, + MODE: 0xC0, // bits 6-7 select 1 of 4 possible transfer modes MODE_DEMAND: 0x00, MODE_SINGLE: 0x40, MODE_BLOCK: 0x80, @@ -1174,8 +1174,18 @@ ChipSet.prototype.reset = function(fHard) * TODO: Provide more control over these 8042 "Input Port" bits (eg, the keyboard lock) */ this.b8042InPort = ChipSet.KBC.INPORT.MFG_OFF | ChipSet.KBC.INPORT.KBD_UNLOCKED; - if (this.getSWMemorySize() >= 512) this.b8042InPort |= ChipSet.KBC.INPORT.ENABLE_256KB; - if (this.getSWVideoMonitor() == ChipSet.MONITOR.MONO) this.b8042InPort |= ChipSet.KBC.INPORT.MONO; + + if (this.getSWMemorySize() >= 512) { + this.b8042InPort |= ChipSet.KBC.INPORT.ENABLE_256KB; + } + + if (this.getSWVideoMonitor() == ChipSet.MONITOR.MONO) { + this.b8042InPort |= ChipSet.KBC.INPORT.MONO; + } + + if (COMPAQ386 && this.model == ChipSet.MODEL_DESKPRO386) { + this.b8042InPort |= ChipSet.KBC.INPORT.COMPAQ_NO80387 | ChipSet.KBC.INPORT.COMPAQ_NOWEITEK; + } this.b8042OutPort = ChipSet.KBC.OUTPORT.NO_RESET | ChipSet.KBC.OUTPORT.A20_ON; @@ -2381,11 +2391,11 @@ ChipSet.prototype.inDMAChannelAddr = function(iDMAC, iChannel, port, addrFrom) controller.bIndex ^= 0x1; /* * Technically, aTimers[1].fOut is what drives DMA requests for DMA channel 0 (ChipSet.DMA_REFRESH), - * every 15us, once the BIOS has initialized the channel's "mode" with MODE_SINGLE, INCREMENT, AUTOINIT, and XFER_READ (0x58) - * and initialized TIMER1 appropriately. + * every 15us, once the BIOS has initialized the channel's "mode" with MODE_SINGLE, INCREMENT, AUTOINIT, + * and TYPE_READ (0x58) and initialized TIMER1 appropriately. * - * However, we don't need to be that particular. Simply simulate an ever-increasing address after every read of the full - * DMA channel 0 address. + * However, we don't need to be that particular. Simply simulate an ever-increasing address after every + * read of the full DMA channel 0 address. */ if (!iDMAC && iChannel == ChipSet.DMA_REFRESH && !controller.bIndex) { channel.addrCurrent[0]++; @@ -2443,9 +2453,10 @@ ChipSet.prototype.inDMAChannelCount = function(iDMAC, iChannel, port, addrFrom) /* * Technically, aTimers[1].fOut is what drives DMA requests for DMA channel 0 (ChipSet.DMA_REFRESH), * every 15us, once the BIOS has initialized the channel's "mode" with MODE_SINGLE, INCREMENT, AUTOINIT, - * and XFER_READ (0x58) and initialized TIMER1 appropriately. + * and TYPE_READ (0x58) and initialized TIMER1 appropriately. * - * However, we don't need to be that particular. Simply simulate an ever-decreasing count after every read of the full DMA channel 0 count. + * However, we don't need to be that particular. Simply simulate an ever-decreasing count after every + * read of the full DMA channel 0 count. */ if (!iDMAC && iChannel == ChipSet.DMA_REFRESH && !controller.bIndex) { channel.countCurrent[0]--; @@ -2731,7 +2742,7 @@ ChipSet.prototype.inDMAPageSpare = function(iSpare, port, addrFrom) ChipSet.prototype.outDMAPageSpare = function(iSpare, port, bOut, addrFrom) { /* - * TODO: Remove the DEBUG-only DESKPRO386 code once we're done debugging DeskPro 386 ROMs; + * TODO: Remove this DEBUG-only DESKPRO386 code once we're done debugging DeskPro 386 ROMs; * it enables logging of all DeskPro ROM checkpoint I/O to port 0x84. */ if (this.messageEnabled(Messages.DMA | Messages.PORT) || DEBUG && this.model == ChipSet.MODEL_DESKPRO386 && port == 0x84) { @@ -2789,10 +2800,10 @@ ChipSet.prototype.connectDMA = function(iDMAChannel, component, sFunction, obj) * @param {number} iDMAChannel * @param {function(boolean)} [done] * - * For DMA_MODE_XFER_WRITE transfers, fnTransfer(-1) must return bytes as long as we request them (although it may + * For DMA_MODE.TYPE_WRITE transfers, fnTransfer(-1) must return bytes as long as we request them (although it may * return -1 if it runs out of bytes prematurely). * - * Similarly, for DMA_MODE_XFER_READ transfers, fnTransfer(b) must accept bytes as long as we deliver them (although + * Similarly, for DMA_MODE.TYPE_READ transfers, fnTransfer(b) must accept bytes as long as we deliver them (although * it is certainly free to ignore bytes it no longer wants). */ ChipSet.prototype.requestDMA = function(iDMAChannel, done) @@ -2846,7 +2857,7 @@ ChipSet.prototype.advanceDMA = function(channel, fInit) { if (fInit) { channel.count = (channel.countCurrent[1] << 8) | channel.countCurrent[0]; - channel.xfer = (channel.mode & ChipSet.DMA_MODE.XFER); + channel.type = (channel.mode & ChipSet.DMA_MODE.TYPE); channel.fWarning = channel.fError = false; if (DEBUG && DEBUGGER) { channel.cbDebug = channel.count + 1; @@ -2876,12 +2887,12 @@ ChipSet.prototype.advanceDMA = function(channel, fInit) var addr = (channel.bPage << 16) | (channel.addrCurrent[1] << 8) | channel.addrCurrent[0]; if (DEBUG && DEBUGGER && channel.sAddrDebug === null) { channel.sAddrDebug = str.toHex(addr >> 4, 4) + ":" + str.toHex(addr & 0xf, 4); - if (this.messageEnabled(this.messageBitsDMA(iDMAChannel)) && channel.xfer != ChipSet.DMA_MODE.XFER_WRITE) { + if (this.messageEnabled(this.messageBitsDMA(iDMAChannel)) && channel.type != ChipSet.DMA_MODE.TYPE_WRITE) { this.printMessage("advanceDMA(" + iDMAChannel + ") transferring " + channel.cbDebug + " bytes from " + channel.sAddrDebug, true); this.dbg.doDump("db", channel.sAddrDebug, "l" + channel.cbDebug); } } - if (channel.xfer == ChipSet.DMA_MODE.XFER_WRITE) { + if (channel.type == ChipSet.DMA_MODE.TYPE_WRITE) { fAsyncRequest = true; (function advanceDMAWrite(addrCur) { channel.fnTransfer.call(channel.component, channel.obj, -1, function onTransferDMA(b, fAsync, obj, off) { @@ -2893,7 +2904,7 @@ ChipSet.prototype.advanceDMA = function(channel, fInit) channel.fWarning = true; } /* - * TODO: Determine whether to abort, as we do for DMA_MODE_XFER_READ. + * TODO: Determine whether to abort, as we do for DMA_MODE.TYPE_READ. */ b = 0xff; } @@ -2916,7 +2927,7 @@ ChipSet.prototype.advanceDMA = function(channel, fInit) }); }(addr)); } - else if (channel.xfer == ChipSet.DMA_MODE.XFER_READ) { + else if (channel.type == ChipSet.DMA_MODE.TYPE_READ) { /* * TODO: Determine whether we should support async dmaWrite() functions (currently not required) */ @@ -2924,15 +2935,20 @@ ChipSet.prototype.advanceDMA = function(channel, fInit) if (channel.fnTransfer.call(channel.component, channel.obj, b) < 0) { /* * In this case, I think I have no choice but to terminate the DMA operation in response to a failure, - * because the ROM BIOS FDC.REG_DATA.CMD.FORMAT_TRACK command specifies a count that is MUCH too large (a side-effect - * of the ROM BIOS using the same "DMA_SETUP" code for reads, writes AND formats). + * because the ROM BIOS FDC.REG_DATA.CMD.FORMAT_TRACK command specifies a count that is MUCH too large + * (a side-effect of the ROM BIOS using the same "DMA_SETUP" code for reads, writes AND formats). */ channel.fError = true; } } + else if (channel.type == ChipSet.DMA_MODE.TYPE_VERIFY) { + /* + * Nothing to read or write; just call updateDMA() + */ + } else { if (DEBUG && this.messageEnabled(Messages.DMA | Messages.WARN)) { - this.printMessage("advanceDMA(" + iDMAChannel + ") unsupported xfer mode: " + str.toHexWord(channel.xfer), true); + this.printMessage("advanceDMA(" + iDMAChannel + ") unsupported transfer type: " + str.toHexWord(channel.type), true); } channel.fError = true; } @@ -2986,7 +3002,7 @@ ChipSet.prototype.updateDMA = function(channel) channel.component = channel.obj = null; } - if (DEBUG && this.messageEnabled(this.messageBitsDMA(iDMAChannel)) && channel.xfer == ChipSet.DMA_MODE.XFER_WRITE && channel.sAddrDebug) { + if (DEBUG && this.messageEnabled(this.messageBitsDMA(iDMAChannel)) && channel.type == ChipSet.DMA_MODE.TYPE_WRITE && channel.sAddrDebug) { this.printMessage("updateDMA(" + iDMAChannel + ") transferred " + channel.cbDebug + " bytes to " + channel.sAddrDebug, true); this.dbg.doDump("db", channel.sAddrDebug, "l" + channel.cbDebug); } @@ -4078,7 +4094,7 @@ ChipSet.prototype.inPPIC = function(port, addrFrom) /* * The ROM BIOS polls this port incessantly during its memory tests, checking for memory parity errors - * (which of course we never report), so we further restrict these port messages to MESSAGE_MEM. + * (which of course we never report), so we further restrict these port messages to Messages.MEM. */ this.printMessageIO(port, null, addrFrom, "PPI_C", b, Messages.CHIPSET | Messages.MEM); return b; @@ -4301,8 +4317,8 @@ ChipSet.prototype.in8042RWReg = function(port, addrFrom) */ var b = this.bPPIB & ~(ChipSet.KBC.RWREG.NMI_ERROR | ChipSet.KBC.RWREG.REFRESH_BIT) | ((this.cpu.getCycles() & 0x40)? ChipSet.KBC.RWREG.REFRESH_BIT : 0); /* - * Thanks to the WAITF function, this has become a very "busy" port, so let's not generate messages - * unless both MESSAGE_8042 *and* MESSAGE_LOG are set. + * Thanks to the WAITF function, this has become a very "busy" port, so if this generates too + * many messages, try adding Messages.LOG to the criteria. */ this.printMessageIO(port, null, addrFrom, "8042_RWREG", b, Messages.C8042); return b; @@ -4347,7 +4363,7 @@ ChipSet.prototype.in8042Status = function(port, addrFrom) * * This provides a single poll delay, so that the aforementioned "flush" won't toss our response. * If longer delays are needed down the road, we may need to set a delay count in the upper (hidden) - * bits of b8042Status, instead of using a single "OUTBUFF_DELAY" bit. + * bits of b8042Status, instead of using a single delay bit. */ if (this.b8042Status & ChipSet.KBC.STATUS.OUTBUFF_DELAY) { this.b8042Status |= ChipSet.KBC.STATUS.OUTBUFF_FULL; @@ -4391,16 +4407,11 @@ ChipSet.prototype.out8042InBuffCmd = function(port, bOut, addrFrom) break; case ChipSet.KBC.CMD.WRITE_CMD: // 0x60 - case ChipSet.KBC.CMD.WRITE_OUTPORT: // 0xD1 /* - * No further action required for this first group of commands; more data is expected via out8042InBuffData() + * No further action required for this command; more data is expected via out8042InBuffData() */ break; - case ChipSet.KBC.CMD.READ_INPORT: // 0xC0 - this.set8042OutBuff(this.b8042InPort); - break; - case ChipSet.KBC.CMD.DISABLE_KBD: // 0xAD this.set8042CmdData(this.b8042CmdData | ChipSet.KBC.DATA.CMD.NO_CLOCK); if (DEBUG) this.printMessage("keyboard disabled", Messages.KEYBOARD | Messages.PORT); @@ -4432,6 +4443,20 @@ ChipSet.prototype.out8042InBuffCmd = function(port, bOut, addrFrom) this.set8042OutBuff(ChipSet.KBC.DATA.INTF_TEST.OK); break; + case ChipSet.KBC.CMD.READ_INPORT: // 0xC0 + this.set8042OutBuff(this.b8042InPort); + break; + + case ChipSet.KBC.CMD.READ_OUTPORT: // 0xD0 + this.set8042OutBuff(this.b8042OutPort); + break; + + case ChipSet.KBC.CMD.WRITE_OUTPORT: // 0xD1 + /* + * No further action required for this command; more data is expected via out8042InBuffData() + */ + break; + case ChipSet.KBC.CMD.READ_TEST: // 0xE0 this.set8042OutBuff((this.b8042CmdData & ChipSet.KBC.DATA.CMD.NO_CLOCK)? 0 : ChipSet.KBC.TESTPORT.KBD_CLOCK); break; @@ -4516,7 +4541,9 @@ ChipSet.prototype.set8042OutBuff = function(b) ChipSet.prototype.set8042OutPort = function(b) { this.b8042OutPort = b; - this.bus.setA20(!!(b & ChipSet.KBC.OUTPORT.A20_ON)); + + this.cpu.setA20(!!(b & ChipSet.KBC.OUTPORT.A20_ON)); + if (!(b & ChipSet.KBC.OUTPORT.NO_RESET)) { /* * Bit 0 of the 8042's output port is connected to RESET. Normally, it's "pulsed" with the diff --git a/modules/pcjs/lib/debugger.js b/modules/pcjs/lib/debugger.js index 2499a3e3f..46590e737 100644 --- a/modules/pcjs/lib/debugger.js +++ b/modules/pcjs/lib/debugger.js @@ -210,7 +210,7 @@ if (DEBUGGER) { 0x33: Messages.MOUSE }; - Debugger.aCommands = { + Debugger.COMMANDS = { '?': "help", 'a [#]': "assemble", 'b [#]': "breakpoint", @@ -1660,7 +1660,7 @@ if (DEBUGGER) { * messageDump(bitMessage, fnDumper) * * @this {Debugger} - * @param {number} bitMessage is one Debugger MESSAGE_* category flag + * @param {number} bitMessage is one Messages category flag * @param {function(string)} fnDumper is a function the Debugger can use to dump data for that category * @return {boolean} true if successfully registered, false if not */ @@ -1936,7 +1936,7 @@ if (DEBUGGER) { * @param {number|null} [addrFrom] * @param {string|null} [name] of the port, if any * @param {number|null} [bIn] is the input value, if known, on an input operation - * @param {number} [bitsMessage] is one or more Debugger MESSAGE_* category flag(s) + * @param {number} [bitsMessage] is one or more Messages category flag(s) */ Debugger.prototype.messageIO = function(component, port, bOut, addrFrom, name, bIn, bitsMessage) { @@ -2414,7 +2414,7 @@ if (DEBUGGER) { * The rest of the instruction tracking logic can only be performed if historyInit() has allocated * the necessary data structures; note that there is no explicit UI for enabling/disabling history, * other than adding/removing breakpoints, simply because it's breakpoints that trigger the call to - * checkInstruction() -- well, OK, and a few other things now, like enabling MESSAGE_INT messages. + * checkInstruction() -- well, OK, and a few other things now, like enabling Messages.INT messages. */ if (nState >= 0 && this.aaOpcodeCounts.length) { this.cInstructions++; @@ -2734,7 +2734,17 @@ if (DEBUGGER) { if (aBreak != this.aBreakExec) { this.bus.addMemBreak(this.getAddr(aAddr), aBreak == this.aBreakWrite); } - if (!fTemp) this.println("breakpoint enabled: " + this.hexAddr(aAddr) + " (" + aBreak[0] + ")"); + if (fTemp) { + /* + * Force temporary breakpoints to be interpreted as physical breakpoints + * (hence the assertion that there IS a physical address stored in aAddr); + * this allows us to step over calls or interrupts that change the processor mode + */ + aAddr[0] = -1; + this.assert(aAddr[2]); + } else { + this.println("breakpoint enabled: " + this.hexAddr(aAddr) + " (" + aBreak[0] + ")"); + } this.historyInit(); return true; } @@ -3826,8 +3836,8 @@ if (DEBUGGER) { Debugger.prototype.doHelp = function() { var s = "commands:"; - for (var sCommand in Debugger.aCommands) { - s += '\n' + sCommand + " ".substr(0, 7-sCommand.length) + Debugger.aCommands[sCommand]; + for (var sCommand in Debugger.COMMANDS) { + s += '\n' + sCommand + " ".substr(0, 7-sCommand.length) + Debugger.COMMANDS[sCommand]; } if (!this.checksEnabled()) s += "\nnote: frequency/history disabled if no exec breakpoints"; this.println(s); @@ -4969,6 +4979,7 @@ if (DEBUGGER) { break; } } while (fPrefix); + if (this.fProcStep) { this.setTempBreakpoint(aAddr); if (!this.runCPU()) { diff --git a/modules/pcjs/lib/mouse.js b/modules/pcjs/lib/mouse.js index 3cbebbf63..20099ae59 100644 --- a/modules/pcjs/lib/mouse.js +++ b/modules/pcjs/lib/mouse.js @@ -616,7 +616,7 @@ Mouse.prototype.notifyMCR = function(bMCR) * driver shuts down and Windows exits, since it DID turn RTS on), that doesn't appear to actually happen. * At the very least, Windows will have (re)masked the serial port's IRQ, so what does it matter? Not much, * I just would have preferred that fActive properly reflect whether we should continue dispatching mouse - * events, displaying MESSAGE_MOUSE messages, etc. + * events, displaying MOUSE messages, etc. * * We could ask the ChipSet component to notify the SerialPort component whenever its IRQ is masked/unmasked, * and then have the SerialPort pass that notification on to us, but I'm assuming that in the real world, diff --git a/modules/pcjs/lib/state.js b/modules/pcjs/lib/state.js index 0b4f079cb..fb3bfd502 100644 --- a/modules/pcjs/lib/state.js +++ b/modules/pcjs/lib/state.js @@ -389,7 +389,7 @@ State.prototype = { * messageEnabled(bitsMessage) * * @this {State} - * @param {number} [bitsMessage] is one or more Debugger MESSAGE_* category flag(s) + * @param {number} [bitsMessage] is one or more Messages category flag(s) * @return {boolean} */ messageEnabled: function(bitsMessage) { diff --git a/modules/pcjs/lib/x86cpu.js b/modules/pcjs/lib/x86cpu.js index e20b7b04a..7f933efd6 100644 --- a/modules/pcjs/lib/x86cpu.js +++ b/modules/pcjs/lib/x86cpu.js @@ -186,6 +186,11 @@ function X86CPU(parmsCPU) * so that if/when we call restore(), it will have something to fill in. */ this.resetRegs(); + + /* + * Initially, the logical A20 state should be true, but from this point on, it's up to the machine to decide. + */ + this.fA20 = true; } Component.subclass(X86CPU, CPU); @@ -637,10 +642,42 @@ X86CPU.prototype.initMemory = function(aMemBlocks, blockShift, blockLimit, block } }; +/** + * setA20(fEnable) + * + * setA20() used to ONLY be a Bus function, but we now route all setA20() calls through the CPU, + * so that the CPU can maintain a logical A20 state (fA20), separate from the physical A20 state. + * + * In real-mode, all cpu.setA20() calls go straight to bus.setA20(), and we update the logical + * A20 state (fA20); in protected-mode, we only update the logical A20 state (fA20). + * + * In addition, when transitioning from real-mode to protected-mode, we call bus.setA20(true), and + * when transitioning back to real-mode, we call bus.setA20(fA20). See setProtMode() for details. + * + * This gives the CPU an unusual amount of control over the A20 line, but it protects us from "bad" + * protected-mode code that fails to ensure A20 is enabled; I've run into code in the Compaq DeskPro + * 386 ROM BIOS that fails without this work-around. This seems like a fairly safe hack, because + * 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. + * + * @this {X86CPU} + * @param {boolean} fEnable is true to enable A20, false to disable + */ +X86CPU.prototype.setA20 = function(fEnable) +{ + this.fA20 = fEnable; + if (!(this.regCR0 & X86.CR0.MSW.PE)) { + this.bus.setA20(fEnable); + } +}; + /** * setAddressMask(busMask) * - * Notification from Bus.setA20(), called whenever the A20 line changes. + * Notification from Bus.setA20(), called whenever the physical A20 line changes; this is + * independent of the CPU's own logical A20 state (fA20). * * @this {X86CPU} * @param {number} busMask @@ -1173,6 +1210,7 @@ X86CPU.prototype.setAddrSize = function() this.aOpModGrpWord = X86ModW.aOpModGrp; } else { if (this.addrSize == 2) { + this.getAddr = this.getShort; this.aOpModRegByte = X86ModB16.aOpModReg; this.aOpModMemByte = X86ModB16.aOpModMem; this.aOpModGrpByte = X86ModB16.aOpModGrp; @@ -1180,6 +1218,7 @@ X86CPU.prototype.setAddrSize = function() this.aOpModMemWord = X86ModW16.aOpModMem; this.aOpModGrpWord = X86ModW16.aOpModGrp; } else { + this.getAddr = this.getLong; this.aOpModRegByte = X86ModB32.aOpModReg; this.aOpModMemByte = X86ModB32.aOpModMem; this.aOpModGrpByte = X86ModB32.aOpModGrp; @@ -1304,7 +1343,7 @@ X86CPU.prototype.checkIntNotify = function(nInt) } } /* - * The enabling of MESSAGE_INT messages is one of the criteria that's also included in the Debugger's + * The enabling of INT messages is one of the criteria that's also included in the Debugger's * checksEnabled() function, and therefore in fDebugCheck, so for maximum speed, we check fDebugCheck first. */ if (DEBUGGER && this.aFlags.fDebugCheck) { @@ -1378,6 +1417,9 @@ X86CPU.prototype.checkIntReturn = function(addr) * (ie, LLDT, LTR, SLDT, STR are invalid instructions in real-mode, and are among the opcode handlers that we * update here). * + * NOTE: Ideally, this function would do its work ONLY on mode *transitions*, but we assume calls to setProtMode() + * are sufficiently infrequent that it doesn't really matter. + * * @this {X86CPU} * @param {boolean} [fProt] (use the current MSW PE bit if not specified) */ @@ -1398,6 +1440,16 @@ X86CPU.prototype.setProtMode = function(fProt) this.segFS.updateMode(fProt); this.segGS.updateMode(fProt); } + /* + * Work-around to update the A20 line whenever transitioning modes; see cpu.setA20() for details. + * + * Unfortunately, we can't immediately update the physical A20 line on return to real-mode, because + * segment registers are likely still loaded with base addresses above 1Mb, so we leave the physical + * A20 line enabled for now. + * + * if (this.bus) this.bus.setA20(fProt? true : this.fA20); + */ + if (this.bus && fProt) this.bus.setA20(true); }; /** @@ -1411,7 +1463,7 @@ X86CPU.prototype.setProtMode = function(fProt) X86CPU.prototype.saveProtMode = function() { if (this.addrGDT != null) { - return [this.regCR0, this.addrGDT, this.addrGDTLimit, this.addrIDT, this.addrIDTLimit, this.segLDT.save(), this.segTSS.save(), this.nIOPL]; + return [this.regCR0, this.addrGDT, this.addrGDTLimit, this.addrIDT, this.addrIDTLimit, this.segLDT.save(), this.segTSS.save(), this.nIOPL, this.fA20]; } return null; }; @@ -1435,6 +1487,7 @@ X86CPU.prototype.restoreProtMode = function(a) this.segLDT.restore(a[5]); this.segTSS.restore(a[6]); this.nIOPL = a[7]; + this.fA20 = (a[8] !== undefined? a[8] : this.bus.getA20()); this.setProtMode(); } }; @@ -1495,6 +1548,7 @@ X86CPU.prototype.restore = function(data) * properly AND to ensure the CPU's default ADDRESS and OPERAND sizes are set properly. */ this.setCSIP(a[0], this.segCS.sel); + /* * It's also important to call setSP(), so that the linear SP register (regLSP) will be updated properly; * we also need to call setSS(), to ensure that the lower and upper stack limits are properly initialized. @@ -2370,8 +2424,8 @@ X86CPU.prototype.setMSW = function(w) w |= (this.regCR0 & X86.CR0.MSW.PE) | X86.CR0.MSW.ON; this.regCR0 = (this.regCR0 & ~X86.CR0.MSW.MASK) | (w & X86.CR0.MSW.MASK); /* - * Since the 80286 cannot return to real-mode via this instruction, the only transition we - * must worry about is to protected-mode. And don't worry, there's no harm calling setProtMode() + * Since the 80286 cannot return to real-mode via this instruction, the only transition + * we must worry about is to protected-mode. And there's no harm calling setProtMode() * if the CPU is already in protected-mode (we could certainly optimize the call out in that * case, but this instruction isn't used frequently enough to warrant it). */ @@ -3127,11 +3181,34 @@ X86CPU.prototype.getIPLong = function() return l; }; +/** + * getIPAddr() + * + * @this {X86CPU} + * @return {number} word at the current IP; IP advanced by 2 or 4, depending on address size + */ +X86CPU.prototype.getIPAddr = function() +{ + /* + * TODO: Add PREFETCH support to this function + */ + var w = this.getAddr(this.regLIP); + if (BACKTRACK) { + this.bus.updateBackTrackCode(this.regLIP, this.backTrack.btiMemLo); + this.bus.updateBackTrackCode(this.regLIP + 1, this.backTrack.btiMemHi); + } + this.regLIP += this.addrSize; + if (this.regLIP > this.regLIPLimit) { + this.setIP(this.regLIP - this.segCS.base); + } + return w; +}; + /** * getIPWord() * * @this {X86CPU} - * @return {number} word at the current IP; IP advanced by 2 or 4 + * @return {number} word at the current IP; IP advanced by 2 or 4, depending on operand size */ X86CPU.prototype.getIPWord = function() { diff --git a/modules/pcjs/lib/x86func.js b/modules/pcjs/lib/x86func.js index 77e461401..7431ef82f 100644 --- a/modules/pcjs/lib/x86func.js +++ b/modules/pcjs/lib/x86func.js @@ -1042,7 +1042,7 @@ X86.fnLAR = function LAR(dst, src) X86.fnLCR0 = function LCR0(l) { this.regCR0 = l; - this.setProtMode(!!(this.regCR0 & X86.CR0.MSW.PE)); + this.setProtMode(); }; /** diff --git a/modules/pcjs/lib/x86ops.js b/modules/pcjs/lib/x86ops.js index 3ce6b884d..d0df2878a 100644 --- a/modules/pcjs/lib/x86ops.js +++ b/modules/pcjs/lib/x86ops.js @@ -53,7 +53,8 @@ X86.opADDmb = function ADDmb() * point you can also choose to halt if Messages.HALT is enabled). */ if (DEBUG && !b) { - this.printMessage("suspicious opcode: 0x00 0x00"); + this.printMessage("suspicious opcode: 0x00 0x00", DEBUGGER || this.bitsMessage); + if (DEBUGGER) this.stopCPU(); } this.aOpModMemByte[b].call(this, X86.fnADDb); }; @@ -2493,7 +2494,7 @@ X86.opLAHF = function LAHF() */ X86.opMOVALm = function MOVALm() { - this.regEAX = (this.regEAX & ~0xff) | this.getSOByte(this.segData, this.getIPWord()); + this.regEAX = (this.regEAX & ~0xff) | this.getSOByte(this.segData, this.getIPAddr()); if (BACKTRACK) this.backTrack.btiAL = this.backTrack.btiMemLo; this.nStepCycles -= this.cycleCounts.nOpCyclesMovAM; }; @@ -2505,7 +2506,7 @@ X86.opMOVALm = function MOVALm() */ X86.opMOVAXm = function MOVAXm() { - this.regEAX = (this.regEAX & ~this.dataMask) | this.getSOWord(this.segData, this.getIPWord()); + this.regEAX = (this.regEAX & ~this.dataMask) | this.getSOWord(this.segData, this.getIPAddr()); if (BACKTRACK) { this.backTrack.btiAL = this.backTrack.btiMemLo; this.backTrack.btiAH = this.backTrack.btiMemHi; } @@ -2523,7 +2524,7 @@ X86.opMOVmAL = function MOVmAL() /* * setSOByte() truncates the value as appropriate */ - this.setSOByte(this.segData, this.getIPWord(), this.regEAX); + this.setSOByte(this.segData, this.getIPAddr(), this.regEAX); this.nStepCycles -= this.cycleCounts.nOpCyclesMovMA; }; @@ -2540,7 +2541,7 @@ X86.opMOVmAX = function MOVmAX() /* * setSOWord() truncates the value as appropriate */ - this.setSOWord(this.segData, this.getIPWord(), this.regEAX); + this.setSOWord(this.segData, this.getIPAddr(), this.regEAX); this.nStepCycles -= this.cycleCounts.nOpCyclesMovMA; }; @@ -3188,7 +3189,7 @@ X86.opGRP2wn = function GRP2wn() */ X86.opRETn = function RETn() { - var n = this.getIPWord() << (this.dataSize >> 2); + var n = this.getIPShort() << (this.dataSize >> 2); var newIP = this.popWord(); if (DEBUG) this.printMessage(" returning to " + str.toHex(this.segCS.sel, 4) + ':' + str.toHex(newIP, this.dataSize << 1), this.bitsMessage, true); this.setIP(newIP); @@ -3285,7 +3286,7 @@ X86.opMOVw = function MOVw() */ X86.opENTER = function ENTER() { - var wLocal = this.getIPWord(); + var wLocal = this.getIPShort(); var bLevel = this.getIPByte() & 0x1f; /* * NOTE: 11 is the minimum cycle time for the 80286; the 80186/80188 has different cycle times: 15, 25 and @@ -3330,7 +3331,7 @@ X86.opLEAVE = function LEAVE() */ X86.opRETFn = function RETFn() { - X86.fnRETF.call(this, this.getIPWord()); + X86.fnRETF.call(this, this.getIPShort()); this.nStepCycles -= this.cycleCounts.nOpCyclesRetFn; }; diff --git a/tests/pc/80386/makefile b/tests/pc/80386/makefile index b5084d6c3..6f89190d2 100644 --- a/tests/pc/80386/makefile +++ b/tests/pc/80386/makefile @@ -4,4 +4,4 @@ tests.rom: tests.nasm nasm -f bin tests.nasm -l tests.lst -o tests.rom tests.json: tests.rom - node ../../../modules/filedump/bin/filedump --file=tests.rom --output=tests.json + node ../../../modules/filedump/bin/filedump --file=tests.rom --output=tests.json --overwrite diff --git a/tests/pc/80386/tests.nasm b/tests/pc/80386/tests.nasm index 6c7fd8112..b5cc9e00e 100644 --- a/tests/pc/80386/tests.nasm +++ b/tests/pc/80386/tests.nasm @@ -9,11 +9,14 @@ start: mov eax,0x44332211 mov edx,0x88776655 shrd eax,edx,4 - times 0xfff0-($-$$) db 0x90 ; NOP + call dword 0xf000:start + + times 0xfff0-($-$$) nop bits 16 jmp 0xf000:start - db ' 04/04/15' + db 0x20 + db '04/04/15' db 0xFC ; 0000FFFE FC (Model ID byte) db 0x00 ; 0000FFFF 00 (location of checksum byte)