// vim: et:sw=12:ts=12:sts=12 // lboot.S: boot code for legacy BIOS boot over PXE. /* The assembly code in this project follows its own * made up calling convention, because it is more convenient * than the conventional ones for working in unreal mode. * * Callees preserve all registers, * except those which they use to return values. * All 32 bits of a register are preserved. * Saved registers are push to the stack before the frame pointer, * as this simplifies frame cleanup. * Addresses/pointers are returned in EBX, * all other values are returned in EAX. * * DS, ES have a 4Gb limit, and an offset of 0. * CS has an offset of 0. SS is unspecified. */ .global _start .text .code16 .set PACKET_SIZE, 512 .set TX_BUF_SIZE, 4096 .macro pxe_call, opcode push %eax push %ebx push %ecx push %edx mov %sp, %bx add $16, %bx push %ss push %bx push $\opcode lcall *pxe_api add $6, %sp mov %ss:(%bx), %cx or %cx, %ax jnz _pcerr pop %edx pop %ecx pop %ebx pop %eax .endm // _start: entry point _start: cli cld mov %sp, %bp xor %ax, %ax mov %ax, %ds mov %ax, %es call init_com1 mov $msg_start, %si call print mov $msg_start, %si call print mov %ss:4(%bp), %si mov %ss:6(%bp), %ax mov %ax, %fs .set PXE_MAGIC, 0x45585021 cmpl $PXE_MAGIC, %fs:(%si) je 1f mov $msg_pserr, %si call print jmp hang 1: mov %fs:16(%si), %eax mov %eax, pxe_api mov $msg_a20, %si call print call enable_a20 .set PXE_GET_CACHED_INFO, 0x0071 push $0 push %cs push $tx_buf push $TX_BUF_SIZE push $2 push $0 pxe_call PXE_GET_CACHED_INFO add $12, %sp mov tx_buf+20, %eax mov %eax, server_ip mov tx_buf+24, %eax mov %eax, gateway_ip 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_read, %si call print mov $fn_initrd, %esi call read_file mov %ebx, bb_ird_ptr 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 push %es lgdt gdt16_ptr mov %cr0, %eax or $0x01, %al mov %eax, %cr0 ljmp $0x8, $_urprot _urprot: mov $0x10, %cx mov %cx, %ds mov %cx, %es and $0xFE, %al mov %eax, %cr0 ljmp $0x0, $_urunreal _urunreal: pop %es pop %ds ret // get_map: Retrieve memory map using e820 BIOS function get_map: mov $bb_memmap, %edi 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 sub $bootboot, %edi mov %edi, bb_size ret // paging: Set up initial page tables for long mode paging: mov heap_ptr, %eax mov %eax, %ecx add $6*4096, %ecx cmp heap_end, %ecx ja out_of_mem mov %ecx, heap_ptr mov %eax, pd_ptr add $4*4096, %eax mov %eax, pdp_ptr add $4096, %eax 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 $bb_memmap-24, %esi _mhnext: add $24, %si mov $bootboot, %eax add bb_size, %eax cmp %ax, %si jae _mhdone cmpl $1, 16(%si) jne _mhnext cmpl $0, 4(%si) ja _mhnext mov 0(%si), %eax mov 8(%si), %ecx // find end of range, clip to 4Gb add %eax, %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, %eax jae 1f mov $0x10000, %eax // align start to 4Kb boundary 1: add $0xFFF, %eax and $0xFFFFF000, %eax // Is this entry larger than current heap? mov %ecx, %edx sub %eax, %edx mov heap_end, %ebx sub heap_ptr, %ebx cmp %ebx, %edx jbe _mhnext // If so, switch to it. mov %eax, heap_ptr mov %ecx, heap_end mov %esi, heap_mment jmp _mhnext _mhdone: ret .set PXE_TFTP_OPEN, 0x0020 .set PXE_TFTP_CLOSE, 0x0021 .set PXE_TFTP_READ, 0x0022 read_file: push %bp mov %sp, %bp call tftp_open mov heap_ptr, %ebx mov %ebx, %edi _rdloop: call tftp_read mov $tx_buf, %esi mov $PACKET_SIZE, %ecx addr32 rep movsb cmp heap_end, %edi ja out_of_mem cmp $PACKET_SIZE, %ax je _rdloop mov %edi, heap_ptr call tftp_close leave ret // tftp_open: Open a file stream over TFTP. // A pointer to the filename is passed in ESI. tftp_open: push %ecx push %esi push %edi push %bp mov %sp, %bp mov %esi, %ebx mov $msg_topen, %si call print push $PACKET_SIZE push $69<<8 mov $128, %ecx sub %cx, %sp mov %ss, %edi shl $4, %edi add %esp, %edi xor %ax, %ax cld addr32 rep stosb mov %ebx, %esi mov $6, %ecx mov %ss, %edi shl $4, %edi add %esp, %edi cld addr32 rep movsb push gateway_ip+2 push gateway_ip push server_ip+2 push server_ip push $0 mov %ss, %esi shl $4, %esi add %esp, %esi mov $128, %ecx call dump pxe_call PXE_TFTP_OPEN leave pop %edi pop %esi pop %ecx ret // tftp_close: Close the TFTP stream. tftp_close: push %bp mov %sp, %bp mov $msg_tclose, %si call print push $0 pxe_call PXE_TFTP_CLOSE leave ret // tftp_read: Read the next TFTP packet into tx_buf, return size in AX. tftp_read: push %ebx push %bp mov %sp, %bp mov $msg_tread, %si call print push %cs push $tx_buf push $0 push $0 push $0 pxe_call PXE_TFTP_READ mov %sp, %bx mov %ss:4(%bx), %ax leave pop %ebx ret out_of_mem: mov $msg_memerr, %si call print jmp hang // print: print NUL-terminated string pointed to by SI print: push %eax push %si push %bp mov %sp, %bp 1: lodsb or %al, %al jz 2f call printch jmp 1b 2: leave pop %si pop %eax ret printch: push %eax push %ebx push %edx push %ebp mov %ax, %bx 1: mov $COM1+5, %dx inb %dx, %al test $0x20, %al jz 1b mov %bx, %ax mov $COM1, %dx outb %al, %dx xor %bx, %bx mov $0x0E, %ah int $0x10 pop %ebp pop %edx pop %ebx pop %eax ret dump: push %eax push %ebx push %ecx push %edx push %esi push %bp mov %sp, %bp xor %dx, %dx 1: cmp %cx, %dx jae 2f inc %dx addr32 lodsb push %ax mov %al, %bl shr $4, %bx and $0x0F, %bx mov hex_digits(%bx), %al call printch pop %bx and $0x0F, %bx mov hex_digits(%bx), %al call printch mov $' ', %al call printch test $0xF, %dx jnz 1b mov $'\r', %al call printch mov $'\n', %al call printch jmp 1b 2: mov $'\r', %al call printch mov $'\n', %al call printch leave pop %esi pop %edx pop %ecx pop %ebx pop %eax 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_memerr: .asciz "panic: Out of heap space.\r\n" msg_topen: .asciz "tftp_open\r\n" msg_tclose: .asciz "tftp_close\r\n" msg_tread: .asciz "tftp_read\r\n" fn_initrd: .asciz "initrd" pxe_api: .long 0 server_ip: .space 4 gateway_ip: .space 4 heap_ptr: .long 0 heap_end: .long 0 heap_mment: .long tx_buf // 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: .short 0 tx_buf: .space TX_BUF_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 // Remove our heap from the memory map so it doesn't get overwritten mov heap_mment, %esi mov 8(%rsi), %rcx add 0(%rsi), %rcx mov heap_ptr, %eax sub %rax, %rcx mov %rax, 0(%rsi) mov %rcx, 8(%rsi) // Mangle e820 memmap into bootboot's format mov $bb_memmap, %rsi mov $bb_memmap, %rdi mov $bootboot, %edx add bb_size, %edx 1: mov 0(%rsi), %rax mov 8(%rsi), %rcx mov 16(%rsi), %ebx cmp $6, %ebx jb 2f mov $2, %ebx 2: mov type_table(%rbx), %bl and $-16, %rcx or %rbx, %rcx mov %rax, 0(%rdi) mov %rcx, 8(%rdi) add $24, %rsi add $16, %rdi cmp %rdx, %rsi jb 1b sub $bootboot, %edi mov %edi, bb_size mov $bootboot, %edi jmp loader_main type_table: .byte 0 .byte 1 .byte 0 .byte 2 .byte 2 .byte 0 // ToDo List: // - Sorting the memmap // - Sanitizing the memmap // - Parsing a config file hex_digits: .ascii "0123456789ABCDEF" .section .data.bootboot bootboot: .ascii "BOOT" bb_size: .long 128 .byte 1 .byte 0 .short 1 .short 0 .space 10, 0 bb_ird_ptr: .quad 0 bb_ird_size:.quad 0 .space 24, 0 .space 64, 0 bb_memmap: .space 4096-128, 0