summaryrefslogtreecommitdiff
path: root/bootler/asm/boot.asm
diff options
context:
space:
mode:
authorandromeda <andromeda@lenovo>2026-03-06 20:33:51 +0100
committerandromeda <andromeda@lenovo>2026-03-06 20:33:51 +0100
commit9c65697dd8119ec9cee13f57f870b07e305d27ec (patch)
tree27b61bd7a27df6e25db8b21dc0ee3daed01d79fb /bootler/asm/boot.asm
parentfdf5bb9daf8574b7f993e2e38e400c35aedc3b96 (diff)
bunch of stuff idek
Diffstat (limited to 'bootler/asm/boot.asm')
-rw-r--r--bootler/asm/boot.asm286
1 files changed, 286 insertions, 0 deletions
diff --git a/bootler/asm/boot.asm b/bootler/asm/boot.asm
new file mode 100644
index 0000000..5291d58
--- /dev/null
+++ b/bootler/asm/boot.asm
@@ -0,0 +1,286 @@
+; 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