A20 Line
The A20 Address Line is the physical representation of the 21st bit (number 20, counting from 0) of any memory access. When the IBM-AT (Intel 286) was introduced, it was able to access up to sixteen megabytes of memory (instead of the 1 MByte of the 8086). But to remain compatible with the 8086, a quirk in the 8086 architecture (memory wraparound) had to be duplicated in the AT. To achieve this, the A20 line on the address bus was disabled by default.
The wraparound was caused by the fact the 8086 could only access 1 megabyte of memory, but because of the segmented memory model it could effectively address up to 1 megabyte and 64 kilobytes (minus 16 bytes). Because there are 20 address lines on the 8086 (A0 through A19), any address above the 1 megabyte mark wraps around to zero. For some reason a few short-sighted programmers decided to write programs that actually used this wraparound (rather than directly addressing the memory at its normal location at the bottom of memory). Therefore in order to support these 8086-era programs on the new processors, this wraparound had to be emulated on the IBM AT and its compatibles; this was originally achieved by way of a latch that by default set the A20 line to zero. Later the 486 added the logic into the processor and introduced the A20M pin to control it.
For an operating system developer (or Bootloader developer) this means the A20 line has to be enabled so that all memory can be accessed. This started off as a simple hack but as simpler methods were added to do it, it became harder to program code that would definitely enable it and even harder to program code that would definitely disable it.
Keyboard Controller
The traditional method for A20 line enabling is to directly probe the keyboard controller. The reason for this is that Intel's 8042 keyboard controller had a spare pin which they decided to route the A20 line through. This seems foolish now given their unrelated nature, but at the time computers weren't quite so standardized. Keyboard controllers are usually derivatives of the 8042 chip. By programming that chip accurately, you can either enable or disable bit #20 on the address bus.
When your PC boots, the A20 gate is generally disabled, but some BIOSes (and emulators, like QEMU) do enable it for you, as do some high-memory managers (HIMEM.SYS) or bootloaders (GRUB).
Testing the A20 line
Before enabling the A20 with any of the methods described below it is better to test whether the A20 address line was already enabled by the BIOS. This can be achieved by comparing, at boot time in real mode, the bootsector identifier (0xAA55) located at address 0000:7DFE with the value 1 MiB higher which is at address FFFF:7E0E. When the two values are different it means that the A20 is already enabled otherwise if the values are identical it must be ruled out that this is not by mere chance. Therefore the bootsector identifier needs to be changed, for instance by rotating it left by 8 bits, and again compared to the 16 bits word at FFFF:7E0E. When they are still the same then the A20 address line is disabled otherwise it is enabled.
The following code performs a check (not like described above -- more directly).
; The following code is public domain licensed [bits16] ; Function: check_a20 ; ; Purpose: to check the status of the a20 line in a completely self-contained state-preserving way. ; The function can be modified as necessary by removing push's at the beginning and their ; respective pop's at the end if complete self-containment is not required. ; ; Returns: 0 in ax if the a20 line is disabled (memory wraps around) ; 1 in ax if the a20 line is enabled (memory does not wrap around) check_a20: pushf pushds pushes pushdi pushsi cli xorax,ax; ax = 0 moves,ax notax; ax = 0xFFFF movds,ax movdi,0x0500 movsi,0x0510 moval,byte[es:di] pushax moval,byte[ds:si] pushax movbyte[es:di],0x00 movbyte[ds:si],0xFF cmpbyte[es:di],0xFF popax movbyte[ds:si],al popax movbyte[es:di],al movax,0 jecheck_a20__exit movax,1 check_a20__exit: popsi popdi popes popds popf ret
Note: The above code may seem confusing to you, if so, below is the simplified code.
; out: ; ax - state (0 - disabled, 1 - enabled) get_a20_state: pushf pushsi pushdi pushds pushes cli movax,0x0000; 0x0000:0x0500(0x00000500) -> ds:si movds,ax movsi,0x0500 notax; 0xffff:0x0510(0x00100500) -> es:di moves,ax movdi,0x0510 moval,[ds:si]; save old values movbyte[.BufferBelowMB],al moval,[es:di] movbyte[.BufferOverMB],al movah,1 movbyte[ds:si],0 movbyte[es:di],1 moval,[ds:si] cmpal,[es:di]; check byte at address 0x0500 != byte at address 0x100500 jne.exit decah .exit: moval,[.BufferBelowMB] mov[ds:si],al moval,[.BufferOverMB] mov[es:di],al shrax,8; move result from ah to al register and clear ah sti popes popds popdi popsi popf ret .BufferBelowMB:db0 .BufferOverMBdb0
Testing The A20 Line From Protected Mode
When in Protected Mode it's easier to test A20 because you can access A20's set memory addresses using any odd megabyte address and compare it to it's even megabyte neighbor.
[bits32] ; Check A20 line ; Returns to caller if A20 gate is cleared. ; Continues to A20_on if A20 line is set. ; Written by Elad Ashkcenazi is_A20_on?: pushad movedi,0x112345;odd megabyte address. movesi,0x012345;even megabyte address. mov[esi],esi;making sure that both addresses contain diffrent values. mov[edi],edi;(if A20 line is cleared the two pointers would point to the address 0x012345 that would contain 0x112345 (edi)) cmpsd;compare addresses to see if the're equivalent. popad jneA20_on;if not equivalent , A20 line is set. ret;if equivalent , the A20 line is cleared. A20_on: ; *your code from here*
Enabling
There are several sources that enable A20, commonly each of the inputs are or'ed together to form the A20 enable signal. This means that using one method (if supported by the chipset) is enough to enable A20. If you want to disable A20, you might have to disable all present sources. Always make sure that the A20 has the requested state by testing the line as described above.
Keyboard Controller
For the original method to enable the A20 line, some hardware IO using the Keyboard Controller chip (8042 chip) is necessary.
voidinit_A20(void) { uint8_ta; disable_ints(); kyb_wait_until_done(); kyb_send_command(0xAD);// disable keyboard kyb_wait_until_done(); kyb_send_command(0xD0);// Read from input kyb_wait_until_done(); a=kyb_get_data(); kyb_wait_until_done(); kyb_send_command(0xD1);// Write to output kyb_wait_until_done(); kyb_send_data(a|2); kyb_wait_until_done(); kyb_send_command(0xAE);// enable keyboard enable_ints(); }
or in assembly
;; ;; NASM 32bit assembler ;; [bits32] [section.text] enable_A20: cli calla20wait moval,0xAD out0x64,al calla20wait moval,0xD0 out0x64,al calla20wait2 inal,0x60 pusheax calla20wait moval,0xD1 out0x64,al calla20wait popeax oral,2 out0x60,al calla20wait moval,0xAE out0x64,al sti ret a20wait: inal,0x64 testal,2 jnza20wait ret a20wait2: inal,0x64 testal,1 jza20wait2 ret
Fast A20 Gate
On most newer computers starting with the IBM PS/2, the chipset has a FAST A20 option that can quickly enable the A20 line. To enable A20 this way, there is no need for delay loops or polling, just 3 simple instructions.
inal,0x92 oral,2 out0x92,al
As mentioned at the see also site, it would be best to do the write only when necessary, and to make sure bit 0 is 0, as it is used for fast reset. An example follows:
inal,0x92 testal,2 jnzafter oral,2 andal,0xFE out0x92,al after:
However, the Fast A20 method is not supported everywhere and there is no reliable way to tell if it will have some effect or not on a given system. Even worse, on some systems, it may actually do something else like blanking the screen, so it should be used only after the BIOS has reported that FAST A20 is available. Code for systems lacking FAST A20 support is also needed, so relying only on this method is discouraged. Also, on some chipsets you might have to enable Fast A20 support in the BIOS configuration screen.
INT 15
Another way is to use the BIOS.
;FASM use16 movax,2403h;--- A20-Gate Support --- int15h jba20_ns;INT 15h is not supported cmpah,0 jnza20_ns;INT 15h is not supported movax,2402h;--- A20-Gate Status --- int15h jba20_failed;couldn't get status cmpah,0 jnza20_failed;couldn't get status cmpal,1 jza20_activated;A20 is already activated movax,2401h;--- A20-Gate Activate --- int15h jba20_failed;couldn't activate the gate cmpah,0 jnza20_failed;couldn't activate the gate a20_activated:;go on
If only one interrupt fails, you will have to use another method. (See below.)
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
Enable A20:
inal,0xee
Disable A20:
out0xee,al
NOTE that it doesn't matter what AL contains when writing and AL is undefined while reading (to / from port 0xee)
Recommended Method
Because there are several different methods that may or may not be supported, and because some of them cause problems on some computers; the recommended method is to try all of them until one works in the "order of least risk". Essentially:
- Test if A20 is already enabled - if it is you don't need to do anything at all
- Try the BIOS function. Ignore the returned status.
- Test if A20 is enabled (to see if the BIOS function actually worked or not)
- Try the keyboard controller method.
- Test if A20 is enabled in a loop with a time-out (as the keyboard controller method may work slowly)
- Try the Fast A20 method last
- Test if A20 is enabled in a loop with a time-out (as the fast A20 method may work slowly)
- If none of the above worked, give up
Final code example
; out: ; ax - state (0 - disabled, 1 - enabled) get_a20_state: pushf pushsi pushdi pushds pushes cli movax,0x0000; 0x0000:0x0500(0x00000500) -> ds:si movds,ax movsi,0x0500 notax; 0xffff:0x0510(0x00100500) -> es:di moves,ax movdi,0x0510 moval,[ds:si]; save old values movbyte[.BufferBelowMB],al moval,[es:di] movbyte[.BufferOverMB],al movah,1; check byte [0x00100500] == byte [0x0500] movbyte[ds:si],0 movbyte[es:di],1 moval,[ds:si] cmpal,[es:di] jne.exit decah .exit: moval,[.BufferBelowMB] mov[ds:si],al moval,[.BufferOverMB] mov[es:di],al shrax,8 sti popes popds popdi popsi popf ret .BufferBelowMB:db0 .BufferOverMBdb0 ; out: ; ax - a20 support bits (bit #0 - supported on keyboard controller; bit #1 - supported with bit #1 of port 0x92) ; cf - set on error query_a20_support: pushbx clc movax,0x2403 int0x15 jc.error testah,ah jnz.error movax,bx popbx ret .error: stc popbx ret enable_a20_keyboard_controller: cli call.wait_io1 moval,0xad out0x64,al call.wait_io1 moval,0xd0 out0x64,al call.wait_io2 inal,0x60 pusheax call.wait_io1 moval,0xd1 out0x64,al call.wait_io1 popeax oral,2 out0x60,al call.wait_io1 moval,0xae out0x64,al sti ret .wait_io1: inal,0x64 testal,2 jnz.wait_io1 ret .wait_io2: inal,0x64 testal,1 jz.wait_io2 ret ; out: ; cf - set on error enable_a20: clc; clear cf pusha movbh,0; clear bh callget_a20_state jc.fast_gate testax,ax jnz.done callquery_a20_support movbl,al testbl,1; enable A20 using keyboard controller jnz.keybord_controller testbl,2; enable A20 using fast A20 gate jnz.fast_gate .bios_int: movax,0x2401 int0x15 jc.fast_gate testah,ah jnz.failed callget_a20_state testax,ax jnz.done .fast_gate: inal,0x92 testal,2 jnz.done oral,2 andal,0xfe out0x92,al callget_a20_state testax,ax jnz.done testbh,bh; test if there was an attempt using the keyboard controller jnz.failed .keybord_controller: callenable_a20_keyboard_controller callget_a20_state testax,ax jnz.done movbh,1; flag enable attempt with keyboard controller testbl,2 jnz.fast_gate jmp.failed .failed: stc .done: popa ret