Gate A20 Under Bochs

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
~
Member
Member
Posts: 1226
Joined: Tue Mar 06, 2007 11:17 am
Libera.chat IRC: ArcheFire

Gate A20 Under Bochs

Post by ~ »

Does anybody know if the following is correct?:
  • Bochs has A20 enabled (set to 1) when it starts
  • A20 is NOT hardwired to 1
  • If, however, it's disabled (set to 0), environments like DOS emulation will freeze (cursor may still be blinking or not)
YouTube:
http://youtube.com/@AltComp126

My x86 emulator/kernel project and software tools/documentation:
http://master.dl.sourceforge.net/projec ... 7z?viasf=1
User avatar
bewing
Member
Member
Posts: 1401
Joined: Wed Feb 07, 2007 1:45 pm
Location: Eugene, OR, US

Re: Gate A20 Under Bochs

Post by bewing »

The A20 gate in bochs is set at compile time, to be either "functional", or hardwired ON.
It can be either way, but default is for it to be "functional" -- that is, it starts out OFF (when control is handed from the bochs BIOS to your bootloader), and you need to turn it on, yourself.
Bochs runs faster if A20 is hardwired ON, but you have to edit your config.h (after you run configure, and before you run make).

DOS generally does not use memory above 1M (unless you tell it to "load high") -- so the A20 gate should have no effect on DOS (usually). But I would think that all OSes should be able to handle either setting, easily.
Last edited by bewing on Wed Oct 29, 2008 12:15 am, edited 3 times in total.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Gate A20 Under Bochs

Post by Brendan »

Hi,
~ wrote:Does anybody know if the following is correct?:
Bochs can emulate the A20 line; however emulating A20 adds a little bit of overhead because Bochs needs to work out if physical addresses should be masked or not, so to improve performance it's possible to configure Bochs so that A20 is always enabled (and so that the A20 test and masking can be skipped).

When a modern computer (including Bochs) is first turned on A20 is enabled. This is because the BIOS ROM entry point is at 0xFFFFFFF0 - if A20 was disabled the computer would crash because the CPU would actually start executing at 0xFFEFFFF0. The BIOS is meant to disable A20 before it boots an OS. If the OS is old then it won't know about A20 and will leave it disabled so that everything works like it used to on very old CPUs, and if the OS is modern it enables A20.

Some BIOSs in real computers might not disable A20 before they boot an OS (but AFAIK this is rare for normal desktop systems), and some computers (e.g. embedded systems and/or thin clients) may be designed for a specific purpose and might not support A20 at all.

A modern OS can't assume A20 is enabled or disabled to begin with, and then it'd test if it A20 is enabled or not (and then it'd enable A20 if it was disabled).

[EDIT] Doh - Bewing was faster!

I don't think DOS itself crashes or anything if A20 is enabled - AFAIK the problem was some dodgy applications that assumed addresses above 1 MiB would wrap (e.g. the real mode address "0xFFFF:0x1010" would wrap around and be 0x00000010 rather than 0x00101000), and it may not have been a dodgy OS that assumed addresses above 1 MiB would wrap. I know modern versions of DOS (e.g. DOS 3.0 and later) support the "high memory area" (it can use the area from 0x00100000 to 0x0010FFEF to store TSRs, etc) and therefore must be aware of A20 and enable it. I'm not sure about earlier versions of DOS (e.g. DOS 1.0), or other ancient OSs. It'd be nice to see a list of old software that does need A20 disabled - it'd be funny if we've had to mess with A20 for years just because of one dodgy text editor (for e.g.) that nobody has used since 1982... ;)
[/EDIT]


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
Love4Boobies
Member
Member
Posts: 2111
Joined: Fri Mar 07, 2008 5:36 pm
Location: Bucharest, Romania

Re: Gate A20 Under Bochs

Post by Love4Boobies »

Sounds a bit like homework to me...
"Computers in the future may weigh no more than 1.5 tons.", Popular Mechanics (1949)
[ Project UDI ]
User avatar
~
Member
Member
Posts: 1226
Joined: Tue Mar 06, 2007 11:17 am
Libera.chat IRC: ArcheFire

Re: Gate A20 Under Bochs

Post by ~ »

Like homework? Of course not.

It's just that I need to know why my test programs tell me that A20 is NOT hardwired under Bochs (it's determined by actually altering it and then putting it back as it was), and is enabled.

And then, when I try to disable it and pass control to the system, the DOS emulation under Bochs just freezes.

If not, run the following programs under Bochs DOS emulation.
The first one tells whether A20 is currently enabled or disabled.

The second one tells whether A20 is hardwired to 1 or not.

The third one turns off A20. THIS program is the one that freezes my Bochs emulation when I disable it. I'm using the default Bochs 2.3.7 for Win32 (June 3, 2008) from SourceForge, using 4MB of emulated RAM in a Pentium III at 450Mhz.

I don't know why that happens, if A20 is not hardwired in Bochs (according to the second program which actually manipulates and alters it)... and it wouldn't be good if it ever happened in a real system.


This is my BXRC configuration file:

Code: Select all

###############################################################
# bochsrc.txt file for DLX Linux disk image.
###############################################################

# how much memory the emulated machine will have
megs: 4

# filename of ROM images
romimage: file="$BXSHARE/BIOS-bochs-latest"
vgaromimage: file="$BXSHARE/VGABIOS-lgpl-latest-cirrus"

vga: extension=cirrus

i440fxsupport: enabled=1, slot1=cirrus


# what disk images will be used 
###floppya: 1_44=a:, status=inserted

floppya: 1_44="c:/win.img", status=inserted

#floppyb: 1_44=floppyb.img, status=inserted
floppyb: 1_44=a:, status=inserted

# hard disk
#ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
#ata0-master: type=disk, path=""$BXSHARE/dlxlinux/hd10meg.img", cylinders=306, heads=4, spt=17

#ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
#ata0-slave:  type=cdrom, path=e:, status=inserted


# choose the boot disk.
boot: floppy

# default config interface is textconfig.
#config_interface: textconfig
#config_interface: wx

#display_library: x
# other choices: win32 sdl wx carbon amigaos beos macintosh nogui rfb term svga

# where do we send log messages?
log: bochsout.txt

# disable the mouse, since DLX is text only
mouse: enabled=0

# enable key mapping, using US layout as default.
#
# NOTE: In Bochs 1.4, keyboard mapping is only 100% implemented on X windows.
# However, the key mapping tables are used in the paste function, so 
# in the DLX Linux example I'm enabling keyboard_mapping so that paste 
# will work.  Cut&Paste is currently implemented on win32 and X windows only.

#keyboard_mapping: enabled=1, map=$BXSHARE/keymaps/x11-pc-us.map
#keyboard_mapping: enabled=1, map=$BXSHARE/keymaps/x11-pc-fr.map
#keyboard_mapping: enabled=1, map=$BXSHARE/keymaps/x11-pc-de.map
#keyboard_mapping: enabled=1, map=$BXSHARE/keymaps/x11-pc-es.map
Program 1:

Code: Select all

bits 16
org 100h




cli
call waitUntilCommandable
	mov AL, 0D0h
	out 64h, AL	

call waitForDataForUs
	;; Read the current port status from port 60h
	xor AX, AX
	in AL, 60h

	;; Is A20 enabled?
	and al,00000010b

	;; Check the result.
        cmp al,0
	jne _a20yes_
         mov dx,a20no
         jmp zzz

        _a20yes_:
         mov dx,a20yes

        zzz:


sti


mov ah,9
int 0x21



mov ah,0x4C
int 0x21






a20yes db "Status: A20 Found Enabled.",13,10,'$'
a20no  db "Status: A20 Found Disabled.",13,10,'$'


;This function waits until the
;Keybard Controller is ready to
;accept commands.
;;
;;;
  waitUntilCommandable:
  push ax


  .waitUntilReady:
   in al,0x64    ;Port 0x64 is the Status
                 ;Register of the keyboard
                 ;controller port located
                 ;on the motherboard.


   test al,00000010b ;We see if bit 1 of the returned
                     ;byte at 0x64 is set to 1, which
                     ;means that the controller is NOT
                     ;ready to accept commands. It means
                     ;"Input Buffer Full (port 0x60 or 0x64);
                     ;don't write yet".



   jnz .waitUntilReady  ;Repeat until bit 1 of byte from
                        ;port 0x64 is cleared to 0 or, in
                        ;other words, until the keyboard
                        ;controller is ready to accept
                        ;commands. It would mean "Input
                        ;Buffer Empty (port 0x60 or 0x64); can
                        ;be written".

     ;Note that writing the Input Buffer through
     ;port 0x64 causes the byte to be interpreted
     ;as a command.
     ;
     ;And if we write the Input Buffer through
     ;port 0x60 causes the byte to be interpreted
     ;as data, usually as a parameter to an immediate
     ;previously sent command at port 0x64.

  pop ax
    ret



;This function waits until the
;Keybard Controller has a data
;byte ready for us to gather.
;;
;;;
  waitForDataForUs:
  push ax



  .waitUntilReady:
   in al,0x64    ;Port 0x64 is the Status
                 ;Register of the keyboard
                 ;controller port located
                 ;on the motherboard.


   test al,00000001b


   jz .waitUntilReady
  pop ax
    ret



Program 2:

Code: Select all

bits 16
org 100h


cli



call waitUntilCommandable
	mov AL, 0D0h
	out 64h, AL	

call waitForDataForUs
	;; Read the current port status from port 60h
	xor AX, AX
	in AL, 60h

        mov [try1],al


	;; Is A20 enabled?
	and al,00000010b

	;; Check the result.
        cmp al,0
	jne _a20yes_
          call enableA20
         jmp zzz

        _a20yes_:
          call disableA20

        zzz:




call waitUntilCommandable
	mov AL, 0D0h
	out 64h, AL	

call waitForDataForUs
	;; Read the current port status from port 60h
	xor AX, AX
	in AL, 60h



     cmp al,[try1]   ;see if A20 is the same after trying to alter it
     je isHardwired
       mov dx,nohardwired
       jmp zzz2


     isHardwired: 
       mov dx,hardwired


     zzz2:   


    mov al,[try1]
    and al,00000010b
    jnz ea20
     call disableA20
     jnz zzz3


    ea20:
     call enableA20


    zzz3:


sti


mov ah,9
int 0x21



mov ah,0x4C
int 0x21






hardwired   db "Status: A20 is Hardwired.",13,10,'$'
nohardwired db "Status: A20 is NOT Hardwired.",13,10,'$'


;This function waits until the
;Keybard Controller is ready to
;accept commands.
;;
;;;
  waitUntilCommandable:
  push ax


  .waitUntilReady:
   in al,0x64    ;Port 0x64 is the Status
                 ;Register of the keyboard
                 ;controller port located
                 ;on the motherboard.


   test al,00000010b ;We see if bit 1 of the returned
                     ;byte at 0x64 is set to 1, which
                     ;means that the controller is NOT
                     ;ready to accept commands. It means
                     ;"Input Buffer Full (port 0x60 or 0x64);
                     ;don't write yet".



   jnz .waitUntilReady  ;Repeat until bit 1 of byte from
                        ;port 0x64 is cleared to 0 or, in
                        ;other words, until the keyboard
                        ;controller is ready to accept
                        ;commands. It would mean "Input
                        ;Buffer Empty (port 0x60 or 0x64); can
                        ;be written".

     ;Note that writing the Input Buffer through
     ;port 0x64 causes the byte to be interpreted
     ;as a command.
     ;
     ;And if we write the Input Buffer through
     ;port 0x60 causes the byte to be interpreted
     ;as data, usually as a parameter to an immediate
     ;previously sent command at port 0x64.

  pop ax
    ret



;This function waits until the
;Keybard Controller has a data
;byte ready for us to gather.
;;
;;;
  waitForDataForUs:
  push ax



  .waitUntilReady:
   in al,0x64    ;Port 0x64 is the Status
                 ;Register of the keyboard
                 ;controller port located
                 ;on the motherboard.


   test al,00000001b


   jz .waitUntilReady
  pop ax
    ret





enableA20:
;;;Wait for controller to accept
;;;commands
;;
;
 call waitUntilCommandable


;;;Write Output Port (Command -- Step 1/2):
;;;Write Output Port (Command -- Step 1/2):
;;;Write Output Port (Command -- Step 1/2):
  mov al,0xD1    ;This command is to write the
                 ;status byte. This command is
                 ;called WRITE OUTPUT PORT.

  out 0x64,al    ;Here we send the command, and
                 ;it gets processed.


;;;Wait for controller to accept
;;;commands
;;
;
 call waitUntilCommandable


;;;Write The Output Port (Command Parameter -- Step 2/2):
;;;Write The Output Port (Command Parameter -- Step 2/2):
;;;Write The Output Port (Command Parameter -- Step 2/2):
  mov al,11111111b ;This is a data byte to serve
                   ;as a parameter to the previous
                   ;controller command (0xD1). We
                   ;are setting the necessary bits
                   ;here to the normal operation
                   ;of the controller, and, of course,
                   ;it is here that we enable the A20
                   ;Address Line by setting its bit
                   ;to 1.
  ;bit 0-Normal           (1) or Reset CPU (0)
  ;bit 1-Enable A20       (1) or Disable A20 (0)
  ;bit 2-Data to Mouse    (1) or Disabled (0)
  ;bit 3-Mouse Clock      (1) or Disabled (0)
  ;bit 4-IRQ1 Active      (1) or IRQ1 Inactive (0)
  ;bit 5-IRQ12 Active     (1) or IRQ12 Inactive (0)
  ;bit 6-Keyboard Clock   (1) or Disabled (0)
  ;bit 7-Data to Keyboard (1) or Disabled (0)


  out 0x60,al    ;We send this bit-set to the
                 ;data port of the keyboard. At
                 ;this point is where really the
                 ;A20 line gets enabled.



ret


disableA20:
;;;Wait for controller to accept
;;;commands
;;
;
 call waitUntilCommandable


;;;Write Output Port (Command -- Step 1/2):
;;;Write Output Port (Command -- Step 1/2):
;;;Write Output Port (Command -- Step 1/2):
  mov al,0xD1    ;This command is to write the
                 ;status byte. This command is
                 ;called WRITE OUTPUT PORT.

  out 0x64,al    ;Here we send the command, and
                 ;it gets processed.


;;;Wait for controller to accept
;;;commands
;;
;
 call waitUntilCommandable


;;;Write The Output Port (Command Parameter -- Step 2/2):
;;;Write The Output Port (Command Parameter -- Step 2/2):
;;;Write The Output Port (Command Parameter -- Step 2/2):
  mov al,11111101b ;This is a data byte to serve
                   ;as a parameter to the previous
                   ;controller command (0xD1). We
                   ;are setting the necessary bits
                   ;here to the normal operation
                   ;of the controller, and, of course,
                   ;it is here that we enable the A20
                   ;Address Line by setting its bit
                   ;to 1.
  ;bit 0-Normal           (1) or Reset CPU (0)
  ;bit 1-Enable A20       (1) or Disable A20 (0)
  ;bit 2-Data to Mouse    (1) or Disabled (0)
  ;bit 3-Mouse Clock      (1) or Disabled (0)
  ;bit 4-IRQ1 Active      (1) or IRQ1 Inactive (0)
  ;bit 5-IRQ12 Active     (1) or IRQ12 Inactive (0)
  ;bit 6-Keyboard Clock   (1) or Disabled (0)
  ;bit 7-Data to Keyboard (1) or Disabled (0)


  out 0x60,al    ;We send this bit-set to the
                 ;data port of the keyboard. At
                 ;this point is where really the
                 ;A20 line gets enabled.



ret




try1 db 0



Program 3 (trouble):

Code: Select all

bits 16
org 100h




cli
call disableA20
sti




mov ah,0x4C
int 0x21








;This function waits until the
;Keybard Controller is ready to
;accept commands.
;;
;;;
  waitUntilCommandable:
  push ax


  .waitUntilReady:
   in al,0x64    ;Port 0x64 is the Status
                 ;Register of the keyboard
                 ;controller port located
                 ;on the motherboard.


   test al,00000010b ;We see if bit 1 of the returned
                     ;byte at 0x64 is set to 1, which
                     ;means that the controller is NOT
                     ;ready to accept commands. It means
                     ;"Input Buffer Full (port 0x60 or 0x64);
                     ;don't write yet".



   jnz .waitUntilReady  ;Repeat until bit 1 of byte from
                        ;port 0x64 is cleared to 0 or, in
                        ;other words, until the keyboard
                        ;controller is ready to accept
                        ;commands. It would mean "Input
                        ;Buffer Empty (port 0x60 or 0x64); can
                        ;be written".

     ;Note that writing the Input Buffer through
     ;port 0x64 causes the byte to be interpreted
     ;as a command.
     ;
     ;And if we write the Input Buffer through
     ;port 0x60 causes the byte to be interpreted
     ;as data, usually as a parameter to an immediate
     ;previously sent command at port 0x64.

  pop ax
    ret










disableA20:
;;;Wait for controller to accept
;;;commands
;;
;
 call waitUntilCommandable


;;;Write Output Port (Command -- Step 1/2):
;;;Write Output Port (Command -- Step 1/2):
;;;Write Output Port (Command -- Step 1/2):
  mov al,0xD1    ;This command is to write the
                 ;status byte. This command is
                 ;called WRITE OUTPUT PORT.

  out 0x64,al    ;Here we send the command, and
                 ;it gets processed.


;;;Wait for controller to accept
;;;commands
;;
;
 call waitUntilCommandable


;;;Write The Output Port (Command Parameter -- Step 2/2):
;;;Write The Output Port (Command Parameter -- Step 2/2):
;;;Write The Output Port (Command Parameter -- Step 2/2):
  mov al,11111101b ;This is a data byte to serve
                   ;as a parameter to the previous
                   ;controller command (0xD1). We
                   ;are setting the necessary bits
                   ;here to the normal operation
                   ;of the controller, and, of course,
                   ;it is here that we enable the A20
                   ;Address Line by setting its bit
                   ;to 1.
  ;bit 0-Normal           (1) or Reset CPU (0)
  ;bit 1-Enable A20       (1) or Disable A20 (0)
  ;bit 2-Data to Mouse    (1) or Disabled (0)
  ;bit 3-Mouse Clock      (1) or Disabled (0)
  ;bit 4-IRQ1 Active      (1) or IRQ1 Inactive (0)
  ;bit 5-IRQ12 Active     (1) or IRQ12 Inactive (0)
  ;bit 6-Keyboard Clock   (1) or Disabled (0)
  ;bit 7-Data to Keyboard (1) or Disabled (0)


  out 0x60,al    ;We send this bit-set to the
                 ;data port of the keyboard. At
                 ;this point is where really the
                 ;A20 line gets enabled.



ret
YouTube:
http://youtube.com/@AltComp126

My x86 emulator/kernel project and software tools/documentation:
http://master.dl.sourceforge.net/projec ... 7z?viasf=1
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Gate A20 Under Bochs

Post by Brendan »

Hi,

Some general notes...

I wouldn't necessarily recommend disabling A20 and then using DOS functions, as modern versions of DOS can use RAM above 1 MiB (and therefore DOS can crash if A20 is disabled). For example, if you've got himem.sysy installed and put "DOS = high" in your config.sys, then DOS will load itself into the RAM above 1 MiB (the area of RAM from 0x00100000 to 0x0010FFE0 that can be accessed in real mode). Also, if you're using a memory manager like EMM386 then all of the RAM can be used from real mode via. emulated bank switching (where extended memory is used to emulate expanded memory).

To test if A20 is enabled or not (in real mode), the simplest way is:

Code: Select all

    push ds
    push es
    mov ax,0x0000
    mov bx,0xFFFF
    mov ds,ax
    mov es,bx
    mov ax,[ds:0x0000]           ;ax = word at 0x0000:0x0000 (or 0x00000000)
    cmp [es:0x0010],ax           ;Is it the same as the word at 0xFFFF:0x0010 (or 0x00100000)?
    jne .enabled                ; no, A20 must be enabled
    inc word [es:0x0010]         ;Change the word at 0xFFFF:0x0010 (or 0x00100000)
    wbinvd                       ;Flush caches (normally not necessary, but done just in case)
    cmp [ds:0x0000],ax           ;Did the word at 0x0000:0x0000 (or 0x00000000) change?
    je .disabled                 ; yes, A20 must be disabled

    dec word [es:0x0010]         ;Restore the word at 0xFFFF:0x0010
.enabled:
    pop es
    pop ds
    clc                          ;carry = return status
    ret

.disabled:
    dec word [es:0x0010]         ;Restore the word at 0xFFFF:0x0010 and 0x0000:0x0000 (it's the same word)
    pop es
    pop ds
    stc                          ;carry = return status
    ret
For enabling A20 and disabling A20, different computers need different things. Sometimes "fast A20" works, sometimes "keyboard controller" works, and sometimes attempting to use the wrong thing causes crashes. For example, on some computers "fast A20" works but if you enter a deep sleep state it's not saved/restored like it should be so you crash when the computer wakes up (you;d need to use the keyboard controller method even thought "fast A20" looks like it works perfectly); and on other computers using the keyboard controller doesn't work.

The most reliable method is the BIOS functions (e.g. this one to enable it and this one to disable it). These BIOS functions might not be supported, but it's worth trying them before messing with the hardware. I don't worry about detecting if these BIOS functions are supported before using them and don't worry about checking for any error codes they might return - I just use them and then test if A20 is enabled or not (using the code above).

If you do use the keyboard controller method, then there can be a relatively large delay after you tell the keyboard controller to enable/disable A20 before the A20 actually does become enabled/disabled. It's best to continually test if A20 has become enabled/disabled until it does change, with a long time-out (e.g. 1000 ms) in case it never changes. Note: if the keyboard controller isn't connected to the A20 line then testing if the keyboard controller's output becomes set or clear isn't going to test if A20 is enabled/disabled (it's better to use something like the code above to test if A20 actually is enabled or disabled).

Also, I'd be worried about the user pressing a key at the exact wrong time (e.g. "capslock"), and the BIOS keyboard IRQ handler messing up your enable/disable A20 code. It might be a good idea to test if the keyboard controller is ready to accept a command, then disable interrupts, then test if the keyboard controller is still ready to accept a command, then send the command, then enable interrupts again.

Taking all of this into account, the most reliable sequence is:
  • Test if A20 is already enabled/disabled (and exit if it's already in the right state)
  • Try the BIOS function
  • Test if A20 is became enabled/disabled (and exit if the BIOS function worked)
  • Try the keyboard controller method
  • Repeatedly test if A20 became enabled/disabled until your time-out expired (and exit if the keyboard controller method worked)
  • Try the "fast A20" method
  • Test if A20 is became enabled/disabled (and exit if the "fast A20" method worked)
  • Display a "Can't enable/disable A20" error message and refuse to continue.
Also, if you're planning on enabling/disabling A20 several times, then you could remember which method worked last time and use that method the next time. However, most OSs make sure A20 is enabled during boot and then forget about it after that (which also means no code to disable A20 is needed).

Of course a lot of the stuff above could be ignored if you only care about getting it to work on Bochs (rather than getting it to work on lots of different computers), or if you're doing some sort of temporary test (rather than writing "production code").


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
MichaelPetch
Member
Member
Posts: 772
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Gate A20 Under Bochs

Post by MichaelPetch »

Brendan wrote:I don't think DOS itself crashes or anything if A20 is enabled - AFAIK the problem was some dodgy applications that assumed addresses above 1 MiB would wrap
Although early versions of DOS itself wouldn't crash, a documented DOS 1.x backwards compatibility interface for COM programs was the main culprit (besides anyone deliberately coding assuming wrap around works). I wouldn't call software that used a documented backward compatibility interface (at the time) dodgy software. I'd say the OS inspired people coming (porting) from CP/M to write the kind of code that caused problems.

TL;DR DOS itself had a documented interface that early CP/M developers used to port their code to DOS. Well behaved programs that used the CALL 5 backwards compatibility interface on DOS 1.x likely won't work if A20 is enabled. Later versions of DOS that supported loading DOS into HMA supported CALL 5 even if A20 was enabled.

Basically the story is that CALL 5 was a call instruction on CP/M that allowed control to be transferred to the system call interface. In CP/M The bytes at offset 5 were a JMP opcode and a 2-byte address pointing at the end of the program memory (which also happened to be the system call interface entry point). In DOS 1.x+, a COM programs PSP has a FAR JMP opcode at offset 5 and the next 2 bytes point to top of the program (usually 0xFEF0) to remain compatible with CP/M. This meant that the offset portion of the FAR JMP instruction was a high value that was fixed. DOS would fill in the bytes at offset 8 and 9 with a segment value that would yield a segment:offset pair that was the equivalent to physical address 0x10000C0. This is what required A20 on later processors to be disabled so that this FAR JMP would wrap to the beginning of memory so the ultimate address would be 0x0000C0. 0x0000C0 happens to be the address for Interrupt 30h. But early versions of DOS didn't end the hackery there. Int 30h wasn't an Interrupt address - it combined with the first byte of Int 31h to form a FAR JMP to the actual CP/M system call compatibility interface in DOS. The compatibility interface was effectively a stub that transferred CL to AH and then transferred control to INT 21h.

To make things worse, by DOS 2.0 the CP/M compatibility interface remained but a bug actually had the FAR JMP going to address 0x0000BE instead of 0x0000C0 if a COM program was being debugged with DEBUG.EXE. Effectively the DOS debugger breaks code that does a call 5.

Many emulators with their own DOS do not support CALL 5 including DOSBox.
tom9876543
Member
Member
Posts: 170
Joined: Wed Jul 18, 2007 5:51 am

Re: Gate A20 Under Bochs

Post by tom9876543 »

Michael thank you for the historical information about CP/M CALL 5 compatibility.

This is another example of crap Microsoft design. This code is convoluted and inefficient and it was stupid of Microsoft to assume future 8086 cpus wouldn't have more than 1MB of memory.

Surely a well behaved CP/M application never modifies the bytes at offset 5.
Microsoft should have simply overwritten the bytes at offset 5 with INT 22h instruction. Then INT 22h does equivalent of DOS INT 21h.
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: Gate A20 Under Bochs

Post by Schol-R-LEA »

tom9876543 wrote:This is another example of crap Microsoft design. This code is convoluted and inefficient and it was stupid of Microsoft to assume future 8086 cpus wouldn't have more than 1MB of memory.
The assumption was that there weren't going to be future 8086 models, something which Intel had basically assured both IBM and Microsoft would be the case. Intel saw the 8086 as a stopgap until the 432 was ready, which would allow them to get out of the muck of the microcomputer market and focus on the two business lines they expected to be viable: 8-bit and 16-bit microcontrollers, and 32-bit academic workstations.

You also need to realize that in the late 1970s and early 1980s, new home computer models were generally sui generis - no one cared about either backwards compatibility or forward compatibility, because the next generation of hardware was going to be completely different anyway, possibly with new (and often unrelated) CPUs and all-new software.

(Furthermore, the design decision about CALL 5 was probably made by Tim Paterson when he was writing 86-DOS at Seattle Computer Products, before the IBM PC was even on the drawing board. But that's neither here nor there.)

Since IBM was just trying to 'deal with' (i.e., smother with any eye to destroying) the small-computer market, they didn't care; actually, Intel probably wouldn't have agreed in the first place if they didn't know this, as they thought micros were a dead end that needed to be sunk rather than acting as a drag on their 'real' business.

That also suited Microsoft fine as well, as they were was more than happy to write code for other processors - by then they were as sick of the 8080 line as Intel was (and from a programmer's perspective the 8086 - which started out as a super-8080 - was, if anything, even worse despite the larger memory), and would have preferred to focus on the 6502 (which was the 8-bit chip at the time), the 6809, and 68000 (which was looking to steamroll everything ahead of it in the growing market for low-end workstations - Microsoft was expecting to be selling Xenix as their main OS offering). They were in the deal for the opposite reason - they figured IBM would misread the market and embarrass themselves, leading them to abandon the project and leave the home and small business markets free to develop on their own.

Also, they didn't really expect PC-DOS to be much of a much - it was there mainly to give customers a less expensive alternative to CP/M-86 and UCSD, but it was expected that Digital Research would get the lion's share of the customers. They knew that Kildall had the same view of the project they did, but unlike them, he resented it rather than seeing as a chance to stick it to the Evil Empire.

(OK, so it probably wasn't quite like that for Gates, who really did see it as a business opportunity; however, he also seems to have thought that IBM were going to fall on their face, and given how universally hated Big Blue was among the microcomputer community which were his main customer base, he probably found some amusement in the idea. Oh, and because he could get them to foot a large chunk of the development bill for software Microsoft was already going to be writing for other 8086/8088 systems anyway.)

In any case, it was only a quirk of the PC/AT's design that it became possible later, as when the AT was designed, the A20 line was meant to be permanently latched down in hardware. Microsoft probably had no input on this, other than possibly to point out the CALL 5 issue (by then PC-DOS was a much bigger concern than expected, but it still was IBM's call, not Microsoft's). Since there were other reasons to wrap the high memory, this probably wasn't the main reason for the A20 latch.

It wasn't until after the AT had been on the market for a while that a Lotus developer noticed that he could hack the keyboard controller to re-enable it, and at first people didn't believe him. It was seen as a bug at first, and the only reason it didn't get 'fixed' was because by then programs like Lotus needed more memory (which is why the developer was messing around with it in the first place - he was looking to see if there was a way to trim the size of the keyboard handling routines, and stumbled on it by accident). Up to that point, the assumption was that 640KiB was as much a hard limit for the AT as it was for the PC and XT, even for 286 protected mode, and finding out that this wouldn't be the case was a shocking revelation.

Not that it will matter in newer systems much longer; Intel has wanted to drop 16-bit mode outright for years. They haven't done so yet, because backward-compatibility is indeed a thing going forward from the early 1980s, but they do seem to planning that once there is a critical mass of UEFI-only motherboards to let them force the issue of abandoning the legacy BIOS entirely - and at this point, ~ to the contrary, firmware compatibility is the main sticking point, not compatibility with older application software.
Last edited by Schol-R-LEA on Sun Dec 10, 2017 1:19 pm, edited 25 times in total.
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.
MichaelPetch
Member
Member
Posts: 772
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Gate A20 Under Bochs

Post by MichaelPetch »

tom9876543 wrote:Microsoft should have simply overwritten the bytes at offset 5 with INT 22h instruction. Then INT 22h does equivalent of DOS INT 21h.
Problem is they couldn't. CP/M only had a 3 byte JMP at offset 5 but CP/M also used the 2 byte JMP target address as a way for CP/M programs to determine the size of the program area. The idea was that the system call interface on CP/M was where the program space ends. So the offset of the JMP also tells you the extent of the program area. Some CP/M programs relied on that 2 byte value so DOS developers devised this novel hack lol. For compatibility purposes DOS couldn't use an int call at offset 5 because then the next byte (byte 6) would be the interrupt number. The interrupt number itself wouldn't allow them to place a value at byte 6 and 7 that denoted the extent of the program area. The FAR JMP was an interesting hack because it was a 5 byte instruction where bytes 6 and 7 could be hard coded with the program extent and act as a FAR JMP address with a specially crafted segment value at byte 8 and 9.

On a side note DOS developers took your advice to use int for the instruction at offset 0. I should point out first that the idea of a 0x100 (256) area (DOS PSP) before a COM program is also a holdover from CP/M. A CP/M program always started at 0x100 from the bottom of memory. COM programs in DOS took on the same characteristics. Difference being with x86 real mode segmentation offset 0x100 was relative to the segment the program was running in, not the bottom of physical memory.

In CP/M the 3 byte instruction at offset zero was usually a JMP to the BIOS warm reboot entry point (usually to offset 0xF200). In CP/M if you did a call 0 it would warm reboot the computer. In DOS they decided instead of warm rebooting the computer it would exit back to the DOS prompt. To do this they placed the instruction int 20h at offset 0. int 20h is DOS interrupt call that exits a program (was intended for COM programs, not EXEs). If you do call 0 it also exits back to DOS.
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: Gate A20 Under Bochs

Post by Schol-R-LEA »

Holy Water Cannons, Batman, I didn't notice until after I wrote all of this just how incredibly old this thread is - until Michael replied, it had been dead since 2008. EEEK, ZOMBIE THREAD, RUN AWAY!
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.
Octocontrabass
Member
Member
Posts: 5494
Joined: Mon Mar 25, 2013 7:01 pm

Re: Gate A20 Under Bochs

Post by Octocontrabass »

While we're responding to long-dead threads (and editing posts 25 times), I figured I might as well offer a correction.
Schol-R-LEA wrote:when the AT was designed, the A20 line was meant to be permanently latched down in hardware. [...] It wasn't until after the AT had been on the market for a while that a Lotus developer noticed that he could hack the keyboard controller to re-enable it
This may be what the Lotus developer believed, but it's wrong. The A20 line was meant to be disabled in real mode and enabled in protected mode. The IBM 5170 Technical Reference clearly documents how the keyboard controller could be used to enable and disable A20.
MichaelPetch
Member
Member
Posts: 772
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Gate A20 Under Bochs

Post by MichaelPetch »

I wasn't aware of how old the thread was either OOPS. I'm not entirely sure how I found Brendan's answer, it was probably a Google search. For some reason I thought the thread was more recent LOL. Anyway, although an old thread at least there is additional information now about the A20 issue and DOS that wasn't part of the original discussion.
tom9876543
Member
Member
Posts: 170
Joined: Wed Jul 18, 2007 5:51 am

Re: Gate A20 Under Bochs

Post by tom9876543 »

MichaelPetch wrote:
tom9876543 wrote:Microsoft should have simply overwritten the bytes at offset 5 with INT 22h instruction. Then INT 22h does equivalent of DOS INT 21h.
Problem is they couldn't. CP/M only had a 3 byte JMP at offset 5 but CP/M also used the 2 byte JMP target address as a way for CP/M programs to determine the size of the program area. The idea was that the system call interface on CP/M was where the program space ends. So the offset of the JMP also tells you the extent of the program area. Some CP/M programs relied on that 2 byte value so DOS developers devised this novel hack lol.
Thank you Michael for the interesting information about CP/M.
Microsoft still made a stupid decision pointing the far jump to linear address 0x1000C0.
Microsoft could have made the far jump point to somewhere inside IO.SYS, below the 1MiB barrier.

Microsoft's crappy myopic approach to programming cost IBM money because it forced IBM to implement the nasty keyboard controller work around on the AT.
Post Reply