A20 Line: Difference between revisions

7,147 bytes added ,  26 days ago
m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(37 intermediate revisions by 17 users not shown)
Line 1:
The A20 Address Line is the physical representation of the 20th21st 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 20thA20 line on the address bus (A20) was disabled by default.
 
The wraparound was caused by the fact the 8086 computers could only access 1 megabyte of memory, but because of therethe segmented memory access methodmodel theyit could effectively address up to 1 megabyte and 6364 andkilobytes a(minus bit16 kilobytesbytes). In orderBecause tothere keepare the20 number ofaddress transistorslines on theirthe chips8086 to(A0 athrough minimumA19), Intel decided to wrap aroundany address above the 1 megabyte mark towraps thearound beginningto of memoryzero. 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.
 
==A20 Pin==
On most modern systems the A20 pin allows automatic enabling of the A20 line. Unfortunately this was not implemented until recent computers so it is necessary to check whether the A20 line is enabled and try alternate methods if it is not.
 
==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" PS/2 Controller|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 [http://www.diakom.ru:8000/el/elfirms/datashts/Smsc/42w11.pdf 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 alwaysgenerally 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 iswas 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. Don't forget to restore the original bootsector identifier.
 
The following code performs a check (not like described above -- more directly).
<sourcesyntaxhighlight lang="asm">
; The following code is public domain licencedlicensed
 
[bits 16]
Line 33 ⟶ 30:
check_a20:
pushf
push fsds
push gses
push di
push si
Line 41 ⟶ 38:
 
xor ax, ax ; ax = 0
mov fses, ax
 
not ax ; ax = 0xFFFF
mov gsds, ax
 
mov di, 0x0500
mov si, 0x0510
 
mov al, byte [fses:di]
push ax
 
mov al, byte [gsds:si]
push ax
 
mov byte [fses:di], 0x00
mov byte [gsds:si], 0xFF
 
cmp byte [fses:di], 0xFF
 
pop ax
mov byte [gsds:si], al
 
pop ax
mov byte [fses:di], al
 
mov ax, 0
Line 74 ⟶ 71:
pop si
pop di
pop gses
pop fsds
popf
 
ret
</syntaxhighlight>
</source>
'''Note:''' The above code may seem confusing to you, if so, below is the simplified code.
<syntaxhighlight lang="asm">
; out:
; ax - state (0 - disabled, 1 - enabled)
get_a20_state:
pushf
push si
push di
push ds
push es
cli
 
mov ax, 0x0000 ; 0x0000:0x0500(0x00000500) -> ds:si
mov ds, ax
mov si, 0x0500
 
not ax ; 0xffff:0x0510(0x00100500) -> es:di
mov es, ax
mov di, 0x0510
 
mov al, [ds:si] ; save old values
mov byte [.BufferBelowMB], al
mov al, [es:di]
mov byte [.BufferOverMB], al
 
mov ah, 1
mov byte [ds:si], 0
mov byte [es:di], 1
mov al, [ds:si]
cmp al, [es:di] ; check byte at address 0x0500 != byte at address 0x100500
jne .exit
dec ah
.exit:
mov al, [.BufferBelowMB]
mov [ds:si], al
mov al, [.BufferOverMB]
mov [es:di], al
shr ax, 8 ; move result from ah to al register and clear ah
sti
pop es
pop ds
pop di
pop si
popf
ret
.BufferBelowMB: db 0
.BufferOverMB db 0
</syntaxhighlight>
 
===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.
 
<syntaxhighlight lang="asm">
[bits 32]
 
; 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
mov edi,0x112345 ;odd megabyte address.
mov esi,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
jne A20_on ;if not equivalent , A20 line is set.
ret ;if equivalent , the A20 line is cleared.
 
A20_on:
; *your code from here*
</syntaxhighlight>
 
==Enabling==
Line 86 ⟶ 160:
===Keyboard Controller===
For the original method to enable the A20 line, some hardware IO using the Keyboard Controller chip (8042 chip) is necessary.
<sourcesyntaxhighlight lang="c">
void init_A20(void)
{
UCHARuint8_t a;
 
disable_ints();
Line 113 ⟶ 187:
enable_ints();
}
</syntaxhighlight>
</source>
 
or in [[assembly]]
 
<sourcesyntaxhighlight lang="asm">
;;
;; NASM 32bit assembler
Line 169 ⟶ 243:
jz a20wait2
ret
</syntaxhighlight>
</source>
 
===Fast A20 Gate===
On severalmost 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.
 
<sourcesyntaxhighlight lang="asm">
in al, 0x92
or al, 2
out 0x92, al
</syntaxhighlight>
</source>
 
As mentioned at [http://www.win.tue.nl/~aeb/linux/kbd/A20.html 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:
 
<syntaxhighlight lang="asm">
in al, 0x92
test al, 2
jnz after
or al, 2
and al, 0xFE
out 0x92, al
after:
</syntaxhighlight>
 
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.
<syntaxhighlight lang="asm">
;FASM
use16
mov ax,2403h ;--- A20-Gate Support ---
int 15h
jb a20_ns ;INT 15h is not supported
cmp ah,0
jnz a20_ns ;INT 15h is not supported
 
mov ax,2402h ;--- A20-Gate Status ---
int 15h
jb a20_failed ;couldn't get status
cmp ah,0
jnz a20_failed ;couldn't get status
 
cmp al,1
jz a20_activated ;A20 is already activated
 
mov ax,2401h ;--- A20-Gate Activate ---
int 15h
jb a20_failed ;couldn't activate the gate
cmp ah,0
jnz a20_failed ;couldn't activate the gate
 
a20_activated: ;go on
</syntaxhighlight>
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:
<syntaxhighlight lang="asm">
in al,0xee
</syntaxhighlight>
 
Disable A20:
<syntaxhighlight lang="asm">
out 0xee,al
</syntaxhighlight>
 
'''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===
<syntaxhighlight lang="asm">
; out:
; ax - state (0 - disabled, 1 - enabled)
get_a20_state:
pushf
push si
push di
push ds
push es
cli
 
mov ax, 0x0000 ; 0x0000:0x0500(0x00000500) -> ds:si
mov ds, ax
mov si, 0x0500
 
not ax ; 0xffff:0x0510(0x00100500) -> es:di
mov es, ax
mov di, 0x0510
 
mov al, [ds:si] ; save old values
mov byte [.BufferBelowMB], al
mov al, [es:di]
mov byte [.BufferOverMB], al
 
mov ah, 1 ; check byte [0x00100500] == byte [0x0500]
mov byte [ds:si], 0
mov byte [es:di], 1
mov al, [ds:si]
cmp al, [es:di]
jne .exit
dec ah
.exit:
mov al, [.BufferBelowMB]
mov [ds:si], al
mov al, [.BufferOverMB]
mov [es:di], al
shr ax, 8
sti
pop es
pop ds
pop di
pop si
popf
ret
.BufferBelowMB: db 0
.BufferOverMB db 0
 
; 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:
push bx
clc
 
mov ax, 0x2403
int 0x15
jc .error
 
test ah, ah
jnz .error
 
mov ax, bx
pop bx
ret
.error:
stc
pop bx
ret
 
enable_a20_keyboard_controller:
cli
 
call .wait_io1
mov al, 0xad
out 0x64, al
call .wait_io1
mov al, 0xd0
out 0x64, al
call .wait_io2
in al, 0x60
push eax
call .wait_io1
mov al, 0xd1
out 0x64, al
call .wait_io1
pop eax
or al, 2
out 0x60, al
call .wait_io1
mov al, 0xae
out 0x64, al
call .wait_io1
sti
ret
.wait_io1:
in al, 0x64
test al, 2
jnz .wait_io1
ret
.wait_io2:
in al, 0x64
test al, 1
jz .wait_io2
ret
 
; out:
; cf - set on error
enable_a20:
clc ; clear cf
pusha
mov bh, 0 ; clear bh
 
call get_a20_state
jc .fast_gate
 
test ax, ax
jnz .done
 
call query_a20_support
mov bl, al
test bl, 1 ; enable A20 using keyboard controller
jnz .keybord_controller
 
test bl, 2 ; enable A20 using fast A20 gate
jnz .fast_gate
.bios_int:
mov ax, 0x2401
int 0x15
jc .fast_gate
test ah, ah
jnz .failed
call get_a20_state
test ax, ax
jnz .done
.fast_gate:
in al, 0x92
test al, 2
jnz .done
 
or al, 2
and al, 0xfe
out 0x92, al
 
call get_a20_state
test ax, ax
jnz .done
 
test bh, bh ; test if there was an attempt using the keyboard controller
jnz .failed
.keybord_controller:
call enable_a20_keyboard_controller
call get_a20_state
test ax, ax
jnz .done
 
mov bh, 1 ; flag enable attempt with keyboard controller
 
test bl, 2
However, this 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.
jnz .fast_gate
jmp .failed
.failed:
stc
.done:
popa
ret
</syntaxhighlight>
 
==See Also==
Line 187 ⟶ 512:
 
[[Category:X86]]
[[Category:Memory management]]
[[de:A20-Gate]]