// vim: et:sw=12:ts=12:sts=12 .global _start .text .code16 // _start: entry point _start: cli cld // we keep our text and data close to each other xor %ax, %ax mov %ax, %ds call init_com1 mov $msg_start, %si call print mov $msg_a20, %si call print call enable_a20 mov $msg_unreal, %si call print call unreal mov $msg_getmap, %si call print call get_map mov $msg_mkheap, %si call print call make_heap mov $msg_paging, %si call print call paging mov $msg_long, %si call print call long jmp hang .set COM1, 0x3F8 .macro com1_write offset=0, byte mov $COM1+\offset, %dx mov $\byte, %al outb %al, %dx .endm // init_com1: Set up COM1 port for debug output init_com1: com1_write 1, 0x00 // clear interrupts com1_write 3, 0x80 // set DLAB to 1 com1_write 0, 0x0C // 9600 baud rate com1_write 1, 0x00 com1_write 3, 0x07 // 8 bit data + 1 parity bit ret // enable_a20: Allow use of 'high' memory enable_a20: // TODO more thorough implementation inb $0x92, %al or $2, %al outb %al, $0x92 ret // unreal: Enter unreal mode unreal: push %ds lgdt gdt16_ptr mov %cr0, %eax or $0x01, %al mov %eax, %cr0 ljmp $0x8, $_urprot _urprot: mov $0x10, %cx mov %cx, %ds and $0xFE, %al mov %eax, %cr0 ljmp $0x0, $_urunreal _urunreal: pop %ds ret // get_map: Retrieve memory map using e820 BIOS function get_map: mov %ds, %ax mov %ax, %es mov $memmap, %di xor %ebx, %ebx mov $0x534D4150, %edx // e820 magic number _gmnext: movl $0, 20(%di) mov $24, %ecx mov $0xE820, %eax int $0x15 jc _gmdone test %ebx, %ebx jz _gmdone add $24, %di jmp _gmnext _gmdone: add $24, %di mov %di, memmap_end ret // paging: Set up initial page tables for long mode paging: mov $4*4096, %ecx call alloc mov %eax, pd_ptr mov $4096, %ecx call alloc mov %eax, pdp_ptr mov $4096, %ecx call alloc mov %eax, pml4_ptr // fill PDEs with identity map < 4Gb mov pd_ptr, %edi xor %ecx, %ecx _pgnext: mov %ecx, %eax shl $21, %eax or $0b10000011, %eax movl %eax, 0(%edi) movl $0, 4(%edi) add $8, %edi inc %ecx cmp $4*512, %ecx jne _pgnext // link to PDs in PDP mov pdp_ptr, %edi mov pd_ptr, %eax orl $0b11, %eax movl %eax, 0(%edi) movl $0, 4(%edi) add $0x1000, %eax movl %eax, 8(%edi) movl $0, 12(%edi) add $0x1000, %eax movl %eax, 16(%edi) movl $0, 20(%edi) add $0x1000, %eax movl %eax, 24(%edi) movl $0, 28(%edi) // link to PDP in PML4 mov pml4_ptr, %edi mov pdp_ptr, %eax orl $0b11, %eax movl %eax, 0(%edi) movl $0, 4(%edi) ret // make_heap: find a memory range suitable for heap usage make_heap: mov $memmap-24, %si _mhnext: add $24, %si cmp memmap_end, %si jae _mhdone cmpl $1, 16(%si) jne _mhnext cmpl $0, 4(%si) ja _mhnext mov 0(%si), %ebx mov 8(%si), %ecx // find end of range, clip to 4Gb add %ebx, %ecx jnc 1f mov $0xFFFFFFFF, %ecx // handle wraparound if length > 4Gb 1: cmpl $0, 12(%si) je 1f mov $0xFFFFFFFF, %ecx // adjust base to above 1Mb, above the bootloader 1: cmp $0x10000, %ebx jae 1f mov $0x10000, %ebx // align to 4Kb boundaries 1: add $0xFFF, %ebx and $0xFFFFF000, %ebx and $0xFFFFF000, %ecx sub %ebx, %ecx cmp heap_size, %ecx jbe _mhnext mov %ebx, heap_start mov %ecx, heap_size jmp _mhnext _mhdone: ret // alloc: take ECX bytes from heap, return ptr in EAX // The allocation does not get marked in the memmap. // No realignment is performed, so only alloc aligned sizes. alloc: cmp heap_size, %ecx ja _aerr mov heap_start, %eax add %ecx, heap_start sub %ecx, heap_size ret _aerr: mov $msg_aerr, %si call print jmp hang // print: print NUL-terminated string pointed to by SI print: xor %bx, %bx _prnext: mov $COM1+5, %dx inb %dx, %al test $0x20, %al jz _prnext lodsb or %al, %al jz _prdone mov $COM1, %dx outb %al, %dx mov $0x0E, %ah int $0x10 jmp _prnext _prdone: ret // long: Enter long mode long: // Enable PAE mov %cr4, %eax or $0b100000, %eax mov %eax, %cr4 // Load page table mov pml4_ptr, %eax mov %eax, %cr3 // Enable long mode .set IA32_EFER, 0xC0000080 mov $IA32_EFER, %ecx rdmsr or $0x100, %eax wrmsr // Enable protected mode + paging mov %cr0, %eax or $0x80000001, %eax mov %eax, %cr0 lgdt gdt64_ptr ret // hang: sleep indefinitely hang: hlt jmp hang // gdt16: Protected mode / Unreal mode 16-bit GDT gdt16: // entry 0: null descriptor .word 0 .word 0 .byte 0 .byte 0 .byte 0 .byte 0 // entry 1: code segment .word 0xFFFF .word 0 .byte 0 .byte 0b10011010 .byte 0x8F .byte 0 // entry 2: data segment .word 0xFFFF .word 0 .byte 0 .byte 0b10010010 .byte 0x8F .byte 0 .set gdt16_size, .-gdt16 gdt16_ptr: .word gdt16_size-1 .long gdt16 // gdt64: Long mode 64-bit GDT gdt64: // entry 0: null descriptor .quad 0 // entry 1: code segment .word 0 .word 0 .byte 0 .byte 0x98 .byte 0x60 .byte 0 // entry 2: data segment .word 0 .word 0 .byte 0 .byte 0x90 .byte 0x00 .byte 0 .set gdt64_size, .-gdt64 gdt64_ptr: .word gdt64_size-1 .quad gdt64 // Messages to print msg_start: .asciz "Netboot via fernlader v1 ...\r\n" msg_a20: .asciz " * Enabling A20\r\n" msg_unreal: .asciz " * Unreal Mode\r\n" msg_getmap: .asciz " * Memory Map\r\n" msg_mkheap: .asciz " * Making Space\r\n" msg_paging: .asciz " * Paging\r\n" msg_long: .asciz " * Long Mode\r\n" msg_aerr: .asciz "panic: Out of heap space.\r\n" heap_start: .long 0 heap_size: .long 0 // Long mode initial page tables pd_ptr: .long 0 pdp_ptr: .long 0 pml4_ptr: .long 0 // Memory map area memmap_end: .word 0 .align 8 memmap: // begins at end of binary