Booting Linux from network

Yuhei Horibe
4 min readApr 12, 2020
Network booting

Motivation

In last article, I mentioned, booting Raspberry pi using “u-boot”. If you know boot process very well, you might have wondered, “why using u-boot? Changing boot arguments is enough to boot 64 bit Linux from USB”. That is true. But true reason for that is, to boot other nodes from network.

u-boot is capable of connecting to network, and be able to download various files from network. This is really useful if you are using devices like Zedboard. Zedboard has SoC (System on Chip), which has FPGA. FPGA is configurable hardware. If you are using this device, this might happen;

  1. Change hardware design, and generate new hardware bitstream and dtb
  2. Compile the kernel again with new device drivers
  3. Generate boot file, and put everything onto SD card

Step 3 needs “physical access” to the device, and this is really painful. But if, you configure u-boot once, the device can download all other things (hardware bitstream, device tree blob, kernel and rootfs) from network, it will save your work significantly. Also, it makes the back up really easy. If SD card is broken, you just have to recover quite a few boot related files.

To achieve this goal, 2 different steps are needed; “booting Linux kernel from network”, and “mounting root filesystem from network”.

Booting Linux kernel from network

Because u-boot can’t handle NFS (actually, u-boot can, but not really well supported), we need to use TFTP (Trivial File Transfer Protocol) for this. Through TFTP, u-boot can download “hardware bitstream for FPGA”, “device tree blob”, and “kernel image” from network. In this article, setting up TFTP server will be skipped (it’s quite easy, and you can easily find the article for that).

Create uEnv.txt with the contents like below;

serverip=192.168.150.250
ipaddr=192.168.150.210
tsrc_file=zed-node/boot.scr
tsrc_addr=0x200000
bootcmd=tftpboot ${tsrc_addr} ${tsrc_file}; source ${tsrc_addr};

“serverip” environment variable is pointing to TFTP server. Replace it for your TFTP server. “ipaddr” is the device’s static IP. If you want to configure it with DHCP, it’s even easier. Because I wanted to change “kernel bootargs” with other changes, I put “boot script” for u-boot on the TFTP server side as well. The last line will download the boot script image, and executes it. On TFTP server, you would have actual boot script like below (zed_boot.txt);

setenv tftp_dir "/zed-node"
setenv kernel_img "zImage"
setenv fdt_file "zynq-zed.dtb"
setenv bit_file "zynq_pl.bit.bin"
setenv bitsize 3dbafc
setenv tftp_ld_kernel "tftpboot ${kernel_addr_r} ${tftp_dir}/${kernel_img}"
setenv tftp_ld_dtb "tftpboot ${fdt_addr_r} ${tftp_dir}/${fdt_file}"
setenv tftp_ld_bit "tftpboot ${loadbit_addr} ${tftp_dir}/${bit_file}"
setenv load_fpga "echo fpga load 0 ${loadbit_addr} ${bitsize}; fpga load 0 ${loadbit_addr} ${bitsize};"
setenv nfs_rootpath "/srv/nfsroot/zed-node"
setenv nfs_serverip "192.168.150.250"
setenv bootargs "console=ttyPS0,115200n8 ip=192.168.150.210:192.168.150.250:192.168.150.254:255.255.255.0:zed-node::none:192.168.150.235::: rootfstype=nfs root=/dev/nfs nfsroot=${nfs_serverip}:${nfs_rootpath},tcp,nfsvers=4.2 rw rootwait devtmpfs.mount=0"
run tftp_ld_bit
run load_fpga
run tftp_ld_kernel
run tftp_ld_dtb
bootz ${kernel_addr_r} - ${fdt_addr_r}

Because I had problem with downloading bit file to FPGA, I would note this. Maybe because I’m running this boot script from memory, “${filesize}” environment variable doesn’t show correct size. I had to put bit file size manually like above to get it working.

Make u-boot image using “mkimage” tool like below;

u-boot-xlnx/tools/mkimage -A arm -O linux -T script -a 0 -e 0 -C none -d zed_boot.txt boot.scr

Copy “boot.scr” onto TFTP server.

When you put “hardware bitstream (zynq_pl.bit.bin)”, “device tree blob (zynq-zed.dtb)”, and Linux kernel image “zImage” onto TFTP server (under “zed-node” directory), it’s ready to boot.

Mounting root filesystem from NFS

This should be easy, but I got some problems, so I will note this.

To mount root filesystem from NFS, what you have to do is to point NFS location in kernel bootarg.

ip=192.168.150.210:192.168.150.250:192.168.150.254:255.255.255.0:zed-node::none:192.168.150.235::: rootfstype=nfs root=/dev/nfs nfsroot=${nfs_serverip}:${nfs_rootpath},tcp,nfsvers=4.2

Also note that, IP address set here will be used for this device’s IP address after booting, and you won’t be able to change this. Because, you will have mounted NFS using this, specific IP address, imagine what happens if you change IP address later (NFS will be dismounted, meaning, root filesystem will have gone…).

Other thing to note is, double check NFS version. By default, NFS server uses “version 4.2”. Check it by running “nfsstat -m” on server. If this doesn’t match, root filesystem won’t be mounted. Also note that, by default, NFS version 4.2 is not supported. If you want to use version 4.2, you have to configure kernel like below; In menuconfig, go to “Filesystem” -> “Network filesystem”, then you will see this.

Kernel menuconfig

NFS client support for NFSv4.2” will appear when you check “NFS client support for NFSv4.1”. If you want to skip this step, you can specify NFS version 3 in bootargs (nfsvers=3), but it didn’t work for me.

One more thing to note is that, you must specify “no_root_squash” in /etc/exports on NFS server side.

/srv/nfsroot/zed-node 192.168.150.210/32(rw,nohide,no_root_squash,no_subtree_check,async)

If you put root filesystem on NFS server, and configured /etc/exports, then now the device is ready to boot from TFTP/NFS server.

--

--