Emulating Raspberry Pi with QEMU

Arvind Devarajan
Techscape
Published in
9 min readJun 25, 2022

--

Table of contents

Introduction
Downloading Raspberry Pi OS
QEMU
- Installation
- Raspberry Pi OS Image Creation
- Starting Raspberry Pi OS in QEMU
Increasing SD card size to about 8GB
Connecting to Raspberry Pi OS via SSH
- Enabling SSH in Raspberry Pi
- Connecting to Rpi via SSH (using password)
- Connecting to Rpi via SSH (without password)
References

Introduction

Well, a simple search on the internet for this throws up so many links — therefore let me put somethings aside.

As you may have already seen, the steps for such an emulation. None of them really worked directly for me, so here are the steps that worked.

Downloading and extracting files from Raspberry OS

These instructions can be performed on a native Linux system, or on a Windows Subsystem for Linux (WSL). They cannot be done on Windows.

Download Raspberry Pi OS and decompress

Download the Rapberry Pi OS from here. I chose the Raspberry Pi OS 32-bit Lite edition as I need to work only on the terminal. Of course, all that I mention below will also work for the desktop edition.

curl <link-to-raspios> -o raspios.img.xz
xz -dk raspios.img.xz # Let's keep the original image even
# after decompressing

Mount the image to a specific mount directory

sudo mkdir /mnt/rpi
sudo losetup -f --show -P $(pwd)/raspios.img
sudo mount /dev/loop<no>p1 /mnt/rpi

Extract out the kernel and all the dtb files from the image

mkdir dtbs
cp /mnt/rpi/kernel* .
cp /mnt/rpi/*.dtb dtbs

Remove password for the user “pi”

The latest versions of Raspberry Pi OS no longer have the default username-password combo as pi:raspberry. According to its documentation, the Raspberry Pi OS also allows for setting up of the default password in the image it writes.

In our case, since we manually downloaded the built image directly, we have not set any default password.

We therefore will simply mount the root-fs of the Raspberry Pi like how we mounted the kernel partition above:

sudo umount /mnt/rpi
sudo mount /dev/loop<no>p2 /mnt/rpi

Edit the file /mnt/rpi/etc/passwd to empty the password for the “pi” user:

sudo vi /mnt/rpi/etc/passwd

Search for this line in the file:

pi:x:1000:1000:,,,:/home/pi:/bin/bash

and remove the ‘x’ after “pi:”. Now save the file. By doing this, we have simply made it easy for us to log into the Raspberry Pi OS as soon as QEMU boots with it. The line should now look like this:

pi::1000:1000:,,,:/home/pi:/bin/bash

Unmount image — you’ll not need it any more

sudo umount /mnt/rpi
sudo losetup -d /dev/loop<no>

QEMU

Installation

Installation of Qemu normally involves installing multiple packages in a linux distribution. On Windows, a single download install all Qemu.

Typically, on linux distributions, the following packages are needed (tested in OpenSUSE, but these names are also on Ubuntu):

sudo zypper install qemu qemu-arm qemu-tools

Creating QEMU-compatible Raspberry Pi OS Image

The Rpi image we downloaded above is a raw format image. Converting this to qcow2 format makes it very performant while use with QEMU, and also occupies lesser space on host machine.

Additionally, we also increase the SD card size to 8GB so that we can use all that space for installation of additional software in run-time.

### Convert the raw image to QEMU qcow2 format
qemu-img convert -f raw -O qcow2 raspios.img raspios.qcow
### Resize the image to 8GB (it should be a power of 2)
qemu-img resize raspios.qcow 8G
### Remove existing raspberry pi os raw image so that we
### do not get confused and use it.
rm -f raspios.img

Starting Raspberry Pi OS

The instructions below work on both Windows and Linux (or WSL) systems. Of course, on Windows, you will not use the sudo before qemu-system-aarch64.

At the time of writing, I am using QEMU 7.0.0, but also tested this from QEMU version 5.2 — so I can guarantee that this works atleast from this version.

# Start the QEMU with Rpi emulation
sudo qemu-system-aarch64 -M raspi3b -display none -append "rw earlyprintk loglevel=8 console=ttyAMA0,115200 dwc_otg.lpm_enable=0 root=/dev/mmcblk0p2 rootdelay=1" -dtb ./dtbs/bcm2710-rpi-3-b-plus.dtb -sd raspios.qcow -kernel kernel8.img -m 1G -smp 4 -serial mon:stdio -usb -device usb-mouse -device usb-kbd -device usb-net,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22

This will start up QEMU with Raspberry Pi on the console. When it prompts you for the login, just type in pi and press <ENTER>. You’ll now blissfully drop into the Raspberry Pi OS’s terminal. You can get the root shell by simply typing sudo bash on the command prompt.

Brief explanation of the command line

Ok, let’s just try to understand what really went on above. Here is a quick explanation:

### We select the raspberry pi 3b 
-M raspi3b
### Since we start a lite image, we do not need QEMU graphics
-display none
### The path to the SD card image, mounted on the emulated
### SD card. This image was what we downloaded from the
### Raspbery Pi site, and also resized to 8G (i.e., a power of 2)
-sd raspios.qcow
### The device tree for Raspberry Pi and the kernel images.
### These were extracted from the first partition of the
### image above.
-dtb ./dtbs/bcm2710-rpi-3-b-plus.dtb -kernel kernel8.img
### Redirect all IO messages to/from a serial console.
### Specially, we also use "mon:" as an argument to -serial
### to make sure that ctrl-c, if used to stop a long running
### program in the emulated Rpi will not kill QEMU itself.
### "mon:" passes the ctrl-c directly to the guest OS (in our
### case, the rapberry pi OS). Note that this works only on
### Linux (or WSL) systems - on Windows, it still kills QEMU.
-serial mon:stdio
### Pass on some kernel command line: importantly, these
### parameters redirect the early boot messages to the serial
### terminal ttyAMA0 - this is the same terminal from which
### you invoked the QEMU. Since we redirect all IO messages to
### serial above, we will see all messages (early boot messages
### as well as your inputs) directly on the terminal you invoked
### the QEMU. Also, we mention that rootfs is on the
### second partition on the image (remember we modified the
### passwd file from this partition above?)
-append "rw earlyprintk loglevel=8 console=ttyAMA0,115200 dwc_otg.lpm_enable=0 root=/dev/mmcblk0p2 rootdelay=1"
### Memory and number of rocessors that we want QEMU to use.
### For -smp, we could use $(nproc) on Linux / WSL systems,
### or the number returned by "gci $env:NUMBER_OF_PROCESSORS"
### on windows machines.
-m 1G -smp 4
### Finally, we emulate all input devices as well as network
### devices as USB devices. Additionally, we also forward the
### port 22 in the guest (the port in which sshd listens) to
### port 5555 on the host. This will allow us to ssh into this
### Rapberry Pi OS from anywhere (including the localhost).
-usb -device usb-mouse -device usb-kbd -device usb-net,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22

Resizing the SD card size

With all this done to successfully start QEMU, we’ll hit the next problem: the size of the SD card (which hosts the OS and the files in the home-directory of the pi user) would be ridiculously small — about 1.6GB, and most of this is already used up by the base system!

So, we will have to now increase the size of the card so that any useful work can be done with this emulated Rpi.

Now. create a file /etc/udev/rules.d/90-qemu.rules in the guest Raspberry Pi OS:

sudo vi /etc/udev/rules.d/90-qemu.rules

Add these contents in the file:

KERNEL=="sda", SYMLINK+="mmcblk0"
KERNEL=="sda?", SYMLINK+="mmcblk0p%n"
KERNEL=="sda2", SYMLINK+="root"

Now, shutdown QEMU — sudo shutdown -h now — and restart it again with the command-line as above.

After QEMU has gotten back, log into it as pi again, and start raspi-config as root:

sudo raspi-config

In the curses-window that shows up, select “Advanced Options”:

In the advanced options, select “Expand Filesystem”:

This will expand the filesystem to fill up the original image size of about 8GB. After resizing, you will have to reboot QEMU to use all the expanded space.

Accessing the raspberry Pi from host

Enabling SSH on Raspberry Pi

The stock raspberry pi os does not enable ssh connections. The first thing we need to do is to enable that using the raspi-config tool:

sudo raspi-config

You will now see a curses tool. Use your arrow to select the “Interface Options”.

raspi-config tool

Press <ENTER> and select the SSH:

In the dialog that is shown, select “Yes”:

Press <OK> and then <Finish>. This enables SSH on your Raspberry Pi. Now, you can connect to your emulated Rpi using SSH.

Connecting via ssh (and using password)

Set a password for user pi

Since we start QEMU with port 22 in guest forwarded to 5555 on host, we can simply access the Rpi OS with ssh from host.

However, for this, we need to set the password for the pi user in raspberry (remember we removed the password for pi user above). We simply set the password for pi using passwd command in the guest OS.

Configure SSH client

Add this in ~/.ssh/config file in your host:

Host rpi
IdentityFile ~/.ssh/rpikey
HostName localhost
User pi
Port 5555

The IdentityFile you see above will be used if you decide to go for a password-less login via SSH (see below).

Accessing Raspberry Pi with ssh

Access the Rpi guest from the host using this SSH command:

ssh rpi

Password-less login via SSH keys

To enjoy a password-less login via SSH, do this (use these commands on the HOST or RPI):

HOST: Generate SSH keys:

We’ll use a password-less private key (hence the -N option below).

ssh-keygen -b 2048 -t rsa -q -N "" -f ~/.ssh/rpikey

HOST: Copy the public key into your rpi:

ssh-copy-id -i ~/.ssh/rpikey rpi

Note that the above command worked because you had already configured the ssh client with username and port numbers in the ~/.ssh/config file (see above). If you had not done that, use the following command (the options -p 5555 and the host identity pi@localhost will have to be used for all the rest of the command if the SSH client was not configured in ~/.ssh/config file):

ssh-copy-id -p 5555 -i ~/.ssh/rpikey pi@localhost

RPI: Enable password-less on the guest OS in Rapberry Pi

Do this on the original terminal of QEMU (and not on an ssh connected-terminal):

Edit the file /etc/ssh/sshd_config:

sudo vi /etc/ssh/sshd_config

Add the following (or uncomment the existing line and make its value to “no”):

PasswordAuthentication no

Optionally, if you also want to login via SSH as root without a password, you will also have to enable this:

PermitRootLogin without-password

After making these changes, restart your SSH server:

sudo /etc/init.d/ssh restart

HOST: Connect to emulated Rpi without password!

ssh rpi

References

There are a lot of places I got all this information. If something does not work for you, there could be additional information added in the original sources. The original sources of information are these:

--

--