Running RISC-V with Hypervisor Extension on QEMU
RISC-V, Virtualization and the Hypervisor Extension
This section will talk about RISC-V ISA, the role of virtualization, and the hardware support for virtualization.
Running Linux Hypervisor and Linux Guest on QEMU
We will follow the following steps to cross compile the linux hypervisor and guest, compile a qemu with hypervisor extension and then run the hypervisor + guest on qemu.
- Environment used
- Getting the risc-v cross compilation toolchain
- Compile qemu-system-riscv64 that supports the hypervisor extension
- Use buildroot to build a linux hypervisor
- Build kvmtool
- Put everything together
- Full boot
- References
Environment
Ubuntu 20.04 with sudo rights.
$ apt install gcc make g++ rsync unzip wget
Getting the RISC-V Cross Compilation Toolchain
To cross compile for RISC-V linux, we need to use the cross compilation toolchain. It can be downloaded from github. For our use case, it is suffice to download one from their releases, instead of compiling from source. The releases can be found here. We will look for one with name riscv64-glibc-ubuntu-20.04-xxxxx-tar.gz
. You can download with wget
and unzip with tar -xvzf
.
Note: If your OS is not Ubuntu20.04, you should look for a compatible release here.
$ wget https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2023.04.29/riscv64-glibc-ubuntu-20.04-nightly-2023.04.29-nightly.tar.gz
$ tar -xvzf riscv64-glibc-ubuntu-20.04-nightly-2023.04.29-nightly.tar.gz
Compile qemu-system-riscv64 that Supports the Hypervisor Extension
By default, qemu-system-riscv64 does not support the hypervisor extension. Hence we need to compile qemu from source. First, get the essential packages following the qemu wiki guide.
$ apt install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build
It is possible to compile these packages from source and use them, but it would be quite complicated.
Get the repository:
$ git clone https://gitlab.com/qemu-project/qemu
$ cd qemu
$ git submodule init
$ git submodule update --recursive
Build qemu-system-riscv64 with hypervisor extension:
$ ./configure --target-list="riscv64-softmmu"
$ make -j $(nproc)
The built qemu executable will be under ./build/
.
We can check that h-extension is indeed enabled:
$ ./build/qemu-system-riscv64 -M virt -bios default -nographic
We can see that Boot HART Base ISA : rv64imafdch
, which includes h
.
Use ctrl-a
followed by ctrl-x
to terminate qemu.
Use buildroot to Build a Linux Hypervisor
There are many ways to build a linux kernel and a rootfs. Using buildroot is one of the less complicated option.
Get Buildroot: Download build root
Building linux kernel, busybox or buildroot use similar workflows. First
make xxxconfig
to generate a.config
file. Thenmake -j $(nproc)
to actually build it.
First, make sure that the riscv-gnu-toolchain
is on our PATH
.
$ PATH_TO_RISCV_TOOLCHAIN=/path/to/riscv-gnu-toolchain/bin
$ export PATH=$PATH_TO_RISCV_TOOLCHAIN:$PATH
Then use menuconfig to config.
$ export ARCH=riscv
$ export CROSS_COMPILE=riscv64-unknown-linux-gnu-
$ apt install libncurses-dev
$ make menuconfig
-
Under
Target options
, set Target Architecture to riscv -
Under
Kernel
, press space to select the linux kernel. Use the latest kernel version. UnderKernel configuration
, use the architecture default configuration. -
Under
Toolchain
, set toolchain type to external toolchain (Using the default option is fine, just that buildroot will download the toolchain for you).Toolchain has xxx support
should also be configured accordingly. -
Under
System configuration
, setRoot filesystem overlay directories
to a folder on your host machine. The content of the folder will appear in the filesystem in the built image. We will call thisHOST_OVERLAY_DIR
-
Under
Filesystem images
, set the filesystem type toext4
. Increase theexact size
for more spaces on the filesystem. -
Save and exit.
Now we can compile the kernel.
$ make -j $(nproc)
The kernel source as well as built items can be found under ./output/build/linux-?.?
. The kernel image will be under arch/riscv/boot/Image
and the kvm module will be under arch/riscv/kvm/kvm.ko
.
Note: to rebuild the linux kernel and kernel modules, use (under buildroot folder):
$ make -j $(nproc) linux-rebuild
The autogenerated booting instruction can be found under ./ board/qemu/riscv64-virt/readme.txt
. However, this does not seem to work. Instead, we will use the following:
$ QEMU=/mnt/data/qemu/build/qemu-system-riscv64
$ BR_OUT=/mnt/data/buildroot-2023.02/output
$ $QEMU -nographic -machine virt \
-m 2G \
-kernel $BR_OUT/images/Image -append "root=/dev/vda ro console=ttyS0" \
-drive file=$BR_OUT/images/rootfs.ext4,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0
The linux hypervisor should be able to boot with qemu. Use root
as the login. Use poweroff
to shutdown the linux hypervisor.
Next, we will run a guest linux on the hypervisor linux.
Build kvmtool
Firstly build libfdt. This is to be installed under the cross compilation toolchain’s sysroot, so apt installing won’t work.
$ git clone git://git.kernel.org/pub/scm/utils/dtc/dtc.git
$ apt install bison flex
Install dtc to the riscv toolchain sysroot.
$ export ARCH=riscv
$ export CROSS_COMPILE=riscv64-unknown-linux-gnu-
$ export CC="${CROSS_COMPILE}gcc -mabi=lp64d -march=rv64gc"
$ TRIPLET=$($CC -dumpmachine)
$ SYSROOT=$($CC -print-sysroot)
$ make libfdt
$ make NO_PYTHON=1 NO_YAML=1 DESTDIR=$SYSROOT PREFIX=/usr LIBDIR=/usr/lib64/lp64d install-lib install-includes
Note: check if $SYSROOT is pointing to the correct location. It should be something like
.../riscv/sysroot/
.
Then clone and build kvmtool
$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/will/kvmtool.git
$ export ARCH=riscv
$ export CROSS_COMPILE=riscv64-unknown-linux-gnu-
$ make lkvm-static
Note: You can edit the Makefile and point CC, LD OBJCOPY to the absolute location of the riscv toolchain cc, ld and objcopy.
Put everything together
Recall that we have HOST_OVERLAY_DIR
which allows us to put files onto the filesystem that the linux hypervisor is using. To boot a linux guest on top of it, we need some additional files.
- Image: This is the riscv64 linux kernel image that we will run as the guest OS.
- kvm.ko: This is the kernel module that we will insert into the hypervisor to enable kvm.
- lkvm-static: This is the vm manager that uses kvm to run a guest OS.
Copy the abovementioned files to HOST_OVERLAY_DIR
and rebuild using buildroot.
Note: Use
find . -name linux -type d
in buildroot to locate the linux build dir. Copy overarch/riscv/boot/Image
andarch/riscv/kvm/kvm.ko
.
Full boot
First boot the hypervisor. Make sure path to Image
and rootfs.ext4
is correct for your machine.
$ QEMU=/mnt/data/qemu/build/qemu-system-riscv64
$ BR_OUT=./br_output
$ $QEMU -nographic -machine virt \
-m 2G \
-kernel $BR_OUT/images/Image -append "root=/dev/vda ro console=ttyS0" \
-drive file=$BR_OUT/images/rootfs.ext4,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0
Login with the user name root
. ls
to see if the files are here.
# ls
(expecting Image kvm.ko lkvm-static)
Insert the kvm module and boot the guest
# insmod kvm.ko
# ./lkvm-static run -m 256 -c1 --console serial -p "console=ttyS0 earlycon" -k ./Image --debug
Now the guest should be booting, with the host filesystem available at /host
.
References
This blog is mainly based on these existing sources. Links to documentations are also included.
There is also a repository that helps to organize steps mentioned in this post. It can be found here. However, it is very fragile and full of global vars.