QEMU Emulate Raspberry Pi 3 and 4
In this blog, QEMU is employed to emulate Raspberry Pi 3/4 in mac M1 host(it's also supposed to work in Windows/Linux with a little tweak). I will demonstrate two different ways to emulate Raspberry Pi 3 and Raspberry Pi 4 in respect. These two ways are different by using different QEMU machines as you would like to use:
-machine raspi3b
: raspberry pi 3b machine to emulate Raspberry Pi 3.-machine virt
: general arm machine to emulate Raspberry Pi 4.
In mac M1 with setting -machine virt
, I use the hardware acceleration by -accel hvf
. In Windows(x86_64), the hardware acceleration for aarch64
is not available, so removing the hardware acceleration will work as well in Windows.
For both of these two, we still need prepare some common steps before running QEMU:
- Extract the appropriate kernel, device tree or root filesystem
This blog will emulate Raspberry Pi using QEMU in mac M1 host using the new image 2023-05-03-raspios-bullseye-arm64-lite.img
.
The default user:pi
and password:raspberry
have been removed from this image. In order to log in, we have to write user
and password
to the image before booting. These steps can be skipped when booting previous images.
Prerequisites
- Docker
- be required in macOS
- can be skipped in Linux
- can use
wsl
as an alternative in Windows
- QEMU
homebrew
install in macOS
- Raspberry Pi image:
2023-05-03-raspios-bullseye-arm64-lite.img
Since I am in mac M1, and the raspberry pi
image which contains a fat
filesystem as boot and a ext4
filesystem as OS, we need write some configuration into it. So I will use a Docker Ubuntu container to do the operation on the the filesystem. There some other tools to do the like of these operations:
ext4fuse
is free and easy to install viahomebrew
, but it has limit as read-only access.ExtFS
fromParagon
supports read-write access while you need pay for it.virtual machine
Docker
in OSX make use ofvirtual machine
while it is quick and flexible to use.
Raspberry Pi image
cd ~
wget https://downloads.raspberrypi.org/raspios_arm64/images/raspios_arm64-2023-05-03/2023-05-03-raspios-bullseye-arm64-lite.img.xz
xz -d 2023-05-03-raspios-bullseye-arm64-lite.img.xz
Docker Ubuntu container
Mount the folder including 2023-05-03-raspios-bullseye-arm64-lite.img
docker run -it -d --privileged -v $PWD:/qemu --name ubuntu ubuntu
docekr exec -it ubuntu bash
Extracting Kernel and device tree
Operations all in Ubuntu container.
root@f36a3251391d:/qemu# fdisk -l 2023-05-03-raspios-bullseye-arm64-lite.img
Disk 2023-05-03-raspios-bullseye-arm64-lite.img: 1.96 GiB, 2101346304 bytes, 4104192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x544c6228
Device Boot Start End Sectors Size Id Type
2023-05-03-raspios-bullseye-arm64-lite.img1 8192 532479 524288 256M c W95 FAT32 (LBA)
2023-05-03-raspios-bullseye-arm64-lite.img2 532480 4104191 3571712 1.7G 83 Linux
- The first partition is boot filesystem.
- The second partition is real root filesystem.
All the data we need is in the first partition, to do the operation is mounting it.
The offset of the first partition: 8192 * 512 = 4194304,
root@f36a3251391d:/qemu# mount -o loop,offset=4194304 2023-05-03-raspios-bullseye-arm64-lite.img /mnt/rpi-boot/
root@f36a3251391d:/qemu# ls -ls /mnt/rpi-boot/
total 30244
20 -rwxr-xr-x 1 root root 18693 Apr 5 11:32 COPYING.linux
2 -rwxr-xr-x 1 root root 1594 Apr 5 11:32 LICENCE.broadcom
30 -rwxr-xr-x 1 root root 30390 Apr 5 11:32 bcm2710-rpi-2-b.dtb
32 -rwxr-xr-x 1 root root 32753 Apr 5 11:32 bcm2710-rpi-3-b-plus.dtb
32 -rwxr-xr-x 1 root root 32142 Apr 5 11:32 bcm2710-rpi-3-b.dtb
30 -rwxr-xr-x 1 root root 30285 Apr 5 11:32 bcm2710-rpi-cm3.dtb
32 -rwxr-xr-x 1 root root 31318 Apr 5 11:32 bcm2710-rpi-zero-2-w.dtb
32 -rwxr-xr-x 1 root root 31318 Apr 5 11:32 bcm2710-rpi-zero-2.dtb
52 -rwxr-xr-x 1 root root 52593 Apr 5 11:32 bcm2711-rpi-4-b.dtb
52 -rwxr-xr-x 1 root root 52682 Apr 5 11:32 bcm2711-rpi-400.dtb
38 -rwxr-xr-x 1 root root 38182 Apr 5 11:32 bcm2711-rpi-cm4-io.dtb
52 -rwxr-xr-x 1 root root 53202 Apr 5 11:32 bcm2711-rpi-cm4.dtb
50 -rwxr-xr-x 1 root root 50504 Apr 5 11:32 bcm2711-rpi-cm4s.dtb
52 -rwxr-xr-x 1 root root 52476 Apr 5 11:32 bootcode.bin
2 -rwxr-xr-x 1 root root 154 May 3 03:11 cmdline.txt
4 -rwxr-xr-x 1 root root 2109 May 3 02:53 config.txt
...
2 -rwxr-xr-x 1 root root 145 May 3 03:11 issue.txt
8028 -rwxr-xr-x 1 root root 8219600 Apr 5 11:32 kernel8.img
...
To run QEMU we will need the kernel and device tree, so let’s copy them out:
root@f36a3251391d:/qemu# cp /mnt/rpi-boot/kernel8.img .
root@f36a3251391d:/qemu# cp /mnt/rpi-boot/bcm2710-rpi-3-b.dtb .
Setting up default user
Operations all in docker container.
Now in order to set up user and enable ssh in default, we need write files into /userconf
and /ssh
under the boot filesystem mounted as /mnt/rpi-boot/
.
Set up a default user:pi
and password:raspberry
.
Hash password raspberry
using openssl
,
root@f36a3251391d:/qemu# openssl passwd
Password:
Verifying - Password:
$1$d...AvcL$wqfUqTIauUP1TVJ/uU1td0
root@f36a3251391d:/qemu# echo 'pi:$1$d...AvcL$wqfUqTIauUP1TVJ/uU1td0' | tee /mnt/rpi-boot/userconf
Enable ssh
,
root@f36a3251391d:/qemu# touch /mnt/rpi-boot/ssh
root@f36a3251391d:/qemu# umount /mnt/rpi-boot
Running QEMU
Emulate Raspberry Pi 3
Now switch back to the host macOS to run QEMU
,
Resize the image to the next power of 2 size,
The original size,
❯ stat -f%z 2023-05-03-raspios-bullseye-arm64-lite.img
2101346304
To resize to 4GB
,
qemu-img resize ./2023-05-03-raspios-bullseye-arm64-lite.img 4G
qemu-system-aarch64 \
-machine raspi3b \
-cpu cortex-a72 \
-nographic \
-m 1G \
-smp 4 \
-dtb bcm2710-rpi-3-b.dtb \
-kernel kernel8.img \
-append "rw earlyprintk loglevel=8 console=ttyAMA0,115200 dwc_otg.lpm_enable=0 root=/dev/mmcblk0p2 rootdelay=1" \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device usb-net,netdev=net0 \
-sd 2023-05-03-raspios-bullseye-arm64-lite.img
Options in detail:
-machine raspi3b
: use raspberry pi 3 machine.-append
:console=ttyAMA0
: output the VM std to QEMU console.root=/dev/mmcblk0p2
: mount real root filesystem to/dev/mmcblk0p2
(the second partition ofmmcblk0
) as we-sd xx
will be mounted to/dev/mmcblk0
.
-netdev user,id=net0,hostfwd=tcp::2222-:22
: network mapping host port2222
to the VM22
-device usb-net,netdev=net0
: exposenetdev=net0
asusb-net
in the raspberry pi 3 machine.-sd 2023-05-03-raspios-bullseye-arm64-lite.img
:sd
drive is available in the raspberry pi 3 machine.
Emulate Raspberry Pi 4 with virt
We will use generic virtual machine virt
to act as raspi4
, since there is no raspi4
machine defined in QEMU official machines. However you can still use raspi3
to act as raspi4
as they are same!
Hardware Acceleration can be enable in virt
machine by using -accel hvf
option in my mac M1 host as it's arm-based.
So virt
will bring high performance and increase efficiency!
After tuning options and searching from many resources, the operational setting for QEMU to emulate is,
- Use
ubuntu-22.04.3-preinstalled-server-arm64+raspi.img
, of which the default user isubuntu
and password isubuntu
.
kernel="$PWD/ubuntu-22.04.3-preinstalled-server-arm64+raspi-boot/vmlinuz"
initrd="$PWD/ubuntu-22.04.3-preinstalled-server-arm64+raspi-boot/initrd.img"
img="$PWD/ubuntu-22.04.3-preinstalled-server-arm64+raspi.img"
For SCSI hard disk
This storage device file will be named /dev/sdX
,
qemu-system-aarch64 \
-machine virt \
-accel hvf \
-cpu host \
-smp 4 \
-m 4G \
-nographic \
-kernel $kernel \
-initrd $initrd \
-append "earlyprintk loglevel=8 root=/dev/sda2 rootfstype=ext4 rw console=ttyAMA0" \
-drive file=$img,format=raw,if=none,id=drive0 \
-device virtio-scsi-pci,id=scsi \
-device scsi-hd,drive=drive0,bus=scsi.0 \
-netdev user,id=mynet,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=mynet
Options in detail:
-accel hvf
: hardware acceleration in mac M1. Don't use in x86_64 host.-cpu host
: change to-cpu cortex-a72
when no hardware acceleration available such as in x86_64 host.-append
root=/dev/sda2
: the second partition of theubuntu-22.04.3-preinstalled-server-arm64+raspi.img
disk image hold the real root filesystem.
-initrd $initrd
- the boot loader works using configuration like
vmlinuz initrd=initrd.img root=/dev/sda2
.
- the boot loader works using configuration like
For virtual disk
storage device
This storage device file will be named /dev/vdX
,
qemu-system-aarch64 \
-machine virt \
-accel hvf \
-cpu host \
-smp 4 \
-m 4G \
-nographic \
-kernel $kernel \
-initrd $initrd \
-append "earlyprintk loglevel=8 root=/dev/vda2 rootfstype=ext4 rw console=ttyAMA0" \
-drive file=$img,format=raw,if=none,id=drive0 \
-device virtio-blk-pci,drive=drive0 \
-netdev user,id=mynet,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=mynet
For NVMe
storage device
This storage device file will be named /dev/nvmeX
,
qemu-system-aarch64 \
-machine virt \
-accel hvf \
-cpu host \
-smp 4 \
-m 4G \
-nographic \
-kernel $kernel \
-append "earlyprintk loglevel=8 root=/dev/nvme0n1p2 rootfstype=ext4 rw console=ttyAMA0" \
-drive file=$img,format=raw,if=none,id=drive0 \
-device nvme,drive=drive0,serial=deadbeaf1 \
-netdev user,id=mynet,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=mynet
Options in detail:
- no
-initrd $initrd
- the boot loader works using configuration like
vmlinuz root=/dev/nvme0n1p2
. - we directly mount the real filesystem
/dev/nvme0n1p2
, skipping to mount the initial RAM disk. - I test other type storage device must binding
-initrd $initrd
while there is no need forNVME
. In my assumption, thosestorage devices
need to be configured in theinitramfs
.
- the boot loader works using configuration like
For usb storage
This storage device file will be named /dev/sdX
,
qemu-system-aarch64 \
-machine virt \
-cpu cortex-a57 \
-smp 4 \
-m 4G \
-no-reboot \
-nographic \
-kernel $kernel \
-initrd $initrd \
-append "earlyprintk loglevel=8 root=/dev/sda2 rootfstype=ext4 console=ttyAMA0 raid=noautodetect" \
-device usb-ehci \
-device usb-storage,drive=disk0 \
-drive file=$img,format=raw,if=none,id=disk0 \
-device virtio-net-pci,netdev=mynet \
-netdev user,id=mynet,hostfwd=tcp::2222-:22
Options in detail:
-device usb-ehci
: usb bus -> PCI bus-device usb-storage
: usb storage device -> usb bus
Test Raspberry Pi VM
Log into the Raspberry Pi via ssh
from the macOS host,
❯ ssh -p 2222 pi@localhost
The authenticity of host '[localhost]:2222 ([127.0.0.1]:2222)' can't be established.
ED25519 key fingerprint is SHA256:6igL6iaigBCszv8m6nyNl+tsB2siV/tL+TRQANC6nBw.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[localhost]:2222' (ED25519) to the list of known hosts.
pi@localhost's password:
Linux raspberrypi 6.1.21-v8+ #1642 SMP PREEMPT Mon Apr 3 17:24:16 BST 2023 aarch64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Sep 22 16:30:58 2023
SSH is enabled and the default password for the 'pi' user has not been changed.
This is a security risk - please login as the 'pi' user and type 'passwd' to set a new password.
pi@raspberrypi:~ $
Resources
Emulating a Raspberry Pi in QEMU | InterruptEmulating a Raspberry Pi in QEMU
How to emulate block devices with QEMU
Emulation of block devices — Das U-Boot unknown version documentation