Ferffy, the EmiBSD mascot: a centolla, the child of Ferris (Rust's crab) and Puffy (OpenBSD's pufferfish).
The OpenBSD kernel, re-implemented in Rust, one file at a time.
Not for production. EmiBSD is an experiment. It runs only in QEMU, and its crypto, network and storage code is unaudited. Do not use it to protect real data or real networks.
- A file-by-file port of the OpenBSD kernel, pinned to commit
3ce1f3f79392of EmiBSD/OpenBSD-src, the organisation's fork of openbsd/src (reference/PINNED.md). - Not a new kernel design, not a Rust-for-Linux style hybrid, not a wrapper around C.
- The C sources are the specification. Names, structure and semantics stay OpenBSD's. Every deviation is written down, in the file and in docs/ARCHITECTURE.md.
- A standalone
#![no_std]kernel for amd64 and arm64, booted by Limine, run in QEMU.
flowchart LR
obsd["OpenBSD-src (mirror)"] -- "the C under sys/, read-only:<br/>the specification" --> lz["LandingZone"]
lz -- "the tree at lz-origin,<br/>then every commit, synced" --> k["Kernel"]
k --> os["OS"]
This repository is the first stage: the faithful port, checkable file by file against the C.
What grows from it, the redesign in Kernel and the
assembled system in OS, reads this tree and never writes to
it; nothing here adapts to them. The whole picture is in
Governance (docs/architecture.md).
Status: M16 (QEMU drivers) met with its last part, M16g (install images: install80.img on
both archs and amd64's cd80.iso, as distrib/ makes them; an install from the stick with no
HTTP server); M17 (real hardware and virtualisation) next.
| Milestone | Scope | State |
|---|---|---|
| M0 | Toolchain and boot | met |
| M1 | libkern and sys/sys |
met |
| M2 | Console, printf, panic, ddb-lite | met |
| M3 | Physical memory and uvm basics | met |
| M4 | Traps and interrupts | met |
| M5 | Timers, scheduler, proc | met |
| M6 | System calls and a minimal init | met |
| M7a, M7b | uvm (demand paging); mbufs, virtio-net, IPv4 ping | met |
| M8, M8b | OpenBSD's userland on an ffs ramdisk; multi-user boot and login | met |
| M9a..M9d | Sockets, WireGuard, IPsec, pf | met |
| M9+ | Network completion: TCP, bpf, divert, IPComp, HTTPS, tcpdump, INET6 | met |
| M10a | Persistent disk (vioblk, SCSI midlayer) | met |
| M10b | UFS options (quotas, dirhash, mfs) | met |
| M10c | Memory and removable file systems (tmpfs, msdosfs, cd9660, udf, vnd) | met |
| M10f | softraid (RAID 0, 1, 5, 6, concat, RAID 1C, CRYPTO; bio(4), bioctl) | met |
| M10e | NFS client and server (portmap, mountd, nfsd, mount_nfs, showmount) | met |
| M10d | ext2fs, ntfs (amd64), fuse | met |
| M11a | MP bring-up: APs started through Limine, the kernel lock, per-CPU run queues, SMR, percpu and pool caches, IPIs and TLB shootdowns | met |
| M11b | MP timekeeping: the TSC synchronisation test per AP, clock interrupts on every CPU | met |
| M11c | ddb on MP: the command loop, the other CPUs stopped by IPI, machine cpuinfo, machine ddbcpu |
met |
| M11d | Network parallelism: one softnet task queue per CPU (up to 8), kern_intrmap.c, SMR for the interface index |
met |
| M11e | The MP audit: every MULTIPROCESSOR site, MPSAFE flags and SY_NOLOCK honoured, unlocked page faults; every smoke runs on four CPUs |
met |
| M12 | Devices in QEMU: audio(4) with azalia and auich, USB with xhci, uhub, umass and ukbd; arm64's PCI bus | met |
| M12+ | Measurement and verification: unsafe-report, JOURNAL, diff-openbsd against a real OpenBSD | met |
| M13 | Storage, firmware and console: NVMe and AHCI roots, ACPI on amd64, PSCI, the RTC, em/re/vmx, the frame buffer with wsdisplay and the USB keyboard | met |
| M14 | Installable: our efiboot on both archs, bsd.rd, install.sub with the base and comp sets (clang, lld), the installed disk booting to login: with cc working; arm64 ACPI |
met |
| M15 | Code and test layout: LICENSES, CODE and TESTS zones in every .rs under sys/ and tools/, the 324 tests.rs inline, validated by ports check |
met |
| M16f | arm64 platform: agintc(4) (GICv3, LPIs, the ITS), smmu(4) (SMMUv2 and v3), gpio(4), plgpio(4) and gpiokeys(4); every arm64 smoke on gic-version=3 |
met |
| M16e | Platform drivers: UKC (boot -c), ppb(4), acpidmar(4) (VT-d and AMD-Vi), iic(4) with ichiic(4) and piixpm(4), ipmi(4) with the watchdog and SMBIOS, tpm(4) on swtpm, acpicpu(4) |
met |
| M16b | USB drivers: ehci(4), uhci(4), ohci(4), ums(4) and uwacom(4) over hidms, uhid(4), ugen(4) with usbdevs(8), cdce(4), ucom(4) with uftdi(4), uaudio(4); ehci, and a write through ohci, behave as on OpenBSD 8.0 in QEMU | met |
| M16c | Network drivers: pcn(4), ne(4) (ne2000, dp8390, rtl80x9), fxp(4) with loadfirmware(9) and its microcode, dc(4); the inphy, lxtphy and dcphy PHYs; em(4) on igb and e1000e. tulip and igb pass no traffic, as on OpenBSD 8.0 in QEMU | met |
| M16d | Console, virtio and legacy devices: pckbc(4), pckbd(4) and pms(4); viogpu(4), viomb(4), viornd(4) with rnd(4)'s entropy pool and /dev/random, viocon(4) (cargo feature); lpt(4); pcppi(4) and spkr(4); eap(4) with midi(4) | met |
| M16a | Storage drivers: pciide(4) with every chip of its table, wdc and wd(4) (wd0 on PIIX3), mpi(4) on mptsas1068, sdhc(4) with the sdmmc(4) stack on sdhci-pci, fd(4) and fdc(4) with isadma(4); vmwpvs(4) behaves as on OpenBSD 8.0. mfi, mfii, pcscp and ufshci moved to M17: OpenBSD 8.0 itself fails on QEMU's devices |
met |
| M16g | Install images: install80.img (both archs) and cd80.iso (amd64) laid out as distrib/ makes them, the ISO 9660 image written by xtask; OpenBSD's installer, answered over the console, installs from the stick with no HTTP server; the CD boots to the installer |
met |
| M17 | Real hardware and virtualisation (vmm, vmd; optional) | next |
Stage 2 of the diagnostic tools (ps, fstat, vmstat, df) is also met. Exit criteria and dates are in docs/ROADMAP.md; the current state is in docs/STATUS.md.
Every line below is a recipe of just smoke, run on both architectures, on the
multiprocessor kernel with two processors (-smp 2; four for smoke-mp, smoke-vmx,
smoke-net-mp and smoke-softraid, and for every recipe in just ci-full); smoke-up
boots the uniprocessor kernel once per arch.
On one VM, with OpenBSD's own binaries from the ramdisk:
-
Boot, autoconf, kernel self-tests, the ddb(4) prompt at a
-dstop, a deliberate panic with a stack trace (smoke). -
init(8) and ksh(1) in single-user mode (
smoke-shell). -
/etc/rc, getty(8), login(1) as root, the clock from the RTC (smoke-login). -
ifconfig(8), ping(8), route(8) over the routing socket (
smoke-net,smoke-route). -
ps(1), fstat(1), vmstat(8), df(1), mount(8) over sysctl(2) (
smoke-diag). -
pfctl(8) loading a ruleset that blocks a ping (
smoke-pf); ipsecctl(8) over PF_KEY (smoke-ipsec). -
ftp(1) and nc(1) over TLS with LibreSSL, against servers on the host (
smoke-https). -
A persistent disk:
sd0on vioblk(4), fdisk(8), disklabel(8), newfs(8); after a second boot fsck(8) finds it clean and the file reads back (smoke-disk). -
Disk quotas: quotacheck(8), quotaon(8), edquota(8); a write as a user over its hard limit fails with EDQUOT and repquota(8) shows it; a hashed 5,000-entry directory; mount_mfs(8) (
smoke-ufsopts). -
tmpfs(5) on /tmp; FAT, ISO 9660 and UDF images attached with vnconfig(8) and mounted with mount_msdos(8), mount_cd9660(8), mount_udf(8); newfs_msdos(8) on a vnd(4) over a tmpfs file and fsck_msdos(8) passing it (
smoke-fs). -
softraid(4) over four vioblk disks: RAID 0, 1, 5, concat, RAID 1C and CRYPTO made with bioctl(8), RAID 6 with our own
sr6create(bioctl has no-c 6); each gets an ffs and a file; after a reboot the volumes are assembled at boot,bioctl -punlocks the encrypted ones and every file reads back; with a disk missing, RAID 1 and RAID 6 come up degraded and still read (smoke-softraid). -
ext2fs: newfs_ext2fs(8) on a persistent disk, files written with mount_ext2fs(8); after a reboot fsck_ext2fs(8) finds it clean and the files read back, and e2fsprogs'
e2fsck -fnon the Mac passes the same disk image (smoke-ext2fs). -
FUSE: our own file system over OpenBSD's libfuse and /dev/fuse0 mounts, serves its files, refuses a write and unmounts (
smoke-fuse). -
NTFS, amd64 only (as in GENERIC): an image made by our own generator, checked first by macOS's NTFS driver, attached with vnconfig(8) and mounted with mount_ntfs(8); a resident and a non-resident file read back (
smoke-ntfs). -
Four processors (
-smp 4) with themultiprocessorkernel: the application processors start, take IPIs and TLB shootdowns, two kernel threads ping-pong across CPUs, a thread per CPU stresses the pools, the page allocator and page faults through uvm, and the init self-test passes; amd64 tests each application processor's TSC against the boot CPU's, and on both archs every CPU runs its own clock interrupts with an uptime that never goes back (smoke-mp). -
ddb(4) on four processors:
sysctl ddb.trigger=1from the shell stops every other CPU by IPI,machine ddbcpu 1moves the debugger to CPU 1,machine cpuinfoshows the other three stopped, andcontinueresumes them all (smoke-ddbmp). -
Audio: aucat(1) plays a tone through
/dev/audio0on Intel HD Audio (azalia(4)) on both archs and on AC97 (auich(4)) on amd64; audioctl(8) and mixerctl(8) show and set the device, and QEMU's WAV capture must hold the tone (smoke-audio). -
USB: xhci(4) and uhub(4) enumerate QEMU's stick and keyboard; umass(4) makes the stick an sd(4) disk whose FAT partition mount_msdos(8) mounts, reads, writes and compares after a remount; uhidev(4) and ukbd(4) attach the keyboard (
smoke-usb). -
Root on an NVMe namespace and on an AHCI disk, both archs (
virt's AHCI controller on its PCIe bus), mounted by the label's DUID without a ramdisk (smoke-nvme,smoke-ahci); cd(4) on vioscsi(4) mounting an ISO with mount_cd9660(8) (smoke-cd); siop(4) on QEMU's LSI 53C895A, amd64 (smoke-siop). -
More disk controllers, each partitioned, newfs'd, written, mounted read-only and compared: wd(4) through pciide(4) on the PIIX3 IDE of QEMU's
pc, amd64 (smoke-wd); mpi(4) on mptsas1068 (smoke-mpi) and an SD card through sdhc(4) and sdmmc(4) on sdhci-pci (smoke-sdmmc), both archs; fd(4) reads a FAT floppy with mount_msdos(8), amd64 (smoke-fd); vmwpvs(4) on pvscsi stops atvmwpvs0: get configuration failed, as OpenBSD 8.0 does (smoke-vmwpvs). -
ACPI on amd64: the AML interpreter, CPUs and I/O APICs from the MADT, PCI routing, MSI and MSI-X (vio(4)'s multiqueue path through intrmap(9),
smoke-mp), acpitimer and acpihpet (smoke-clock). -
halt -ppowers off andrebootrestarts QEMU, through ACPI on amd64 and PSCI on arm64 (smoke-power); the date from the RTC within a minute of the host (smoke-rtc); com(4) on QEMU's pci-serial through puc(4), amd64 (smoke-puc). -
em(4) on QEMU's e1000e (both archs) and e1000 (amd64), re(4) on rtl8139 and vmx(4) on vmxnet3 with four MSI-X queues ping QEMU's gateway (
smoke-em,smoke-re,smoke-vmx); so do pcn(4) on pcnet, ne(4) on ne2k_pci and fxp(4) with inphy(4) on i82559er, amd64 (smoke-pcn,smoke-ne,smoke-fxp). dc(4) with lxtphy(4) on tulip (amd64) and em(4) on igb (both archs) attach, link and, as on OpenBSD 8.0, pass no traffic (smoke-dc,smoke-igb). -
The frame buffer (efifb(4), simplefb) with wsdisplay(4) and the vt100 emulation: text written to
/dev/ttyC0is read back from a QEMU screendump (smoke-fb,smoke-wscons); keys typed on QEMU's USB keyboard reach a reader of/dev/ttyC0and/dev/wskbd0through wskbd(4) and wsmux(4) (smoke-kbd). vga(4) is in and, as on OpenBSD under OVMF, attaches nowhere (smoke-vga). -
Our efiboot, OpenBSD's boot(8) for UEFI (BOOTX64.EFI, BOOTAA64.EFI), boots the MP kernel from an OpenBSD disk to
login:beside Limine (smoke-efiboot); on arm64virt,acpi=onit builds the device tree from the ACPI tables and the kernel attaches acpi0, acpipci(4) and pluart(4) at acpi, its root on a PCI disk (smoke-acpi). -
arm64 on QEMU's GICv3 (
virt,gic-version=3): agintc(4) with its redistributors, IPIs on every CPU and MSI-X through the ITS for an NVMe root and, on ACPI, virtio-pci (smoke-gicv3);EMIBSD_GIC=3 just smokeputs every arm64 boot on it. -
smmu(4) on
virt,iommu=smmuv3: the root on an NVMe namespace whose DMA the SMMUv3 translates (smoke-smmu). -
QEMU's power key on the PL061: plgpio(4) and gpiokeys(4) attach, and
system_powerdownis ignored, as on OpenBSD 8.0 (smoke-powerbtn). -
boot -c: UKC enables and disables devices before autoconfiguration (smoke-ukc), as the smokes of the devices GENERIC disables use it. -
ppb(4): a virtio disk behind a
pcie-root-portand one behind apci-bridgeare labelled, formatted, mounted and read back, on both archs (smoke-ppb). -
acpidmar(4) on q35's
intel-iommuandamd-iommu: the NVMe root mounts with every PCI device's DMA remapped (smoke-dmar). -
iic(4): piixpm(4) on
-machine pcscans its SMBus; on q35 ichiic(4) finds the SMBus disabled by OVMF and stops, as OpenBSD 8.0 does (smoke-iic). -
ipmi(4) on QEMU's simulated BMC, and bios0 reading SMBIOS (
hw.vendor=QEMU): the same lines as OpenBSD 8.0 on the same machine, the watchdog set through the BMC (smoke-ipmi). -
tpm(4) on QEMU's tpm-tis and tpm-crb backed by swtpm: TPM2_SelfTest answers
rc 0x0(smoke-tpm); acpicpu(4) idles every CPU (smoke-clock). -
USB host controllers: uhci(4) on
piix3-usb-uhcimounts, reads, writes and compares the stick (smoke-uhci); ohci(4) onpci-ohcimounts and reads it, and its first write halts QEMU's controller as on OpenBSD 8.0 (smoke-ohci); ehci(4) onusb-ehciattaches and, as on OpenBSD 8.0, gets no interrupt on amd64 and faults on a 16-bit register write on arm64 (smoke-ehci). -
USB devices: ums(4) on QEMU's usb-mouse, usb-tablet and Wacom tablet, events read from
/dev/wsmouse*(smoke-mouse); usbdevs(8) lists usb-ccid under ugen(4), which carries a CCID command (smoke-ugen); cdce(4) on usb-net pings the gateway (smoke-cdce); ucom(4) over uftdi(4) on usb-serial carries text both ways (smoke-ucom); uaudio(4) plays the tone on usb-audio (smoke-uaudio). -
PS/2, amd64: keys sent with QEMU's
sendkeyreach the shell through pckbc(4) and pckbd(4), and the PS/2 mouse gives wsmouse(4) events through pms(4) (smoke-pckbc); opening the keyboard fails now and then, as on OpenBSD 8.0 (a race in the C), so the smoke retries. -
virtio: QEMU's balloon command inflates and deflates viomb(4) (its sensors and
vmstat -sfollow), viornd(4) feeds rnd(4)'s pool and/dev/randomreads (smoke-virtio, both archs); viogpu(4) on arm64 carries the console, read back from a QEMU screendump (smoke-viogpu). -
Legacy devices, amd64: bytes written to
/dev/lpt0reach QEMU's parallel port (smoke-lpt); the console bell and a spkr(4) tune reach QEMU's PC speaker through pcppi(4) (smoke-bell); eap(4) plays the tone on QEMU's ES1370, with midi0 on its UART (smoke-eap). -
The boot-only CD, amd64:
cd80.isoas q35's CD-ROM boots our efiboot from its El Torito image, thenbsd.rdfrom the CD, to the installer's first question; mount_cd9660(8) lists the CD by its Rock Ridge names (smoke-cd80).
Outside just smoke, because they take minutes under TCG (the user requires them at every
milestone close): just smoke-install boots bsd.rd through our efiboot and lets OpenBSD's
install.sub, under autoinstall(8), install the signed base80 and comp80 sets onto a
fresh disk (amd64, arm64, and arm64 on ACPI); just smoke-install-boot-<arch> boots that
disk on a fresh VM through the efiboot installboot(8) put on it, OpenBSD's /etc/rc runs,
and cc hello.c && ./a.out prints its line with OpenBSD's clang (just comp builds it).
just smoke-install80-<arch> (M16g) does the same from install80.img attached as a USB
stick, OpenBSD's installer answered over the serial console as a person would and the sets
read from the stick: no response file, no network, no HTTP server.
Between two VMs on a private link (cargo xtask smoke2):
- ping across the link (
smoke-link); a wg(4) tunnel, with a pf rule onwg0(smoke-wg). - An ESP tunnel (
smoke-esp); IPComp inside ESP (smoke-ipcomp). - pfsync(4) and pflow(4) (
smoke-pfsync); pfdivert-to(smoke-divert). - TCP with nc(1): directly, through
wg0and through ESP (smoke-tcp). - tcpdump(8) on
vio1and onpflog0(smoke-tcpdump). - IPv6: ping(8) as ping6 to the other VM's global and link-local addresses, ::1 on lo0 (
smoke-inet6). - NFS over UDP and TCP with OpenBSD's portmap(8), mountd(8), nfsd(8), mount_nfs(8) and
showmount(8): one VM exports a directory, the other lists and mounts it, reads a file and
writes files the first one reads (
smoke-nfs). - Both VMs on the
multiprocessorkernel with-smp 4: four softnet threads (eight with-smp 8), a ping across the link and throughwg0, TCP with nc(1) directly and throughwg0, loopback interfaces created and destroyed (smoke-net-mp). - tcpbench(1) both ways at once, four connections each, for 15 seconds (
smoke-tcpbench).
An excerpt of the serial console, from smoke-login on amd64 (trimmed):
bsd: booted on amd64 by Limine 12.9.1
EmiBSD 8.0 (GENERIC) #221: Sun Oct 4 07:56:52 UTC 2026
root on rd0a swap on rd0b dump on rd0b
rc: multi-user
EmiBSD/amd64 (Amnesiac) (tty00)
login: root
Password:
Welcome to EmiBSD 8.0: OpenBSD's init(8) and ksh(1) on an ffs ramdisk root.
# uname -a
EmiBSD 8.0 GENERIC#221 amd64
And from smoke-install-boot-amd64, the system install.sub installed, booted by its own
efiboot (trimmed; the smoke types the source with ksh's print -r, then cc hello.c && ./a.out):
>> EmiBSD/amd64 BOOTX64 3.71
bsd: booted on amd64 by boot(8) efiboot
EmiBSD 8.0 (GENERIC) #445: Thu Oct 8 03:07:29 UTC 2026
root on sd1a (f71160858c5a8c8b.a) swap on sd1b dump on sd1b
Automatic boot in progress: starting file system checks.
EmiBSD/amd64 (emibsd.emibsd.test) (tty00)
login: root
emibsd# uname -a; mount; echo up-$((6*7))
EmiBSD emibsd.emibsd.test 8.0 GENERIC#445 amd64
/dev/sd1a on / type ffs (local, wxallowed)
up-42
hello from cc 42
And from smoke-net, another boot (trimmed):
# ifconfig vio0
vio0: flags=4008843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
lladdr 52:54:00:12:34:56
inet 10.0.2.15 netmask 0xffffff00 broadcast 10.0.2.255
# ping -c 1 10.0.2.2
64 bytes from 10.0.2.2: icmp_seq=0 ttl=255 time=5.278 ms
And from smoke-fuse on amd64 (trimmed):
# mkdir -p /fuse && fusehello /fuse && mount && echo fuse-up-$((40+2))
/dev/rd0a on / type ffs (local)
fusefs on /fuse type fuse
fuse-up-42
# cat /fuse/hello.txt /fuse/sub/deep.txt
m10d-fuse-42
m10d-fuse-sub-42
And from smoke-mp, the multiprocessor kernel with -smp 4 on amd64 (trimmed; the last
three lines come from its selftest=mpstress boot):
bsd: 4 processors, boot processor hwid 0x0
cpu1 at mainbus0: apid 1 (application processor)
tsc: cpu0/cpu1: sync test passed
cpu3 at mainbus0: apid 3 (application processor)
tsc: cpu0/cpu3: sync test passed
x86_ipi_selftest: X86_IPI_NOP taken by 3 cpus, tlb shootdowns acknowledged
selftest: 4 cpus running
selftest: cpu1 clockintr: 20 uptime checks, 0 behind
selftest: clockintr on 4 cpus ok, uptime monotonic on each
selftest: mpstress pool ok (4 cpus, 432000 gets, 431592 through the per-cpu caches, 71904 items exchanged between threads, 0 PR_NOWAIT refused, 30 pages reclaimed, 485 ms)
selftest: mpstress pmemrange ok (4 cpus, 14400 page lists, 88389 pages, 0 UVM_PLA_NOWAIT refused, 95997 pages free before and after; 96108 free at the start, 96101 at the end)
selftest: mpstress uvm ok (4 cpus, 9988 pageable kernel pages and 16000 anonymous pages faulted in, 4000 slices unmapped and mapped again, per-cpu page caches 26017 hits 3718 misses, 378 ms)
And from smoke-tcpbench, VM a of two MP VMs on amd64 (trimmed):
# until tcpbench -n 4 -t 15 192.168.77.2; do sleep 1; done; echo bench-a-$((5+5))
Conn: 4 Mbps: 367.652 Peak Mbps: 367.652 Avg Mbps: 91.913
Conn: 4 Mbps: 241.680 Peak Mbps: 367.652 Avg Mbps: 60.420
--- 192.168.77.2 tcpbench statistics ---
445265160 bytes sent over 15.352 seconds
bandwidth min/avg/max/std-dev = 127.436/232.681/367.652/65.268 Mbps
bench-a-10
And from smoke-ddbmp, ddb on the same kernel and four processors, amd64 (trimmed):
# sysctl ddb.trigger=1
Stopped at 0xffffffff802954aa
ddb{0}> machine ddbcpu 2
Stopped at 0xffffffff80195655
ddb{2}> machine ddbcpu 1
Stopped at 0xffffffff80195655
ddb{1}> machine cpuinfo
0: stopped
* 1: ddb
2: stopped
3: stopped
ddb{1}> continue
ddb.trigger: 0 -> 1
And from smoke-net-mp, VM A on amd64 with -smp 4 (trimmed):
# ifconfig lo3 destroy && echo if-destroyed-$((3+3))
if-destroyed-6
# ps -axk -o pid,cpuid,comm
PID CPUID COMMAND
97671 1 softnet0
13175 1 softnet1
72457 1 softnet2
92012 1 softnet3
81307 2 smr
38591 3 wg_crypt
softnets-4
And from smoke-usb and smoke-audio on amd64 (trimmed):
xhci0 at pci0 dev 4 function 0 vendor 0x1b36 product 0x000d rev 0x01: irq 10, xHCI 1.0
usb0 at xhci0: USB revision 3.0
umass0 at uhub0 port 1 configuration 1 interface 0 "QEMU QEMU USB HARDDRIVE" rev 3.00/0.00 addr 2
sd1 at scsibus1 targ 1 lun 0: <QEMU, QEMU HARDDISK, 2.5+> serial.46f4000100:00:04.0-1
ukbd0 at uhidev0
emibsd m12: hello from a usb stick
4071711340 1048576 /mnt/BIG.BIN
azalia0 at pci0 dev 4 function 0 vendor 0x8086 product 0x2668 rev 0x01: irq 10
audio0 at azalia0
outputs.master=126,126
# aucat -i /root/tone.wav && echo tone-$((40+2))
tone-42
The real console also prints unported: <name> lines. Each one is a known gap, reported once.
You need:
- Homebrew
rustup,qemu,limineandjust. The pinned toolchain installs itself. - For the userland: Apple clang and an LLD 17 or newer with OpenBSD support.
- The sparse clone of the OpenBSD sources in
reference/openbsd-src(reference/README.md).
Every step is in docs/SETUP.md. Then:
just build # kernel and the Rust init, amd64 and arm64
just userland # OpenBSD's libc and programs, and the ffs ramdisk (slow)
just run-amd64 # boot in QEMU, serial console on stdio
just run-arm64
just smoke # every boot test, headless, both architecturesThe test image's root password is in docs/SETUP.md ("The test image's login").
just with no arguments lists every recipe.
sys/mirrors OpenBSD'ssys/path for path:kern/tty.cbecomessys/kern/tty.rs. Types live where the C header is; functions live where the C file is.- Generic code reaches the hardware only through
crate::machine, a set of traits that stands in for<machine/*.h>. Each arch implements them; the compiler checks it. - One kernel crate,
bsd, because kern, uvm and arch call each other. Only true leaves (libkern,libz) are separate crates. - The userland is OpenBSD's C, cross-compiled unmodified with clang, on an ffs ramdisk made by OpenBSD's own makefs(8). So the kernel must speak OpenBSD's system call ABI exactly.
#![no_std], stable Rust only. Nostatic mut. Everyunsafeblock has a// SAFETY:comment.- Gaps are explicit:
unported!()yieldsENOSYSand says so on the console. Notodo!().
Against a real OpenBSD (just diff-openbsd, beside just ci): the same 102 steps (291 system
call probes, file-system operations through OpenBSD's own utilities) on EmiBSD and on the
OpenBSD 8.0 snapshot nearest the pin, on both archs: 99 equal, 3 expected differences
(fifofs, core dumps, branding).
Details, boot flow and deviations: docs/ARCHITECTURE.md.
From cargo xtask ports status at the commit of this README:
| todo | wip | ported | skipped | total |
|---|---|---|---|---|
| 36 | 139 | 1169 | 37 | 1381 |
The tracker lists the files claimed by the milestones so far, not all of OpenBSD's sys/.
wip files are in use with visible stubs. Per subsystem: docs/PORTING.md.
Source of truth: ports.toml.
Four tiers:
- Host unit tests (
just test): pure logic runs on macOS throughsys/arch/host. - Reference-backed tests (
just test-ref): constants are cross-checked against the C headers. - QEMU smoke tests (
just smoke): boot both architectures headless and assert serial lines and exit codes. A full run boots 67 single VMs and 24 pairs of VMs, all on themultiprocessorkernel with-smp 2(-smp 4for thesmp4group, and for every recipe injust ci-full) exceptsmoke-up's uniprocessor boot per arch. The recipes run four at a time, each in its owntarget/smoke/<recipe>/with its own log;JOBS=N just smokechanges N. - Differential tests (
just diff-openbsd, besidejust ci): the same scenarios on EmiBSD and on a real OpenBSD VM (the -current snapshot nearest the pin, installed once with autoinstall(8) undertarget/openbsd/), compared step by step; every difference is fixed or listed with its reason intools/xtask/diff-openbsd/expected.toml. About two minutes for both archs once installed.
just ci runs fmt, clippy for amd64, arm64 and the host, all tests, both builds, every smoke and
the tracker checks. Green just ci is the definition of done.
Rules: .claude/rules/testing.md.
reference/openbsd-src/ OpenBSD sources, sparse clone, gitignored, read-only
sys/ the kernel (package `bsd`), mirroring OpenBSD's sys/
kern/ uvm/ dev/ net/ netinet/ crypto/ ufs/ ddb/
sys/ header types
machine/ the <machine/*.h> contract (traits)
arch/{amd64,arm64}/ per-arch code
arch/host/ std-backed test double for `cargo test`
stand/ Limine boot glue (replaces boot(8) for now)
lib/libkern/ lib/libz/
init/ the Rust init, now the kernel's self-test
tools/xtask/ images, QEMU, smoke tests, userland build, ports tracker
docs/ architecture, porting, roadmap, setup, status
ports.toml porting tracker
.claude/ rules, agent roles, agent memory, the /progress skill
| Question | Where |
|---|---|
| How do I set up the toolchain on macOS? | docs/SETUP.md |
| How do I get the OpenBSD sources? | reference/README.md |
| Why is it built this way? What deviates from OpenBSD? | docs/ARCHITECTURE.md |
| How does a C file become a Rust file? | docs/PORTING.md, tracker in ports.toml |
| How is this C idiom written in Rust? | docs/C_TO_RUST.md |
| What comes next? | docs/ROADMAP.md |
| What happens after the faithful port? | docs/PHASE2.md (draft) |
| Where are we right now? | docs/STATUS.md |
| What rules does every change follow? | CLAUDE.md, .claude/rules/ |
| Who does the porting, and how is a batch run? | docs/PORTING.md ("Who ports"), .claude/agents/ |
| How far is the port? | /progress in Claude Code; the method in docs/PORTING.md ("Measuring progress") |
Porting one file:
- Pick:
cargo xtask ports nextliststodofiles whose dependencies are done. - Read the
.c, its headers and the man pages it cites, completely. - Write
sys/<same path>.rs: the original licence block, anUpstream:line, aDeviationslist. - Test: host tests in the same file;
just cigreen. - Record: mark the file
portedinports.toml, then commit.
One commit per file or coherent cluster, with a trailer per ported C file:
kern: port subr_prf.c (printf, panic)
Upstream: sys/kern/subr_prf.c@3ce1f3f79392
The full process is in docs/PORTING.md.
Most of the porting is done by Claude Code subagents with fixed roles (.claude/agents/:
porter, porter-mechanical, reviewer, integrator, debugger, milestone-coordinator, ...)
under one contract, .claude/rules/subagents.md. A coordinator decides the
fan-out of each batch (no script does); /progress measures the port per milestone; their lessons
persist in .claude/agent-memory/.
Feedback is welcome: Issues, for concrete problems as well as feedback and design questions. External pull requests are not accepted, for now.
Ferffy is a centolla, the king crab of Chile's southern seas, and the child of Ferris, Rust's crab, and Puffy, OpenBSD's pufferfish: the claws from one parent, the spikes from the other.
New code is under the ISC license. Ported files keep their OpenBSD copyright notice and license, whole, at the top of the file. Every license in the pinned OpenBSD tree is accepted. The ramdisk's userland keeps the licenses of its sources. See LICENSE.
- The OpenBSD project and its authors, whose code is the specification. EmiBSD is not affiliated with or endorsed by OpenBSD; their names are not used to promote it (BSD-3-Clause, clause 3).
- Limine, the boot loader.
- QEMU, where every test runs.
- The Rust project and its ecosystem: the compiler, cargo, clippy, rustfmt and rust-analyzer.
