global start

extern long_mode_start

section .text
bits 32

start:
  mov esp, stack_top    ; stack
  mov edi, ebx          ; move multiboot pointer to edi

  ; error suite
  call check_multiboot
  call check_cpuid
  call check_long_mode

  ; paging
  call set_up_page_tables
  call enable_paging

  ; 64-bit gdt
  lgdt [gdt64.pointer]
  jmp gdt64.code:long_mode_start  ; change cs to gdt64 and long jump

set_up_page_tables:
  mov eax, p3_table     ; map p3_table's address to the first entry in p4_table
  or eax, 0b11           ; present, writable
  mov [p4_table], eax   ; p4_table's first entry points to p3_table

  mov eax, p2_table     ; sim.
  or eax, 0b11
  mov [p3_table], eax
  
  mov ecx, 0            ; counter
  .map_p2_table:
  mov eax, 0x200000      ; 2MiB for a huge page
  mul ecx               ; destination is ax register
  or eax, 0b10000011    ; huge ..... present, writable
  mov [p2_table + ecx * 8], eax ; map entry

  inc ecx               ; scuffed for-loop section
  cmp ecx, 512
  jne .map_p2_table

  ret

enable_paging:
  mov eax, p4_table
  mov cr3, eax          ; load p4_table to cr3, where it lives

  mov eax, cr4
  or eax, 1 << 5
  mov cr4, eax          ; set PAE flag in cr4

  mov ecx, 0xC0000080
  rdmsr
  or eax, 1 << 8
  wrmsr                 ; set long bit in MSR

  mov eax, cr0
  or eax, 1 << 31
  mov cr0, eax          ; set paging bit in cr0
  ret

; # | error
; 0 | .no_multiboot
; 1 | .no_cpuid
; 2 | .no_long_mode
error:                  ; prints RE:R <al>. <al> is used to store error codes
  mov dword [0xb8000], 0x4f524f45
  mov dword [0xb8004], 0x4f3a4f52
  mov dword [0xb8008], 0x4f204f20
  mov byte [0xb800a], al
  hlt

check_multiboot:
  cmp eax, 0x36d76289    ; eax magic value
  jne .no_multiboot
  ret
.no_multiboot:
  mov al, "0"
  jmp error

check_cpuid:            ; check if cpuid flag is supported
  pushfd
  pop eax               ; put flags into eax

  mov ecx, eax          ; copy to ecx
  xor eax, 1 << 21      ; flip the ID bit

  push eax
  popfd                 ; return eax to flags

  pushfd
  pop eax               ; put flags to eax again

  push ecx
  popfd                 ; restore flags to old version

  cmp eax, ecx
  je .no_cpuid
  ret
.no_cpuid:
  mov al, "1"
  jmp error

check_long_mode:        ; checks ensure cpu is new enough to be 64 bit
  mov eax, 0x80000000
  cpuid
  cmp eax, 0x80000001
  jb .no_long_mode

  mov eax, 0x80000001
  cpuid
  test edx, 1 << 29
  jz .no_long_mode
  ret
.no_long_mode:
  mov al, "2"
  jmp error

section .bss            ; GRUB initialises this to 0

align 4096              ; tables must be aligned
p4_table:
  resb 4096
p3_table:
  resb 4096
p2_table:
  resb 4096

stack_bottom:
  resb 4096 * 4         ; 16kB
stack_top:

section .rodata
gdt64:
  dq 0                                             ; null
.code: equ $ - gdt64    ; code offset
  dq (1 << 43) | (1 << 44) | (1 << 47) | (1 << 53) ; code
.pointer:
  dw $ - gdt64 - 1      ; $ is .pointer here; length of gdt
  dq gdt64              ; pointer to gdt table
