Booting Debian on the StarFive VisionFive 2022-06-18

Recently, I got my hands on a StarFive VisionFive Single Board Computer thanks to the RISC-V Developer Boards program. For a quick start, StarFive provides an image based on Fedora 33 that can be flashed to a microSD card. This boots without problems and gives you a working environment in a couple of minutes. However, with the installation based on Fedora 33 and apparently no recent updates available, this doesn’t feel like a future-proof setup. So I started looking for an alternative distribution, and one active port for the RISC-V architecture is provided by Debian.

There already exist a couple of instructions how to create a bootable Debian system image for the VisionFive. This includes a guide on the forum by Houge Langley and recently also a page on the Debian Wiki. At least the latter did not yet exist when I received my board, so I started experimenting based on the guide by Houge and the generic instructions for the RISC-V port of Debian. My goal was to simplify as much as possible, and ideally not create partitions just so that the boot configuration in uEnv.txt ends up on the third partition. This can be achieved by having a careful look at the U-Boot configuration for the JH7100 SoC and realizing that there is a second place this file can be stored: Before looking for /boot/uEnv.txt on the third partition (must be formatted as ext4), the fatload looks for the uEnv.txt in the root of the first FAT partition1. A secondary goal was to avoid copying binary files from the Fedora image, in particular grubriscv64.efi. Instead, I use the booti or bootefi command to let U-Boot give control to the Linux kernel directly.

In the remainder of this post, I’m going to give instructions how to create a bootable Debian system image. I will skip over most of the details, please consult the mentioned guides and wiki pages for more information.

Building the Linux Kernel

The heart of the system image is a Linux kernel that supports the board’s hardware. Sadly, it’s not (yet) possible to use Debian’s kernel image for this or even boot the latest mainline version. Instead, StarFive and in particular Emil Renner Berthing maintain a fork at with the necessary changes. To build a bootstable installation, you need to (cross-)compile the kernel sources. This is explained in the repository’s README and eventually boils down to the following two commands:

 $ make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- visionfive_defconfig
 $ make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu-

One word of caution: The default branch in the repository is based on mainline kernel versions. It should be thought of as “a collection of patches that will hopefully mature enough to be submitted upstream eventually”. As such, it is rebased regularly, which comes with the caveat of running Linux release candidates.

Preparing the System Image

I find it convenient to prepare the system image in a file, instead of directly on a microSD card:

 $ dd if=/dev/zero of=debian-visionfive.img bs=1M count=900

900 MB might sound little, but is enough to bootstrap a minimal Debian system.

Next, create two partitions with fdisk debian-visionfive.img or another tool of your choice. The first partition will be the /boot partition and 128 MB should be plenty (one kernel is around 20 MB). The second partition will the root of the system image, and should take the rest of available space. Upon flashing, you should obviously enlarge the root filesystem to fill the rest of the microSD card.

Now set up a loop device:

 $ sudo losetup --partscan /dev/loop0 debian-visionfive.img

The --partscan option to losetup is necessary to scan the partition table and make the loop0p1 and loop0p2 devices appear.

Finally create the filesystems, and mount them:

 $ sudo mkfs.fat /dev/loop0p1
 $ sudo mkfs.ext4 /dev/loop0p2

 $ sudo mkdir /mnt/debian
 $ sudo mount /dev/loop0p2 /mnt/debian/
 $ sudo mkdir /mnt/debian/boot
 $ sudo mount /dev/loop0p1 /mnt/debian/boot/

Please note that the /boot partition must be formatted as FAT, or U-Boot won’t find the uEnv.txt file. In principle, the root partition could be any filesystem, assuming the kernel built in the previous step has a driver for it. However, I didn’t test this and I think ext4 is a good default choice for a Single Board Computer.

Bootstrapping Debian

At this point, we need to (cross-)bootstrap the Debian system. This step requires static qemu-user to emulate RISC-V programs on x862. For Debian, the necessary packages are listed under the section “Creating a riscv64 chroot”. On Arch Linux, the official qemu-user package has dynamically linked binaries but there is the AUR package binfmt-qemu-static.

Before finally running deboostrap, I recommend setting up the keyring:

 $ wget
 $ gpg --import-options import-export --import archive_2022.key > debian-ports-archive-keyring.gpg

(On Debian, you can skip this step and instead install the debian-ports-archive-keyring package.)

Now bootstrap the system:

 $ sudo debootstrap --arch=riscv64 --keyring $PWD/debian-ports-archive-keyring.gpg \
	--include=debian-ports-archive-keyring sid /mnt/debian

This might take a while…

Final Setup

After deboostrap has finished, there are couple of steps required to make the image work well out-of-the-box. First, chroot into it to install at least an NTP client and set the root user’s password:

 $ sudo chroot /mnt/debian/
 # apt update
 # apt install systemd-timesyncd

 # passwd

You can also create other users and install the OpenSSH server, but it’s also possible to do this directly on the device once booted.

Next, configure DHCP on the Ethernet interface (assuming you want this) and delete the resolv.conf from the host environment:

 $ cat <<EOF | sudo tee /mnt/debian/etc/network/interfaces.d/eth0
auto eth0
iface eth0 inet dhcp
 $ /mnt/debian/etc/resolv.conf

Now set the hostname and make sure /boot is mounted automatically:

 $ echo Debian-VisionFive | sudo tee /mnt/debian/etc/hostname
 $ echo "/dev/mmcblk0p1 /boot vfat rw 0 2" | sudo tee /mnt/debian/etc/fstab

Afterwards, go the Linux build directory from the beginning and copy the kernel image as well as the device tree file:

 $ sudo cp arch/riscv/boot/Image /mnt/debian/boot/
 $ sudo cp arch/riscv/boot/dts/starfive/jh7100-starfive-visionfive-v1.dtb /mnt/debian/boot/

Now put the uEnv.txt in place to boot this kernel:

 $ echo "bootcmd=\
env set bootargs 'earlycon console=ttyS0,115200 stmmac.chain_mode=1 root=/dev/mmcblk0p2 rw rootwait'; \
load mmc 0:1 0xa0000000 /Image; load mmc 0:1 0xa8000000 /jh7100-starfive-visionfive-v1.dtb; \
bootefi 0xa0000000 0xa8000000" | sudo tee /mnt/debian/boot/uEnv.txt

Addendum 2022-06-19
The command originally said booti 0xa0000000 - 0xa8000000, which hands off control to the Linux kernel without EFI. bootefi uses the kernel’s EFI Boot Stub and seems to work equally well, so I guess it should be preferred.

Finally, unmount and detach the system image that you just created:

 $ sudo umount -R /mnt/debian
 $ sudo losetup -d /dev/loop0

Now flash debian-visionfive.img to a microSD card and enlarge the root filesystem. Then plug the microSD card into your VisionFive and boot.

  1. The loadbootenv variable expands to fatload mmc 0 0xa0000000 uEnv.txt when substituting the other variables. According to the documentation of fatload, the second parameter is <dev[:part]>. If part is omitted, it “defaults to 0 (whole device)” with no mention what happens if the device actually has a partition table. Neither is there a clear statement what happens if the filename parameter is a relative path. However, given that the setup I’m going to describe works, I assume that the command works the way I wrote above. 

  2. Unless you already have a RISC-V computer for your main development work… 

You do not need to agree with my opinions expressed in this blog post, and I'm fine with different views on certain topics. However, if there is a technical fault please send me a message so that I can correct it!