Using QEMU to emulate a Raspberry Pi

If you're building software for the Raspberry Pi (like I sometimes do), it can be a pain to have to constantly keep Pi hardware around and spotting Pi-specific problems can be difficult until too late.

One option (and the one I most like) is to emulate a Raspberry Pi locally before ever hitting the device. Why?

  • Works anywhere you can install QEMU
  • No hardware setup needed (no more scratching around for a power supply)
  • Faster feedback cycle compared to hardware
  • I can use Pi software (like Raspbian) in a virtual context
  • I can prep my "virtual Pi" with all the tools I need regardless of my physical Pi's use case

Given I'm next-to-useless at Python, that last one is pretty important as it allows me to install every Python debugging and testing tool known to man on my virtual Pi while my end-product hardware stays comparatively pristine.

Getting started

First, you'll need a few prerequisites:

QEMU (more specifically qemu-system-arm)

You can find all the packages for your chosen platform on the QEMU website and is installable across Linux, macOS and even Windows.

Raspbian

Simply download the copy of Raspbian you need from the official site. Personally, I used the 2017-08-16 version of Raspbian Lite, since I don't need an X server.

Kernel

Since the standard RPi kernel can't be booted out of the box on QEMU, we'll need a custom kernel. We'll cover that in the next step.

Preparing

Get your kernel

First, you'll need to download a kernel. Personally, I (along with most people) use the dhruvvyas90/qemu-rpi-kernel repository's kernels. Either clone the repo:

git clone https://github.com/dhruvvyas90/qemu-rpi-kernel.git

or download a kernel directly:

curl https://github.com/dhruvvyas90/qemu-rpi-kernel/raw/master/kernel-qemu-4.4.34-jessie

For the rest of these steps I'm going to be using the kernel-qemu-4.4.34-jessie kernel, so update the commands as needed if you're using another version.

Filesystem image

This step is optional, but recommended

When you download the Raspbian image it will be in the raw format, a plain disk image (generally with an .img extension).

A more efficient option is to convert this to a qcow2 image first. Use the qemu-img command to do this:

qemu-img convert -f raw -O qcow2 2017-08-16-raspbian-stretch-lite.img raspbian-stretch-lite.qcow

Now we can also easily expand the image:

qemu-img resize raspbian-stretch-lite.qcow +6G

You can check on your image using the qemu-img info command

Starting

You've got everything you need now: a kernel, a disk image, and QEMU!

Actually running the virtual Pi is done using the qemu-system-arm command and it can be quite complicated. The full command is this (don't worry it's explained below):

sudo qemu-system-arm \
-kernel ./kernel-qemu-4.4.34-jessie \
-append "root=/dev/sda2 panic=1 rootfstype=ext4 rw" \
-hda raspbian-stretch-lite.qcow \
-cpu arm1176 -m 256 \
-M versatilepb \
-no-reboot \
-serial stdio \
-net nic -net user \
-net tap,ifname=vnet0,script=no,downscript=no

So, in order:

  • sudo qemu-system-arm: you need to run QEMU as root
  • -kernel: this is the path to the QEMU kernel we downloaded in the previous step
  • -append: here we are providing the boot args direct to the kernel, telling it where to find it's root filesytem and what type it is
  • -hda: here we're attaching the disk image itself
  • -cpu/-m: this sets the CPU type and RAM limit to match a Raspberry Pi
  • -M: this sets the machine we are emulating. versatilepb is the 'ARM Versatile/PB' machine
  • -no-reboot: just tells QEMU to exit rather than rebooting the machine
  • -serial: redirects the machine's virtual serial port to our host's stdio
  • -net: this configures the machine's network stack to attach a NIC, use the user-mode stack, connect the host's vnet0 TAP device to the new NIC and don't use config scripts.

If it's all gone well, you should now have a QEMU window pop up and you should see the familiar Raspberry Pi boot screen show up.

Now, go get yourself a drink to celebrate, because it might take a little while.

Networking

Now, that's all well and good, but without networking, we may as well be back on hardware. When the machine started, it will have attached a NIC and connected it to the host's vnet0 TAP device. If we configure that device with an IP and add it to a bridge on our host, you should be able to reliably access it like any other virtual machine.

(on host) Find a bridge and address

This will vary by host, but on my Fedora machine, for example, there is a pre-configured virbr0 bridge interface with an address in the 192.168.122.0/24 space:

virbr0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.122.1  netmask 255.255.255.0  broadcast 192.168.122.255
        ether 00:00:00:1e:77:43  txqueuelen 1000  (Ethernet)

I'm going to use this bridge and just pick a static address for my Pi: 192.168.122.200

Reusing an existing (pre-configured) bridge means you won't need to sort your own routing

(in guest) Configure interface

NOTE: I'm assuming Stretch here.

Open /etc/dhcpcd.conf in your new virtual Pi and configure the eth0 interface with a static address in your bridge's subnet. For example, for my bridge:

# in /etc/dhcpcd.conf
interface eth0
static ip_address=192.168.122.200/24
static routers=192.168.122.254
static domain_name_servers=8.8.8.8 8.8.4.4

You may need to reboot for this to take effect

(in host) Add TAP to bridge

Finally, add the machine's TAP interface to your chosen bridge with the brctl command:

sudo brctl addif virbr0 vnet0

Now, on your host, you should be able to ping 192.168.122.200 (or your Pi's address).

Set up SSH

Now, in your machine, you can run sudo raspi-config and enable the SSH server (in the "Interfacing Options" menu at time of writing).

Make sure you change the password from default while you're there!

Finally, on your host, run ssh-copy-id [email protected] to copy your SSH key into the Pi's pi user and you can now SSH directly into your Pi without a password prompt.

Comments

comments powered by Disqus