Enable A20 Gate

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, so ES: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, so DS: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, so ES:DI points to 0x000500.
  • mov si, 0x0510 sets SI to 0x0510, so DS:SI points to 0x10FF0.

4 Save Original Memory Values:

  • The code reads and saves the original byte values at 0x000500 and 0x10FF0 to ensure they can be restored later.

5 Modify Memory and Check A20 Line:

  • The code writes 0x00 to 0x000500 and 0xFF to 0x10FF0.
  • It then compares the byte at 0x000500 with 0xFF. If the A20 line is disabled, the address 0x10FF0 wraps around to 0x000500, making the comparison true.

6 Restore Original Memory Values:

  • The original bytes read from 0x10FF0 and 0x000500 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 value 0x2401. The BIOS interrupt INT 15h function 0x2401 is specifically used to control the A20 line. The subfunction 01 (the lower byte) in 0x2401 is the command to enable the A20 line.

2 Calling the BIOS Interrupt:

  • int 0x15: This calls the BIOS interrupt 0x15 (INT 15h). When the interrupt is called with AX set to 0x2401, 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