// vim: et:sw=12:ts=12:sts=12 // lboot.S: boot code for legacy BIOS boot over PXE. .global _start .text .code16 .set PACKET_SIZE, 1024 .macro pxe_call, opcode mov %sp, %bx push %ss push %bx push $\opcode lcall *pxe_api add $6, %sp mov %sp, %bx or %ss:(%bx), %ax jnz _pcerr .endm // _start: entry point _start: cli cld mov %sp, %bp // 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 %ss:4(%bp), %si mov %ss:6(%bp), %ax mov %ax, %es .set PXE_MAGIC, 0x45585021 cmpl $PXE_MAGIC, %es:(%si) je 1f mov $msg_pserr, %si call print jmp hang 1: mov %es:16(%si), %eax mov %eax, pxe_api 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 .set PXE_GET_CACHED_INFO, 0x0071 push $0 push %cs push $tx_buf push $PACKET_SIZE push $2 push $0 pxe_call PXE_GET_CACHED_INFO add $12, %sp mov tx_buf+20, %eax mov %eax, server_ip mov $msg_read, %si call print call read_file 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' (>1Mb) memory enable_a20: // Of all the ways to toggle A20, we only try the Fast A20 Gate. // This is known to cause problems on some ancient systems, // but as our bootloader exclusively runs on 64-bit machines, // we should not run into any of those systems. // Modern machines apparently don't even have the A20 line anymore. 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 read_file: .set PXE_TFTP_OPEN, 0x0020 push $PACKET_SIZE push $69<<8 sub $128, %sp mov $fn_config, %esi mov %ss, %ax mov %ax, %es mov %esp, %edi mov $fn_config_l, %ecx rep movsb push $0 push $0 sub $4, %sp mov %sp, %di mov server_ip+0, %al mov %al, %ss:0(%di) mov server_ip+1, %al mov %al, %ss:1(%di) mov server_ip+2, %al mov %al, %ss:2(%di) mov server_ip+3, %al mov %al, %ss:3(%di) push $0 pxe_call PXE_TFTP_OPEN add $14+128, %sp .set PXE_TFTP_READ, 0x0022 push %cs push $tx_buf push $0 push $0 push $0 pxe_call PXE_TFTP_READ add $10, %sp .set PXE_TFTP_CLOSE, 0x0021 push $0 pxe_call PXE_TFTP_CLOSE add $2, %sp mov $tx_buf, %si call print ret // 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 // Linearize stack address mov %ss, %eax shl $4, %eax add %eax, %esp mov %esp, %ebp // Load long mode GDT, switch to 64-bit CS lgdt gdt64_ptr ljmp $0x8, $trampo64 // hang: sleep indefinitely hang: hlt jmp hang _pcerr: mov $msg_pcerr, %si call print 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 0x92 .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_read: .asciz " * Retrieving\r\n" msg_long: .asciz " * Long Mode\r\n" msg_pserr: .asciz "panic: Missing !PXE structure.\r\n" msg_pcerr: .asciz "panic: PXE call failed.\r\n" msg_aerr: .asciz "panic: Out of heap space.\r\n" fn_config: .asciz "fernlader.cfg" .set fn_config_l, .-fn_config pxe_api: .long 0 server_ip: .space 4 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 // Points to the end of the memory map memmap_end: .word 0 tx_buf: .space PACKET_SIZE .code64 // trampo64: Trampoline function to load long-mode segments // before entering the loader. trampo64: mov $0x10, %eax mov %eax, %ds mov %eax, %es mov %eax, %fs mov %eax, %gs mov %eax, %ss jmp loader_main // ToDo List: // - Sorting the memmap // - Sanitizing the memmap // - Translating the memmap to bootboot format // - Parsing a config file