Xen on ARM in QEMU

The official documentation for running Xen on ARM in QEMU is quite old (latest update on 2019 by the time this post is published). Luckily, I managed to get it working, so I’m writing this post for future reference.

Major components and their versions:

  • Linux: 6.11.7
  • Xen: 4.18.5
  • qemu-system-aarch64: 8.2.2
  • gcc: 13.3.0

Note: Run all commands below that update the QEMU VM (e.g., scp, passwd) with patience, as QEMU can take longer to sync changes than it appears. As a best practice, wait at least 30 seconds before shutting down QEMU after making changes.

Install Dependencies

sudo apt update

sudo apt install git build-essential ninja-build pkg-config libglib2.0-dev \
libpixman-1-dev libcap-ng-dev python3-pip python3-venv wget gcc qemu-system-arm \
flex bison libelf-dev bc libssl-dev curl zstd binutils gcc-aarch64-linux-gnu \
libncurses-dev

Cross Compile Linux for ARM

Use the following script to compile Linux-6.11.7 for ARM.

wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.11.7.tar.xz -O linux-6.11.7.tar.xz
tar Jxvf linux-6.11.7.tar.xz
rm linux-6.11.7.tar.xz

cd linux-6.11.7
ARM_FLAGS='ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-'

make $ARM_FLAGS defconfig
./scripts/config -d CONFIG_WERROR
make $ARM_FLAGS olddefconfig
make $ARM_FLAGS -j$(nproc)

Download the Xen Server Disk Image

QEMU works well with UEFI firmware for the emulated machine. To get the system working, download an Aarch64 UEFI ready distro image,

wget https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-arm64-uefi1.img

Build Xen for ARM

First, download the Xen repository.

git clone https://xenbits.xen.org/git-http/xen.git
cd xen
git checkout RELEASE-4.18.5

Cross compile the Xen hypervisor.

make distclean
make dist-xen XEN_TARGET_ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

The result of the compilation is stored as xen/xen/xen.efi, this file will be used later.

Run QEMU (1)

Use the following command to launch QEMU for the first time, the goal is to set root password. The MAC address used in the command is just a common convention for QEMU.

qemu-system-aarch64 \
   -machine virt,gic_version=3 -machine virtualization=true \
   -cpu cortex-a57 -machine type=virt -nographic \
   -smp 4 -m 4000 \
   -kernel linux-6.11.7/arch/arm64/boot/Image.gz --append "console=ttyAMA0 root=/dev/vda1 init=/bin/sh" \
   -netdev user,id=hostnet0,hostfwd=tcp::2222-:22 -device virtio-net-device,netdev=hostnet0,mac=52:54:00:12:34:56 \
   -drive if=none,file=xenial-server-cloudimg-arm64-uefi1.img,id=hd0 -device virtio-blk-device,drive=hd0

Remount Root FS and Set Root Password

Once the shell shows up in QEMU, remount the root fs:

mount -o remount,rw /dev/vda1 /

Then, set the root password with

passwd

This command triggers a session for setting root password, set the password as a non-empty string.

Turn Off QEMU

After setting root password, turn off QEMU by typing ctrl-a x.

Run QEMU (2)

Launch QEMU for the second time, but without the init shell. Using the following command:

qemu-system-aarch64 \
   -machine virt,gic_version=3 -machine virtualization=true \
   -cpu cortex-a57 -machine type=virt -nographic \
   -smp 4 -m 4000 \
   -kernel linux-6.11.7/arch/arm64/boot/Image.gz --append "console=ttyAMA0 root=/dev/vda1" \
   -netdev user,id=hostnet0,hostfwd=tcp::2222-:22 -device virtio-net-device,netdev=hostnet0,mac=52:54:00:12:34:56 \
   -drive if=none,file=xenial-server-cloudimg-arm64-uefi1.img,id=hd0 -device virtio-blk-device,drive=hd0

Note that this QEMU launch will take much longer, a cloud-init feature during booting will take 120 seconds before its timeout.

Disable Cloud-init

It took a long time for the Ubuntu to boot, now disable this by:

sudo touch /etc/cloud/cloud-init.disabled

Set Up SSH Access

Inside the VM, create the .ssh/authorized_keys file:

mkdir -p /root/.ssh
echo "YOUR_PUBLIC_KEY_CONTENTS" >> /root/.ssh/authorized_keys
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys

Where YOUR_PUBLIC_KEY_CONTENTS is the contents of host machine’s ~/.ssh/id_rsa.pub or ~/.ssh/id_ed25519.pub.

SSH from Host (Optional)

Now it is possible to ssh into the VM from host,

ssh -p 2222 root@localhost

SCP the Xen EFI and the Dom0 Image

Copy the Xen EFI and the Dom0 image from host to QEMU with scp,

scp -P2222 linux-6.11.7/arch/arm64/boot/Image.gz root@127.0.0.1:/boot/efi/kernel
scp -P2222 xen/xen/xen.efi root@127.0.0.1:/boot/efi

SCP the Xen Config File and the Device Tree Blob

Write a Xen config file named xen.cfg on the host machine,

 options=console=dtuart noreboot dom0_mem=512M
 kernel=kernel root=/dev/vda1 rw console=hvc0
 dtb=virt-gicv3.dtb

SCP the config file into the QEMU VM,

scp -P2222 xen.cfg root@127.0.0.1:/boot/efi

On the host machine, generate a device tree blob file with the following command,

qemu-system-aarch64 \
   -machine virt,gic_version=3 \
   -machine virtualization=true \
   -cpu cortex-a57 -machine type=virt \
   -smp 4 -m 4096 -display none \
   -machine dumpdtb=virt-gicv3.dtb

Before copying the device tree blob file, it needs to be modified with device tree compiler. Install device tree compiler:

sudo apt install device-tree-compiler

Update the dtb file:

dtc -I dtb -O dts virt-gicv3.dtb > virt-gicv3.dts ;
sed 's/compatible = "arm,pl061.*/status = "disabled";/g' virt-gicv3.dts > virt-gicv3-edited.dts ;
dtc -I dts -O dtb virt-gicv3-edited.dts > virt-gicv3.dtb ;
rm virt-gicv3-edited.dts virt-gicv3.dts

SCP the config file into the QEMU VM,

scp -P2222 virt-gicv3.dtb root@127.0.0.1:/boot/efi

Turn Off QEMU

With all the files copied, turn off QEMU by typing ctrl-a x.

Obtain the QEMU EFI Binary

Install qemu-efi-aarch64 on the host machine, it comes with a QEMU EFI binary.

sudo apt install qemu-efi-aarch64

Copy the QEMU EFI file to the working directory,

cp /usr/share/qemu-efi-aarch64/QEMU_EFI.fd QEMU_EFI.bin

Run QEMU (3)

Finally, start the Xen emulation with:

qemu-system-aarch64 \
   -machine virt,gic_version=3 \
   -machine virtualization=true \
   -cpu cortex-a57 -machine type=virt \
   -smp 4 -m 4096 -display none \
   -serial mon:stdio \
   -bios QEMU_EFI.bin \
   -netdev user,id=hostnet0,hostfwd=tcp::2222-:22 -device virtio-net-device,netdev=hostnet0,mac=52:54:00:12:34:56 \
   -drive if=none,file=xenial-server-cloudimg-arm64-uefi1.img,id=hd0 -device virtio-blk-device,drive=hd0 -boot order=d

Launch Xen System

Once the QEMU emulation starts, keep on pressing the esc key to enter the UEFI manual, pick Boot Manager, then EFI Internal Shell.

In the shell, type the commands below to start Dom0, with the ls command, you are supposed to see kernel, xen.cfg, and virt-gicv3.dtb being listed.

Shell> fs0:
FS0:\> ls
FS0:\> xen.efi

References

  1. Xen ARM with Virtualization Extensions/qemu-system-aarch64.
  2. Building Xen on ARM.
  3. How do I run XEN on arm64 and Qemu 6.0.0 with Linux 4.20.11 as Dom0.
  4. qemu-alpine-arm64.sh.
Written on July 26, 2025