This article is currently an experimental machine translation and may contain errors. If anything is unclear, please refer to the original Chinese version. I am continuously working to improve the translation.
While writing my previous blog post, I accidentally discovered that my daily driver, the Redmi Note 10 Pro 5G (chopin), actually supports KVM.
So I decided to stop theorizing and jump into action—time to test KVM virtualization on my phone(RIP my poor device).
This blog post might be a bit long, and will likely cover the following:
- The current state of KVM on Android devices
- ARM SoC boot process and privilege levels
- How I ported an OSS kernel to chopin and ran latest near-stock Android
- Running Linux / WoA via QEMU on chopin
KVM on Mobile Devices
Pixel 6 and Above
Google officially supports KVM on the entire Pixel 6 lineup (Pixel 6, 6a, 6 Pro) and newer models. A detailed article on this can be found here: https://lwn.net/Articles/836693/
At first, I was pretty excited—after all, this is the first official KVM support on a flagship series, and maybe it could push other OEMs toward similar capabilities. Could Google be planning something like ASL (Android Subsystem for Linux) (just kidding)?
But upon closer inspection, I realized that Pixel devices default to “protected” KVM, which differs from the common nVHE (native Virtualization Host Extension). In “protected” mode, the VM’s internal state is secured—even the host can’t modify it. There is a kernel parameter to disable pKVM and switch to nVHE, but that requires OEM unlocking (and breaks hardware attestation).
If your spidey sense is tingling—good. The pKVM support on Pixel devices is just Google, the world’s largest ad company, using “device security” as a cover to protect DRM. Classic.
The only silver lining? Google hasn’t done much yet with pKVM, but the KVM support is already there. If you own a Pixel, you can try running Linux or Windows on ARM via KVM. There are already some success stories online (e.g., Limbo Tensor).
MediaTek Dimensity 1100/1200 and Similar SoCs
Lucky me—my chopin uses the Dimensity 1100 SoC, and several nearby chips (including Dimensity 700/720/800u/810/1100/1200) also support KVM.
To understand why these SoCs support KVM, it helps to first explain why most Qualcomm and MediaTek devices don’t.
In the ARM world, similar to x86, there are different “privilege levels” (called Exception Levels, or ELs): EL0 through EL3.
But unlike x86, where the kernel runs at the highest ring0, ARM firmware starts at the topmost EL3.
According to ARM’s documentation, EL3 is for firmware, EL2 is for the Hypervisor, EL1 is for the OS kernel, and EL0 is for user applications.
ARM Exception Levels
ARM’s architecture inherently supports virtualization in hardware. But for Linux to use KVM, the kernel must run at EL2 or at least have support from EL2. Only then does it have enough privilege.
p.s. “Firmware” here refers to all OEM code that runs before the kernel takes control during normal boot.
In a standard mobile boot process, OEM firmware initializes hardware, loads the device tree and kernel into memory, then hands over control. (See kernel docs for details.)
On most modern mobile devices, the firmware explicitly drops privilege to EL1 when jumping to the kernel—occupying EL2 but doing nothing with it, and offering no way back. This effectively blocks KVM functionality on phones. (It’s like having virtualization disabled in BIOS.)
Moreover, modern devices enforce firmware signature checks (e.g., Qualcomm’s Secure Boot). OEMs permanently burn their public keys into the SoC at manufacturing, meaning only signed firmware can run. This is crucial for secure chain-of-trust boot, but it also means we can’t modify the firmware. Without OEM-provided interfaces, enabling KVM is impossible.
Unless the OEM’s private key leaks or a secure boot bypass is found, the only way to run custom firmware (and thus free up EL2 for the kernel) is to physically replace the SoC with an unprovisioned one. (This is also how some hardware-based bootloader unlocks work.)
But exceptions exist… and my MTK 1100-powered chopin is one of them.
In the officially signed Xiaomi firmware, for some unknown reason, the SoC remains at EL2 after firmware hands control to the kernel. Given that this “feature” was removed in later SoCs and wasn’t present in earlier ones, it’s likely a bug in MediaTek’s reference firmware—forgetting to drop to EL1—leaving devices like mine accidentally KVM-enabled.
Since I’ve got a KVM-capable phone by sheer luck, why not try running some VMs?
Kernel Porting
Obviously, MIUI’s prebuilt kernel doesn’t include KVM support. I needed to build my own KVM-enabled kernel.
Newer Android versions (Android 12+, kernel >=5.10) now use GKI (Generic Kernel Image), which, along with GSI, aims to reduce kernel and system fragmentation. On GKI devices, OEM drivers are loaded as kernel modules, so you can just build from the AOSP common kernel source.
But… chopin runs kernel 4.14—no GKI for me. After searching, I found only one open-source kernel on GitHub, and it only supports Android 11.
(Lazy attempt) Trying GSI directly
The Android 11 kernel from GitHub could be built with KVM support, but MIUI 12.5 is a pain to use, and its HAL implementation has numerous bugs. I tried many GSIs, but none worked properly.
Tried a bunch of near-stock ROMs, all failed
(Forced) Kernel porting
I couldn’t accept being stuck on Android 11 or using MIUI. So I began porting a newer Android kernel to chopin.
After some research, I found that the Redmi K40 Gaming (codename ares) had similar hardware and an open-source Android 12 kernel. I started porting ares’ kernel to chopin.
Kernel porting is essentially a game of whack-a-bug. I copied the old defconfig and started compiling, fixing issues as they arose:
Environment setup
The first evil step: setting up the build environment. Different kernels may need different compilers—sometimes even obscure forks. Newer Android kernels use Clang, older ones GCC. Without docs, this often involves guesswork.Compilation errors
- Missing files: search for relevant drivers, copy them or disable the feature in config
- Missing definitions: enable required features in config, or (reasonably) edit code to remove unused variables
- Warnings treated as errors: ignore non-critical warnings, adjust compiler flags
Linking errors
- Symbol conflicts: make symbols static or rename them manually
- Missing symbols: search for related modules, enable them in config or patch code
Runtime issues
The first bootable kernel often has bugs. My initial build booted, but battery, USB, and vibration didn’t work.
Android kernel dmesg logs are notoriously messy, full of false alarms.(e.g., “Error” spam that doesn’t affect functionality—even the stock kernel has it)
My trick: compare logs from your custom kernel vs. the stock one. Ignore the “Error” noise—focus on differences. Reverse-searching those lines in the kernel source usually reveals the problematic module. Adjust config or port drivers from other versions if needed.
I’ve open-sourced the kernel source and CI build environment. Unless you own the same device, these probably won’t help much. The process isn’t technically hard, but it’s tedious—lots of trial and error on real hardware.
After some twists and turns, I finally built a working kernel. The MIUI13 base (Android 12) had far fewer HAL bugs, and GSIs mostly worked. Since I’d already ditched the stock system, I went all the way and upgraded to Android 14.
Android GSI 14 + freshly baked kernel
Once I could build kernels, enabling KVM was just a config toggle away. But here’s another twist: MIUI13’s firmware does drop to EL1. So after flashing full MIUI13, I had to manually flash the older MIUI12 preloader.bin (thankfully compatible) to stay at EL2 and enable KVM in Android.
After many hurdles, success—over a week gone just for this...
QEMU, Fire Up!
(Smooth) Linux
Running Linux with KVM acceleration was smooth. I downloaded Ubuntu’s official ARM ISO, configured it in Limbo Tensor with a disk image, and booted straight in. Installation completed flawlessly.
Ubuntu 24.04 runs smoothly, resource usage is reasonable
Only one minor issue: under high memory pressure, KVM may crash with Bad Address. Likely due to incompatibility between Android’s zRAM and KVM. Temporarily solved by disabling zRAM: swapoff /dev/block/zram0.
(Rocky) Windows
Trying the same for Windows 11 on ARM didn’t go so well. The boot process just froze. Following Limbo Tensor’s guide, I enabled Full UEFI—only for QEMU to crash immediately with Function not implemented. dmesg showed load/store instruction decoding not implemented.
Error looked like this:
1 | root@localhost:~/roms# ./bin/qemu-system-aarch64 -display vnc=:0 -device qemu-xhci,id=usb-bus -device usb-tablet,bus=usb-bus.0 -device usb-kbd,bus=usb-bus.0 -L . -smp 4 -M virt -cpu host -m 3072 -drive if=pflash,format=raw,unit=0,file=./edk2/edk2_qemu_aarch64.fd,readonly=on -drive if=pflash,format=raw,unit=1,file=./edk2/edk2_vars.fd -device nvme,drive=drive0,serial=drive0 -drive if=none,media=disk,id=drive0,file=/mnt/android/tiny11arm64.qcow2 -net none -device virtio-ramfb -accel kvm -machine kernel_irqchip=off -s -S |
Turns out, the current kernel KVM implementation is incomplete—BIOS attempting MMIO writes to NVRAM crashes KVM.
So I tried booting Windows without Full UEFI, and focused on fixing the boot freeze.
I attached GDB to QEMU, and after the freeze, ^C revealed a dead loop. The surrounding instructions looked off—likely waiting for an interrupt that never came. No errors in QEMU or dmesg.
1 | (gdb) x/10i $pc-20 |
My initial plan: backport KVM modules from a newer kernel to 4.14. Should fix MMIO and IRQ issues.
But the backport was messy—new KVM features depend on other kernel components, leading to tons of undefined macros/variables/functions. Needed manual fixes.
Halfway through, I stumbled upon the -machine kernel_irqchip=off option on the QEMU site. It enables KVM acceleration while simulating the interrupt controller in userspace. I tried it—and to my surprise, Windows 11 booted successfully! (So the backport is on indefinite hold.)
Finally... hardware-accelerated Windows on ARM on MediaTek
The first boot used Tiny11, but it had bugs—3GB RAM usage at idle, and WerFault processes maxing out CPU. I also realized: using -smp 4 created four vCPUs (1 core each), but regular Windows doesn’t scale well across many CPUs—so it was effectively running on one core.
I reinstalled Windows 10 on ARM, adjusted to -smp cores=4, manually pinned QEMU to big cores, installed virtio network drivers, and enabled port forwarding + RDP.
On my 8GB phone, I could only allocate 4–5GB RAM—more and Android would OOM-kill QEMU or freeze.
1 | # Final launch command; CPU4-7 are big cores on my device |
I recorded a demo video here. Performance-wise, single-core beats Snapdragon 860 and rivals Microsoft SQ2. Multi-core is weaker (only 4 cores), but overall, it’s usable.
Benchmark: single-core close to Microsoft SQ2, experience is decent
Even without GPU, web browsing is surprisingly smooth
Summary
Tinkering with Android is always time-consuming. This whole adventure took over two weeks. While cleaning up, I found over 200GB of downloaded OS images… After deleting most of the junk, I uploaded the useful files to my cloud.
Sure, the results aren’t “universal”—only works on chopin. Other rare MTK devices need their own kernel builds, and all Qualcomm phones (plus most MediaTek ones that can’t reach EL2) are out of luck.
But this is likely the first time Windows on ARM ran with near-native performance on a MediaTek device. And honestly? That’s just plain cool.
This article is licensed under the CC BY-NC-SA 4.0 license.
Author: lyc8503, Article link: https://blog.lyc8503.net/en/post/android-kvm-on-mediatek/
If this article was helpful or interesting to you, consider buy me a coffee¬_¬
Feel free to comment in English below o/