Running Android VMs on ARM: Rebuilding the Minisforum MS-R1 Kernel for Cuttlefish

2026年8月17日1 次浏览来源:Dev.to阅读原文

Part 1 of

2.

This part covers getting a kernel that can actually host virtual machines.

Why bother I wanted a box that could run a dozen Android instances at once — real ones, not emulated-on-x86 ones — to benchmark peer-to-peer sync behaviour at scale.

Native arm64 Android on native arm64 silicon, no translation layer, enough cores and RAM to make the peer count interesting.

The Minisforum MS-R1 looked ideal.

It's built on the CIX P1 ("Sky1"), a 12-core ARMv9 SoC, and it's one of the first genuinely affordable ARM desktops with server-class amounts of memory.

Google's Cuttlefish — AOSP's official virtual device — runs arm64 Android guests on arm64 hosts with KVM acceleration, with a flag that does exactly what I wanted.

Everything lined up.

Then I hit this: This post is what it took to fix that.

If you have this hardware and want to run VMs on it, you'll hit the same wall, and there are four separate traps between you and the other side.

I hit all of them so you don't have to.

Rough time: an afternoon.

Most of it is a compile you can walk away from.

The problem: no vhost, no Cuttlefish Cuttlefish uses vsock — a virtual socket transport — for all communication between the host and its guest VMs.

ADB, logs, control messages, everything.

Without , Cuttlefish doesn't start.

It's not a soft dependency.

The kernel Minisforum ships is .

Check what it thinks about virtualization: On mine, the output was more interesting for what was missing than what was there: doesn't appear at all — not even as "is not set".

That happens when the parent symbol is disabled, so Kconfig never emits the dependent symbols.

The vendor didn't disable vsock specifically; they disabled the entire vhost subsystem.

Confirmed at the device level: KVM is there.

Vhost isn't.

This costs you more than Cuttlefish being off is worth dwelling on.

Without it, every VM on the machine gets its virtio networking serviced in QEMU userspace instead of in the kernel.

If you're running Proxmox on this box — and the MS-R1 docs walk you through exactly that — all your guests are quietly getting the slow path.

So this isn't a niche Cuttlefish fix.

It's restoring standard virtualization support to a machine marketed partly on being able to virtualize.

Why is it off?

Not a hardware limitation.

CIX's own mainline config (cixtech/cix-linux-main) sets: There's a clue in the vendor tree about why the shipped build differs.

Look at what CIX ships as config fragments: There's a 222-line fragment for redroid — containerized Android — and none of the fragments enable vhost anywhere.

The vendor's supported Android story is containers, which share the host kernel and need no vsock at all.

Their kernel has compiled in and vhost stripped out.

Kernel and documentation agree with each other; it's just not the story I wanted.

A trap worth naming. sounds like the virtualization options.

It isn't.

Its header reads "Virtualization guest" — it configures a kernel to run inside a VM, disabling every platform plus , , , and .

Applying it gets you a kernel that won't drive your hardware.

I lost time on this so you don't have to.

Choosing a source tree Three options, and the obvious one is wrong.

Mainline.

CIX Sky1 support landed upstream around v6.17 (LWN coverage).

But it's minimal enablement — mailbox, SCMI clocks, basic device tree — and it targets the Radxa Orion O6, a different board with different firmware and different NICs.

You'd likely lose networking or fail to boot. .

Patch sets for mainline 6.18/7.0/7.1.

Its defconfig has everything Cuttlefish needs, which is how I confirmed vhost isn't hardware-limited.

But the README says tested on Orion O6 with specific edk2 firmware, and it wants .

Jumping 6.6 → 6.18 on unvalidated firmware is the high-risk path. , branch . ← this one.

Full BSP source tree, CIX P1 specific, same 6.6 lineage as the shipped kernel.

This is the GPL source drop your running kernel came from.

One wrinkle: branch HEAD is at 6.6.89 while the shipped kernel is 6.6.10.

That's 79 stable releases of drift, and as you'll see, it's the direct cause of two of the four traps.

It's still the right choice — same BSP, same hardware support — but plan on keeping the original kernel bootable.

Before anything else: understand how this thing boots This is where the MS-R1 gets genuinely unusual, and where I'd have bricked my access if I hadn't looked first.

No bootloader package.

No EFI partition mounted.

But: is a raw arm64 kernel.

Something is loading it.

Mount the ESP and the picture resolves: So the real chain is: Three consequences that break normal kernel-install habits: is decorative.

The files there are leftovers nothing reads.

Installing a kernel writes to , reports success, and changes nothing about booting. does not exist.

GRUB was placed by Minisforum's installer, not apt.

You edit by hand.

The ESP is small. 500 MB, with ~200 MB free, and the existing initramfs eats 239 MB of it.

Space is a real constraint.

The shipped has two entries: Default is entry 1 (ACPI), matching .

The DTB files aren't used by it.

And is two seconds — not enough to select a fallback, which we'll fix.

Safety, non-negotiable A new kernel can rename network interfaces.

If yours come back different, your bridge config breaks and the box drops off the network — and if you're on SSH, you're locked out.

Attach a monitor and USB keyboard.

Verify you can log in at the console.

Photograph your and .

Back up the whole ESP: 299 MB onto a 1.8 TB disk.

It makes the worst case fully recoverable.

Building the kernel Tools Source matters — the full history is over a million commits and roughly 20 GB.

The snapshot is about 2 GB and is all you need to build.

Config Start from the running kernel's config so the result is as close as possible to what already works: Now the actual point of the exercise: Plus two more you'd otherwise discover the hard way — details in the traps section: Give the new kernel a distinct name.

This is the step that protects your fallback — without it, the new kernel's modules overwrite the old kernel's, and you lose the ability to boot back: Two housekeeping items.

The first avoids a classic Debian build failure where the config points at certificate files that don't exist; the second roughly halves build time and saves ~15 GB: Apply and verify — this is the checkpoint that matters most, because a build missing completes successfully and is useless: Compile Two habits worth adopting. means a dropped SSH doesn't kill a 60-minute build. matters more than it looks: when a kernel build fails, your terminal shows a cascade of lines and the actual error is thousands of lines above, often scrolled away.

Without a log you'll rebuild just to read the message. 30–90 minutes on this SoC.

Thousands of warnings are normal; only and a deep matter.

The four traps

  1. — a compile error "Implicit declaration" normally means a missing header.
    Here the functions are and defined in the same file: Definitions inside the , call sites outside it.
    With the symbol off, the calls survive and the definitions vanish.
    It's a vendor bug that never bites CIX because they always build with it on. is CIX's CPU power measurement for this SoC — enabling it is correct configuration, not a workaround.
  2. — a link error, 40 minutes later The provider is , guarded by .
    The config had: The cause is the version drift.
    The 6.6.10 config calls this ; 6.6.89 renamed it to . can't map an old name to a new one, so the new symbol took its default (off) while every consumer stayed on.
    It must be , not — is built into vmlinux and can't link against a module.
    Catch both before compiling Both traps are the same root cause: a 6.6.10 config grafted onto a 6.6.89 tree.
    Diff them and you'll see everything queued up: Reading it: in old, absent in new → renamed or removed upstream.
    Find the new name.
    New symbol defaulting to off → usually a genuinely new optional feature; fine.
    Enabled consumer, disabled provider → a link error waiting to happen.
    On my build the drift was ~9 l
分享
Baike.dev

baike.dev helps you discover great languages, frameworks, databases, DevOps and cloud-native tools.

Quick links

About

Contribute

Found a great developer tool? Share it with the community.

Submit a tool
© 2026 baike.dev Developer EncyclopediaUpdated daily · Discover great developer tools