Emulate Raspberry Pi4 on QEMU
I was looking into emulating the Raspberry Pi OS on QEMU. This short post summarizes my findings.
#raspberry #homelab #debian #qemu
I needed a virtual host to test Ansible scripts for my Raspberry Pi in the home lab. I found my way around this task by reading through the many great posts and examples online.
Extract kernel and device tree
First, you have to get the image (in my case, I used a modified build of the bookworm image) and extract the “kernel” and the “device tree” (to be honest, this was new for me when I read about this today).
We do this by mounting the boot partition and extracting the relevant files.
# Check image partitions
$ fdisk -l ./HashiPi-pi0.img
Setup loop device with image, scan partitions. This is easier than fiddling with fdisk and partition offsets for mounting.
$ sudo losetup -P /dev/loop0 HashiPi-pi0.img
$ sudo mount /dev/loop0p1 /mnt
# copy kernel and device tree binary (dtb)
$ cp /mnt/kernel8.img .
$ cp /mnt/bcm2711-rpi-4-b.dtb .
# unmount
$ sudo umount /mnt
$ sudo losetup --detach /dev/loop0
Patch device tree to enable USB controller
The “device tree binary” (.dtb) needs to be translated into a readable “device tree source” (.dts) file.
$ dtc -I dtb -O dts -o bcm2711-rpi-4-b.dts bcm2711-rpi-4-b.dtb
The .dts file can be patched to enable the usb controller. This is required, if we want to boot later using the usbnet device and port-forward (hostfwd
) the ssh port.
--- bcm2711-rpi-4-b.dts.orig 2025-09-21 15:05:59.304575294 +0200
+++ bcm2711-rpi-4-b.dts 2025-09-21 15:04:56.709581742 +0200
@@ -1450,7 +1450,7 @@
phy-names = "usb2-phy";
interrupt-names = "usb", "soft";
power-domains = <0x10 0x06>;
- status = "disabled";
+ status = "okay";
phandle = <0xbf>;
};
Unfortunately, we need to use this usb device, because all other emulated network devices are pci based which is not supported by QEMU for the Raspberry Pi
$ qemu-system-aarch64 -device help
...
Network devices:
name "e1000", bus PCI, alias "e1000-82540em", desc "Intel Gigabit Ethernet"
name "e1000-82544gc", bus PCI, desc "Intel Gigabit Ethernet"
name "e1000-82545em", bus PCI, desc "Intel Gigabit Ethernet"
name "e1000e", bus PCI, desc "Intel 82574L GbE Controller"
name "i82550", bus PCI, desc "Intel i82550 Ethernet"
name "i82551", bus PCI, desc "Intel i82551 Ethernet"
name "i82557a", bus PCI, desc "Intel i82557A Ethernet"
name "i82557b", bus PCI, desc "Intel i82557B Ethernet"
name "i82557c", bus PCI, desc "Intel i82557C Ethernet"
name "i82558a", bus PCI, desc "Intel i82558A Ethernet"
name "i82558b", bus PCI, desc "Intel i82558B Ethernet"
name "i82559a", bus PCI, desc "Intel i82559A Ethernet"
name "i82559b", bus PCI, desc "Intel i82559B Ethernet"
name "i82559c", bus PCI, desc "Intel i82559C Ethernet"
name "i82559er", bus PCI, desc "Intel i82559ER Ethernet"
name "i82562", bus PCI, desc "Intel i82562 Ethernet"
name "i82801", bus PCI, desc "Intel i82801 Ethernet"
name "igb", bus PCI, desc "Intel 82576 Gigabit Ethernet Controller"
name "ne2k_pci", bus PCI
name "pcnet", bus PCI
name "rocker", bus PCI, desc "Rocker Switch"
name "rtl8139", bus PCI
name "tulip", bus PCI
-> name "usb-net", bus usb-bus
name "virtio-net-device", bus virtio-bus # No 'virtio-bus' bus found for device 'virtio-net-device'
name "virtio-net-pci", bus PCI, alias "virtio-net"
name "virtio-net-pci-non-transitional", bus PCI
name "virtio-net-pci-transitional", bus PCI
name "vmxnet3", bus PCI, desc "VMWare Paravirtualized Ethernet v3"
We really need the usb device (virtio-net-device does not work either I tried), that's the reason for the patch.
A comment of the “interrupt.memfault blog” describes it nicely (very helpful community):
Note that for raspi4, the bcm2711-rpi-4-b.dtb devicetree file has disabled the USB controller. So, to enable USB keyboard & mouse, the .dtb file must be decompiled to .dts, patched, and recompiled back to .dtb.
Unfortunately, it's not only needed for keyboard and mouse, but also to make our network adapter work (for ssh port-forwarding).
Thaa patching.. Then recompiling into binary form with dtc
:
$ dtc -I dtb -O dts -o bcm2711-rpi-4-b.dts bcm2711-rpi-4-b.dtb
Would be really nice to have this usb controller enabled by default in the next Trixie Pi OS 🤞
Boot the image
For booting the image, I had to ensure two things for the kernel arguments for the Raspberry 4:
- Use the
ttyAMA1
console - Use the root partition
mmcblk1p2
Note that I'm not enabling the keyboard & mouse devices (as suggested in the references online), because I'm mainly interested to connect to the machine remotely via the ssh port fowarding:
$ sudo qemu-system-aarch64 \
-machine raspi4b -cpu cortex-a72 \
-dtb bcm2711-rpi-4-b-mod.dtb \ # use mod device tree
-m 2G -smp 4 \
-kernel kernel8.img -sd HashiPi-pi0.img \
-append "rw earlyprintk loglevel=8 console=ttyAMA1,115200 dwc_otg.lpm_enable=0 root=/dev/mmcblk1p2 rootdelay=1" \
-device usb-net,netdev=net0 \
-netdev user,id=net0,hostfwd=tcp::2222-:22
That's it. Is still a bit slow, but good enough to throw some Ansible against the wall and see if it sticks.
My idea here is to do less with the HashiCorp packer scripts and go back to more Ansible, because that might be more sustainable in the long run.. Let's see. Next I'm probably also going to give the Trixie (nightly image) a try.
References
- https://interrupt.memfault.com/blog/emulating-raspberry-pi-in-qemu
- https://www.qemu.org/docs/master/system/qemu-manpage.html
- https://www.qemu.org/docs/master/system/arm/raspi.html
- https://github.com/trinitronx/qemu-raspbian/blob/main/run-raspi4.sh
- https://github.com/trinitronx/qemu-raspbian/blob/main/bcm2711-rpi-4-b.dts.patch
- https://downloads.raspberrypi.org
- https://www.man7.org/linux/man-pages/man8/losetup.8.html