A20 - a pain from the past

Everybody hates the CapsLock key, but keyboard manufacturers continue producing keyboards with CapsLock - it could be that someone wants it.

With A20 it is similar but worse. Really nobody wants it, but it continues to haunt us.

History

The 8088 in the original PC had only 20 address lines, good for 1 MB. The maximum address FFFF:FFFF addresses 0x10ffef, and this would silently wrap to 0x0ffef. When the 286 (with 24 address lines) was introduced, it had a real mode that was intended to be 100% compatible with the 8088. However, it failed to do this address truncation (a bug), and people found that there existed programs that actually depended on this truncation. Trying to achieve perfect compatibility, IBM invented a switch to enable/disable the 0x100000 address bit. Since the 8042 keyboard controller happened to have a spare pin, that was used to control the AND gate that disables this address bit. The signal is called A20, and if it is zero, bit 20 of all addresses is cleared.

Present

Why do we have to worry about this nonsense? Because by default the A20 address line is disabled at boot time, so the operating system has to find out how to enable it, and that may be nontrivial since the details depend on the chipset used.

Classical A20 control, via the keyboard controller

The output port of the keyboard controller has a number of functions.
Bit 0 is used to reset the CPU (go to real mode) - a reset happens when bit 0 is 0.
Bit 1 is used to control A20 - it is enabled when bit 1 is 1, disabled when bit 1 is 0.
One sets the output port of the keyboard controller by first writing 0xd1 to port 0x64, and the the desired value of the output port to port 0x60. One usually sees the values 0xdd and 0xdf used to disable/enable A20. Thus:
        call    empty_8042
        mov     al,#0xd1                ! command write
        out     #0x64,al
        call    empty_8042
        mov     al,#0xdf                ! A20 on
        out     #0x60,al
        call    empty_8042
where empty_8042 has to wait for the kbd to finish handling input, say
empty_8042:
        call    delay
        in      al,#0x64
        test    al,#2
        jnz     empty_8042
        ret

Variation

The HP Vectra accepts a shortcut, where writing 0xdd or 0xdf to port 0x64 will disable/enable A20.
! For the HP Vectra
        call    empty_8042
        jnz     err
        mov     al,#0xdf
        out     #0x64,al
        call    empty_8042
        jnz     err
        mov     al,#0xdf        ! Do it again
        out     #0x64,al
        call    empty_8042
        jnz     err
! Success
(HIMEM.SYS in DOS 5.0 incorrectly identifies some computers as HP Vectra - this may cause a hang at boot. Fixed in DOS5.0a.)

A20 control via System Control Port A

Some operating systems use the switching off and on of A20 as part of the standard procedure to switch between real (16-bit) and protected mode. Since the keyboard microcontroller is slow, it was desirable to avoid it, and a Fast Gate A20 Option was introduced, where I/O port 0x92 (System Control Port A) is used to handle A20, circumventing the keyboard controller.

Thus, MCA, EISA and other systems can also control A20 via port 0x92. This port has a number of functions, and the details depend on the manufacturer. Bits 0,1,3,6,7 seem to have the same meaning everywhere this port is implemented.
Bit 0 (w): writing 1 to this bit causes a fast reset (used to switch back to real mode; for MCA this took 13.4 ms).
Bit 1 (rw): 0: disable A20, 1: enable A20.
Bit 3 (rw?): 0/1: power-on password bytes (stored in CMOS bytes 0x38-0x3f or 0x36-0x3f) accessible/inaccessible. This bit can be written to only when it is 0.
Bits 6-7 (rw): 00: hard disk activity LED off, 01,10,11: hard disk activity LED on.
Bits 2,4,5 are unused or have varying meanings. (On MCA bit 4 (r): 1: watchdog timeout occurred.)

Using 0x92 may be necessary

Sometimes (especially on embedded systems) no keyboard controller is present, and it may be necessary to use 0x92. Often however, the chip will catch accesses to ports 0x64 and 0x60 and simulate the expected behaviour, also when no keyboard controller is present. Sometimes, this snooping behaviour must be enabled first.

Using 0x92 may be dangerous

Gianluca Anzolin reports: I have a TRIDENT 9660 video card integrated on the mainboard. Linux boots well, but after LILO has loaded the kernel, the screen becomes black and remains black ever after. Removing
        inb     $0x92, %al                      #
        orb     $02, %al                        # "fast A20" version
        outb    %al, $0x92                      # some chips have only this
from setup.S solved this. Apparently on his machine writing to some of these bits is dangerous and does something to the on-board video card (disable it?).

Petr Vandrovec suggests to do the write only when it is really necessary:

        inb     $0x92, %al                      #
+       testb   $02, %al
+       jnz     no92
        orb     $02, %al                        # "fast A20" version
        outb    %al, $0x92                      # some chips have only this
+no92:
Since bit 0 sometimes is write-only, and writing a one there causes a reset, it must be a good idea to add the line
        andb    $0xfe, %al
before the outb.

FreeBSD

FreeBSD does
/*
 * Gate A20 for high memory
 */
void
gateA20(void)
{
#ifdef  IBM_L40
        outb(0x92, 0x2);
#else   IBM_L40
        while (inb(K_STATUS) & K_IBUF_FUL);
        while (inb(K_STATUS) & K_OBUF_FUL)
                (void)inb(K_RDWR);

        outb(K_CMD, KC_CMD_WOUT);
        while (inb(K_STATUS) & K_IBUF_FUL);
        outb(K_RDWR, KB_A20);
        while (inb(K_STATUS) & K_IBUF_FUL);
#endif  IBM_L40
}
that is, uses 0x92 only for a IBM_L40 (whatever that may be).

Minix and HIMEM.ASM

Here is a patch fragment for minix. It contains the interesting part
!       movb    al, #0xff       ! Pulse output port
!       outb    0x64
!       call    kb_wait         ! Wait for the A20 line to settle down
from some old HIMEM.ASM source (that one still can find on the net). I have seen no other places where command 0xff is described as doing something useful.

Access of 0xee

On some systems reading ioport 0xee enables A20, and writing it disables A20. (Or, sometimes, this action only occurs when ioport 0xee is enabled.) And similar things hold for ioport 0xef and reset (a write causes a reset).

The i386SL/i486SL documents say

The following ports are visible only when enabled,
Any writes to these ports cause the action named.
Name of Register     Address   Default Value  Where placed    Size
FAST CPU RESET         EFh          N/A         82360SL         8
FAST A20 GATE          EEh          N/A         82360SL         8  

The AMD Elan SC400 docs (21032.pdf) say: Register EEh can be used to cause the same type of masking of the CPU A20 signal that was historically performed by an external SCP (System Control Processor) in a PC/AT Compatible system, but much faster. This control defaults to not forcing the propagation of A20: Dummy Read = Returns FFh, and forces the A20 signal to propagate. Dummy Write = Deasserts the forcing of the propagation of the A20 signal via this particular control, data value written is N/A. For software compatibility and other reasons, there are several sources of GateA20 control. These controls are effectively ORed together with the output of the OR gate driving the Enhanced Am486 microprocessor A20M pin. Therefore, A20 will propagate if ANY of the independent sources are forcing A20 to propagate.

Other ports

It is rumoured that systems exist that use bit 2 of ioport 0x65 or bit 0 of ioport 0x1f8 for A20 control (0: disabled, 1: enabled). Don't know what systems that might be. The AT&T 6300+ needs a write of 0x90 to port 0x3f20 to enable (and a write of 0x0 to disable) A20.

Disabling A20

It may be necessary to do both the keyboard controller write and the 0x92 write (and the 0xee write) to disable A20.

A20 and reset

If (in protected mode) A20 is disabled, the odd megabytes are inaccessible. After a reset, execution begins at top-of-memory: 0xfffff0 on the 286 and 0xfffffff0 on 386 and later. With disabled A20 this becomes 0xeffff0 or 0xffeffff0 and the machine will probably crash, having no memory mapped there.

A20 and cache

One tests A20 by writing something to an address with bit 0x100000 set, and seeing whether the corresponding location in low memory changes. However, this plan may be thwarted by the cache that remembers the old value and doesn't know about A20.

Neutrino describes the following function x86_enable_a20(): Enable address line A20, which is often disabled on many PCs on reset. It first checks if address line A20 is enabled and if so returns 0. Otherwise, it sets bit 0x02 in port 0x92, which is used by many systems as a fast A20 enable. It again checks to see if A20 is enabled and if so returns 0. Otherwise, it uses the keyboard microcontroller to enable A20 as defined by the old PC/AT standard. It again checks to see if A20 is enabled and if so returns 0. Otherwise, it returns -1. If cpu is a 486 or greater, it issues a wbinvd opcode to invalidate the cache when doing a read/write test of memory to see if A20 is enabled. In the rare case where setting bit 0x02 in port 0x92 may affect other hardware, you can skip this by setting only_keyboard to 1. In this case, it will attempt to use only the keyboard microcontroller.

hpa comments: As far as I know the only machines which have the cache problem are i386 boxen, but the i386 doesn't have WBINVD. The i486 has a pin on the CPU for A20, which takes effect inside the L1 cache, and so it shouldn't have any A20 cache issues.