commit a6dd6b4328b70778660af44ea4a8acfbc9f68aaf Author: Thomas Oltmann Date: Tue Feb 17 19:44:29 2026 +0100 Clean slate; Bochs + IPXE boot diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d6415b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +boot.bin +boot.elf diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b5fe466 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +-include config.mk + +.PHONY: all clean + +all: boot.bin + +clean: + rm -f *.o boot.elf boot.bin + +config.mk: | config.default.mk + cp config.default.mk $@ + +boot.bin: boot.elf + objcopy -O binary -j .text -j .data boot.elf $@ + wc -c $@ + +boot.elf: Rnbp.o Ptftp.o Qcommon.o fernlader.ld + $(LD) $(LDFLAGS) -o $@ Rnbp.o Ptftp.o Qcommon.o + +%.o: %.S + $(CC) $(CFLAGS) -c -o $@ $(@:.o=.S) + +%.o: %.c + $(CC) $(CFLAGS) -O0 -c -o $@ $(@:.o=.c) diff --git a/Ptftp.S b/Ptftp.S new file mode 100644 index 0000000..e69de29 diff --git a/Qcommon.c b/Qcommon.c new file mode 100644 index 0000000..f08e752 --- /dev/null +++ b/Qcommon.c @@ -0,0 +1,38 @@ +#include + +#define COM1_BASE_PORT 0x3F8 + +static inline uint8_t +inb(uint16_t port) +{ + uint8_t data; + __asm__ ("inb %%dx, %%al" : "=a"(data) : "d"(port)); + return data; +} + +static inline void +outb(uint16_t port, uint8_t data) +{ + __asm__ ("outb %%al, %%dx" :: "a"(data), "d"(port)); +} + +void serial_write_char(char c) +{ + while (1) { + int line_status = inb(COM1_BASE_PORT + 5); + if (line_status & 0x20) { + break; + } + } + outb(COM1_BASE_PORT, c); +} + +void +main() +{ + outb(0xE9, 'X'); + serial_write_char('X'); + for (;;) { + __asm__ ("hlt" :); + } +} diff --git a/Rnbp.S b/Rnbp.S new file mode 100644 index 0000000..15ae2c3 --- /dev/null +++ b/Rnbp.S @@ -0,0 +1,119 @@ + // vim: et:sw=12:ts=12:sts=12 + + .global _start + .text + .code16 + + .set SS_CODE16, 0x08 + .set SS_DATA16, 0x10 + .set SS_CODE32, 0x18 + .set SS_DATA32, 0x20 + + + + // _start: entry point +_start: cli + cld + mov %sp, %bp + xor %ax, %ax + mov %ax, %ds + mov %ax, %es + + push $msg_start + call dbgmsg + add $2, %sp + + // initialize our own BSS section + mov $_bss_start, %di + mov $_bss_end, %cx + sub %di, %cx + xor %al, %al + rep stosb + + // a20_enable: Allow use of 'high' (>1Mb) memory +a20_enable: // 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. + push $msg_a20 + call dbgmsg + add $2, %sp + + inb $0x92, %al + or $2, %al + outb %al, $0x92 + + // prot_enter: Set up GDT, switch into 32-bit protected mode. +prot_enter: + push $msg_prot + call dbgmsg + add $2, %sp + + lgdt GDT_PTR + + // Set Protection Enable (PE) bit + mov %cr0, %eax + or $1, %al + mov %eax, %cr0 + + ljmp $SS_CODE32, $prot_trampo + + .code32 +prot_trampo: + mov $SS_DATA32, %eax + mov %eax, %ds + mov %eax, %es + mov %eax, %fs + mov %eax, %gs + mov %eax, %ss + + mov 'Y', %al + outb %al, $0xE9 + + // TODO load proper stack pointer + mov $0x90000, %ebp + mov %ebp, %esp + + jmp main + + .code16 +dbgmsg: push %bp + mov %sp, %bp + push %ax + push %si + mov 4(%bp), %si + +1: lodsb + test %al, %al + jz 2f + outb %al, $0xE9 + jmp 1b + +2: pop %si + pop %ax + pop %bp + ret + + .data + + .global GDT + +GDT: // entry 0: null descriptor + .space 8, 0 + // entry 1: 16-bit code segment + .byte 0xFF, 0xFF, 0, 0, 0, 0b10011010, 0x8F, 0 + // entry 2: 16-bit data segment + .byte 0xFF, 0xFF, 0, 0, 0, 0b10010010, 0x8F, 0 + // entry 3: 32-bit code segment + .byte 0xFF, 0xFF, 0, 0, 0, 0b10011010, 0xCF, 0 + // entry 4: 32-bit data segment + .byte 0xFF, 0xFF, 0, 0, 0, 0b10010010, 0xCF, 0 + // TODO: 32-bit TSS + .set GDT_SIZE, . - GDT +GDT_PTR: .word GDT_SIZE - 1 + .quad GDT + +msg_start: .asciz "Netboot via fernlader v2 ...\r\n" +msg_a20: .asciz " * Enabling A20 Gate\r\n" +msg_prot: .asciz " * Protected Mode\r\n" diff --git a/bochsrc.txt b/bochsrc.txt new file mode 100644 index 0000000..91f80c1 --- /dev/null +++ b/bochsrc.txt @@ -0,0 +1,9 @@ +cpu: model=tigerlake, count=1 + +floppya: 1_44=ipxe.dsk, status=inserted, write_protected=1 +boot: floppy + +e1000: mac=b0:c4:20:00:00:00, ethmod=tuntap, ethdev=/dev/net/tun, script=tunconfig.sh + +keyboard: keymap=de +port_e9_hack: enabled=1 diff --git a/boot.ipxe b/boot.ipxe new file mode 100644 index 0000000..33258c1 --- /dev/null +++ b/boot.ipxe @@ -0,0 +1,14 @@ +#!ipxe +ifopen net0 + +set net0/ip 10.0.0.2 +set net0/netmask 255.255.255.0 +set net0/dns 10.0.0.1 +set net0/gateway ${net0/dns} +set net0/next-server ${net0/dns} +set net0/filename boot.bin + +# Verify settings with user +# config net0 + +chain ${net0/filename} diff --git a/bootboot.h b/bootboot.h new file mode 100644 index 0000000..406023f --- /dev/null +++ b/bootboot.h @@ -0,0 +1,155 @@ +/* + * bootboot.h + * https://gitlab.com/bztsrc/bootboot + * + * Copyright (C) 2017 - 2021 bzt (bztsrc@gitlab) + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * This file is part of the BOOTBOOT Protocol package. + * @brief The BOOTBOOT structure + * + */ + +#ifndef _BOOTBOOT_H_ +#define _BOOTBOOT_H_ + +#ifdef __cplusplus +extern "C" { +#endif +#ifndef _MSC_VER +#define _pack __attribute__((packed)) +#else +#define _pack +#pragma pack(push) +#pragma pack(1) +#endif + +#define BOOTBOOT_MAGIC "BOOT" + +/* default virtual addresses for level 0 and 1 static loaders */ +#define BOOTBOOT_MMIO 0xfffffffff8000000 /* memory mapped IO virtual address */ +#define BOOTBOOT_FB 0xfffffffffc000000 /* frame buffer virtual address */ +#define BOOTBOOT_INFO 0xffffffffffe00000 /* bootboot struct virtual address */ +#define BOOTBOOT_ENV 0xffffffffffe01000 /* environment string virtual address */ +#define BOOTBOOT_CORE 0xffffffffffe02000 /* core loadable segment start */ + +/* minimum protocol level: + * hardcoded kernel name, static kernel memory addresses */ +#define PROTOCOL_MINIMAL 0 +/* static protocol level: + * kernel name parsed from environment, static kernel memory addresses */ +#define PROTOCOL_STATIC 1 +/* dynamic protocol level: + * kernel name parsed, kernel memory addresses from ELF or PE symbols */ +#define PROTOCOL_DYNAMIC 2 +/* big-endian flag */ +#define PROTOCOL_BIGENDIAN 0x80 + +/* loader types, just informational */ +#define LOADER_BIOS (0<<2) +#define LOADER_UEFI (1<<2) +#define LOADER_RPI (2<<2) +#define LOADER_COREBOOT (3<<2) + +/* framebuffer pixel format, only 32 bits supported */ +#define FB_ARGB 0 +#define FB_RGBA 1 +#define FB_ABGR 2 +#define FB_BGRA 3 + +/* mmap entry, type is stored in least significant tetrad (half byte) of size + * this means size described in 16 byte units (not a problem, most modern + * firmware report memory in pages, 4096 byte units anyway). */ +typedef struct { + uint64_t ptr; + uint64_t size; +} _pack MMapEnt; +#define MMapEnt_Ptr(a) ((a)->ptr) +#define MMapEnt_Size(a) ((a)->size & 0xFFFFFFFFFFFFFFF0) +#define MMapEnt_Type(a) ((a)->size & 0xF) +#define MMapEnt_IsFree(a) (((a)->size&0xF)==1) + +#define MMAP_USED 0 /* don't use. Reserved or unknown regions */ +#define MMAP_FREE 1 /* usable memory */ +#define MMAP_ACPI 2 /* acpi memory, volatile and non-volatile as well */ +#define MMAP_MMIO 3 /* memory mapped IO region */ + +#define INITRD_MAXSIZE 16 /* Mb */ + +typedef struct { + /* first 64 bytes is platform independent */ + uint8_t magic[4]; /* 'BOOT' magic */ + uint32_t size; /* length of bootboot structure, minimum 128 */ + uint8_t protocol; /* 1, static addresses, see PROTOCOL_* and LOADER_* above */ + uint8_t fb_type; /* framebuffer type, see FB_* above */ + uint16_t numcores; /* number of processor cores */ + uint16_t bspid; /* Bootsrap processor ID (Local APIC Id on x86_64) */ + int16_t timezone; /* in minutes -1440..1440 */ + uint8_t datetime[8]; /* in BCD yyyymmddhhiiss UTC (independent to timezone) */ + uint64_t initrd_ptr; /* ramdisk image position and size */ + uint64_t initrd_size; + uint64_t fb_ptr; /* framebuffer pointer and dimensions */ + uint32_t fb_size; + uint32_t fb_width; + uint32_t fb_height; + uint32_t fb_scanline; + + /* the rest (64 bytes) is platform specific */ + union { + struct { + uint64_t acpi_ptr; + uint64_t smbi_ptr; + uint64_t efi_ptr; + uint64_t mp_ptr; + uint64_t unused0; + uint64_t unused1; + uint64_t unused2; + uint64_t unused3; + } x86_64; + struct { + uint64_t acpi_ptr; + uint64_t mmio_ptr; + uint64_t efi_ptr; + uint64_t unused0; + uint64_t unused1; + uint64_t unused2; + uint64_t unused3; + uint64_t unused4; + } aarch64; + } arch; + + /* from 128th byte, MMapEnt[], more records may follow */ + MMapEnt mmap; + /* use like this: + * MMapEnt *mmap_ent = &bootboot.mmap; mmap_ent++; + * until you reach bootboot->size, while(mmap_ent < bootboot + bootboot->size) */ +} _pack BOOTBOOT; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/config.default.mk b/config.default.mk new file mode 100644 index 0000000..576b634 --- /dev/null +++ b/config.default.mk @@ -0,0 +1,5 @@ +CC = cc +LD = ld + +CFLAGS = -no-pie -fno-pic +LDFLAGS = -Tfernlader.ld diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..fbe74d1 --- /dev/null +++ b/config.mk @@ -0,0 +1,5 @@ +CC = cc +LD = ld + +CFLAGS = -no-pie -fno-pic -fno-stack-protector +LDFLAGS = -Tfernlader.ld diff --git a/dnsmasq.conf b/dnsmasq.conf new file mode 100644 index 0000000..163f8c6 --- /dev/null +++ b/dnsmasq.conf @@ -0,0 +1,13 @@ +listen-address=10.0.0.1 +bind-dynamic +#bind-interfaces +#except-interface=lo + +log-async + +# Disable DNS server +port=0 + +enable-tftp +tftp-root=/home/thomas/karlos/fernlader2/serve + diff --git a/fernlader.ld b/fernlader.ld new file mode 100644 index 0000000..e0357c5 --- /dev/null +++ b/fernlader.ld @@ -0,0 +1,21 @@ +ENTRY(_start) +PHDRS { + all PT_LOAD; +} +SECTIONS { + .text 0x7C00: { + Rnbp.o(.text) + *(.text*, .rodata*) + } :all + .data : { + *(.data*) + } :all + .bss (NOLOAD) : { + _bss_start = .; + *(COMMON) + *(.bss) + . = ALIGN(4K); + *(.bootboot) + _bss_end = .; + } :all +} diff --git a/ipxe.dsk b/ipxe.dsk new file mode 100644 index 0000000..94e643f Binary files /dev/null and b/ipxe.dsk differ diff --git a/run-qemu.sh b/run-qemu.sh new file mode 100755 index 0000000..d82f8a1 --- /dev/null +++ b/run-qemu.sh @@ -0,0 +1,2 @@ +#!/bin/sh +qemu-system-x86_64 -netdev user,id=n1,net=10.0.0.5/24,tftp=.,bootfile=/boot.bin -device virtio-net-pci,netdev=n1,bootindex=0 "$@" diff --git a/tunconfig.sh b/tunconfig.sh new file mode 100755 index 0000000..962f7b6 --- /dev/null +++ b/tunconfig.sh @@ -0,0 +1,5 @@ +#!/bin/bash +DEV="${1##/*/}" +HOST_IP="10.0.0.1/24" +ip addr add "$HOST_IP" dev "$DEV" +ip link set up dev "$DEV"