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 {
.text 0x7C00: {
lboot.o(.text)
*(.text)
*(.text, .rodata*)
*(.data)
*(.bss, COMMON)
} :all

226
lboot.S
View file

@ -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:

View file

@ -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");
}