Sprint 1 — Boot & Serial Output

Get the kernel running on bare metal and prove it with visible output.

✅ Complete

Table of contents

Overview #

Sprint 1 is the foundation — everything needed to boot the kernel and get diagnostic output. After this sprint, the kernel boots via UEFI, prints to the serial port, and renders text on the framebuffer.


Toolchain Configuration #

rust-toolchain.toml

Pins the exact Rust nightly version and includes rust-src (needed to rebuild core and alloc for bare metal):

[toolchain]
channel = "nightly-2026-02-13"
components = ["rust-src", "rustfmt", "clippy"]
targets = ["x86_64-unknown-none"]

.cargo/config.toml

Configures the bare-metal compilation target:


Linker Script (kernel/linker.ld) #

Controls the kernel's memory layout in virtual address space.

Higher-Half Mapping

The kernel is placed at 0xFFFFFFFF80200000 — the top 2 GB of the 64-bit address space plus a 2 MB offset:

Sections

Each section is page-aligned (4 KiB) to enable per-section page table permissions:

SectionContentsPermissions
.textExecutable code (kmain first)R+X
.rodataConstants, strings, Limine requests, .got entriesR
.dataInitialized mutable globalsR+W
.bssZero-initialized globalsR+W

Boundary symbols (_kernel_start, _kernel_end, _text_start, etc.) let the kernel know its own memory layout at runtime.


Boot Protocol (arch/x86_64/boot.rs) #

Uses the Limine boot protocol to communicate with the bootloader. The kernel defines static request structures; Limine fills in the responses before jumping to kmain().

Requests

RequestData Provided
HhdmRequestHHDM offset — where all physical RAM is mapped in virtual space
MemoryMapRequestPhysical memory map — usable, reserved, ACPI, framebuffer regions
FramebufferRequestFramebuffer address, dimensions, pixel format
RsdpRequestACPI RSDP table pointer (for hardware discovery)
ExecutableAddressRequestKernel physical/virtual load addresses

Bootloader Configuration (boot/limine.conf)

timeout: 0
serial: yes

/MinimalOS NextGen
    protocol: limine
    kernel_path: boot():/boot/minimalos-kernel

Serial UART (arch/x86_64/serial.rs) #

A 16550-compatible UART driver for COM1 (0x3F8), the first output device initialized during boot.

Configuration

Key Details


CPU Primitives (arch/x86_64/cpu.rs) #

Low-level CPU operations used throughout the kernel:

FunctionInstructionPurpose
halt()hltHalt CPU until next interrupt
halt_forever()cli; hlt loopPermanent halt (used after boot)
read_cr2()mov rax, cr2Faulting address on page fault
read_cr3()mov rax, cr3Current page table base
write_cr3()mov cr3, raxSwitch page tables
invlpg()invlpg [addr]Flush single TLB entry
flush_tlb_all()Write CR3Flush entire TLB
rdtsc()rdtscRead timestamp counter
rdmsr() / wrmsr()rdmsr / wrmsrRead/write model-specific registers

Logging (util/logger.rs) #

Provides kprint!() and kprintln!() macros that write to the serial port. These are the kernel's primary debugging tool.

kprintln!("[boot] HHDM offset: {:#018X}", hhdm_offset);
kprintln!("[pmm] {} free frames", stats.free_frames);

Uses Rust's core::fmt formatting infrastructure — supports {}, {:#X}, {:?}, and all standard format specifiers.


Framebuffer Console (drivers/framebuffer.rs) #

A text-mode console rendered on the UEFI framebuffer using a built-in 8×16 bitmap font.

Features

How It Works

  1. Limine provides a framebuffer with address, dimensions, and pixel format
  2. The console tracks a cursor position (row, column) on a character grid
  3. Each character write renders 8×16 pixels from the font bitmap
  4. When the cursor reaches the bottom, the screen scrolls up by one row (memcopy)

Synchronization (sync/spinlock.rs) #

An IRQ-safe ticket spinlock used to protect shared kernel state.

How Ticket Locks Work

Unlike test-and-set spinlocks that cause thundering herd, ticket locks provide FIFO fairness:

  1. Arriving thread atomically increments next_ticket and saves its ticket number
  2. Thread spins until now_serving equals its ticket
  3. On unlock, now_serving is incremented, waking the next waiter

Properties


Address Types (memory/address.rs) #

Type-safe wrappers for physical and virtual addresses:

TypeRangeUse
PhysAddrPhysical memory (0 → ~8 GB)PMM, DMA, page table entries
VirtAddrVirtual memory (full 64-bit)Pointers, kernel addresses

HHDM Translation

The Higher Half Direct Map (HHDM) provides a simple, total mapping of all physical memory into the kernel's virtual address space:

// Convert physical address to virtual via HHDM
let virt = phys_addr.to_virt();  // phys + HHDM_OFFSET

// Convert virtual HHDM address back to physical
let phys = VirtAddr::to_phys(virt);  // virt - HHDM_OFFSET

The HHDM offset is provided by Limine at boot and stored in a global.


Panic Handler (util/panic.rs) #

On panic, the kernel:

  1. Prints the panic message and source location to serial
  2. Halts all CPU cores indefinitely

In a microkernel, panics should be extremely rare — they indicate a bug in the trusted computing base.


Entry Point (main.rs) #

The kmain() function orchestrates the entire boot sequence through 5 phases. Sprint 1 implements Phases 1–2:

#[unsafe(no_mangle)]
extern "C" fn kmain() -> ! {
    // Phase 1: Init serial → kprintln!() works
    // Phase 2: Parse boot info, init framebuffer
    // Phase 3: Memory management (Sprint 2)
    // Phase 4: Scheduler + processes (Sprint 3-4)
    // Phase 5: Userspace entry (Sprint 5-7)

    arch::cpu::halt_forever()
}

Files Implemented #

FileLinesPurpose
rust-toolchain.toml~15Nightly toolchain pin
.cargo/config.toml~30Bare-metal build config
Cargo.toml~50Workspace configuration
kernel/Cargo.toml~60Kernel dependencies
kernel/linker.ld~180Memory layout
boot/limine.conf~5Bootloader config
kernel/src/main.rs~420Entry point
kernel/src/arch/x86_64/boot.rs~200Limine protocol
kernel/src/arch/x86_64/serial.rs~200Serial UART
kernel/src/arch/x86_64/cpu.rs~180CPU primitives
kernel/src/memory/address.rs~120Address types
kernel/src/sync/spinlock.rs~150Ticket spinlock
kernel/src/drivers/framebuffer.rs~350Framebuffer console
kernel/src/util/logger.rs~50kprint! macros
kernel/src/util/panic.rs~20Panic handler
Makefile~290Build system