Skip to content

Repository files navigation

kluster

Disclaimer: This README is AI-generated but was thoroughly reviewed and edited. The actual source code of this project is purely hand-written.

Introduction

A small bare-metal AArch64 operating system, written in Rust. It boots two ways:

  • On Raspberry Pi hardware (BCM2712/BCM2711) via the Pi's own firmware boot path, driving the display through the VideoCore mailbox — no bootloader beyond the Pi firmware, no host OS, no std.
  • As a generic UEFI application (BOOTAA64.EFI) on anything with AArch64 UEFI firmware — QEMU's virt machine, VirtualBox's ARM64 guests, and in principle real non-Pi hardware — driving the display through UEFI's Graphics Output Protocol (GOP) instead of the mailbox.

Both backends feed the same board-agnostic FrameConfig/DisplayBuffer in motherboards/display.rs, so the drawing code, font, and console are written once and shared across every target.


Working today

Boot & CPU

  • Raspberry Pi path — custom AArch64 boot path (_start) linked at the Pi firmware's load address (0x80000) via a hand-written linker script. Boot-core selection parks the three secondary cores in wfe and runs the kernel on core 0 only. Zeroes BSS, sets up the boot stack, enters Rust.
  • UEFI path — built for the aarch64-unknown-uefi target using the uefi crate's #[entry] macro. Firmware loads and relocates the PE/COFF binary itself, so there's no hand-written boot assembly or linker script for this variant.
  • Panic handler halts the CPU (wfe); no console output on panic yet.

Graphics

Two backends, each producing the same FrameConfig (base address, size, width, height, stride, bytes-per-pixel, BGR/RGB order):

  • VideoCore mailbox driver (Pi) — property-tags channel (channel 8) request/response with polling. HDMI framebuffer setup — physical + virtual size, depth, pixel order, framebuffer allocation, pitch — all read back from the firmware at runtime. Runtime bpp = pitch / width handles the Pi 5's actual 16-bit RGB565 framebuffer and QEMU raspi4b's 32-bit framebuffer from the same code path.
  • UEFI GOP driver (generic) — locates the GraphicsOutput protocol, reads the current mode's resolution/stride/pixel format, and takes the linear framebuffer pointer directly. Only direct-write pixel formats (PixelFormat::Rgb/Bgr) are supported today; Bitmask/BltOnly GOP implementations (e.g. QEMU's virtio-gpu-pci) are rejected with a diagnostic over UEFI's stdout rather than hanging silently — see Running on QEMU (UEFI) for why the choice of display device matters here.

Once either backend produces a FrameConfig, the shared code in motherboards/display.rs takes over — it doesn't know or care which board built the config:

  • Drawing primitives (all i32 coordinates with on-screen clipping; off-screen vertices are silently dropped): clear(color), draw_pixel, draw_rect (auto-clipped, order-agnostic), draw_triangle (half-space rasterizer, both windings).
  • Color — RGBA Color with explicit packing methods (rgba/bgra for 32bpp, rgb565/bgr565 for 16bpp). write_color picks the right one at write time from the active FrameConfig's bpp and bgr fields, so the Pi 5's real 16-bit hardware framebuffer and every 32-bit framebuffer (QEMU, UEFI GOP) go through the same drawing code without either corrupting the other.

Text

  • 8×8 bitmap font covering uppercase A–Z, digits 0–9, and :.
  • Scalable glyph rendering (draw_char) that paints white glyph / black cell so re-drawing self-clears.
  • Framebuffer-backed println! console (motherboards::console::GenericWriter) — cursor state in the module, handles \n/\r, folds lowercase to uppercase, wraps at screen width, wraps back to the top when it runs out of vertical space. Shared by both backends: text is drawn as pixels onto whichever FrameConfig is active, not routed through a separate UART/serial console.

Build & run

  • Cargo features select the target board (see Feature flags).
  • cargo make tasks:
    • compile-raspi5 / compile-qemu-raspi4 — objcopy the ELF into a flat kernel_2712.img.
    • sync-raspi5 — copy the image to a mounted SD card at /Volumes/bootfs.
    • qemu-raspi4 — build for QEMU's raspi4b machine and launch it.
    • compile-uefi — build the aarch64-unknown-uefi binary.
    • qemu-uefi — build the UEFI binary, package it as BOOTAA64.EFI on a FAT boot disk, and launch QEMU's virt machine with OVMF firmware.
    • vbox-disk — build the UEFI binary and produce a VirtualBox-ready VDI disk image (see Running on VirtualBox).

Feature flags

kluster uses Cargo features to select the target board.

Feature What it selects
raspi Base marker enabling the motherboards::raspberrypi module.
rpi5 BCM2712 addresses: mailbox at 0x10_7C01_3880, A76 tuning. Implies raspi + device.
rpi4 BCM2711 addresses: mailbox at 0xFE00_B880, A72 tuning. Implies raspi.
device Running on real Pi hardware (framebuffer resolution/defaults).
emulator Running under QEMU raspi4b.
generic-uefi Build for aarch64-unknown-uefi; drive the display through UEFI GOP instead of the mailbox. Pulls in the uefi crate. Default.

Recommended combinations

Where I'm running Cargo flags
Raspberry Pi 5 hardware --no-default-features --features rpi5
QEMU raspi4b --no-default-features --features emulator,rpi4
QEMU virt / VirtualBox (default) — same as --features generic-uefi

cargo make tasks bake the right combination in, so in day-to-day use you don't pass features by hand.

Adding a new target

Bringing up another board means adding a new motherboards/<board>/ module whose job is just to produce a FrameConfig (see get_raspi_frame_config / get_uefi_frame_config) — the drawing, font, and console code in motherboards/display.rs and motherboards/console.rs is already shared and doesn't need touching. Wire the new module into motherboards::mod's #[cfg] gates and display::set_display_buffer(), and add a matching cargo make task with the right target/RUSTFLAGS/features.


In development

These are on the roadmap and in various states of design — not shipping yet.

🗂 FAT32 read-only filesystem

A BlockDevice trait with a RAM-backed impl first (embedded disk image via include_bytes!), then the same trait implemented by a real SD driver. Lets the kernel open and read files by path (/HELLO.TXT) and print their contents through the console. Short 8.3 names first; LFN later.

⚠️ Exception vectors

Install a VBAR_EL1 table so CPU faults (data aborts, unaligned access, instruction faults) print a diagnostic instead of silently hanging.

🧠 MMU + caches

Set up page tables, enable the MMU, and mark RAM as Normal cacheable memory.

⏱ Timers & interrupts

Bring up the ARM generic timer and the GIC-400 on the Pi 5. Enable IRQs. Foundation for any preemptive scheduling later.

🧵 Multicore

Wake secondary cores via PSCI (smc #0 with PSCI_CPU_ON) — the method both the Pi 5 device tree and generic UEFI/ACPI platforms specify, so this lands once and benefits both backends. Per-core stacks and a per-core entry.

💾 SD host controller driver

BCM2712 SDHCI initialization sequence (CMD0ACMD41 → …), CSD/CID parsing, high-speed switch, and DMA-based block reads. Drops into the same BlockDevice trait FAT32 targets.

🔤 Full ASCII font

Extend the 8×8 glyph table to cover lowercase and common punctuation, so println! can render real messages without the current auto-uppercase fold.

🧮 Global allocator (alloc)

A small bump / linked-list allocator so the kernel can use Vec, Box, String after early boot. Enables ergonomic data structures for the filesystem, scheduler, etc.

🖥 Host-testable rasterizer

Refactor the drawing primitives to target a &mut [u8] + geometry struct so the rasterizer runs unchanged inside a minifb window on macOS/Linux for tight visual iteration — no SD-card swap loop required. Also the natural path to real cargo test coverage: splitting the board-agnostic logic (color.rs, geometry.rs, the rasterizer math in display.rs) into a lib.rs marked #![cfg_attr(not(test), no_std)] would let those pieces run as ordinary host-native tests on macOS, with no QEMU or custom test runner involved — only the boot entry and hardware-specific drivers in main.rs would remain untestable this way, which is inherent to what they are.

🖼 Ultrawide 3440×1440 output

config.txt tuning (hdmi_cvt, max_framebuffer_*, framebuffer_depth=32) to get the Pi 5's HDMI to actually output the panel's native resolution instead of clamping to 1080p. Requires experimenting with BCM2712 firmware quirks.

🎨 Blt-based GOP fallback

The UEFI backend currently only supports direct-write GOP framebuffers (PixelFormat::Rgb/Bgr). Devices that report BltOnly (QEMU's virtio-gpu-pci, notably) have no CPU-writable linear buffer at all — pixels have to be assembled in a RAM-side backing buffer and submitted via GraphicsOutput::blt() once per frame instead. Worth doing eventually since it's the more general path (and unlocks resolutions bigger than ramfb's fixed mode list), but real UEFI firmware overwhelmingly supports direct-write GOP already, so it isn't blocking anything today.


Getting started

Prereqs (macOS):

# rustup + nightly + AArch64 targets
rustup toolchain install nightly
rustup target add aarch64-unknown-none-softfloat --toolchain nightly
rustup target add aarch64-unknown-uefi --toolchain nightly
rustup component add llvm-tools-preview --toolchain nightly
cargo install cargo-binutils cargo-make
brew install qemu

Running on QEMU (Raspberry Pi emulation)

cargo make qemu-raspi4

Running on QEMU (UEFI)

cargo make qemu-uefi

This builds the aarch64-unknown-uefi binary, copies it to target/uefi_disk/EFI/BOOT/bootaa64.efi, and boots QEMU's virt machine with OVMF firmware plus a ramfb display device. ramfb matters here — it's what gives UEFI a genuine direct-write linear framebuffer. QEMU's other common display option, virtio-gpu-pci, reports a BltOnly pixel format instead, which this kernel doesn't support yet (see 🎨 Blt-based GOP fallback) — swapping devices in Makefile.toml will silently regress this to a blank screen.

Running on VirtualBox

Requires VirtualBox 7.x with ARM64 guest support (Apple Silicon hosts).

bash setup_vbox_vm.sh

This tears down any existing kluster VM, builds the UEFI binary and a FAT boot disk (create_vbox_disk.sh — a real disk image file, since VirtualBox can't synthesize a FAT filesystem from a host directory the way QEMU's fat: driver can), creates an ARM64 VM (--chipset=armv8virtual --firmware=efi64), and attaches the disk. VirtualBox's default vboxvga graphics controller crashes outright on the ARM chipset (VERR_PGM_RAM_CONFLICT) — the script sets --graphicscontroller=qemuramfb, VirtualBox's own direct-write framebuffer device and the ARM-side analogue of QEMU's ramfb.

VBoxManage startvm kluster --type=gui

Serial diagnostics (GOP handle/pixel-format failures, resolution/stride once it succeeds) are written to /tmp/vbox_serial.log.

Updating after a code change — the VM itself doesn't need to be recreated, only the disk image:

VBoxManage controlvm kluster poweroff   # only if currently running
cargo make vbox-disk
VBoxManage startvm kluster --type=gui

create_vbox_disk.sh stamps the rebuilt VDI with a fixed UUID (sethduuid), so VirtualBox's media registry never drifts out of sync between rebuilds and the VM doesn't need to be recreated.

Flash and run on a Pi 5

SD card mounted at /Volumes/bootfs:

cargo make sync-raspi5

Project structure

src/
├── main.rs              // crate root, module declarations
├── kernel.rs             // kernel::main — the platform-agnostic entry
├── color.rs               // Color: RGBA storage + component access
├── geometry.rs            // Point + basic geometry types
├── panic_cfg.rs           // #[panic_handler] + eh_personality
├── print.rs               // print! / println! macros
├── types.rs               // shared type aliases (KernelError/KernelResult)
├── processors/
│   └── aarch64/           // arch-specific: boot.S + Pi entry, UEFI entry, cpu helpers
└── motherboards/
    ├── display.rs          // FrameConfig + DisplayBuffer — shared drawing, font; board-agnostic
    ├── console.rs          // GenericWriter — framebuffer-backed println! console, shared
    ├── raspberrypi/        // Pi-specific: mailbox, HDMI mode setup, kernel.ld linker script
    └── generic_uefi/       // UEFI-specific: GOP framebuffer discovery

kernel.rs talks to motherboards::display; each board module's only job is producing a FrameConfig — all the pixel-pushing, font rendering, and console logic lives once in motherboards/display.rs and motherboards/console.rs, shared across every target. Same shape for the CPU: processors::aarch64 for arch code, split by feature between the hand-written Pi boot path and the UEFI entry point.

About

WIP: A rust based clustered operating system

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages