Headless Raspberry Pi OS virtualization, 64-bit edition
With Raspbian (now named Raspberry Pi OS) having been released as 64-bit, I can finally write a proper sequel to the previous post that dealt with virtualizing ARM/Linux distributions headlessly using QEMU.
You can read the original article here: Virtualizing Raspbian (or any ARM/Linux distro) headless using QEMU. Since the process is the same I will skip detailed explanations here.
Native emulation in QEMU
QEMU includes a raspi3b
machine type and emulates UART, SD, USB controllers and more.
This is enough for working headless usage.
With the root filesystem prepared and the appropriate files extracted from the boot partition the command line woud look as follows:
qemu-system-aarch64 -M raspi3b -kernel kernel8.img -dtb bcm2710-rpi-3-b.dtb \ -drive file=rootfs.qcow2,if=sd -usb -device usb-net,netdev=u1 -netdev user,id=u1 \ -append "root=/dev/mmcblk0 rw console=ttyAMA0" -nographic
The system boots up fine (with a few errors here and there) and is usable but I don't suggest using it like this.
A better alternative
The virt
machine type is much better suited for this, our plan is to attach both the disk and network via Virtio.
For the kernel (and modules) we'll grab the linux-aarch64 package from Arch Linux ARM.
Extracting the root filesystem into a virtual disk image
Download Raspberry Pi OS (64-bit) from the official website, then run the script below or follow the steps manually.
The only difference from before is that we have to unlock the "root" user so we can actually log in later.
#!/bin/bash -e input=2022-04-04-raspios-bullseye-arm64-lite.img [ -f $input ] mkdir mnt cp --reflink=auto $input source.img truncate -s 10G source.img echo ", +" | sfdisk -N 2 source.img dev=$(sudo losetup -fP --show source.img) [ -n "$dev" ] sudo resize2fs ${dev}p2 sudo mount ${dev}p2 ./mnt -o rw sudo sed '/^PARTUUID/d' -i ./mnt/etc/fstab sudo sed '/^root:/ s|\*||' -i ./mnt/etc/shadow remove_services=rpi-eeprom-update,hciuart,dphys-swapfile,rng-tools-debian sudo bash -c "rm -f \ ./mnt/etc/systemd/system/multi-user.target.wants/{$remove_services}.service \ ./mnt/etc/rc?.d/?01{$remove_services}" sudo umount ./mnt sudo chmod a+r ${dev}p2 qemu-img convert -O qcow2 ${dev}p2 rootfs.qcow2 sudo losetup -d $dev rm source.img; rmdir mnt
The kernel and initramfs
Kernel
tar -xvf linux-aarch64*.pkg.tar.* --strip-components=1 boot/Image.gz
Building an initramfs
The differences to the previous iteration of the script are:
Recompressing of zstd kernel modules as gzip (no busybox support for it)
Busybox isn't downloaded. You need to compile it for 64-bit ARM yourself and insert the path [1]
#!/bin/bash -e pkg=$(echo linux-aarch64-*.pkg.tar.*) [ -f "$pkg" ] mkdir initrd; pushd initrd mkdir bin dev mnt proc sys tar -xaf "../$pkg" --strip-components=1 usr/lib/modules rm -rf lib/modules/*/kernel/{sound,drivers/{net/{wireless,ethernet},media,gpu,iio,staging,scsi}} find lib/modules -name '*.zst' -exec zstd -d --rm {} ';' find lib/modules -name '*.ko' -exec gzip -9 {} ';' install -p /FILL/ME/IN/busybox-aarch64 bin/busybox cat >init <<"SCRIPT" #!/bin/busybox sh busybox mount -t proc none /proc busybox mount -t sysfs none /sys busybox mount -t devtmpfs none /dev for mod in virtio-pci virtio-blk virtio-net; do busybox modprobe $mod done busybox mount -o rw /dev/vda /mnt || exit 1 busybox umount /proc busybox umount /sys busybox umount /dev exec busybox switch_root /mnt /sbin/init SCRIPT chmod +x bin/busybox init bsdtar --format newc --uid 0 --gid 0 -cf - -- * | gzip -9 >../initrd.gz popd; rm -r initrd
Booting the virtual machine
With the initramfs built, we have all parts needed to actually run the VM:
qemu-system-aarch64 -M virt -cpu cortex-a53 -m 2048 -smp 4 -kernel Image.gz -initrd initrd.gz \ -drive file=rootfs.qcow2,if=virtio -nic user,model=virtio \ -append "console=ttyAMA0" -nographic
Debian GNU/Linux 11 raspberrypi ttyAMA0
and a login prompt.