; DISK_ID EQU 0x80

KERNEL_START EQU 0x100000
ELF_START EQU 0x8000

NUM_SECTORS EQU 65

org 0x7C00
bits 16

jmp .start

[bits 16]
 
; Function: check_a20
;
; Returns: 0 in ax if the a20 line is disabled (memory wraps around)
;          1 in ax if the a20 line is enabled (memory does not wrap around)
 
check_a20:
    cli
 
    xor ax, ax ; ax = 0
    mov es, ax
 
    not ax ; ax = 0xFFFF
    mov ds, ax
 
    mov di, 0x0500
    mov si, 0x0510
 
    mov al, byte [es:di]
    mov bx, ax
 
    mov al, byte [ds:si]
 
    mov byte [es:di], 0x00
    mov byte [ds:si], 0xFF
 
    cmp byte [es:di], 0xFF
 
    mov byte [ds:si], al
 
    mov ax, bx
    mov byte [es:di], al
 
    mov ax, 0
    je .check_a20__exit
 
    mov ax, 1
 
.check_a20__exit:
    ret

;;; Print string at bx
puts:
.puts_loop:

mov al, [bx]        ; load the character
cmp al, 0           ; check for null byte
je .end_puts_loop

mov ah, 0x0e
int 0x10            ; print charachter
inc bx
jmp .puts_loop
.end_puts_loop:
ret



;;; Print bl as hex
puthex:

; Print high bits
mov cl, bl
shr cl, 4

cmp cl, 9
jg .puthex_high_alpha

; It's < 10, print as digit
mov al, cl
add al, 0x30
mov ah, 0x0e
int 0x10
jmp .puthex_low

.puthex_high_alpha:
; It's >= 10, print as letter
mov al, cl
add al, 0x57 ; 0x57 = (0x61 - 10)
mov ah, 0x0e
int 0x10

.puthex_low:
; Print low bits
mov cl, bl
and cl, 0b1111

cmp cl, 9
jg .puthex_low_alpha

; It's < 10, print as digit
mov al, cl
add al, 0x30
mov ah, 0x0e
int 0x10
jmp .puthex_end

.puthex_low_alpha:
; It's >= 10, print as letter
mov al, cl
add al, 0x57 ; 0x57 = (0x61 - 10)
mov ah, 0x0e
int 0x10

.puthex_end:
ret



.start:

; set up stack
mov ax, 0
mov ss, ax
mov sp, 0x7C00
mov ds, ax
mov es, ax


; disable vga cursor
mov ah, 0x01
mov ch, 0b00100000
mov cl, 0b00000000
int 0x10

; reset disk system
mov ah, 0x00
; mov dl, DISK_ID
int 0x13

; print result on error
jnc .reset_disk_no_error
mov bl, ah
call puthex
.reset_disk_no_error:

mov cx, 2       ; start at sector 2 (skip bootloader)
mov bx, ELF_START  ; write to ELF_START
.read_disk_loop:

; Read sector dl into memory
mov ah, 0x02
mov al, 0x01    ; number of sectors
mov ch, 0x00    ; cylinder number, low 8 bits
; mov cl, 0x01    ; cylinder number, high 2 bits + sector number (6 bits)
mov dh, 0x00    ; head number
; mov dl, DISK_ID    ; drive number
int 0x13

; exit on error
jc .read_disk_error

cmp cx, NUM_SECTORS
jge .read_disk_end ; we have reached the sector limit, time to boot

inc cl      ; next sector
add bx, 512 ; add 512 to data start so we don't overwrite sectors

jmp .read_disk_loop

.read_disk_error:

mov bl, ah
call puthex

.read_disk_end:

call check_a20
cmp ax, 1
je .a20_enabled

mov ah, 0x24     ; set a20 gate
mov al, 0x01     ; turn it on
int 0x15

jnc .a20_enabled

mov bl, ah
call puthex
jmp .end

.a20_enabled:

; disable interrupts
cli

; reset ds
xor ax, ax
mov ds, ax

; load the GDT
lgdt [.gdt_desc]

; switch to protected mode
mov eax, cr0
or eax, 1
mov cr0, eax

; jump to 32 bit code
jmp 0x08:.clear_cs

[BITS 32]
.clear_cs:

; set Data Segment, Stack Segment and other segments
mov ax, 0x10
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax

; set up stack
mov esp, 0x090000

; parse elf file at ELF_START to KERNEL_START

; verify ELF header (scrapped for memory)
mov esi, ELF_START
; cmp dword [esi], 464C457Fh ; ELF magic
; jne .invalid_elf
; cmp word [esi+4], 0101h    ; lsb 32 bit, little endian
; jne .invalid_elf
; cmp word [esi+18], 03      ; x86 architecture
; jne .invalid_elf

; read the entrypoint and store it
mov eax, dword [esi+0x18] ; program entry position
mov dword [.entrypoint], eax

mov ax, word [esi+0x2C]     ; read phnum (number of program headers)

; move esi to the start of the program header
add esi, dword [esi+0x1C]

; set up for loop
sub esi, 0x20
inc ax
mov dword [.edi_backup], esi

.elf_ph_loop:
mov ebx, ELF_START
mov esi, dword [.edi_backup]
add esi, 0x20
mov dword [.edi_backup], esi
dec ax
jz .start_kernel             ; there is no valid code block
cmp word [esi], 1           ; check if p_type is loadable
jne .elf_ph_loop

; set destination
mov edi, dword [esi+0x08]

; add offset to ebx     (ebx = pointer to code)
add ebx, dword [esi+0x04]

; store p_filesz        (ecx = size of the segment)
mov ecx, dword [esi+0x10]

; calculate size of bss (edx = size of bss)
mov edx, dword [esi+0x14]
sub edx, ecx

; check if ecx is zero
or ecx, ecx
jz .invalid_elf

; set source
mov esi, ebx

; repeat ecx/4 times (because it moves 4 bytes at a time)
shr ecx, 2

; copy
repnz movsd

or ax, ax
jnz .elf_ph_loop

.start_kernel

cmp edi, KERNEL_START
je .invalid_elf

; jump to start of kernel
jmp [.entrypoint]

.end:
cli
hlt
jmp .end

.invalid_elf:
mov byte [ds:0x0B8000], 'E'
mov byte [ds:0x0B8002], 'L'
mov byte [ds:0x0B8004], 'F'
jmp .end

; padding for bootloader - SFS superblock
times 404 - ($ - $$) db 0

; room for SFS superblock
times 42 db 0

; padding for SFS superblock - data
times 66 - (.file_end - .data) db 0

.data:

.gdt:
dd 0x00000000
dd 0x00000000

dw 0xffff
dw 0x0000
db 0x00
db 0b10011110
db 0b11001111
db 0x00

dw 0xffff
dw 0x0000
db 0x00
db 0b10010110
db 0b11001111
db 0x00
.gdt_end:

.gdt_desc:
dw .gdt_end - .gdt - 1
dd .gdt

.entrypoint: dd 0
.edi_backup: dd 0

; magic string
dw 0xAA55

.file_end: