A20 Line: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
(→‎Testing the A20 line: Maybe the code in this function is a bit excessive, but I wanted it to be self-contained.)
Line 16: Line 16:
Before enabling the A20 with any of the methods described below it better to test whether the A20 address line is 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.
Before enabling the A20 with any of the methods described below it better to test whether the A20 address line is 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):
The following code performs a check (not like described above -- more directly).
<pre>
<pre>
; The following code is public domain licenced
; The following code is public domain licenced
Line 23: Line 23:


; Function: check_a20
; 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 if complete self-containment is not required.
;
; Returns: 0 in ax if the a20 line is disabled (memory wraps around)
; 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)
; 1 in ax if the a20 line is enabled (memory does not wrap around)


check_a20:
check_a20:
cli
pushf

push fs
push fs
push gs
push gs
push di
push di
push si
push si

cli


xor ax, ax ; ax = 0
xor ax, ax ; ax = 0
Line 70: Line 75:
pop gs
pop gs
pop fs
pop fs
popf

sti


ret
ret

Revision as of 12:00, 8 February 2009

The A20 Address Line is the physical representation of the 20th bit 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 20th line on the address bus (A20) was disabled by default.

The wraparound was caused by the fact 8086 computers could only access 1 megabyte of memory but because of there segmented memory access method they could effectively address up to 1 megabyte and 63 and a bit kilobytes. In order to keep the number of transistors on their chips to a minimum, Intel decided to wrap around address above the 1 megabyte mark to the beginning of memory. 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 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 always disabled, but some BIOSes 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 better to test whether the A20 address line is 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).

; The following code is public domain licenced

[bits 16]

; 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 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
    push fs
    push gs
    push di
    push si

    cli

    xor ax, ax ; ax = 0
    mov fs, ax

    not ax ; ax = 0xFFFF
    mov gs, ax

    mov di, 0x0500
    mov si, 0x0510

    mov al, byte [fs:di]
    push ax

    mov al, byte [gs:si]
    push ax

    mov byte [fs:di], 0x00
    mov byte [gs:si], 0xFF

    cmp byte [fs:di], 0xFF

    pop ax
    mov byte [gs:si], al

    pop ax
    mov byte [fs:di], al

    xor ax, ax
    je check_a20__exit

    mov al, 1

check_a20__exit:
    pop si
    pop di
    pop gs
    pop fs
    popf

    ret

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.

void init_A20(void)
{
   UCHAR   a;

   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
;;

[bits 32]
[section .text]

enable_A20:
        cli

        call    a20wait
        mov     al,0xAD
        out     0x64,al

        call    a20wait
        mov     al,0xD0
        out     0x64,al

        call    a20wait2
        in      al,0x60
        push    eax

        call    a20wait
        mov     al,0xD1
        out     0x64,al

        call    a20wait
        pop     eax
        or      al,2
        out     0x60,al

        call    a20wait
        mov     al,0xAE
        out     0x64,al

        call    a20wait
        sti
        ret

a20wait:
        in      al,0x64
        test    al,2
        jnz     a20wait
        ret


a20wait2:
        in      al,0x64
        test    al,1
        jz      a20wait2
        ret

Fast A20 Gate

On several newer computers, 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.

in al, 0x92
or al, 2
out 0x92, al

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.

See Also

External links