diff --git a/fernlader.ld b/fernlader.ld index f40031d..0413c75 100644 --- a/fernlader.ld +++ b/fernlader.ld @@ -4,7 +4,7 @@ PHDRS { SECTIONS { .text 0x7C00: { lboot.o(.text) - *(.text) + *(.text, .rodata*) *(.data) *(.bss, COMMON) } :all diff --git a/lboot.S b/lboot.S index 21d1c53..f33df4f 100644 --- a/lboot.S +++ b/lboot.S @@ -1,32 +1,59 @@ // 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, 1024 + .set PACKET_SIZE, 512 .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 %sp, %bx + or %ss:(%bx), %ax jnz _pcerr + + pop %edx + pop %ecx + pop %ebx + pop %eax .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 + mov %ax, %es call init_com1 @@ -35,14 +62,14 @@ _start: cli mov %ss:4(%bp), %si mov %ss:6(%bp), %ax - mov %ax, %es + mov %ax, %fs .set PXE_MAGIC, 0x45585021 - cmpl $PXE_MAGIC, %es:(%si) + cmpl $PXE_MAGIC, %fs:(%si) je 1f mov $msg_pserr, %si call print jmp hang -1: mov %es:16(%si), %eax +1: mov %fs:16(%si), %eax mov %eax, pxe_api mov $msg_a20, %si @@ -79,7 +106,9 @@ _start: cli mov $msg_read, %si call print + mov $fn_initrd, %esi call read_file + mov %ebx, initrd_ptr mov $msg_long, %si call print @@ -115,6 +144,7 @@ enable_a20: // Of all the ways to toggle A20, we only try the Fast A20 Gate. // unreal: Enter unreal mode unreal: push %ds + push %es lgdt gdt16_ptr mov %cr0, %eax @@ -124,18 +154,18 @@ unreal: push %ds _urprot: mov $0x10, %cx mov %cx, %ds + mov %cx, %es and $0xFE, %al mov %eax, %cr0 ljmp $0x0, $_urunreal -_urunreal: pop %ds +_urunreal: pop %es + pop %ds ret // get_map: Retrieve memory map using e820 BIOS function -get_map: mov %ds, %ax - mov %ax, %es - mov $memmap, %di +get_map: mov $memmap, %di xor %ebx, %ebx mov $0x534D4150, %edx // e820 magic number _gmnext: movl $0, 20(%di) @@ -152,14 +182,16 @@ _gmdone: add $24, %di ret // paging: Set up initial page tables for long mode -paging: mov $4*4096, %ecx - call alloc +paging: + mov heap_ptr, %eax + add $6*4096, %eax + cmp heap_end, %eax + ja out_of_mem + mov %eax, pd_ptr - mov $4096, %ecx - call alloc + add $4*4096, %eax mov %eax, pdp_ptr - mov $4096, %ecx - call alloc + add $4096, %eax mov %eax, pml4_ptr // fill PDEs with identity map < 4Gb @@ -209,15 +241,14 @@ _mhnext: add $24, %si cmpl $1, 16(%si) jne _mhnext - cmpl $0, 4(%si) ja _mhnext - mov 0(%si), %ebx + mov 0(%si), %eax mov 8(%si), %ecx // find end of range, clip to 4Gb - add %ebx, %ecx + add %eax, %ecx jnc 1f mov $0xFFFFFFFF, %ecx @@ -227,94 +258,116 @@ _mhnext: add $24, %si mov $0xFFFFFFFF, %ecx // adjust base to above 1Mb, above the bootloader -1: cmp $0x10000, %ebx +1: cmp $0x10000, %eax jae 1f - mov $0x10000, %ebx + mov $0x10000, %eax - // align to 4Kb boundaries -1: add $0xFFF, %ebx - and $0xFFFFF000, %ebx - and $0xFFFFF000, %ecx + // align start to 4Kb boundary +1: add $0xFFF, %eax + and $0xFFFFF000, %eax - sub %ebx, %ecx - cmp heap_size, %ecx + // 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 - mov %ebx, heap_start - mov %ecx, heap_size + // If so, switch to it. + mov %eax, heap_ptr + mov %ecx, heap_end 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 + .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 -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 - + mov $128, %ecx + sub %cx, %sp + mov %ss, %edi + shl $4, %edi + add %esp, %edi + addr32 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 server_ip+2 + push server_ip push $0 pxe_call PXE_TFTP_OPEN - add $14+128, %sp + leave + pop %edi + pop %esi + pop %ecx + ret + // tftp_close: Close the TFTP stream. +tftp_close: push %bp + mov %sp, %bp + + 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 - .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 + 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: xor %bx, %bx @@ -435,16 +488,17 @@ 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" +msg_memerr: .asciz "panic: Out of heap space.\r\n" -fn_config: .asciz "fernlader.cfg" - .set fn_config_l, .-fn_config +fn_initrd: .asciz "initrd" pxe_api: .long 0 server_ip: .space 4 -heap_start: .long 0 -heap_size: .long 0 +heap_ptr: .long 0 +heap_end: .long 0 + +initrd_ptr: .long 0 // Long mode initial page tables pd_ptr: .long 0 @@ -466,6 +520,8 @@ trampo64: mov %eax, %fs mov %eax, %gs mov %eax, %ss + + mov initrd_ptr, %edi jmp loader_main // ToDo List: diff --git a/loader.c b/loader.c index 58b72ca..9362222 100644 --- a/loader.c +++ b/loader.c @@ -128,9 +128,19 @@ typedef struct { } Elf64_Phdr; static void -panic(void) +panic(const char *message) { - for (;;) {} + for (const char *c = message; *c; c++) { + uint8_t status = 0; + do { + __asm__ ("inb\t%%dx" : "=a"(status) : "d"(0x3F8 + 5)); + } while (!(status & 0x20)); + __asm__ __volatile__ ("outb\t%%dx" : : "a"(*c), "d"(0x3F8)); + } + + for (;;) { + __asm__ __volatile__ ("hlt"); + } } static int @@ -175,21 +185,21 @@ load_elf(file_t file, uintptr_t *entry) } void -loader_main(void) +loader_main(void *initrd) { uint64_t entry = 0; - file_t elf = { 0, 0 }; + file_t elf = { initrd, 1024 }; if (load_elf(elf, &entry) < 0) { - panic(); + panic("panic: Malformed ELF64 executable"); } uint64_t stack = 0; __asm__ __volatile__ ( - "mov\t%1, %rbp\n\t" - "mov\t%rbp, %rsp\n\t" + "mov\t%1, %%rbp\n\t" + "mov\t%%rbp, %%rsp\n\t" "call\t*%0" : : "r"(entry), "r"(stack) : "memory"); - panic(); + panic("panic: Ran past kernel invocation"); }