Zero Client: Boot kernel and root filesystem from network with a Raspberry Pi2 or Pi3

A Zero Client is a computer that has nothing on its permanent storage but a bootloader. Rather, it loads everything from the network.

With the method presented in this article, you will be able to boot a Raspberry Pi into a full Debian OS with nothing more on the SD card other than the Raspberry firmware files and the u-boot bootloader on a FAT file system. The Linux kernel and the actual OS will be served over the local ethernet network.

We will only focus on the Raspberry Pi 3, but the instructions should work with minor adaptations also on a Pi 2.

The following instructions assume that you have already built…

a full root file system for the Raspberry

a u-boot binary, and

a Linux kernel

… based on my previous blog post. Thus, you should already have the following directory structure:

1

2

3

4

5

~/workspace

|-rpi23-gen-image

|-linux

|-u-boot

|-raspberry-firmware

We will do all the work inside of the
~/workspace directory.

Preparation of the SD card

You will only need a small SD card with a FAT filesystem on it. The actual storage of files in the running OS will be transparently done over the network. Mount the filesystem on
/mnt/sdcard and do the following:

Copy firmware

Shell

1

cp./raspberry-firmware/*/mnt/sdcard

Copy u-boot bootloader

Shell

1

cp./u-boot/u-boot.bin/mnt/sdcard

Create config.txt

config.txt is the configuration file read by the Raspberry firmware blobs. Most importantly, it tells the firmware what kernel to load. “Kernel” is a misleading term here, since we will boot u-boot rather than the kernel.

Create
/mnt/sdcard/config.txt with the following contents:

1

2

3

4

5

6

7

8

9

10

avoid_warnings=2

# boot u-boot kernel

kernel=u-boot.bin

# run in 64bit mode

arm_control=0x200

# enable serial console

enable_uart=1

Make an universal boot script for the u-boot bootloader

To achieve maximum flexibility — to avoid the repetitive dance of manually removing the SD card, copying files to it, and re-inserting it — we will make an universal u-boot startup script that does nothing else than loading yet another u-boot script from the network. This way, there is nothing specific about the to-be-loaded Kernel or OS on the SD card at all.

Create a file
boot.scr.mkimage with the following contents:

1

2

3

4

5

6

7

8

9

10

setenv autoload no

setenv autostart no

dhcp

setenv serverip 192.168.0.250

tftp 0x100000 /netboot-${serial#}.scr

imi

source 0x100000

Replace the server IP with the actual static IP of your server. Note that this script does nothing else other than loading yet another script called
netboot-${serial#}.scr from the server.
serial# is the serial number which u-boot extracts from the Raspberry Pi hardware. This is usually the ethernet network device HW address. This way, you can have separate startup scripts for several Raspberry Pi’s if you have more than one. To keep the setup simple, set the file name to something predictable.

Compile the script into an u-boot readable image:

Shell

1

2

3

4

./u-boot/tools/mkimage-Aarm64-Olinux-Tscript\

-Cnone-a0x00-e0x00\

-dboot.scr.mkimage\

boot.scr

Copy
boot.scr to the SD card:

Shell

1

cpboot.scr/mnt/sdcard

The SD card preparation is complete at this point. We will now focus on the serving of the files necessary for boot.

Preparation of the file server

Do all of the following as ‘root’ user on a regular PC running Debian 9 (“Stretch”). This PC will act as the “server”. This server will serve the files necessary to network-boot the Raspberry.

The directory
/srv/tftp will hold …

an u-boot start script file

the kernel uImage file

and the binary device tree file.

… to be served by a TFTP server.

Shell

1

mkdir/srv/tftp

The directory
/srv/rootfs_rpi3 will hold our entire root file system to be served by a NFS server:

Shell

1

mkdir/srv/rootfs_rpi3

You will find installation instructions of both TFTP and NFS servers further down.

Serve the root file system

Let’s copy the pre-built root file system into the directory from where it will be served by the NFS server:

Shell

1

rsync-a./rpi23-gen-image/images/stretch/build/chroot//srv/rootfs_rpi3

(notice the slash at the end of the source directory)

Fix the root file system for network booting

Edit
/srv/rootfs_rpi3/etc/fstab and comment out all lines. We don’t need to mount anything from the SD card.

When network-booting the Linux kernel, the kernel will configure the network device for us (either with a static IP or DHCP). Any userspace programs attempting to re-configure the network device will cause problems, i.e. a loss of conncection to the NFS server. Thus, we need to prevent
systemd-networkd from managing the Ethernet device. Make the device unmanaged by removing the folowing ethernet configuration file:

Shell

1

rm/srv/rootfs_rpi3/etc/systemd/network/eth.network

If you don’t do that, you’ll get the following kernel message during boot:

1

nfs:server notresponding,still trying

That is because systemd has shut down and then re-started the ethernet device. Apparently NFS transfers are sensitive to that.

In case you want to log into the chroot to make additional changes that can only be done from within (e.g. running systemctl scripts etc.), you can do:

Shell

1

2

cp/usr/bin/qemu-aarch64-static/srv/rpi3fs/usr/bin

LANG=CLC_ALL=Cchroot/srv/rpi3fs

Serve Kernel uImage

In this step, we create a Linux kernel uImage that can be directly read by the u-boot bootloader. We read Image.gz directly from the Kernel source directory, and output it into the
/srv/tftp directory where a TFTP server will serve it to the Raspberry:

Shell

1

2

3

4

./u-boot/tools/mkimage-Aarm64-Olinux-Tkernel\

-Cgzip-a0x80000-e0x80000\

-d./linux/arch/arm64/boot/Image.gz\

/srv/tftp/linux-rpi3.uImage

Serve device tree binary

The u-boot bootloader will also need to load the device tree binary and pass it to the Linux kernel, so copy that too into the
/srv/tftp directory.

Here is another command that tells you if the TFTP server is listening:

Shell

1

netstat-l-u|grepftp

To get help about this server:
man tftpd

Test TFTP

If you want to be sure that the TFTP server works correctly, do the following on another PC:

Shell

1

apt-getinstall tftp-hpa

Then see if the server serves the Linux kernel we’ve installed before:

1

2

3

tftp192.168.0.250

tftp>get linux-rpi3.uImage

tftp>quit

You now should have a local copy of the linux-rpi3.uImage file.

Complete

If you’ve done all of the above correctly, you can insert the prepared SD card into your Raspberry Pi and reboot it. The following will happen:

The Raspberry Pi GPU will load the firmware blobs from the SD card.

The firmware blobs will boot the image specified in config.txt. In our case, this is the u-boot binary on the SD card.

The u-boot bootloader will boot.

The u-boot bootloader loads and runs the universal boot.scr script from the SD card.

The boot.scr downloads the specified secondary boot script from the network and runs it.

The secondary boot script …

downloads the device tree binary from the network and loads it into memory.

downloads the Linux kernel from the network and loads it into memory

passes the device tree binary to the kernel, and boots the kernel

the Linux kernel will bring up the ethernet device, connect to the NFS server, and load the regular OS from there.

Many things can go wrong in this rather long sequence, so if you run into trouble, check the Raspberry boot messages output on an attached screen or serial console, and the log files of the NFS and TFTP servers on your server PC.