MBOOT_PAGE_ALIGN    equ 1<<0
MBOOT_MEM_INFO      equ 1<<1
MBOOT_HEADER_MAGIC  equ 0x1badb002
MBOOT_HEADER_FLAGS  equ MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO
MBOOT_CHECKSUM      equ -(MBOOT_HEADER_MAGIC + MBOOT_HEADER_FLAGS)

section .multiboot
dd MBOOT_HEADER_MAGIC
dd MBOOT_HEADER_FLAGS
dd MBOOT_CHECKSUM
dd 0x00000000; header_addr
dd 0x00000000; load_addr
dd 0x00000000; load_end_addr
dd 0x00000000; bss_end_addr
dd 0x00000000; entry_addr
dd 0x00000000; mode_type
dd 0x00000000; width
dd 0x00000000; height
dd 0x00000000; depth

[bits 32]
section .opuntiaos_boot_text

extern main
extern PREKERNEL_STACK_TOP

global _start
_start:
global prekernel_entry
prekernel_entry:
    cli
    cld

    ; Setting up the stack.
    mov ebp, PREKERNEL_STACK_TOP
    mov esp, ebp

    ; Save entry point to one of callee-saved regs.
    mov esi, ecx

    ; PAE is required for x86_64
    call check_for_pae

    ; Checking for long mode.
    call check_for_long_mode

    ; x86_64 required paging enabled, thus 1GB of physical memmory is mapped
    ; for booting the kernel without any problems.
    call setup_tables

    ; Enabling long mode with a 32-bit compatibility submode.
    call enable_long_mode

    ; Just give control to jumper.
    jmp jump_to_entry64

CPUID_FEATURE_PAE equ (1 << 6)
check_for_pae:
    push ebp
    mov ebp, esp
    push ebx

    mov eax, 0x1
    cpuid
    test edx, CPUID_FEATURE_PAE
    jz pae_unsupported

    pop ebx
    pop ebp
    ret

pae_unsupported:
    push pae_unsupported_msg
    call early_boot_print_string
    hlt

CPUID_FEATURE_LONG_MODE equ (1 << 29)
check_for_long_mode:
    push ebp
    mov ebp, esp
    push ebx

    mov eax, 0x80000001
    cpuid
    test edx, CPUID_FEATURE_LONG_MODE
    jz long_mode_unsupported

    pop ebx
    pop ebp
    ret

long_mode_unsupported:
    push long_mode_unsupported_msg
    call early_boot_print_string
    hlt


setup_tables:
    push ebp
    mov ebp, esp
    push edi

    mov edi, table0
    xor eax, eax
    mov ecx, 4096
    rep stosb

    mov edi, table1
    xor eax, eax
    mov ecx, 4096
    rep stosb

    mov edi, table2
    xor eax, eax
    mov ecx, 4096
    rep stosb

    mov eax, table1
    add eax, 0x3
    mov DWORD [table0], eax

    mov eax, table2
    add eax, 0x3
    mov DWORD [table1], eax
    
    mov edi, table2
    mov eax, 0x83
    mov ecx, 512
.table_fillup_last:
    mov DWORD [edi], eax
    add eax, 0x200000
    add edi, 8
    loop .table_fillup_last

    pop edi
    pop ebp
    ret


enable_long_mode:
    push ebp
    mov ebp, esp

    ; Setting table
    mov eax, table0
    mov cr3, eax

    ; Enabling PAE
    mov eax, cr4
    or eax, 1 << 5
    mov cr4, eax

    ; Enabling Long Mode
    mov ecx, 0xc0000080
    rdmsr
    or eax, 1 << 8
    wrmsr

    mov eax, cr0
    or eax, 1 << 31
    mov cr0, eax

    pop ebp
    ret


VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f

global early_boot_print_string
early_boot_print_string:
    push ebp
    mov ebp, esp

    push esi
    push ecx

    mov esi, [ebp+8]
    mov ecx, VIDEO_MEMORY
    mov ah, WHITE_ON_BLACK

_print_string_loop:
    lodsb; Load from esi to al then increment esi

    test al, al
    jz _print_string_end

    mov [ecx], ax
    add ecx, 2

    jmp _print_string_loop

_print_string_end:
    mov eax, esi
    sub eax, [ebp+8]

    pop ecx
    pop esi

    mov esp, ebp
    pop ebp
    ret


; Access bits
PRESENT        equ 1 << 7
NOT_SYS        equ 1 << 4
EXEC           equ 1 << 3
DC             equ 1 << 2
RW             equ 1 << 1
ACCESSED       equ 1 << 0

; Flags bits
GRAN_4K       equ 1 << 7
SZ_32         equ 1 << 6
LONG_MODE     equ 1 << 5

align 32
gdt_begin:
gdt_null:
    dq 0x0
gdt_code:
    dd 0xffff
    db 0x0
    db PRESENT | NOT_SYS | EXEC | RW
    db GRAN_4K | LONG_MODE | 0xF
    db 0x0
gdt_data:
    dd 0xffff
    db 0x0
    db PRESENT | NOT_SYS | RW
    db GRAN_4K | SZ_32 | 0xF
    db 0x0
gdt_tss:
    dd 0x00000068
    dd 0x00cf8900
gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_begin - 1
    dd gdt_begin
    dd 0x0

CODE_SEG equ gdt_code - gdt_begin
DATA_SEG equ gdt_data - gdt_begin

jump_to_entry64:
    lgdt [gdt_descriptor]
    jmp CODE_SEG:main_entry64

[bits 64]
main_entry64:
    cli
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    mov edi, esi
    mov esi, ebx
    call main
    hlt


global set_cr3
set_cr3:
    mov cr3, rdi
    ret


global jump_to_kernel
jump_to_kernel:
    mov rax, cr3
    mov cr3, rax
    call rsi
    jmp $


pae_unsupported_msg:
    db "Required PAE Feature is unavailable, stopping...", 0

long_mode_unsupported_msg:
    db "Required Long Mode Feature is unavailable, stopping...", 0

section .bss

table0:
    align 4096
    resb 4096

table1:
    align 4096
    resb 4096

table2:
    align 4096
    resb 4096
