Files
bootler/asm/boot.asm
2026-03-05 20:32:55 +01:00

287 lines
5.2 KiB
NASM

; bootler
; legacy bootloader in nasm-flavoured asm
; 2026 Andromeda
; GPL licensed
LOAD_ADDR equ 0x7C00
KERNEL_START equ 2 ; first sector on disk to load kernel from; 1 indexed
KERNEL_SIZE equ 1 ; length of kernel in sectors
KERNEL_LOAD_ADDR_ES equ 0x1000 ; kernel to be loaded at es * 0x10 + 0x0000
PAGE_TABLE_LOAD_ADDR equ 0x1000 ; start of page table; 4 * pt size
PAGE_TABLE_SIZE equ 0x1000 ; size of page table in bytes
; 64 bit IDT stuff
IDT_ADDR equ 0x8000 ; bottom of IDT
IDT_SIZE equ 0x1000 ; size of IDT
IDT_SEGMENT equ 0x0008 ; gdt code segment to jump to for idt calls
; magic numbers and things
A20_LINE_ENABLE equ 0x2401 ; magic number in ax to enable a20 line with 0x15
BIOS_INT_DISK_OP equ 0x13
BIOS_INT_MEMORY_OP equ 0x15
CR0_PE equ 1 << 0 ; Protected Mode Enabled
CR0_PG equ 1 << 31 ; Paging
CR4_PAE equ 1 << 5 ; Physical Address Extension
CR4_PGE equ 1 << 7 ; Page Global Enabled
MSR_IA32_EFER equ 0xC0000080 ; Extended Feature Enable model-specific Register
[bits 16]
[org LOAD_ADDR]
jmp 0x0000:.cs_reset ; in case it loads us at 0x7C00:0x0000
.cs_reset:
xor ax, ax
mov ss, ax
mov sp, LOAD_ADDR ; stack builds down, loader faces up
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
cld
; activate a20
mov ax, A20_LINE_ENABLE
int BIOS_INT_MEMORY_OP
jc error
mov si, msg_a20_line
call print
; prep to read disk
mov al, KERNEL_SIZE ; size in sectors, min. 1
mov ch, 0 ; cylinder
mov cl, KERNEL_START ; start sector, 1-indexed
mov dh, 0 ; head
; dl set by bios
mov bx, KERNEL_LOAD_ADDR_ES ; load addr es:bx
mov es, bx
xor bx, bx
; read disk
mov ah, 0x02
int BIOS_INT_DISK_OP
jc error
mov si, msg_disk_read
call print
; zero es
push ax
xor ax, ax
mov es, ax
pop ax
mov edi, PAGE_TABLE_LOAD_ADDR
jmp lm_load
; expects:
; es:edi -> 16kb for page table
; ss:esp -> stack
; heavily based on https://wiki.osdev.org/Entering_Long_Mode_Directly
lm_load:
; clear page table
push di
mov ecx, PAGE_TABLE_SIZE
xor eax, eax
cld
rep stosd ; clear space for all 4 tables
pop di
; PML4TE0
lea eax, [es:di + PAGE_TABLE_SIZE]
or eax, 11b
mov [es:di], eax
; PDPT0E0
lea eax, [es:di + 2 * PAGE_TABLE_SIZE]
or eax, 11b
mov [es:di + PAGE_TABLE_SIZE], eax
; PDT0E0
lea eax, [es:di + 3 * PAGE_TABLE_SIZE]
or eax, 11b
mov [es:di + 2 * PAGE_TABLE_SIZE], eax
; PT0
push di
lea di, [di + 3 * PAGE_TABLE_SIZE]
mov eax, 0x11
.set_pt_entry
mov [es:di], eax
add eax, 0x1000
add di, 8
cmp eax, 0x200000 ; size of page
jb .set_pt_entry
pop di
; disable interrupts from PIC by masking all interrupts
mov al, 0xFF ; mask
out 0xA1, al
out 0x21, al
; PAE, PGE
mov eax, CR4_PAE | CR4_PGE
mov cr4, eax
; cr3 -> PML4TE0
mov edx, edi
mov cr3, edx
; highs LME bit in EFER
mov ecx, MSR_IA32_EFER
rdmsr
or eax, 0x00000100
wrmsr
; activate long mode
mov ebx, cr0
or ebx, CR0_PG | CR0_PE
mov cr0, ebx
cli
lgdt [gdt_descriptor]
jmp 0x08:lm_start
error:
mov si, .msg
call print
.msg db "err ", 0x00
ret
done:
hlt
jmp done
print:
pushad
.loop
lodsb
test al, al
je .done
mov ah, 0x0E
int 0x10
jmp .loop
.done
popad
ret
; TODO make readable (yoinked from https://wiki.osdev.org/Entering_Long_Mode_Directly)
gdt_data:
dq 0x0000000000000000
dq 0x00209A0000000000
dq 0x0000920000000000
gdt_descriptor:
dw $ - gdt_data - 1 ; size
dd gdt_data ; start
[bits 64]
; long mode entry
lm_start:
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
; set up serial device
; TODO poll and whatnot (in rust??)
mov dx, 0x3F8
; qemu -no-graphic overlaps serial and bios output without this
mov al, 0x0D
out dx, al
mov al, 0x0A
out dx, al
; initialize IDT at IDT_ADDR
call idt_init
int3 ; silly test
jmp KERNEL_LOAD_ADDR_ES * 0x10
idt_init:
; clear space for idt
mov rdi, IDT_ADDR
push di
mov rcx, IDT_SIZE / 4
xor rax, rax
cld
rep stosd ; clear double word for each rcx
pop di
push rdx ; dx contains the output device
; nothing between here and the `pop rdx` will be able to print
; breakpoint
mov rdi, 0x8E ; present, no dpl, interrupt
mov rsi, 0x03 ; breakpoint interrupt
lea rdx, [int_hdl_dummy]
call mk_idt_entry
pop rdx ; get back dx output device
lidt [idt_descriptor]
ret
int_hdl_dummy:
mov rsi, .msg
call lm_print
iretq
.msg db "INTdummy", 0x0D, 0x0A, 0x00
; rdi = flags
; rsi = entry number
; rdx -> handler
mk_idt_entry:
mov rax, rdx
mov rcx, rsi
shl rcx, 4 ; offset = entry number * 16
add rcx, IDT_ADDR
mov word [rcx + 0], ax ; offset
mov word [rcx + 2], IDT_SEGMENT ; code segment
mov word [rcx + 5], di
mov byte [rcx + 4], 0 ; IST (0 = none)
shr rax, 16
mov word [rcx + 6], ax ; offset
shr rax, 16
mov dword [rcx + 8], eax ; offset
mov dword [rcx + 12], 0 ; reserved
ret
lm_print:
.loop
mov al, [rsi]
test al, al
jz .done
out dx, al
inc si
jmp .loop
.done:
ret
idt_descriptor:
dw IDT_SIZE - 1 ; size
dq IDT_ADDR ; start
msg_a20_line db "a20 line", 0x0D, 0x0A, 0x00
msg_disk_read db "disk", 0x0D, 0x0A, 0x00
times 510-($-$$) db 0 ; 2 bytes less now
db 0x55
db 0xAA