Search (using Google):  Web Karig

 

21 November 2003

A20 line

When you first turn on the PC, only one megabyte of memory is available. The OS has to do something special to make all of the installed RAM accessible!

Specifically, the OS has to "enable the A20 line." The bus that allows the processor to access memory has the first twenty lines (A0 through A19) enabled when you turn on the PC. This allows the processor to "see" 220 bytes, or one megabyte. To keep the computer compatible with the original IBM PC, the twenty-first line (A20) is disabled on startup. To allow the processor to "see" all the memory you have installed, the OS has to re-enable this line.

Code #1

This code is the quick-and-dirty version. I extracted it from the boot sector for Go! OS. It works fine on my laptop, and the resulting machine code is only eight bytes long. Make sure that interrupts are turned off when this code is run.

		mov	al, 0xD1
		out	0x64, al
		mov	al, 0x03
		out	0x60, al

Code #2

This code is much longer, but it does its job more carefully, and it is probably more reliable. The code above works fine on my laptop, so that's the code I'm using for now — but this code also works, so I'm passing it along.

This code is based on "enableA20.s," written by J. Andrew McLaughlin for his own OS, Visopsys. McLaughlin claims that his routine "seems to work more reliably than other examples I've tried." I took his code and optimized it for code size — the machine code is less than eighty bytes long.

To use this code, you should be in real mode, and interrupts should be disabled.

[BITS 16]

		cli

To set things up, we set CX to the maximum number of attempts to make before giving up, set DX to the I/O port most frequently accessed here, and jump over the subroutines to the main code.

enableA20:
		mov	cx, 5
		mov	dx, 0x64
		jmp	short .startAttempt1

The main "enableA20" routine contains two subroutines. The first one keeps checking the keyboard controller until the controller indicates that it is ready to accept a command. (Yes, you enable the A20 line through the keyboard controller — odd but true.)

	.commandWait:
		in	al, dx
		test	al, 2
		jnz	.commandWait
		ret

The second subroutine tells the keyboard controller to return the current contents of the 8042 output port. (We'll need to change a bit in the byte returned and then change the contents of the output port to the new byte value.)

	.getData:

The subroutine first waits for the controller to be ready for a command,

		call	.commandWait

sends command 0xD0 ("read output port") to the controller,

		mov	al, 0xD0
		out	dx, al

waits for the controller to say the data is ready,

	.0:	in	al, dx
		test	al, 1
		jz	.0

and finally retrieves the data.

		in	al, 0x60
		ret

Now the main routine begins.

The main routine tries the first of two methods for setting up the A20 line. This method retrieves the content of the output port and saves it on the stack.

	.startAttempt1:
		call	.getData
		push	ax

Then it tells the controller that it wants to write a byte to the output port.

		call	.commandWait
		mov	al, 0xD1
		out	dx, al

When the controller is ready to receive, the routine pops the byte off the stack, sets the enable-A20 bit, and sends the changed byte to the output port.

		call	.commandWait
		pop	ax
		or	al, 2
		out	0x60, al

The routine then reads back the output port byte to verify that the change took. If it did, the routine exits; otherwise the routine tries again (up to five times) before falling back on method #2.

		call	.getData
		and	al, 2
		jnz	.exit
		loop	.startAttempt1

If method #1 failed, try method #2. Re-initialize CX (note that CH is already zero, because LOOP stops looping when CX is zero, so only CL needs to be changed. This makes the code a byte shorter).

		mov	cl, 5

The second method is simpler: Simply send a command telling the controller to turn on the A20 line.

	.startAttempt2:
		call	.commandWait
		mov	al, 0xDF
		out	dx, al

Then read the output port byte back to make sure that the A20-line bit is set. If it is, exit; if not, try again (up to five times).

		call	.getData
		and	al, 2
		jnz	.exit
		loop	.startAttempt2

If AL wound up zero, that means that the A20 line hasn't been enabled. The following instruction stores the result in the zero flag, so you'd follow this with either "jnz .success" or "jz .error" in your code.

	.exit:
		test	al, al

You can now re-enable interrupts.

		sti

Testing

Testing this code requires writing to memory beyond the one-megabyte mark and then reading it back. Unfortunately you can't do that in real mode, because all segments are 64KB long, and the highest possible segment is 0xFFFF. You'd have to set up either protected mode or unreal mode before you can write to or read from high memory. I have done this in testing code, so I know that the code above works. However, I'll discuss unreal mode and protected mode in a future entry.

Other pages about the A20 line

EZResult.com: A20 Line
The A20 line
OS FAQ: What is the A20 line? — [Russian mirror] — [Mega-Tokyo.com]
The OS Project: The A20 Line
A20: A pain from the past
Source code from Linux setup.s
Linux Kernel: Re: A20 Gate enable sequence (setup.S)

You can also check out the links at the bottom of the entry on boot sectors. Some of the links go to source code files that contain code that enable the A20 line.

Check the index for other entries.