Introduction
Long mode is a key feature in x86-64 (also known as AMD64) architecture, enabling 64-bit computing and providing access to a much larger address space. This mode is essential for modern operating systems, allowing them to utilize large amounts of memory and perform efficiently.
What is Long Mode?
Long mode is the 64-bit mode of x86-64 processors. It consists of two sub-modes:
- 64-bit Mode: This mode allows the execution of 64-bit code and provides access to 64-bit registers and 64-bit virtual address space.
- Compatibility Mode: This mode allows the execution of legacy 16-bit and 32-bit code within a 64-bit environment.
Components of Long Mode
1 Registers:
General-Purpose Registers: The x86-64 architecture extends the 8 general-purpose registers (EAX, EBX, etc.) to 64 bits and adds 8 more, making a total of 16 general-purpose registers (RAX, RBX, etc.).
64-bit | 32-bit | 16-bit | 8-bit (high) | 8-bit (low) |
RAX | EAX | AX | AH | AL |
RBX | EBX | BX | BH | BL |
RCX | ECX | CX | CH | CL |
RDX | EDX | DX | DH | DL |
RSI | ESI | SI | (N/A) | SIL |
RDI | ESI | DI | (N/A) | DIL |
RBP | EBP | BP | (N/A) | BPL |
RSP | ESP | SP | (N/A) | SPL |
R8 | R8D | R8W | (N/A) | R8B |
R9 | R9D | R9W | (N/A) | R9B |
R10 | R10D | R10W | (N/A) | R10B |
R11 | R11D | R11W | (N/A) | R11B |
R12 | R12D | R12W | (N/A) | R12B |
R13 | R13D | R13W | (N/A) | R13B |
R14 | R14D | R14W | (N/A) | R14B |
R15 | R15D | R15W | (N/A) | R15B |
In x86-64 architecture, you can access the high bits of some 8-bit registers that are part of the original set of x86 registers, but not for the new registers (R8-R15) introduced with the x86-64 architecture.
For the original x86 registers, the high 8 bits of the 16-bit registers AX, BX, CX, and DX can be accessed using AH, BH, CH, and DH respectively. Here's the breakdown:
- AX: AH (high 8 bits), AL (low 8 bits)
- BX: BH (high 8 bits), BL (low 8 bits)
- CX: CH (high 8 bits), CL (low 8 bits)
- DX: DH (high 8 bits), DL (low 8 bits)
Segment Registers: The x86-64 architecture includes the following segment registers:
- CS (Code Segment Register): Contains the segment selector for the current code segment.
- DS (Data Segment Register): Generally used for accessing data segments.
- ES (Extra Segment Register): Used for additional data segment addressing.
- FS (Additional Segment Register): Often used for thread-specific data.
- GS (Additional Segment Register): Commonly used for operating system structures or thread-specific data.
- SS (Stack Segment Register): Points to the segment containing the stack.
In long mode, the role of segment registers is significantly reduced compared to real mode and protected mode. This is because 64-bit addressing largely ignores segment-based addressing in favor of flat memory models. However, they are not entirely.
Instruction Pointer: The instruction pointer (RIP) is extended to 64 bits.
Control Registers: CR0, CR2, CR3, and CR4 are used for control purposes, with additional bits for 64-bit operation.
2 Memory Addressing:
Virtual Address Space: In 64-bit mode, the virtual address space is significantly expanded. Current implementations support 48-bit
virtual addresses, providing 256 TB
(2^48
) of addressable space, with future support planned for up to 64 bits.
Paging: Long mode requires paging to be enabled. It uses a four-level paging structure (PML4, PDPT, PD, and PT).
3 Segment Descriptors:
In 64-bit mode, segmentation is largely disabled. The segment base is treated as zero, and the limit is ignored. However, segments are still used for compatibility mode and certain exceptions.
4 Global Descriptor Table (GDT):
The GDT in long mode includes descriptors for 64-bit code and data segments, and for compatibility mode.
Transition to Long Mode
Transitioning to long mode in x86-64 architecture involves several crucial steps. This process ensures that the CPU can execute 64-bit instructions and address a larger memory space, which is essential for modern operating systems. Here’s a detailed guide on how to check for long mode support, activate the A20 gate, prepare memory paging, remap the PIC, and finally enter long mode to pass control to the kernel.
1 Check Long Mode Support
Before attempting to enter long mode, ensure that the CPU supports it. This can be done by checking the CPUID instruction.
; Check if CPUID is supported
pushfd ; Save EFLAGS
pop eax
mov ebx, eax
xor eax, 0x00200000 ; Toggle ID bit in EFLAGS
push eax
popfd
pushfd
pop eax
xor eax, ebx
jz .no_cpuid ; CPUID not supported
; Check for long mode support
mov eax, 0x80000001 ; Extended Features
cpuid
test edx, 0x20000000 ; Check if bit 29 (LM) is set
jnz .long_mode_supported
.no_cpuid:
; Handle CPUID not supported case
; Could halt or display an error message
hlt
.long_mode_supported:
2 Enable A20
The A20 line is crucial for accessing memory above 1 MB in x86 architecture. Ensuring the A20 line is enabled is a multi-step process, involving several methods with fallback mechanisms. Here’s a detailed guide on how to check if the A20 line is enabled and the steps to enable it using various methods.
The A20 Issue
The A20 line is the 21st address line. Enabling this line allows the CPU to access memory beyond 1 MB. However, on some older CPUs, this line was left floating, causing memory addresses higher than 1 MB to wrap around to the beginning of memory (address 0).
How the Wrapping Occurs
When the A20 line is disabled, accessing memory locations above 1 MB causes a wraparound effect. For example:
- Accessing address 0x100000 (1 MB) would actually reference address 0x00000.
- Accessing address 0x100001 would reference address 0x00001.
- Accessing address 0x100010 would reference address 0x00010.
This wrapping around can cause serious issues in operating systems or software that requires access to memory above 1 MB.
–[1]) Checking if A20 is Already Enabled
First, we need to test if the A20 line is already enabled. This can be done by comparing memory at 0x0000:0x0500 and 0xFFFF:0x0510
. If the A20 line is enabled, it allows access to memory above 1MB without wrapping around.
The Test Mechanism:
The A20 test mechanism used in the provided code relies on this wrapping effect. Here's how it works:
- The function writes a specific value to two different memory locations: 0x0000:0x0500 and 0xFFFF:0x0510.
- If the A20 line is disabled, the value written to 0xFFFF:0x0510 will wrap around and overwrite the value at 0x0000:0x0500.
- By comparing the value at 0x0000:0x0500, the function can determine whether the A20 line is enabled or disabled. If the value matches what was written (0xFF), it indicates that the A20 line is disabled. If it doesn't match, it indicates that the A20 line is enabled.
call Check_A20 ; Check if A20 is already enabled.
test ax, ax
jnz .end
Check_A20:
;********************************************************************;
; Check the status of the A20 line ;
;********************************************************************;
call Real_mode_check_A20
test ax, ax
jnz .a20_enabled
mov si, a20_disabled_message
call Real_mode_println
ret
.a20_enabled:
mov si, a20_enabled_message
call Real_mode_println
ret
Real_mode_check_A20:
;**************************************************************************;
; Check the status of the A20 line (in real mode) ;
;--------------------------------------------------------------------------;
; Returns: ax = 0 if the a20 line is disabled (memory wraps around) ;
; ax = 1 if the a20 line is enabled (memory does not wrap around) ;
;**************************************************************************;
pushf ; Save the current state of the FLAGS register
; Save the current values of DS, ES, DI, and SI registers
push ds
push es
push di
push si
; Clear interrupts to prevent any interference during this critical operation.
cli ; clear interrupts
xor ax, ax ; ax = 0
mov es, ax ; es = 0
not ax ; ax = 0xFFFF
mov ds, ax ; ds = 0xFFFF
mov di, 0x0500 ; 0500 and 0510 are chosen since they are guaranteed to be free
mov si, 0x0510 ; for use at any point of time after BIOS initialization.
; save the original values found at these addresses.
mov dl, byte [es:di]
push dx
mov dl, byte [ds:si]
push dx
mov byte [es:di], 0x00 ; [es:di] is 0:0500
mov byte [ds:si], 0xFF ; [ds:si] is FFFF:0510
cmp byte [es:di], 0xFF ; if the A20 line is disabled, [es:di] will contain 0xFF
; (as the write to [ds:si] really occured to 00500).
mov ax, 0 ; A20 disabled ([es:di] equal to 0xFF).
je .a20_disabled
mov ax, 1 ; A20 enabled.
.a20_disabled:
; restore original values
pop dx
mov byte [ds:si], dl
pop dx
mov byte [es:di], dl
pop si
pop di
pop es
pop ds
popf
sti ; Enable interrupts.
ret
–[2]) Trying to Enable A20 Using BIOS Function:
Attempt to enable the A20 line using the BIOS function (INT 15h, AX = 2401h). Ignore the returned status as the function may not be supported on all systems.
Enable_A20_using_BIOS:
;*************************************************************;
; Try to enable A20 gate using the BIOS (int 15h, ax = 2401h) ;
;-------------------------------------------------------------;
; Returns: ax = 0 (Failure) ;
; ax = 1 (Success) ;
;*************************************************************;
mov ax,2403h ; Query A20 gate Support (later PS/2s systems)
int 15h
jb .failure ; INT 15h is not supported
cmp ah, 0
jnz .failure ; INT 15h is not supported
mov ax, 2402h ; Get A20 gate Status
int 15h
jb .failure ; Couldn't get status
cmp ah, 0
jnz .failure ; Couldn't get status
cmp al, 1
jz .success ; A20 is already activated
mov ax, 2401h ; Enable A20 gate
int 15h
jb .failure ; Couldn't enable the A20 gate
cmp ah, 0
jnz .failure ; Couldn't enable the A20 gate
.success:
mov ax, 1
ret
.failure:
mov ax, 0
ret
- Query A20 Gate Support (ax = 2403h):
- The function queries if the A20 gate support is available on later PS/2 systems.
- Check A20 Gate Support:
- If AH is not 0, indicating the function is not supported, the function returns failure.
- Get A20 Gate Status (ax = 2402h):
- The function checks the current status of the A20 gate.
- Check A20 Gate Status:
- If AH is not 0, indicating the function is not supported, the function returns failure.
- If AL is 1, indicating the A20 gate is already activated, the function returns success.
- Enable A20 Gate (ax = 2401h):
- If the A20 gate is not already activated, the function attempts to enable it.
- Return:
- If successful, the function returns success (AX = 1). If any step fails, the function returns failure (AX = 0).
–[3]) Re-check if A20 is Enabled:
After attempting to enable A20 using the BIOS function, re-check if A20 is enabled.
call Check_A20 ; Check if A20 is enabled.
test ax, ax
jnz .end
–[4]) Trying to Enable A20 Using the Keyboard Controller Method:
If the BIOS method didn’t work, use the keyboard controller method to enable A20.
; Try to enable A20 using Keyboard Controller.
mov si, a20_trying_keyb
call Real_mode_println
call Enable_A20_using_Keyboard_Controller
Enable_A20_using_Keyboard_Controller:
;******************************************************************;
; Try to enable A20 line using the Keyboard Controller (chip 8042) ;
;------------------------------------------------------------------;
; Returns: ax = 0 (Failure) ;
; ax = 1 (Success) ;
;******************************************************************;
cli ; Clear interrupts.
call a20wait
mov al, 0xAD ; Disable keyboard.
out 0x64, al
call a20wait
mov al, 0xD0 ; Read from input.
out 0x64, al
call a20wait2
in al,0x60
push eax
call a20wait
mov al, 0xD1 ; Write to output.
out 0x64, al
call a20wait
pop eax
or al, 2
out 0x60, al
call a20wait
mov al, 0xAE ; Enable keyboard.
out 0x64, al
call a20wait
sti ; Enables interrupts.
ret
a20wait:
in al, 0x64
test al, 2
jnz a20wait
ret
a20wait2:
in al, 0x64
test al, 1
jz a20wait2
ret
Disable Keyboard:
- Disable interrupts (
cli
). - Send the command 0xAD to the keyboard controller to disable the keyboard.
Wait for Input Buffer to Clear:
a20wait
function waits until the input buffer is clear.
Send Command to Read from Input Buffer:
- Send the command 0xD0 to the keyboard controller to read from the input buffer.
Wait for Input Buffer to Fill:
a20wait2
function waits until the input buffer is filled with the status byte.
Read from Input Buffer:
- Read the status byte from port 0x60 and push it onto the stack.
Send Command to Write to Output Buffer:
- Send the command 0xD1 to the keyboard controller to write to the output buffer.
Write to Output Buffer:
- Pop the status byte from the stack, set bit 1 (A20 gate enable), and write it to port 0x60.
Enable Keyboard:
- Send the command 0xAE to the keyboard controller to enable the keyboard.
Enable Interrupts:
- Enable interrupts (
sti
) before returning.
–[5]) Rechecking A20 with timeout:
Check again if A20 is enabled, allowing for some delay as the keyboard controller method might be slow (it's changes might take some time to reflect).
check_a20_with_timeout:
mov cx, 0xFFFF ; Timeout counter
.loop:
call check_A20 ; Check if A20 is enabled
jmp .a20_enabled ; If A20 is enabled, skip the remaining steps
loop .loop ; Decrement CX and repeat until zero
– [6]) Enabling A20 using the fast A20 method:
Set bit 1 of port 0x92 to enable A20.
; Try to enable A20 using IO port 92h (Fast A20 method).
mov si, a20_trying_io92
call Real_mode_println
call Enable_A20_using_IO_port_92
;*********************************************************************;
; Enable A20 Line via IO port 92h (Fast A20 method) ;
;---------------------------------------------------------------------;
; This method is quite dangerous because it may cause conflicts with ;
; some hardware devices forcing the system to halt. ;
;=====================================================================;
; Bits of port 92h ;
;---------------------------------------------------------------------;
; Bit 0 - Setting to 1 causes a fast reset ;
; Bit 1 - 0: disable A20, 1: enable A20 ;
; Bit 2 - Manufacturer defined ;
; Bit 3 - power on password bytes. 0: accessible, 1: inaccessible ;
; Bits 4-5 - Manufacturer defined ;
; Bits 6-7 - 00: HDD activity LED off, 01 or any value is "on" ;
;*********************************************************************;
Enable_A20_using_IO_port_92:
in al, 0x92 ; Read from port 0x92
test al, 2 ; Check if bit 1 (i.e. the 2nd bit) is set.
jnz .end ; If bit 1 (i.e. the 2nd bit) is already set don't do anything.
or al, 2 ; Activate bit 1 (i.e. the 2nd bit).
and al, 0xFE ; Make sure bit 0 is 0 (it causes a fast reset).
out 0x92, al ; Write to port 0x92
.end:
ret
– [7]) Again, Check if A20 is enabled
call Check_A20 ; Check if A20 is enabled.
test ax, ax
jnz .end
Complete Code of a20.asm
BITS 16
;---Initialized data------------------------------------------------------------
a20_enabled_message dw 15
db 'A20 is enabled.'
a20_disabled_message dw 16
db 'A20 is disabled.'
a20_trying_bios dw 34
db 'Trying to enable A20 using BIOS...'
a20_trying_keyb dw 49
db 'Trying to enable A20 using Keyboard Controller...'
a20_trying_io92 dw 40
db 'Trying to enable A20 using IO port 92...'
;---Code------------------------------------------------------------------------
Enable_A20:
call Check_A20 ; Check if A20 is already enabled.
test ax, ax
jnz .end
; Try to enable A20 using BIOS.
mov si, a20_trying_bios
call Real_mode_println
call Enable_A20_using_BIOS
call Check_A20 ; Check if A20 is enabled.
test ax, ax
jnz .end
; Try to enable A20 using Keyboard Controller.
mov si, a20_trying_keyb
call Real_mode_println
call Enable_A20_using_Keyboard_Controller
call Check_A20 ; Check if A20 is enabled.
test ax, ax
jnz .end
; Try to enable A20 using IO port 92h (Fast A20 method).
mov si, a20_trying_io92
call Real_mode_println
call Enable_A20_using_IO_port_92
call Check_A20 ; Check if A20 is enabled.
test ax, ax
jnz .end
.halt: hlt
jmp .halt ; Infinite loop.
.end:
ret
Check_A20:
;********************************************************************;
; Check the status of the A20 line ;
;********************************************************************;
call Real_mode_check_A20
test ax, ax
jnz .a20_enabled
mov si, a20_disabled_message
call Real_mode_println
ret
.a20_enabled:
mov si, a20_enabled_message
call Real_mode_println
ret
Real_mode_check_A20:
;**************************************************************************;
; Check the status of the A20 line (in real mode) ;
;--------------------------------------------------------------------------;
; Returns: ax = 0 if the a20 line is disabled (memory wraps around) ;
; ax = 1 if the a20 line is enabled (memory does not wrap around) ;
;**************************************************************************;
pushf
push ds
push es
push di
push si
cli ; clear interrupts
xor ax, ax ; ax = 0
mov es, ax ; es = 0
not ax ; ax = 0xFFFF
mov ds, ax ; ds = 0xFFFF
mov di, 0x0500 ; 0500 and 0510 are chosen since they are guaranteed to be free
mov si, 0x0510 ; for use at any point of time after BIOS initialization.
; save the original values found at these addresses.
mov dl, byte [es:di]
push dx
mov dl, byte [ds:si]
push dx
mov byte [es:di], 0x00 ; [es:di] is 0:0500
mov byte [ds:si], 0xFF ; [ds:si] is FFFF:0510
cmp byte [es:di], 0xFF ; if the A20 line is disabled, [es:di] will contain 0xFF
; (as the write to [ds:si] really occured to 00500).
mov ax, 0 ; A20 disabled ([es:di] equal to 0xFF).
je .a20_disabled
mov ax, 1 ; A20 enabled.
.a20_disabled:
; restore original values
pop dx
mov byte [ds:si], dl
pop dx
mov byte [es:di], dl
pop si
pop di
pop es
pop ds
popf
sti ; Enable interrupts.
ret
Enable_A20_using_BIOS:
;*************************************************************;
; Try to enable A20 gate using the BIOS (int 15h, ax = 2401h) ;
;-------------------------------------------------------------;
; Returns: ax = 0 (Failure) ;
; ax = 1 (Success) ;
;*************************************************************;
mov ax,2403h ; Query A20 gate Support (later PS/2s systems)
int 15h
jb .failure ; INT 15h is not supported
cmp ah, 0
jnz .failure ; INT 15h is not supported
mov ax, 2402h ; Get A20 gate Status
int 15h
jb .failure ; Couldn't get status
cmp ah, 0
jnz .failure ; Couldn't get status
cmp al, 1
jz .success ; A20 is already activated
mov ax, 2401h ; Enable A20 gate
int 15h
jb .failure ; Couldn't enable the A20 gate
cmp ah, 0
jnz .failure ; Couldn't enable the A20 gate
.success:
mov ax, 1
ret
.failure:
mov ax, 0
ret
Disable_A20_using_BIOS:
;**************************************************************;
; Try to disable A20 gate using the BIOS (int 15h, ax = 2400h) ;
;**************************************************************;
mov ax, 2400h
int 15h
ret
Enable_A20_using_Keyboard_Controller:
;******************************************************************;
; Try to enable A20 line using the Keyboard Controller (chip 8042) ;
;------------------------------------------------------------------;
; Returns: ax = 0 (Failure) ;
; ax = 1 (Success) ;
;******************************************************************;
cli ; Clear interrupts.
call a20wait
mov al, 0xAD ; Disable keyboard.
out 0x64, al
call a20wait
mov al, 0xD0 ; Read from input.
out 0x64, al
call a20wait2
in al,0x60
push eax
call a20wait
mov al, 0xD1 ; Write to output.
out 0x64, al
call a20wait
pop eax
or al, 2
out 0x60, al
call a20wait
mov al, 0xAE ; Enable keyboard.
out 0x64, al
call a20wait
sti ; Enables interrupts.
ret
a20wait:
in al, 0x64
test al, 2
jnz a20wait
ret
a20wait2:
in al, 0x64
test al, 1
jz a20wait2
ret
;*********************************************************************;
; Enable A20 Line via IO port 92h (Fast A20 method) ;
;---------------------------------------------------------------------;
; This method is quite dangerous because it may cause conflicts with ;
; some hardware devices forcing the system to halt. ;
;=====================================================================;
; Bits of port 92h ;
;---------------------------------------------------------------------;
; Bit 0 - Setting to 1 causes a fast reset ;
; Bit 1 - 0: disable A20, 1: enable A20 ;
; Bit 2 - Manufacturer defined ;
; Bit 3 - power on password bytes. 0: accessible, 1: inaccessible ;
; Bits 4-5 - Manufacturer defined ;
; Bits 6-7 - 00: HDD activity LED off, 01 or any value is "on" ;
;*********************************************************************;
Enable_A20_using_IO_port_92:
in al, 0x92 ; Read from port 0x92
test al, 2 ; Check if bit 1 (i.e. the 2nd bit) is set.
jnz .end ; If bit 1 (i.e. the 2nd bit) is already set don't do anything.
or al, 2 ; Activate bit 1 (i.e. the 2nd bit).
and al, 0xFE ; Make sure bit 0 is 0 (it causes a fast reset).
out 0x92, al ; Write to port 0x92
.end:
ret
3 Memory Paging