Receiving a whole file over PXE

This commit is contained in:
Thomas Oltmann 2025-07-08 02:04:02 +02:00
parent 1c0b8a53d6
commit c13946c7dd
3 changed files with 160 additions and 94 deletions

View file

@ -4,7 +4,7 @@ PHDRS {
SECTIONS { SECTIONS {
.text 0x7C00: { .text 0x7C00: {
lboot.o(.text) lboot.o(.text)
*(.text) *(.text, .rodata*)
*(.data) *(.data)
*(.bss, COMMON) *(.bss, COMMON)
} :all } :all

226
lboot.S
View file

@ -1,32 +1,59 @@
// vim: et:sw=12:ts=12:sts=12 // vim: et:sw=12:ts=12:sts=12
// lboot.S: boot code for legacy BIOS boot over PXE. // 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 .global _start
.text .text
.code16 .code16
.set PACKET_SIZE, 1024 .set PACKET_SIZE, 512
.macro pxe_call, opcode .macro pxe_call, opcode
push %eax
push %ebx
push %ecx
push %edx
mov %sp, %bx mov %sp, %bx
add $16, %bx
push %ss push %ss
push %bx push %bx
push $\opcode push $\opcode
lcall *pxe_api lcall *pxe_api
add $6, %sp add $6, %sp
mov %sp, %bx
or %ss:(%bx), %ax or %ss:(%bx), %ax
jnz _pcerr jnz _pcerr
pop %edx
pop %ecx
pop %ebx
pop %eax
.endm .endm
// _start: entry point // _start: entry point
_start: cli _start: cli
cld cld
mov %sp, %bp mov %sp, %bp
// we keep our text and data close to each other
xor %ax, %ax xor %ax, %ax
mov %ax, %ds mov %ax, %ds
mov %ax, %es
call init_com1 call init_com1
@ -35,14 +62,14 @@ _start: cli
mov %ss:4(%bp), %si mov %ss:4(%bp), %si
mov %ss:6(%bp), %ax mov %ss:6(%bp), %ax
mov %ax, %es mov %ax, %fs
.set PXE_MAGIC, 0x45585021 .set PXE_MAGIC, 0x45585021
cmpl $PXE_MAGIC, %es:(%si) cmpl $PXE_MAGIC, %fs:(%si)
je 1f je 1f
mov $msg_pserr, %si mov $msg_pserr, %si
call print call print
jmp hang jmp hang
1: mov %es:16(%si), %eax 1: mov %fs:16(%si), %eax
mov %eax, pxe_api mov %eax, pxe_api
mov $msg_a20, %si mov $msg_a20, %si
@ -79,7 +106,9 @@ _start: cli
mov $msg_read, %si mov $msg_read, %si
call print call print
mov $fn_initrd, %esi
call read_file call read_file
mov %ebx, initrd_ptr
mov $msg_long, %si mov $msg_long, %si
call print 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: Enter unreal mode
unreal: push %ds unreal: push %ds
push %es
lgdt gdt16_ptr lgdt gdt16_ptr
mov %cr0, %eax mov %cr0, %eax
@ -124,18 +154,18 @@ unreal: push %ds
_urprot: mov $0x10, %cx _urprot: mov $0x10, %cx
mov %cx, %ds mov %cx, %ds
mov %cx, %es
and $0xFE, %al and $0xFE, %al
mov %eax, %cr0 mov %eax, %cr0
ljmp $0x0, $_urunreal ljmp $0x0, $_urunreal
_urunreal: pop %ds _urunreal: pop %es
pop %ds
ret ret
// get_map: Retrieve memory map using e820 BIOS function // get_map: Retrieve memory map using e820 BIOS function
get_map: mov %ds, %ax get_map: mov $memmap, %di
mov %ax, %es
mov $memmap, %di
xor %ebx, %ebx xor %ebx, %ebx
mov $0x534D4150, %edx // e820 magic number mov $0x534D4150, %edx // e820 magic number
_gmnext: movl $0, 20(%di) _gmnext: movl $0, 20(%di)
@ -152,14 +182,16 @@ _gmdone: add $24, %di
ret ret
// paging: Set up initial page tables for long mode // paging: Set up initial page tables for long mode
paging: mov $4*4096, %ecx paging:
call alloc mov heap_ptr, %eax
add $6*4096, %eax
cmp heap_end, %eax
ja out_of_mem
mov %eax, pd_ptr mov %eax, pd_ptr
mov $4096, %ecx add $4*4096, %eax
call alloc
mov %eax, pdp_ptr mov %eax, pdp_ptr
mov $4096, %ecx add $4096, %eax
call alloc
mov %eax, pml4_ptr mov %eax, pml4_ptr
// fill PDEs with identity map < 4Gb // fill PDEs with identity map < 4Gb
@ -209,15 +241,14 @@ _mhnext: add $24, %si
cmpl $1, 16(%si) cmpl $1, 16(%si)
jne _mhnext jne _mhnext
cmpl $0, 4(%si) cmpl $0, 4(%si)
ja _mhnext ja _mhnext
mov 0(%si), %ebx mov 0(%si), %eax
mov 8(%si), %ecx mov 8(%si), %ecx
// find end of range, clip to 4Gb // find end of range, clip to 4Gb
add %ebx, %ecx add %eax, %ecx
jnc 1f jnc 1f
mov $0xFFFFFFFF, %ecx mov $0xFFFFFFFF, %ecx
@ -227,94 +258,116 @@ _mhnext: add $24, %si
mov $0xFFFFFFFF, %ecx mov $0xFFFFFFFF, %ecx
// adjust base to above 1Mb, above the bootloader // adjust base to above 1Mb, above the bootloader
1: cmp $0x10000, %ebx 1: cmp $0x10000, %eax
jae 1f jae 1f
mov $0x10000, %ebx mov $0x10000, %eax
// align to 4Kb boundaries // align start to 4Kb boundary
1: add $0xFFF, %ebx 1: add $0xFFF, %eax
and $0xFFFFF000, %ebx and $0xFFFFF000, %eax
and $0xFFFFF000, %ecx
sub %ebx, %ecx // Is this entry larger than current heap?
cmp heap_size, %ecx mov %ecx, %edx
sub %eax, %edx
mov heap_end, %ebx
sub heap_ptr, %ebx
cmp %ebx, %edx
jbe _mhnext jbe _mhnext
mov %ebx, heap_start // If so, switch to it.
mov %ecx, heap_size mov %eax, heap_ptr
mov %ecx, heap_end
jmp _mhnext jmp _mhnext
_mhdone: ret _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 .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
push $PACKET_SIZE push $PACKET_SIZE
push $69<<8 push $69<<8
sub $128, %sp mov $128, %ecx
sub %cx, %sp
mov $fn_config, %esi mov %ss, %edi
mov %ss, %ax shl $4, %edi
mov %ax, %es add %esp, %edi
mov %esp, %edi addr32 rep movsb
mov $fn_config_l, %ecx
rep movsb
push $0 push $0
push $0 push $0
push server_ip+2
sub $4, %sp push server_ip
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 push $0
pxe_call PXE_TFTP_OPEN 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 %cs
push $tx_buf push $tx_buf
push $0 push $0
push $0 push $0
push $0 push $0
pxe_call PXE_TFTP_READ pxe_call PXE_TFTP_READ
add $10, %sp mov %sp, %bx
mov %ss:4(%bx), %ax
.set PXE_TFTP_CLOSE, 0x0021
push $0
pxe_call PXE_TFTP_CLOSE
add $2, %sp
mov $tx_buf, %si
call print
leave
pop %ebx
ret ret
out_of_mem: mov $msg_memerr, %si
call print
jmp hang
// print: print NUL-terminated string pointed to by SI // print: print NUL-terminated string pointed to by SI
print: xor %bx, %bx print: xor %bx, %bx
@ -435,16 +488,17 @@ msg_read: .asciz " * Retrieving\r\n"
msg_long: .asciz " * Long Mode\r\n" msg_long: .asciz " * Long Mode\r\n"
msg_pserr: .asciz "panic: Missing !PXE structure.\r\n" msg_pserr: .asciz "panic: Missing !PXE structure.\r\n"
msg_pcerr: .asciz "panic: PXE call failed.\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" fn_initrd: .asciz "initrd"
.set fn_config_l, .-fn_config
pxe_api: .long 0 pxe_api: .long 0
server_ip: .space 4 server_ip: .space 4
heap_start: .long 0 heap_ptr: .long 0
heap_size: .long 0 heap_end: .long 0
initrd_ptr: .long 0
// Long mode initial page tables // Long mode initial page tables
pd_ptr: .long 0 pd_ptr: .long 0
@ -466,6 +520,8 @@ trampo64:
mov %eax, %fs mov %eax, %fs
mov %eax, %gs mov %eax, %gs
mov %eax, %ss mov %eax, %ss
mov initrd_ptr, %edi
jmp loader_main jmp loader_main
// ToDo List: // ToDo List:

View file

@ -128,9 +128,19 @@ typedef struct {
} Elf64_Phdr; } Elf64_Phdr;
static void 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 static int
@ -175,21 +185,21 @@ load_elf(file_t file, uintptr_t *entry)
} }
void void
loader_main(void) loader_main(void *initrd)
{ {
uint64_t entry = 0; uint64_t entry = 0;
file_t elf = { 0, 0 }; file_t elf = { initrd, 1024 };
if (load_elf(elf, &entry) < 0) { if (load_elf(elf, &entry) < 0) {
panic(); panic("panic: Malformed ELF64 executable");
} }
uint64_t stack = 0; uint64_t stack = 0;
__asm__ __volatile__ ( __asm__ __volatile__ (
"mov\t%1, %rbp\n\t" "mov\t%1, %%rbp\n\t"
"mov\t%rbp, %rsp\n\t" "mov\t%%rbp, %%rsp\n\t"
"call\t*%0" "call\t*%0"
: : "r"(entry), "r"(stack) : "memory"); : : "r"(entry), "r"(stack) : "memory");
panic(); panic("panic: Ran past kernel invocation");
} }