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