Before switching to protected mode, we must have to unleash the full memory of the system. Which is enabling the A20 Gate (Line), the 21st address line (0-based indexing).
Get the full in detail depth knowledge on A20 gate here: https://thejat.in/learn/the-a20-line
Here we will implement the A20 gate in our TheTaaJ
code:
1️⃣ Check the State of A20
First we need to check the state of A20 if it is set or reset. It is done on the principle of checking if memory after the 1 MB range is getting wrapped around or not.
We will put this code in a20.inc
:
Here is the code:
; ********************************
; CheckA20Gate
; OUT:
; - AX: 0 If A20 is disabled, 1 If enabled
; ********************************
CheckA20Gate:
pushf ; Save flags register
push ds ; Save DS register
push es ; Save ES register
push di ; Save DI register
push si ; Save SI register
cli ; Clear interrupts to prevent interference
xor ax, ax ; Clear AX (set AX to 0)
mov es, ax ; Set ES segment to 0
not ax ; Invert AX (set AX to 0xFFFF)
mov ds, ax ; Set DS segment to 0xFFFF
mov di, 0x0500 ; Set DI to 0x0500 (ES:DI points to 0x000500)
mov si, 0x0510 ; Set SI to 0x0510 (DS:SI points to 0x10FF0)
mov al, byte [es:di] ; Load byte from ES:DI (0x000500) into AL
push ax ; Save the byte read from 0x000500
mov al, byte [ds:si] ; Load byte from DS:SI (0x10FF0) into AL
push ax ; Save the byte read from 0x10FF0
mov byte [es:di], 0x00 ; Write 0x00 to 0x000500
mov byte [ds:si], 0xFF ; Write 0xFF to 0x10FF0
cmp byte [es:di], 0xFF ; Compare byte at 0x000500 with 0xFF
pop ax ; Restore original byte to AL from stack
mov byte [ds:si], al ; Write the original byte back to 0x10FF0
pop ax ; Restore original byte to AL from stack
mov byte [es:di], al ; Write the original byte back to 0x000500
mov ax, 0 ; Set AX to 0 (assume A20 is disabled)
je CheckA20__Exit ; If the comparison was equal (A20 disabled), jump to exit
mov ax, 1 ; Otherwise, set AX to 1 (A20 enabled)
CheckA20__Exit:
pop si ; Restore SI register
pop di ; Restore DI register
pop es ; Restore ES register
pop ds ; Restore DS register
popf ; Restore flags register
ret ; Return from subroutine
Explanation:
This code checks if the A20 line is enabled by writing to two memory locations that would conflict if the A20 line is disabled (causing address wrapping). If the A20 line is enabled, the addresses do not wrap, and the comparison fails. If the A20 line is disabled, the addresses wrap, and the comparison succeeds.
This function returns:
- AX = 0, if A20 line is disabled
- AX = 1, if A20 line is enabled
1 Save Registers and Clear Interrupts:
- The code starts by saving the state of various registers and the flags register. This is important to ensure that the state of the processor is preserved across the subroutine call.
- The
cli
instruction disables interrupts to prevent any interruption during the A20 line check.
2 Set Up Segment Registers:
xor ax, ax
clears the AX register, setting it to 0.mov es, ax
sets the ES segment register to 0, soES:DI
points to addresses within the first 64 KB of memory.not ax
inverts AX, setting it to 0xFFFF.mov ds, ax
sets the DS segment register to 0xFFFF, soDS:SI
points to addresses within the last 64 KB of the 1 MB address space.
3 Set Up Pointers:
mov di, 0x0500
sets DI to 0x0500, soES:DI
points to 0x000500.mov si, 0x0510
sets SI to 0x0510, soDS:SI
points to 0x10FF0.
4 Save Original Memory Values:
- The code reads and saves the original byte values at
0x000500
and0x10FF0
to ensure they can be restored later.
5 Modify Memory and Check A20 Line:
- The code writes
0x00
to0x000500
and0xFF
to0x10FF0
. - It then compares the byte at
0x000500
with0xFF
. If the A20 line is disabled, the address0x10FF0
wraps around to0x000500
, making the comparison true.
6 Restore Original Memory Values:
- The original bytes read from
0x10FF0
and0x000500
are restored to their respective locations.
7 Set Return Value Based on A20 Status:
- If the comparison was true (
je
), it means the A20 line is disabled, and AX is set to 0. - If the comparison was false, the A20 line is enabled, and AX is set to 1.
8 Restore Registers and Return:
- The saved registers and flags are restored, and the subroutine returns.
2️⃣ Trying to Enable 20 using the BIOS method
This method is straightforward way to enable the A20 line using a BIOS function. It uses the BIOS interrupt INT 15h
with function 0x2401
to enable the A20 gate, allowing access to the full 20-bit address space beyond the 1 MB boundary. This method is typically used in bootloaders or early stage operating system initialization where BIOS services are still available and reliable.
This method is not compatible, we should not rely only on this method.
; ********************************
; A20MethodBios
; ********************************
A20MethodBios:
; BIOS function to enable A20 line
mov ax, 0x2401 ; Load AX with BIOS function 2401h (Enable A20)
int 0x15 ; Call BIOS interrupt 15h to enable A20
ret ; Return from subroutine
As we already discussed that this is not the reliable method, as it is not compatible to wide variety of system. So after this method we should check if the A20 is enabled or not. If enabled good else proceed to next method.
Explanation:
1 Setting Up BIOS Function Call:
mov ax, 0x2401
: This instruction sets up the AX register with the value0x2401
. The BIOS interruptINT 15h
function0x2401
is specifically used to control the A20 line. The subfunction01
(the lower byte) in0x2401
is the command to enable the A20 line.
2 Calling the BIOS Interrupt:
int 0x15
: This calls the BIOS interrupt0x15
(INT 15h). When the interrupt is called withAX
set to0x2401
, the BIOS will enable the A20 line.
3 Returning from Subroutine:
ret
: This returns from the subroutine, handing control back to the caller. At this point, the A20 line should be enabled.
3️⃣ Using Keyboard Controller
; ********************************
; A20MethodKeyboardController
; ********************************
A20MethodKeyboardController:
cli ; Clear interrupts to prevent interference
call A20Wait ; Wait for the keyboard controller to be ready
mov al,0xAD ; Command to disable the keyboard
out 0x64,al ; Send command to keyboard controller
call A20Wait ; Wait for the keyboard controller to be ready
mov al,0xD0 ; Command to read the output port
out 0x64,al ; Send command to keyboard controller
call A20Wait2 ; Wait until there is data to read
in al,0x60 ; Read data from the keyboard controller's output port
push eax ; Save the current state of the output port
call A20Wait ; Wait for the keyboard controller to be ready
mov al,0xD1 ; Command to write to the output port
out 0x64,al ; Send command to keyboard controller
call A20Wait ; Wait for the keyboard controller to be ready
pop eax ; Restore the saved state of the output port
or al,2 ; Set the A20 enable bit (bit 1) in the output port value
out 0x60,al ; Write the modified value back to the output port
call A20Wait ; Wait for the keyboard controller to be ready
mov al,0xAE ; Command to enable the keyboard
out 0x64,al ; Send command to keyboard controller
call A20Wait ; Wait for the keyboard controller to be ready
sti ; Enable interrupts
ret ; Return from subroutine
; A20Wait - Wait until the keyboard controller is ready to accept a command
A20Wait:
in al,0x64 ; Read status register from keyboard controller
test al,2 ; Test input buffer status bit (bit 1)
jnz A20Wait ; If bit 1 is set, wait (loop) until it is cleared
ret ; Return from subroutine
; A20Wait2 - Wait until there is data available to read from the keyboard controller
A20Wait2:
in al,0x64 ; Read status register from keyboard controller
test al,1 ; Test output buffer status bit (bit 0)
jz A20Wait2 ; If bit 0 is clear, wait (loop) until it is set
ret ; Return from subroutine
Explanation:
1 Disabling Interrupts:
cli
: Disables interrupts to ensure that the process is not interrupted.
2 Disabling the Keyboard:
call A20Wait
: Waits for the keyboard controller to be ready.mov al,0xAD
: Loads the command to disable the keyboard.out 0x64,al
: Sends the command to the keyboard controller.
3 Reading the Output Port:
call A20Wait
: Waits for the keyboard controller to be ready.mov al,0xD0
: Loads the command to read the output port.out 0x64,al
: Sends the command to the keyboard controller.call A20Wait2
: Waits until there is data available to read.in al,0x60
: Reads the data from the output port.push eax
: Saves the current state of the output port.
4 Setting the A20 Line:
call A20Wait
: Waits for the keyboard controller to be ready.mov al,0xD1
: Loads the command to write to the output port.out 0x64,al
: Sends the command to the keyboard controller.call A20Wait
: Waits for the keyboard controller to be ready.pop eax
: Restores the saved state of the output port.or al,2
: Sets the A20 enable bit (bit 1) in the output port value.out 0x60,al
: Writes the modified value back to the output port.
5 Re-enabling the Keyboard:
call A20Wait
: Waits for the keyboard controller to be ready.mov al,0xAE
: Loads the command to enable the keyboard.out 0x64,al
: Sends the command to the keyboard controller.
6 Re-enabling Interrupts:
call A20Wait
: Waits for the keyboard controller to be ready.sti
: Enables interrupts.ret
: Returns from the subroutine.
7 A20Wait Subroutine:
- A20Wait:
- Waits until the keyboard controller is ready to accept a command by checking the input buffer status bit (bit 1) of the status register.
- A20Wait2:
- Waits until there is data available to read from the keyboard controller by checking the output buffer status bit (bit 0) of the status register.
4️⃣ Complete Example:
%ifndef _STAGE_2_A20_INC_
%define _STAGE_2_A20_INC_
; ********************************
; CheckA20Gate
; OUT:
; - AX: 0 If A20 is disabled, 1 If enabled
; ********************************
CheckA20Gate:
pushf ; Save flags register
push ds ; Save DS register
push es ; Save ES register
push di ; Save DI register
push si ; Save SI register
cli ; Clear interrupts to prevent interference
xor ax, ax ; Clear AX (set AX to 0)
mov es, ax ; Set ES segment to 0
not ax ; Invert AX (set AX to 0xFFFF)
mov ds, ax ; Set DS segment to 0xFFFF
mov di, 0x0500 ; Set DI to 0x0500 (ES:DI points to 0x000500)
mov si, 0x0510 ; Set SI to 0x0510 (DS:SI points to 0x10FF0)
mov al, byte [es:di] ; Load byte from ES:DI (0x000500) into AL
push ax ; Save the byte read from 0x000500
mov al, byte [ds:si] ; Load byte from DS:SI (0x10FF0) into AL
push ax ; Save the byte read from 0x10FF0
mov byte [es:di], 0x00 ; Write 0x00 to 0x000500
mov byte [ds:si], 0xFF ; Write 0xFF to 0x10FF0
cmp byte [es:di], 0xFF ; Compare byte at 0x000500 with 0xFF
pop ax ; Restore original byte to AL from stack
mov byte [ds:si], al ; Write the original byte back to 0x10FF0
pop ax ; Restore original byte to AL from stack
mov byte [es:di], al ; Write the original byte back to 0x000500
mov ax, 0 ; Set AX to 0 (assume A20 is disabled)
je CheckA20Gate__Exit ; If the comparison was equal (A20 disabled), jump to exit
mov ax, 1 ; Otherwise, set AX to 1 (A20 enabled)
CheckA20Gate__Exit:
pop si ; Restore SI register
pop di ; Restore DI register
pop es ; Restore ES register
pop ds ; Restore DS register
popf ; Restore flags register
ret ; Return from subroutine
; ********************************
; A20MethodBios
; ********************************
A20MethodBios:
mov si, sA20MethodBIOSSentence
call PrintString16BIOS
call PrintNewline
; BIOS function to enable A20 line
mov ax, 0x2401 ; Load AX with BIOS function 2401h (Enable A20)
int 0x15 ; Call BIOS interrupt 15h to enable A20
ret ; Return from subroutine
; ********************************
; A20MethodKeyboardController
; ********************************
A20MethodKeyboardController:
cli ; Clear interrupts to prevent interference
mov si, sA20MethodKeyboardControllerSentence
call PrintString16BIOS
call PrintNewline
call A20Wait ; Wait for the keyboard controller to be ready
mov al,0xAD ; Command to disable the keyboard
out 0x64,al ; Send command to keyboard controller
call A20Wait ; Wait for the keyboard controller to be ready
mov al,0xD0 ; Command to read the output port
out 0x64,al ; Send command to keyboard controller
call A20Wait2 ; Wait until there is data to read
in al,0x60 ; Read data from the keyboard controller's output port
push eax ; Save the current state of the output port
call A20Wait ; Wait for the keyboard controller to be ready
mov al,0xD1 ; Command to write to the output port
out 0x64,al ; Send command to keyboard controller
call A20Wait ; Wait for the keyboard controller to be ready
pop eax ; Restore the saved state of the output port
or al,2 ; Set the A20 enable bit (bit 1) in the output port value
out 0x60,al ; Write the modified value back to the output port
call A20Wait ; Wait for the keyboard controller to be ready
mov al,0xAE ; Command to enable the keyboard
out 0x64,al ; Send command to keyboard controller
call A20Wait ; Wait for the keyboard controller to be ready
sti ; Enable interrupts
ret ; Return from subroutine
; A20Wait - Wait until the keyboard controller is ready to accept a command
A20Wait:
in al,0x64 ; Read status register from keyboard controller
test al,2 ; Test input buffer status bit (bit 1)
jnz A20Wait ; If bit 1 is set, wait (loop) until it is cleared
ret ; Return from subroutine
; A20Wait2 - Wait until there is data available to read from the keyboard controller
A20Wait2:
in al,0x64 ; Read status register from keyboard controller
test al,1 ; Test output buffer status bit (bit 0)
jz A20Wait2 ; If bit 0 is clear, wait (loop) until it is set
ret ; Return from subroutine
; ********************************
; EnableA20Gate
; ********************************
EnableA20Gate:
; Save State
pusha ; Push all general-purpose registers onto the stack
; Print the enabling a20 line string
mov si, sA20GateSentence ; Load address of string into SI
call PrintString16BIOS ; Call Print subroutine to print the sentence
call PrintNewline ; \n
; Step 1, check the status of A20 Gate
call CheckA20Gate ; Call CheckA20Gate subroutine to check if A20 is enabled
cmp ax, 1 ; Compare AX with 1 (AX = 1 if A20 is enabled)
je .Done ; If equal (A20 is enabled), jump to .Done
; First we try to enable through BIOS
call A20MethodBios ; Call A20MethodBios subroutine to enable A20 via BIOS
; Did it work?
call CheckA20Gate ; Call CheckA20Gate subroutine to check if A20 is enabled
cmp ax, 1 ; Compare AX with 1 (AX = 1 if A20 is enabled)
je .Done ; If equal (A20 is enabled), jump to .Done
; Failed, not supported then, try keyboard controller
call A20MethodKeyboardController ; Call A20MethodKeyboardController subroutine
; Did it work?
call CheckA20Gate ; Call CheckA20Gate subroutine to check if A20 is enabled
cmp ax, 1 ; Compare AX with 1 (AX = 1 if A20 is enabled)
je .Done ; If equal (A20 is enabled), jump to .Done
.Error:
; Failed :(
mov si, sA20GateFailSentence ; Load address of string into SI
call PrintString16BIOS ; Call Print subroutine to print the failure message
; Halt
; call SystemsFail ; Call SystemsFail subroutine to handle failure
cli ; Clear interrupts
hlt ; Halt the processor
.Done:
; Print
mov si, sA20GateSuccessSentence ; Load address of szSuccess string into ESI
call PrintString16BIOS ; Call Print subroutine to print the success message
call PrintNewline
; Restore & Return
popa ; Restore all general-purpose registers from the stack
ret ; Return from subroutine
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Variables
sA20GateSentence db 'The A20 Area.', 0
sA20GateFailSentence db 'Enabling A20 Failed.', 0
sA20GateSuccessSentence db 'Enabling A20 Succeed.', 0
sA20MethodBIOSSentence db 'Trying to enable A20 using BIOS...', 0
sA20MethodKeyboardControllerSentence db 'Trying to enable A20 using Keyboard Controller...', 0
%endif
And just call the function EnableA20Gate
from the stage2.asm
:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Enable A20 Gate (Line)
call EnableA20Gate
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
The complete source code is available at here: https://github.com/The-Jat/TheTaaJ/tree/aa272ccc698d71713491f2db97d9d8979ed17a2d