SoraOS/elevator.asm
2025-11-18 22:58:28 +00:00

491 lines
11 KiB
NASM

; File: elevator.asm
; Stage 2 Bootloader - No size limit!
; Job: Load kernel, set up GDT, switch to 64-bit mode, jump to kernel
[org 0x7e00]
[bits 16]
KERNEL_OFFSET equ 0x10000 ; Load kernel at 64KB (safer than 0x1000)
BOOT_DRIVE: db 0 ; Store boot drive number
ERROR_CODE: db 0 ; Store error code from disk read
stage2_start:
; Print stage 2 message
mov si, msg_stage2
call print_string
; Save boot drive number (passed from stage1 in DL)
mov [BOOT_DRIVE], dl
; Load kernel from disk
call load_kernel
; Verify kernel was loaded - check for signature
; Check if there's actual code at 0x10000
mov ax, 0x1000
mov es, ax
xor bx, bx
mov ax, [es:bx] ; Read first 2 bytes of kernel
; Show what we read (for debugging)
push ax
xor ax, ax
mov es, ax
mov si, msg_kernel_check
call print_string
pop ax
call print_hex_word ; Print the word we read
; If it's 0x0000, kernel didn't load
test ax, ax
jz kernel_load_failed
mov si, msg_kernel_ok
call print_string
; Disable interrupts
cli
; Load GDT
lgdt [gdt_descriptor]
; Enable Protected Mode
mov eax, cr0
or eax, 0x1
mov cr0, eax
; Far jump to 32-bit code
jmp 0x08:protected_mode
; ============================================================
; LOAD KERNEL FROM DISK
; ============================================================
load_kernel:
push ax
push bx
push cx
push dx
; Reset disk system first
mov ah, 0x00
mov dl, [BOOT_DRIVE]
int 0x13
; Set up ES:BX for the destination
; KERNEL_OFFSET is 0x10000, so we need ES=0x1000, BX=0x0000
mov ax, 0x1000
mov es, ax
xor bx, bx ; BX = 0, so ES:BX = 0x1000:0x0000 = 0x10000
; Now try loading kernel
mov ah, 0x02 ; Read function
mov al, 10 ; Number of sectors
mov ch, 0 ; Cylinder 0
mov cl, 7 ; Start at sector 7
mov dh, 0 ; Head 0
mov dl, [BOOT_DRIVE] ; Drive number
int 0x13
jc kernel_error
; Restore ES to 0
xor ax, ax
mov es, ax
pop dx
pop cx
pop bx
pop ax
ret
kernel_error:
; Restore ES
xor ax, ax
mov es, ax
; Save error code
mov byte [ERROR_CODE], ah
mov si, msg_kernel_error
call print_string
; Show error code
mov si, msg_error_code
call print_string
; Print error code as hex
mov al, [ERROR_CODE]
call print_hex
; Show drive number we tried to use
mov si, msg_drive
call print_string
mov al, [BOOT_DRIVE]
call print_hex
jmp $
kernel_load_failed:
xor ax, ax
mov es, ax
mov si, msg_verify_failed
call print_string
jmp $
; ============================================================
; PRINT STRING (16-bit)
; ============================================================
print_string:
pusha
mov ah, 0x0e
.loop:
lodsb
test al, al
jz .done
int 0x10
jmp .loop
.done:
popa
ret
; ============================================================
; PRINT HEX BYTE (for debugging)
; ============================================================
print_hex:
push ax
push bx
mov bl, al
shr al, 4 ; Get high nibble
call print_nibble
mov al, bl
and al, 0x0F ; Get low nibble
call print_nibble
pop bx
pop ax
ret
print_nibble:
cmp al, 9
jle .digit
add al, 7 ; Convert to A-F
.digit:
add al, '0'
mov ah, 0x0e
int 0x10
ret
; Print a 16-bit word as hex
print_hex_word:
push ax
mov al, ah ; Print high byte first
call print_hex
pop ax
call print_hex ; Print low byte
ret
; ============================================================
; MESSAGES
; ============================================================
msg_stage2: db '[STAGE2] Extended bootloader loaded', 0x0D, 0x0A, 0
msg_kernel_loaded: db '[STAGE2] Kernel loaded, switching to Protected Mode...', 0x0D, 0x0A, 0
msg_kernel_error: db '[ERROR] Kernel load failed!', 0x0D, 0x0A, 0
msg_kernel_check: db '[STAGE2] Verifying kernel load: Read word = 0x', 0
msg_kernel_ok: db '[STAGE2] Kernel load verified.', 0x0D, 0x0A, 0
msg_verify_failed: db '[ERROR] Kernel verification failed!', 0x0D, 0x0A, 0
msg_error_code: db ' Error code: 0x', 0
msg_drive: db ' Drive number: 0x', 0
; ============================================================
; GDT - Global Descriptor Table
; ============================================================
align 8
gdt_start:
gdt_null:
dq 0x0
gdt_code32: ; Selector 0x08
dw 0xffff
dw 0x0000
db 0x00
db 10011010b
db 11001111b
db 0x00
gdt_data32: ; Selector 0x10
dw 0xffff
dw 0x0000
db 0x00
db 10010010b
db 11001111b
db 0x00
gdt_code64: ; Selector 0x18
dw 0xffff
dw 0x0000
db 0x00
db 10011010b
db 10101111b
db 0x00
gdt_data64: ; Selector 0x20
dw 0xffff
dw 0x0000
db 0x00
db 10010010b
db 10101111b
db 0x00
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; Limit (size - 1)
dd gdt_start ; Base address (org already adjusts this)
; ============================================================
; 32-BIT PROTECTED MODE
; ============================================================
[bits 32]
protected_mode:
; Set up segments
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, 0x90000
; Print to VGA
mov edi, 0xb8000
mov esi, pm_msg
call print_pm
; Clear page table area (12KB starting at 0x1000)
mov edi, 0x1000
mov ecx, 3072 ; 12KB / 4 bytes = 3072 dwords
xor eax, eax
rep stosd
; Set up page tables
; PML4[0] -> PDPT at 0x2000
mov dword [0x1000], 0x2003
; PDPT[0] -> PD at 0x3000
mov dword [0x2000], 0x3003
; Identity map first 4MB (two 2MB pages)
; PD[0] -> First 2MB
mov dword [0x3000], 0x00000083
mov dword [0x3004], 0x00000000
; PD[1] -> Second 2MB (covers 0x200000 - 0x3FFFFF)
mov dword [0x3008], 0x00200083
mov dword [0x300C], 0x00000000
; Load page table
mov eax, 0x1000
mov cr3, eax
; Enable PAE
mov eax, cr4
or eax, 0x20
mov cr4, eax
; Enable Long Mode
mov ecx, 0xC0000080
rdmsr
or eax, 0x100
wrmsr
; Enable Paging
mov eax, cr0
or eax, 0x80000000
mov cr0, eax
; Jump to 64-bit mode
jmp 0x18:long_mode
; Print in 32-bit protected mode
print_pm:
mov ah, 0x0f
.loop:
lodsb
test al, al
jz .done
stosw
jmp .loop
.done:
ret
pm_msg: db '[PM32] Setting up Long Mode...', 0
; ============================================================
; 64-BIT LONG MODE
; ============================================================
[bits 64]
long_mode:
; Set up segments
mov ax, 0x20
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov rsp, 0x90000
; Set up a minimal IDT to prevent triple faults
; Point all interrupts to a dummy handler
call setup_idt
; Print message on line 3
mov rdi, 0xb8000 + (160 * 2)
mov rsi, lm_msg
call print_64
; Write a marker before jumping to kernel (for debugging)
mov rdi, 0xb8000 + (160 * 3)
mov rsi, before_kernel_msg
call print_64
; JUMP TO KERNEL!
call KERNEL_OFFSET
; If kernel returns, show error
mov rdi, 0xb8000 + (160 * 4)
mov rsi, kernel_return_msg
call print_64
jmp $
; Set up a minimal IDT
setup_idt:
; Fill IDT with different handlers for different exceptions
mov rdi, 0x4000 ; IDT location
; Set up first 32 entries (CPU exceptions) with specific handlers
mov rcx, 32
mov rax, exception_handler
.fill_exceptions:
mov word [rdi], ax ; Offset low
mov word [rdi + 2], 0x18 ; Code segment
mov byte [rdi + 4], 0 ; IST
mov byte [rdi + 5], 0x8E ; Present, interrupt gate
shr rax, 16
mov word [rdi + 6], ax ; Offset middle
shr rax, 16
mov dword [rdi + 8], eax ; Offset high
mov dword [rdi + 12], 0 ; Reserved
mov rax, exception_handler ; Restore for next iteration
add rdi, 16
loop .fill_exceptions
; Fill remaining entries (32-255) with dummy handler
mov rcx, 224
mov rax, dummy_handler
.fill_rest:
mov word [rdi], ax
mov word [rdi + 2], 0x18
mov byte [rdi + 4], 0
mov byte [rdi + 5], 0x8E
shr rax, 16
mov word [rdi + 6], ax
shr rax, 16
mov dword [rdi + 8], eax
mov dword [rdi + 12], 0
mov rax, dummy_handler
add rdi, 16
loop .fill_rest
; Load IDT
lidt [idt_descriptor]
ret
; Exception handler - shows what went wrong
exception_handler:
; Save registers
push rax
push rbx
; Get exception number from stack
; (not perfectly accurate but good enough for debugging)
; Write "EXCEPTION!" in red at line 5
mov rbx, 0xb8000 + (160 * 5)
mov rax, 0x0C450C580C430C45 ; "ECXE" in red
mov [rbx], rax
mov rax, 0x0C4F0C490C540C50 ; "PTIO" in red
mov [rbx + 8], rax
mov word [rbx + 16], 0x0C21 ; "!" in red
; Show the instruction pointer where it failed
mov rax, [rsp + 16] ; Get RIP from stack (after our 2 pushes)
mov rbx, 0xb8000 + (160 * 6)
; Write "RIP: "
mov word [rbx], 0x0F52 ; 'R'
mov word [rbx + 2], 0x0F49 ; 'I'
mov word [rbx + 4], 0x0F50 ; 'P'
mov word [rbx + 6], 0x0F3A ; ':'
mov word [rbx + 8], 0x0F20 ; ' '
; Print RIP value as hex
mov rcx, 16
mov rbx, 0xb8000 + (160 * 6) + 10
.print_rip:
rol rax, 4
mov dl, al
and dl, 0x0F
cmp dl, 9
jle .digit
add dl, 7
.digit:
add dl, '0'
mov byte [rbx], dl
mov byte [rbx + 1], 0x0F
add rbx, 2
dec rcx
jnz .print_rip
; Restore and halt
pop rbx
pop rax
; Infinite loop instead of iretq
cli
hlt
jmp $
; Dummy interrupt handler for IRQs (not CPU exceptions)
dummy_handler:
iretq
idt_descriptor:
dw (256 * 16) - 1 ; Limit (256 entries * 16 bytes)
dq 0x4000 ; Base address
; Print in 64-bit mode
print_64:
mov ah, 0x0f
.loop:
lodsb
test al, al
jz .done
stosw
jmp .loop
.done:
ret
lm_msg: db '[64-BIT] Long mode active...', 0
before_kernel_msg: db '[64-BIT] Calling kernel...', 0
kernel_return_msg: db '[KERNEL] Returned unexpectedly!', 0