DEV Community

Aaron LaBeau
Aaron LaBeau

Posted on

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

  • 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 --num_instances=N flag that does exactly what I wanted.

Everything lined up. Then I hit this:

$ sudo modprobe vhost_vsock
modprobe: FATAL: Module vhost_vsock not found in directory /lib/modules/6.6.10-cix-build-generic
Enter fullscreen mode Exit fullscreen mode

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 /dev/vhost-vsock, Cuttlefish doesn't start. It's not a soft dependency.

The kernel Minisforum ships is 6.6.10-cix-build-generic. Check what it thinks about virtualization:

grep -E 'VHOST' /boot/config-$(uname -r)
Enter fullscreen mode Exit fullscreen mode

On mine, the output was more interesting for what was missing than what was there:

# CONFIG_VHOST_NET is not set
Enter fullscreen mode Exit fullscreen mode

CONFIG_VHOST_VSOCK doesn't appear at all — not even as "is not set". That happens when the parent CONFIG_VHOST 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:

$ ls -l /dev/vhost-vsock /dev/vhost-net /dev/kvm
ls: cannot access '/dev/vhost-vsock': No such file or directory
ls: cannot access '/dev/vhost-net': No such file or directory
crw-rw----+ 1 root kvm 10, 232 /dev/kvm
Enter fullscreen mode Exit fullscreen mode

KVM is there. Vhost isn't.

This costs you more than Cuttlefish

CONFIG_VHOST_NET 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:

CONFIG_VHOST_NET=m
CONFIG_VHOST_VSOCK=m
CONFIG_VSOCKETS=m
CONFIG_KVM=y
Enter fullscreen mode Exit fullscreen mode

There's a clue in the vendor tree about why the shipped build differs. Look at what CIX ships as config fragments:

arch/arm64/configs/
  cix.config            596 lines   main platform fragment
  cix_redroid.config    222 lines   redroid (containerized Android)
  cix_docker.config      40 lines   Docker support
  virt.config            61 lines   (see the warning below)
Enter fullscreen mode Exit fullscreen mode

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 binder 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. virt.config 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 ARCH_* plus THERMAL, SPI, MTD, and REGULATOR. 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.

cixtech/cix-linux-main. 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 clk_ignore_unused. Jumping 6.6 → 6.18 on unvalidated firmware is the high-risk path.

cixtech/cix_opensource__linux, branch cix_p1_k6.6_master. ← 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.

$ ls /boot/grub/grub.cfg
ls: cannot access '/boot/grub/grub.cfg': No such file or directory

$ dpkg -l | grep -E 'grub|systemd-boot|u-boot'
(nothing)

$ mount | grep -i efi
efivarfs on /sys/firmware/efi/efivars type efivarfs (...)
Enter fullscreen mode Exit fullscreen mode

No bootloader package. No EFI partition mounted. But:

$ cat /proc/cmdline
BOOT_IMAGE=/Image console=tty0 console=ttyAMA0,115200 ... acpi=force ...
Enter fullscreen mode Exit fullscreen mode

/Image is a raw arm64 kernel. Something is loading it. Mount the ESP and the picture resolves:

sudo mkdir -p /mnt/esp
sudo mount /dev/nvme0n1p1 /mnt/esp
ls /mnt/esp
Enter fullscreen mode Exit fullscreen mode
EFI/BOOT/BOOTAA64.EFI              ← GRUB, at the UEFI fallback path
GRUB/GRUB.CFG                      ← its config
Image                              ← the actual kernel
initrd.img-6.6.10-cix-build-generic
SKY1-*.dtb                         ← device trees for various Sky1 boards
Enter fullscreen mode Exit fullscreen mode

So the real chain is:

UEFI firmware
  └─ /dev/nvme0n1p1 (ESP, FAT16, 500 MB)
       └─ /EFI/BOOT/BOOTAA64.EFI   (GRUB)
            └─ /GRUB/GRUB.CFG
                 ├─ /Image
                 └─ /initrd.img-6.6.10-cix-build-generic
Enter fullscreen mode Exit fullscreen mode

Three consequences that break normal kernel-install habits:

  1. /boot is decorative. The files there are leftovers nothing reads. Installing a kernel .deb writes to /boot, reports success, and changes nothing about booting.
  2. update-grub does not exist. GRUB was placed by Minisforum's installer, not apt. You edit GRUB.CFG by hand.
  3. 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 GRUB.CFG has two entries:

set default=1
set timeout=2

menuentry '0 Cix Sky1 on Meigao (Device Tree)' { ... acpi=off ... }
menuentry '1 Cix Sky1 on Meigao (ACPI)'        { ... acpi=force ... }
Enter fullscreen mode Exit fullscreen mode

Default is entry 1 (ACPI), matching /proc/cmdline. The DTB files aren't used by it. And timeout=2 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 ip -br a and /etc/network/interfaces.
  • Back up the whole ESP:
sudo mkdir -p /root/esp-backup
sudo cp -a /mnt/esp/. /root/esp-backup/
Enter fullscreen mode Exit fullscreen mode

299 MB onto a 1.8 TB disk. It makes the worst case fully recoverable.


Building the kernel

Tools

sudo apt update
sudo apt install -y build-essential bc bison flex libssl-dev libelf-dev \
                    libncurses-dev rsync kmod cpio dwarves debhelper \
                    fakeroot git zstd screen
Enter fullscreen mode Exit fullscreen mode

Source

cd ~
git clone --depth 1 -b cix_p1_k6.6_master \
  https://github.com/cixtech/cix_opensource__linux.git cix-kernel
cd cix-kernel

git branch --show-current     # cix_p1_k6.6_master
head -5 Makefile              # VERSION 6, PATCHLEVEL 6, SUBLEVEL 89
Enter fullscreen mode Exit fullscreen mode

--depth 1 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:

cp /boot/config-6.6.10-cix-build-generic .config
make olddefconfig
Enter fullscreen mode Exit fullscreen mode

Now the actual point of the exercise:

./scripts/config --enable VHOST_MENU
./scripts/config --module VHOST_NET
./scripts/config --module VHOST_VSOCK
./scripts/config --module VSOCKETS
Enter fullscreen mode Exit fullscreen mode

Plus two more you'd otherwise discover the hard way — details in the traps section:

./scripts/config --enable CIX_THERMAL
./scripts/config --enable CIX_AP2SE_IPC
Enter fullscreen mode Exit fullscreen mode

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:

./scripts/config --set-str LOCALVERSION "-cix-vhost"
./scripts/config --disable LOCALVERSION_AUTO
Enter fullscreen mode Exit fullscreen mode

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:

./scripts/config --disable MODULE_SIG_FORCE
./scripts/config --set-str SYSTEM_TRUSTED_KEYS ""
./scripts/config --set-str SYSTEM_REVOCATION_KEYS ""
./scripts/config --disable DEBUG_INFO
./scripts/config --enable DEBUG_INFO_NONE
Enter fullscreen mode Exit fullscreen mode

Apply and verify — this is the checkpoint that matters most, because a build missing VHOST_VSOCK completes successfully and is useless:

make olddefconfig
grep -E '^CONFIG_(VHOST|VSOCKETS|CIX_THERMAL|CIX_AP2SE_IPC)' .config
grep '^CONFIG_LOCALVERSION=' .config
Enter fullscreen mode Exit fullscreen mode
CONFIG_VHOST_MENU=y
CONFIG_VHOST=m
CONFIG_VHOST_NET=m
CONFIG_VHOST_VSOCK=m
CONFIG_VSOCKETS=m
CONFIG_CIX_THERMAL=y
CONFIG_CIX_AP2SE_IPC=y
CONFIG_LOCALVERSION="-cix-vhost"
Enter fullscreen mode Exit fullscreen mode

Compile

screen -S kernelbuild
make -j$(nproc) bindeb-pkg 2>&1 | tee ~/kbuild.log
Enter fullscreen mode Exit fullscreen mode

Two habits worth adopting. screen means a dropped SSH doesn't kill a 60-minute build. tee matters more than it looks: when a kernel build fails, your terminal shows a cascade of make[N]: *** Error 2 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 error: and a deep make[N]: ... Error 1 matter.


The four traps

1. CONFIG_CIX_THERMAL — a compile error

drivers/acpi/processor_thermal.c:432:18: error: implicit declaration of function 'processor_get_static_power_cpus' [-Werror=implicit-function-declaration]
Enter fullscreen mode Exit fullscreen mode

"Implicit declaration" normally means a missing header. Here the functions are static and defined in the same file:

 65:  #ifdef CONFIG_CIX_THERMAL
 66:    cix_get_static_power()               ← reads ACPI method \_SB.DPRG
122:    processor_get_static_power_cpus()    ← the "missing" one
134:    processor_get_dynamic_power_cpus()
145:  #endif
...
432:  processor_get_requested_power()        ← calls them, NOT guarded
Enter fullscreen mode Exit fullscreen mode

Definitions inside the #ifdef, 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.

CIX_THERMAL is CIX's CPU power measurement for this SoC — enabling it is correct configuration, not a workaround.

2. CONFIG_CIX_AP2SE_IPC — a link error, 40 minutes later

drivers/soc/cix/cix_dst/blackbox/platform_ap/rdr_ap_adapter.c:149:
    undefined reference to 'cix_ap2se_ipc_send'
make[6]: *** [scripts/Makefile.vmlinux:37: vmlinux] Error 1
Enter fullscreen mode Exit fullscreen mode

The provider is drivers/soc/cix/cix_ap2se_ipc.c, guarded by CONFIG_CIX_AP2SE_IPC. The config had:

# CONFIG_CIX_AP2SE_IPC is not set     ← provider OFF
CONFIG_CIX_DST=y                      ← consumer ON
Enter fullscreen mode Exit fullscreen mode

The cause is the version drift. The 6.6.10 config calls this CONFIG_CIX_SE2AP_MBOX; 6.6.89 renamed it to CONFIG_CIX_AP2SE_IPC. olddefconfig 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 =y, not =mCIX_DST=y 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:

grep '^CONFIG_CIX\|^# CONFIG_CIX' /boot/config-6.6.10-cix-build-generic | sort > /tmp/old.txt
grep '^CONFIG_CIX\|^# CONFIG_CIX' .config | sort > /tmp/new.txt
diff /tmp/old.txt /tmp/new.txt
Enter fullscreen mode Exit fullscreen mode

Reading it:

  • =y 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 lines. These were new-and-off and safe to leave:
CIX_DSP, CIX_GPIO_READER, CIX_HF_TEST_CASE, CIX_KERNEL_SENSORHUB,
CIX_SE_CONFIG, CIX_SF_SCP_SUPPORT.


Installing, on a machine where /boot doesn't matter

Build output:

linux-image-6.6.89-cix-vhost+_6.6.89-g2ec99ef99058-3_arm64.deb
linux-headers-6.6.89-cix-vhost+_...deb
Enter fullscreen mode Exit fullscreen mode

Note the trailing +. scripts/setlocalversion appends it when building from a git tree whose HEAD isn't a tagged release. Your kernel version is 6.6.89-cix-vhost+, and
dropping the + in later commands means quietly targeting a kernel that doesn't exist. (Avoid it next time with touch .scmversion before building.)

Never type it — derive it:

KVER=$(ls -1 /lib/modules/ | grep vhost)
echo "$KVER"    # 6.6.89-cix-vhost+
Enter fullscreen mode Exit fullscreen mode

Shrink the initramfs first

Order matters: installing the kernel package triggers an initramfs build via its post-install hook. Set the lean option before installing, or you generate a 250 MB file you then have to redo.

The default MODULES=most packs drivers for every machine ever built — that's the 239 MB initrd on the ESP. MODULES=dep includes only what this machine needs:

sudo cp /etc/initramfs-tools/initramfs.conf /etc/initramfs-tools/initramfs.conf.backup
sudo sed -i 's/^MODULES=most/MODULES=dep/' /etc/initramfs-tools/initramfs.conf
Enter fullscreen mode Exit fullscreen mode
sudo dpkg -i linux-image-6.6.89-cix-vhost+_*.deb

KVER=$(ls -1 /lib/modules/ | grep vhost)
[ -f /boot/initrd.img-"$KVER" ] || sudo update-initramfs -c -k "$KVER"
ls -lh /boot/initrd.img-"$KVER"
Enter fullscreen mode Exit fullscreen mode

Expect 30–60 MB. Then restore the original setting, so your existing 239 MB initrd can never be regenerated with different settings — the fallback must stay exactly as it is:

sudo cp /etc/initramfs-tools/initramfs.conf.backup /etc/initramfs-tools/initramfs.conf
Enter fullscreen mode Exit fullscreen mode

Copy to the ESP

Note which kernel file. /boot/vmlinuz-* is compressed; the bootloader here wants the raw arch/arm64/boot/Image. On my system that's 13 MB versus 38 MB — copy the wrong one and it won't boot.

Everything here adds files. Nothing existing is touched:

sudo mount /dev/nvme0n1p1 /mnt/esp
sudo cp ~/cix-kernel/arch/arm64/boot/Image /mnt/esp/Image-vhost
sudo cp /boot/initrd.img-"$KVER" /mnt/esp/initrd-vhost.img
sync
df -h /mnt/esp
Enter fullscreen mode Exit fullscreen mode

Add a menu entry

Back up first, then derive the root partition from the running system rather than retyping a UUID:

sudo cp /mnt/esp/GRUB/GRUB.CFG /mnt/esp/GRUB/GRUB.BAK

ROOTSPEC=$(grep -o 'root=PARTUUID=[^ ]*' /proc/cmdline)
echo "$ROOTSPEC"     # must not be empty
Enter fullscreen mode Exit fullscreen mode
sudo tee -a /mnt/esp/GRUB/GRUB.CFG >/dev/null <<EOF

menuentry '2 Cix Sky1 vhost 6.6.89 (ACPI)' {
    linux /Image-vhost console=tty0 console=ttyAMA0,115200 earlycon=pl011,0x040d0000 loglevel=4 arm-smmu-v3.disable_bypass=0 cma=640M acpi=force splash $ROOTSPEC rootwait rw
    initrd /initrd-vhost.img
}
EOF

sudo sed -i 's/^set timeout=.*/set timeout=20/' /mnt/esp/GRUB/GRUB.CFG
Enter fullscreen mode Exit fullscreen mode

Leave set default=1. The default still boots your known-good kernel; you select the new one deliberately.

3. The trap that cost me a boot cycle

I reformatted my entry across multiple lines to match the vendor's style. In doing so I left one trailing space after a continuation backslash:

    linux /Image-vhost \ 
                       ↑
Enter fullscreen mode Exit fullscreen mode

A backslash continues a line only as the final character. With a space after it, the backslash escapes the space and the line ends there. GRUB read the entry as linux /Image-vhost with no arguments — no root=, no console=, nothing.

The symptom looks catastrophic: kernel boots, then a bare prompt, no GUI, and sudo: command not found. That's the BusyBox initramfs shell — with no root=, the initramfs has nothing to mount and gives up.

The tell is one command:

cat /proc/cmdline
Enter fullscreen mode Exit fullscreen mode

If it shows only BOOT_IMAGE= and nothing else, it's this. Fix and verify:

sudo sed -i 's/\\[[:space:]]*$/\\/' /mnt/esp/GRUB/GRUB.CFG
sudo cat -A /mnt/esp/GRUB/GRUB.CFG | grep -n '\\'
Enter fullscreen mode Exit fullscreen mode

cat -A marks end-of-line with $. Every continuation must read \$, never \ $.

Then flush and unmount — it's a FAT filesystem and you want the write committed:

sync && sudo umount /mnt/esp
Enter fullscreen mode Exit fullscreen mode

First boot

Be at the physical machine. At the menu, select entry 2. Do nothing and it boots entry 1 after 20 seconds — deliberately.

uname -r
sudo modprobe vhost_vsock vhost_net
ls -l /dev/vhost-vsock /dev/vhost-net /dev/kvm
ip -br a && ping -c3 8.8.8.8
Enter fullscreen mode Exit fullscreen mode

The payoff:

crw-rw----+ 1 root kvm 10, 232 /dev/kvm
crw-rw----  1 root kvm 10, 238 /dev/vhost-net
crw-rw----  1 root kvm 10, 241 /dev/vhost-vsock
6.6.89-cix-vhost+
Enter fullscreen mode Exit fullscreen mode

4. The GPU driver, and why your desktop dies

For me the desktop flashed, printed "unsupported platform", and went black. SSH worked fine.

$ lsmod | grep -iE 'drm|mali'
drm_display_helper  172032  1 trilin_dpsub
linlon_dp           135168  5
Enter fullscreen mode Exit fullscreen mode

The display controller (linlondp) probes cleanly. But there's no GPU driver, and GNOME Shell on Wayland fails in a retry loop.

The reason:

$ find /lib/modules -iname '*kbase*'
/lib/modules/6.6.10-cix-build-generic/extra/mali_kbase.ko
Enter fullscreen mode Exit fullscreen mode

mali_kbase — the Arm Mali GPU driver — is out-of-tree, shipped prebuilt for the vendor kernel only, in extra/. A module built for 6.6.10 can't load on 6.6.89.

No config flag fixes this. But CIX publishes the source, and it ships a dkms.conf:

sudo dpkg -i ~/linux-headers-6.6.89-cix-vhost+_*.deb
sudo apt install -y dkms

git clone --depth 1 -b cix_p1_k6.6_master \
  https://github.com/cixtech/cix_opensource__gpu_kernel.git mali-gpu

# directory MUST be <PACKAGE_NAME>-<PACKAGE_VERSION> from dkms.conf
sudo cp -r mali-gpu /usr/src/cix-gpu-kmd-1.0.0

sudo dkms add     -m cix-gpu-kmd -v 1.0.0
sudo dkms build   -m cix-gpu-kmd -v 1.0.0 -k 6.6.89-cix-vhost+
sudo dkms install -m cix-gpu-kmd -v 1.0.0 -k 6.6.89-cix-vhost+
sudo depmod -a 6.6.89-cix-vhost+
Enter fullscreen mode Exit fullscreen mode

It builds three modules — mali_kbase, memory_group_manager, and
protected_memory_allocator. All are needed; mali_kbase has
softdep: pre: memory_group_manager.

On Debian, DKMS ignores DEST_MODULE_LOCATION and installs to updates/dkms/, so query by module name rather than path:

modinfo mali_kbase | grep -E 'filename|version'
Enter fullscreen mode Exit fullscreen mode

One thing that looks fatal and isn't. My build produced
r54p1-11eac0 (UK version 1.38) against a userspace blob built for
r53p0-00eac0 (UK version 1.36). The Mali userspace talks to the kernel driver over that versioned interface, and a mismatch is exactly what "unsupported platform" means.

It worked anyway. kbase negotiates the UK version and newer kernel drivers accept somewhat older userspace. Test before you go hunting for a matching revision.

sudo modprobe memory_group_manager
sudo modprobe mali_kbase
sudo systemctl restart gdm
Enter fullscreen mode Exit fullscreen mode

Desktop came back, accelerated. And AUTOINSTALL="yes" in dkms.conf means future kernels rebuild these automatically.

Fallback

If your versions genuinely don't reconcile, /dev/dri/card0 still exists, so software rendering gives a usable if slow desktop:

sudo sed -i 's/^#*WaylandEnable=.*/WaylandEnable=false/' /etc/gdm3/daemon.conf
echo 'LIBGL_ALWAYS_SOFTWARE=1' | sudo tee -a /etc/environment
sudo systemctl restart gdm
Enter fullscreen mode Exit fullscreen mode

Or simply don't. Cuttlefish renders to a virtual display you reach over adb — the host GPU plays no part. Boot entry 1 for an accelerated desktop, entry 2 for Android work. Both stay on the menu permanently.


Make it stick

Only after a clean reboot where everything comes up unattended:

sudo mount /dev/nvme0n1p1 /mnt/esp
sudo sed -i 's/^set default=.*/set default=2/' /mnt/esp/GRUB/GRUB.CFG
sync && sudo umount /mnt/esp

printf 'vhost_vsock\nvhost_net\n' | sudo tee /etc/modules-load.d/vhost.conf
Enter fullscreen mode Exit fullscreen mode

Keep timeout=20 forever. Entries 0 and 1 are a permanent escape hatch that costs nothing.

Rollback, at any point: reboot, pick entry 1. The original kernel, initrd, and menu entries were never modified. If the menu itself breaks, restore GRUB.BAK; if
everything breaks, restore /root/esp-backup.


Where this leaves you

Kernel            6.6.89-cix-vhost+
/dev/vhost-vsock  ✅  Cuttlefish unblocked
/dev/vhost-net    ✅  in-kernel virtio networking for every VM
/dev/kvm          ✅
GPU / desktop     ✅  via DKMS, auto-rebuilds for future kernels
Fallback          ✅  original kernel still on the menu
Enter fullscreen mode Exit fullscreen mode

The vhost_net line is worth restating. Whatever you use this machine for — Proxmox guests, Docker, Cuttlefish — its VMs now get the in-kernel network fast path instead of QEMU userspace. If you plan to measure anything network-related, that alone justifies
the afternoon.

Lessons that generalize

Vendor BSP kernels are configured for the vendor's use case, not yours. The MS-R1 ships configured for containerized Android. That's a coherent choice; it's just not mine. Read the config fragments — they tell you what the vendor actually tests.

Config drift between BSP versions is the main hazard. Two of four traps came from grafting a 6.6.10 config onto a 6.6.89 tree. The diff of CIX symbols between old and new config would have caught both before I compiled anything. Do that diff first.

Look at how the machine boots before you install a kernel. Ten minutes with efibootmgr, /proc/cmdline, and the ESP saved me from installing a kernel into a /boot that nothing reads and wondering why nothing changed.

Additive changes and a preserved fallback turn scary steps into cheap ones. Every failure in this post cost a reboot, not a reinstall — because the original kernel, initrd, and menu entries were never touched.


Part 2 will cover the actual goal: building Cuttlefish on this kernel, launching multiple Android instances, and the networking work needed to make them discover each other — including the bridge setting that silently breaks mDNS and makes it look like your application is broken.

References

Top comments (0)