ARM64 microkernel · Rust · UEFI

Ouroboros

A microkernel-style operating system for aarch64, written from scratch in Rust, still in its earliest stages. Booted entirely through UEFI — no bootloader stage, no emulator-only shortcuts — developed against QEMU day to day and proven, repeatedly, against real Parallels-on-Apple-Silicon hardware.

aarch64Architecture
UEFIBoot path
5Workspace crates
6Postmortems

What this is

Ouroboros draws its design ideas from Linux, Minix, and Plan 9: a microkernel architecture, POSIX-ish system calls, preemptive multitasking, and a filesystem story that started with "research needed" and has since grown into a real, hand-rolled FAT32 read/write stack. It's built and tested primarily against Parallels Desktop on Apple Silicon rather than an emulator alone — a deliberate choice, since Parallels boots ARM VMs exclusively through UEFI firmware with no direct-kernel-boot shortcut, and real hardware has repeatedly disagreed with what "worked" on the emulator.

Every driver in this kernel — the console, the disk controller, the filesystem, the USB host controller — is hand-rolled directly against the relevant specification, with no external hardware crate. That's not a purity rule; it's mostly a consequence of running with no heap after boot services exit, and it's produced a genuinely large amount of real, documented, hard-hat debugging along the way — see the postmortems below.

Where things stand

The short version: there is a real, disk-loaded userland shell, with a working command surface over a real filesystem, running on real Parallels hardware — with a live display, a real, physical USB keyboard typing into it, real preemptive multitasking, and, as of the latest milestones, a real disk (a passed-through USB stick, driven over the kernel's own xHCI driver — the only storage path Parallels actually exposes), programs loaded and spawned from it at runtime, job control, and real message-passing IPC between tasks.

  1. Boot & bring-up

    UEFI entry, exception vectors, an MMU running on the kernel's own identity-mapped tables, a GICv2 + generic-timer preemption tick, and a real EL0/syscall boundary — all confirmed working, not just compiling.

  2. Preemptive task switching

    A real (if still coarse — two hardcoded tasks, strict round-robin) scheduler, alternating two EL0 tasks off the timer tick.

  3. An interactive shell

    First UART input, then a disk-loaded, config-selected userland shell — a genuinely separate program, not kernel code, with real tokenized command dispatch.

  4. Runtime storage: virtio-blk + hand-rolled FAT32

    Synchronous polling block I/O and a from-scratch FAT32 reader/writer, growing over several milestones into a full command surface: ls, cat, cd, pwd, mkdir, rmdir, touch, rm, write, cp, mv.

  5. The console-discovery saga

    Devicetree, ACPI/SPCR, and PCI-serial console discovery all confirmed dead ends on real Parallels hardware; a transmit-only virtio-console driver confirmed working on QEMU but confirmed not what Parallels actually exposes; a GOP framebuffer console — five real-hardware round trips, four real bugs — finally reaching a live, rendering shell prompt on real hardware.

  6. USB HID keyboard input

    A from-scratch xHCI driver, seven independently-confirmed real-hardware bugs (five found in the initial push; a sixth — dropped keystrokes under real preemption — and a seventh — an iteration-bounded timeout that should have been time-bounded, found by a user just booting the thing normally — both found and fixed weeks later), ending in a real physical keyboard typing into the shell on real Parallels hardware — full round trip, letters, backspace, Enter.

  7. Real interrupt-controller discovery, and full preemption on Parallels

    Real ACPI MADT parsing replaced the old QEMU-devicetree-derived GIC address; a new GICv3 driver reaches real hardware for the first time. A second real-hardware-only bug (the task switch hanging via wfe, likely trapped by the host hypervisor) was root-caused and fixed the same session. Preemptive, two-task multitasking now works end to end on real Parallels hardware.

  8. A real relocating loader, and a real task lifecycle

    Userland programs are genuine position-independent ELF now (killing the old no-core::fmt/no-literal-comparison restrictions — and surfacing a latent syscall-path register-clobber bug along the way); tasks block instead of busy-polling, and exec, exit, wait, kill, fg, and a Ctrl+C escape hatch make spawned programs a real, controllable part of the system — including nested interactive shell sessions.

  9. A real disk on real Parallels hardware: USB mass storage

    A dedicated diagnostic first proved Parallels exposes no storage controller of any kind on the guest PCI bus — so the kernel's own xHCI driver grew multi-device support and bulk endpoints, and a from-scratch Bulk-Only Transport + SCSI driver now mounts a passed-through USB 3.x stick: real FAT32, real files, programs exec'd off it at runtime.

  10. Real IPC — driver isolation, part 1

    Fixed-size messages copied through the kernel into bounded per-task mailboxes, a blocking receive on the proven wait machinery, and a real long-lived userland message server — demonstrated end to end on real hardware, off the real stick.

  11. What's next

    Driver isolation, part 2: moving the FAT32 filesystem out of the kernel into a userland server — the textbook microkernel move, speaking the new IPC to clients and raw block syscalls to the kernel.

Architecture at a glance

Before the debugging stories or the build-it-yourself tutorial, the shortest way in: a single visual page laying out the layers, the EL0/EL1 privilege boundary everything crosses, and who is responsible for what — including the one design decision that defines this kernel, a filesystem that runs as an unprivileged, restartable userland process.

Learn: build one yourself

The whole path this project walked, distilled into a staged tutorial — the inverse of the postmortems below. Where they document the debugging that found the working design, the tutorial presents only the design itself: nine ordered stages, each independently bootable, each ending with a concrete way to prove it works, with code samples drawn from this kernel's confirmed-working source.

The postmortems

Every real bug found along the way — the ones that mattered, not the routine typos — is written up in detail: symptom, root cause, how it was actually confirmed, and the generalizable lesson. Four bug-hunt chapters in chronological order, plus five design retrospectives from the microkernel-isolation, network, and userland arcs — each usable on its own.

Part 1 · Boot bring-up

From "it doesn't boot" to a real console

Exception vectors, the MMU switch, GIC/timer bring-up, the EL0/syscall boundary, and a console-discovery saga that killed three mechanisms and one whole driver before finding the one that worked on real hardware.

Part 2 · Shell & filesystem

From a bare prompt to a real, disk-backed OS

A relocation bug found twice before its real shape was understood, a hang with zero diagnostic output, a disk that lied about its own filesystem format, and the write-support arc built on top.

Part 3 · xHCI keyboard

Getting a USB keyboard working on Parallels

Seven real, confirmed hardware bugs bringing keyboard input to the kernel — a PCI Command register bit, a real UEFI firmware panic, a page-table assumption, a silently-dropped USB request class, a driver reading the wrong device, dropped keystrokes under real preemption, and a timeout that was iteration-bounded instead of time-bounded — the last two found weeks later, one of them by a user, not a script.

Part 4 · USB storage

Giving Parallels a disk

Proving the hypervisor exposes no storage controller at all, USB passthrough that routes devices by speed and attaches them after your boot scan already ran, a mid-transfer keystroke that would have permanently killed the keyboard, SCSI Unit Attention found by a hot-plug test, and two error codes whose collapse silently turned append into overwrite.

Retrospective · a different era

Enforce the isolation, rebuild the data flow

Not a bug hunt but a design-led day: MMU-enforced per-task isolation and fault containment, then rebuilding every cheap data path enforcement broke — grant/safecopy IPC, a FAT32 offset-write, and the one works-on-QEMU-fails-on-silicon reversal.

Retrospective · driver isolation

Moving the console into userland

The second component out of the kernel: a driver the kernel itself depends on (a split, not a move), the gated-primitives-vs-mapped-memory decision, a scheduler lie that routing echo through IPC exposed, a missed client that stranded on real hardware, and verifying pixels by screendump.

Retrospective · capability & hardening

Scope it down before you build it

Five hardening milestones in a day — a health ping, the capability model, program-to-program pipes, a stack guard page, a userland heap — where three shrank when asked whether the clean design needed the hard part, the guard page caught a real silent overflow on its first test, and a one-build gate proved the heap couldn't be alloc-backed on stable.

Retrospective · network stack

A network stack in a day

From a virtio-net driver to a concurrent HTTP server — the no-IOMMU driver/protocol split, the async-receive primitive a server needs, testing loss recovery on a wire that can't lose a packet, two bugs visible only in a packet trace, and the guard page catching the stack overflow twice more.

Retrospective · userland maturation

/bin, pipelines, and where a command belongs

Builtins became a real /bin of standalone programs with multi-stage pipelines: the ps/kill/wait revert that showed job control belongs in the shell, a capability bitfield collision caught before it shipped, delegation needing less than feared, a pipeline heuristic replaced wholesale, and building a bootable GPT disk just to test one.

Retrospective · the filesystems arc

exFAT, ext2, and what real hardware revealed

Two filesystems added to the server — each read-only then read-write, all behind one unchanged protocol — with the lessons: an abstraction is only tested by a genuinely different implementation (ext2's inodes), validate against a foreign checker not your own reader, a freed inode's i_dtime that e2fsck misread as an orphan pointer, and a real-hardware pass that found a USB keyboard/storage bug the emulator structurally cannot show.

Every document in this project — the guides, the architecture and reference material, all nine postmortems, and the research notes — is collected in the documentation library.

Following along

The full, unfiltered history — every milestone, in order, with the same level of detail as the postmortems above — lives in the repository itself: CHANGELOG.md for what's done, ROADMAP.md for what's next, and CLAUDE.md for the single, continuously-updated document tracking the whole project's architecture and reasoning in real time.