The Raspberry Pi can be virtualized using libvirt and qemu-system-arm
just by running the RaspiOS image directly from libvirt. This guide installs the Raspberry Pi image to an LVM volume and boots directly from disk with the help of the Raspberry Pi kernel and the device tree blob with the help of libvirt
. The guide assumes that there exists a bridge currently configured to be used by multiple virtual machines.
Download the kernel image and the device tree blob from:
git clone https://github.com/dhruvvyas90/qemu-rpi-kernel
and place the resulting qemu-rpi-kernel
directory in /var/lib/libvirt/images/
.
Next, download the Raspberry Pi image corresponding to the kernel from the official Raspberry Pi repository:
wget https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2021-01-12/2021-01-11-raspios-buster-armhf-lite.zip
Now with the kernel image, the DTB and the RaspiOS image, RaspiOS system can be emulated under libvirt. Since this will be a permanently install, create a logical volume to hold the RaspiOS image:
lvcreate -L 32G -n rpi32.lan vms
where:
32G
is the size of the RaspiOS image to be installed to disk,rpi32.lan
is the name of the domain,vms
is the volume group that holds the logical volumesNow the Raspberry Pi image downloaded previously can be transferred to the LVM logical volume:
unzip 2021-01-11-raspios-buster-armhf-lite.zip dd if=2021-01-11-raspios-buster-armhf-lite.img of=/dev/vms/rpi32.lan bs=16M
such that libvirt will boot the image directly from hard-drive instead of booting the image file.
Aside the flexibility, LVM allows logical volumes to be cached such that a greater perfomance is to be expected compared to reading the image file off the drive.
Create a file named rpi32.lan
with the following contents:
<domain type='qemu' id='24'> <name>rpi32.lan</name> <uuid>d5cd64d6-fdcf-46fd-958b-063af33d04cb</uuid> <memory unit='KiB'>262144</memory> <currentMemory unit='KiB'>262144</currentMemory> <vcpu placement='static'>1</vcpu> <resource> <partition>/machine</partition> </resource> <os> <type arch='armv6l' machine='versatilepb'>hvm</type> <kernel>/var/lib/libvirt/images/qemu-rpi-kernel/kernel-qemu-4.19.50-buster</kernel> <cmdline>root=/dev/vda2 panic=1 console=ttyAMA0 console=ttyS0</cmdline> <dtb>/var/lib/libvirt/images/qemu-rpi-kernel/versatile-pb-buster.dtb</dtb> <boot dev='hd'/> </os> <cpu mode='custom' match='exact' check='none'> <model fallback='forbid'>arm1176</model> </cpu> <clock offset='utc'/> <on_poweroff>destroy</on_poweroff> <on_reboot>destroy</on_reboot> <on_crash>destroy</on_crash> <devices> <emulator>/usr/bin/qemu-system-arm</emulator> <disk type='block' device='disk'> <driver name='qemu' type='raw' cache='none' io='native'/> <source dev='/dev/vms/rpi32.lan'/> <backingStore/> <target dev='hda' bus='virtio'/> <alias name='virtio-disk0'/> </disk> <controller type='pci' index='0' model='pci-root'> <alias name='pci'/> </controller> <interface type='bridge'> <mac address='a2:f3:32:99:d3:af'/> <source bridge='br0'/> <target dev='vnet5'/> <model type='virtio'/> <alias name='net0'/> </interface> <serial type='tcp'> <source mode='bind' host='127.0.0.1' service='2445' tls='no'/> <protocol type='telnet'/> <target port='0'/> <alias name='serial0'/> </serial> <console type='tcp'> <source mode='bind' host='127.0.0.1' service='2445' tls='no'/> <protocol type='telnet'/> <target type='serial' port='0'/> <alias name='serial0'/> </console> </devices> <seclabel type='dynamic' model='dac' relabel='yes'> <label>+0:+0</label> <imagelabel>+0:+0</imagelabel> </seclabel> </domain>
and then import the domain using the libvirt
command line:
virsh -c qemu:///system define rpi32.lan
The configuration defines the following for the RaspiOS install:
/dev/vms/rpi32.lan
to which RaspiOS will be installed,/var/lib/libvirt/images/qemu-rpi-kernel/kernel-qemu-4.19.50-buster
as well as the Raspberry Pi DTB at /var/lib/libvirt/images/qemu-rpi-kernel/versatile-pb-buster.dtb
2445
and redirects all console output to the TCP server via the kernel parameters console=ttyAMA0 console=ttyS0
,virtio
driver and attaches the network device to the br0
bridgeThe virtual machine can now be started:
virsh -c qemu:///system start rpi32.lan
and telnet
can be used to connect over loopback to the virtual machine console:
telnet localhost 2445
If all goes well, the kernel should be booting and init should start setting up the system services.
All of these steps are optional but are mentioned here out of convenience.
Since the SSH daemon is not enabled by default, the daemon can be enabled by issuing:
systemctl enable ssh
and SSH can be started using:
systemctl start ssh
If necessary, the TCP port can now be closed by removing the following stanza from the rpi32.lan
domain:
<serial type='tcp'> <source mode='bind' host='127.0.0.1' service='2445' tls='no'/> <protocol type='telnet'/> <target port='0'/> <alias name='serial0'/> </serial> <console type='tcp'> <source mode='bind' host='127.0.0.1' service='2445' tls='no'/> <protocol type='telnet'/> <target type='serial' port='0'/> <alias name='serial0'/> </console>
The Raspberry Pi image can be cached via LVM caching which is documented separately on a different page.
Since LVM is used, more than likely the filesystem should be expanded to fill the logical volume defined. This can be done inside the virtualized Raspberry Pi OS by issuing:
raspi-config
and following the menus Advanced options
→Expand filesystem
. RaspiOS will then expand the filesystem and grant the install more space to work with. A restart is required.
Unfortunately using Versatile PB will limit the amount of RAM allocated to the VM to and the CPU count to a single CPU. Alternatively, an ARM virtual machine can be created using the default virt
machine type without the limitations of Versatile PB but without emulating the Raspberry Pi hardware.