; 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 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_a20_line 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_disk_read 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_a20_line: mov si, .msg call print jmp done .msg db "err a20 line", 0x00 error_disk_read: mov si, .msg call print jmp done .msg db "err disk read", 0x00 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 real idt empty_idt: dw 0 ; length dd 0 ; base ; 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 jmp KERNEL_LOAD_ADDR_ES * 0x10 msg_a20_line db "a20 line", 0x0D, 0x0A, 0x00 msg_disk_read db "disk read", 0x0D, 0x0A, 0x00 times 510-($-$$) db 0 ; 2 bytes less now db 0x55 db 0xAA