Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

QEMU Machines

yoe ships two QEMU machines that serve as the default development and CI targets: qemu-arm64 and qemu-x86_64. Neither corresponds to physical hardware — both target QEMU’s emulated virt/q35 machines and exist to let you iterate on userspace and the kernel without booting real silicon each time.

This page covers what each machine ships, how the boot path differs between them, and what yoe qemu actually does at run time.

Machine descriptors live at:

  • modules/module-core/machines/qemu-arm64.star
  • modules/module-core/machines/qemu-x86_64.star

Both lean entirely on module-core and module-alpine — no board-specific BSP units.

Comparison at a glance

Aspectqemu-arm64qemu-x86_64
Archarm64x86_64
QEMU machinevirtq35
CPUhosthost
Firmwarenone (direct kernel boot)seabios (QEMU default)
Bootloadernone — QEMU -kernelsyslinux in the rootfs
ConsolettyAMA0 (PL011 UART)ttyS0 (16550 UART)
Root device/dev/vda1 (single part)/dev/vda2
Kernel unitlinux (generic)linux (x86_64_defconfig)
Extra packagesnonesyslinux
Default forwards2222:22, 8080:80, 8118:8118same

Both default to 1 GB RAM and display = "none"; the -nographic flag sends serial to the controlling terminal.

qemu-arm64

machine(
    name = "qemu-arm64",
    arch = "arm64",
    kernel = kernel(
        unit = "linux",
        defconfig = "defconfig",
        cmdline = "console=ttyAMA0 root=/dev/vda1 rw",
    ),
    partitions = [
        partition(label = "rootfs", type = "ext4", size = "512M", root = True),
    ],
    qemu = qemu_config(
        machine = "virt", cpu = "host", memory = "1G",
        display = "none",
        ports = ["2222:22", "8080:80", "8118:8118"],
    ),
)

There is no bootloader and no boot partition. yoe qemu invokes QEMU with -kernel <linux-unit>/destdir/boot/vmlinuz and passes the machine’s cmdline via -append. QEMU loads the image straight into emulated DRAM on the virt machine and starts the A53 cores at the kernel entry point.

This is the one place in yoe where direct-kernel boot is the correct path, not a shortcut. The virt machine has no analog in physical silicon — there is no ROM, no SPL, no need for U-Boot. (For physical aarch64 boards, see BeaglePlay for the full ROM → SPL → TF-A → U-Boot → kernel chain.)

The single ext4 partition becomes /dev/vda1 through QEMU’s virtio-blk disk. The disk is presented to the guest as a raw image file, attached with -drive file=...,format=raw,if=virtio.

qemu-x86_64

machine(
    name = "qemu-x86_64",
    arch = "x86_64",
    kernel = kernel(
        unit = "linux",
        defconfig = "x86_64_defconfig",
        cmdline = "console=ttyS0 root=/dev/vda2 rw",
    ),
    packages = ["syslinux"],
    partitions = [
        partition(label = "rootfs", type = "ext4", size = "600M", root = True),
    ],
    qemu = qemu_config(
        machine = "q35", cpu = "host", memory = "1G",
        firmware = "seabios",
        display = "none",
        ports = ["2222:22", "8080:80", "8118:8118"],
    ),
)

x86_64 goes through a real bootloader: SeaBIOS (QEMU’s built-in legacy BIOS, used by default on q35) reads the MBR off the virtio disk and jumps into syslinux, which loads the kernel from the ext4 rootfs.

This mirrors how a physical x86 board with legacy BIOS boots, so the same image will also boot on bare metal that lacks UEFI. (UEFI/OVMF support is set up in internal/device/qemu.go — pass firmware = "ovmf" instead to swap SeaBIOS for OVMF and boot via EFI.)

Why root=/dev/vda2 when there’s only one partition declared? syslinux installation inserts its own boot sector ahead of the data partition, so the visible partition index starts at 2 once the image is on disk. The rootfs is still that single ext4 — it just lives at vda2 from Linux’s view.

What runs inside the guest

Both machines pick up the generic linux unit from module-core, not a board-specific kernel. That unit builds arch/<arch>/boot/{Image,bzImage} plus the standard module set; no out-of-tree drivers, no custom defconfig fragment.

The userspace stack is whatever the project includes via its package list plus the rootfs base — busybox, OpenRC, apk-tools, and any apks pulled through module-alpine. See libc, init, and the Rootfs Base for the userspace layout.

Networking

yoe qemu wires a single virtio-net device through QEMU’s user-mode networking (SLIRP). The default forwards in the machine descriptor land SSH on host port 2222 and a couple of HTTP ports for app dev. Extra forwards can be passed on the CLI (yoe qemu --port 9000:9000); they append to the machine-declared list, with CLI entries taking precedence on collision.

How yoe qemu runs

The launcher in internal/device/qemu.go:

  1. Picks the binary by arch: qemu-system-aarch64, qemu-system-x86_64, or qemu-system-riscv64.
  2. Builds the arg list: -machine, -cpu, -m, -nographic (if display = "none"), the virtio-blk drive, the virtio-net device with port forwards, and -bios if a firmware (OVMF/AAVMF) is set.
  3. If the machine has no firmware, appends -kernel <vmlinuz> -append <cmdline> for the direct-boot path (this is what qemu-arm64 uses).
  4. Tries host QEMU first; falls back to running QEMU inside the toolchain-musl container with the project bind-mounted at /project if the host doesn’t have it installed.

The image yoe passes is whatever the assembly step produced, attached read-write. Restart-and-iterate workflows: rebuild, then re-run yoe qemu — the image is regenerated, the guest starts clean.

When to use which

  • qemu-x86_64 is the right default for most development. KVM acceleration on an x86 host is essentially native speed; the boot path matches legacy-BIOS bare metal so what you debug here is what runs on similar hardware.
  • qemu-arm64 is for catching arch-specific bugs (byte ordering, alignment, ARM64-only paths in code) without finding a board. It runs under TCG (software emulation) on x86 hosts, which is slow but faithful. On aarch64 hosts (an Apple Silicon Mac, an Ampere server) it uses KVM and is fast.
  • For anything physical-board-shaped — secure boot, vendor blobs, display, real I/O — use the actual board’s machine descriptor.