Sep 21, 2014

Serving up root file system over NFS on an Ubuntu development host

Until now, I've been writing the Zedboard root file system on SD card (and dealing with the vagaries of the SD card and card readers).  After a mixed success with the downloaded Zedboard HDMI and Linux tutorials, I've decided to bite the bullet and study the low level details of HDMI display on Zedboard.  Trial and error is inevitable; for fast turnaround, writing the root file system to the SD card is just too inconvenient.  I will keep the root file system on the development host and just serve them up over NFS.

Fixed IP address for the Ubuntu development host

I have been running with default networking settings on my Ubuntu PC since installing 14.04.1 LTS, which means I am using DHCP at the moment.
  1. I changed my home router to use 192.168.1.10~ for the DHCP address, so I can assign the single digits as fixed addresses.
  2. Added following to /etc/network/interfaces file
    iface eth0 inet static
    address 192.168.1.2
    netmask 255.255.255.0
    gateway 192.168.1.1
    dns-nameservers 75.75.75.75 8.8.8.8
  3. Restarted the host for the setting to change (because the following command, which used to work on Ubuntu as of 8 LTS or so did not work any more on Ubuntu 14 LTS)
    sudo /etc/init.d/networking restart
  4. Check
    henry@Zotac64:~$ ifconfig eth0; ping -c 1 google.com
    eth0      Link encap:Ethernet  HWaddr 00:01:2e:2f:25:26  
              inet addr:192.168.1.2  Bcast:192.168.1.255  Mask:255.255.255.0
    ...
    PING google.com (74.125.239.97) 56(84) bytes of data.
    64 bytes from nuq05s01-in-f1.1e100.net (74.125.239.97): icmp_seq=1 ttl=55 time=13.2 ms

NFS server on the Ubuntu host

Followed Ubuntu help page: https://help.ubuntu.com/community/SettingUpNFSHowTo.  On the server end:
  1. Install Ubuntu package
    $ sudo apt-get install nfs-kernel-server
  2. Confirm that NFS security is NOT applied in /etc/default/nfs-kernel-server
    # Do you want to start the svcgssd daemon? It is only required for Kerberos
    # exports. Valid alternatives are "yes" and "no"; the default is "no".
    NEED_SVCGSSD=""
  3. Confirm that NFS ID mapping uses nobody:nogroup in /etc/idmapd.conf
    [Mapping]

    Nobody-User = nobody
    Nobody-Group = nogroup
  4. And that these IDs are already registered in /etc/passwd and /etc/groups.  This way, the target does not need to have the same user and group as the host.
    $ grep nogroup /etc/group
    nogroup:x:65534:
    $ grep nobody /etc/passwd

    nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
  5.  Create the folders to export.
    $ sudo mkdir -p /export/root
    $ sudo chmod -R 777 /export
  6. Export the folders in /etc/exports to all hosts in 192.168.[0.1|2|3].0 subnet.  Note the shorter subnet mask length (by 2 bits)
    /export       192.168.0.0/22(rw,fsid=0,insecure,no_subtree_check,async)
    /export/root  192.168.0.0/22(rw,no_root_squash,no_subtree_check)
  7. Restart the server
    sudo service nfs-kernel-server restart

NFS enabled kernel

In the kernel folder, see Documentation/filesystems/nfs/nfsroot.txt.  This is what I did after reading it:

In (x|g|menu)config window, navigate to File systems --> check Network File Systems --> check "NFS client support NFS version 4" because the host's nfs-kernel-server supports NFS 4.  (a bit tricky: you have to explicitly CHECK--meaning, built-in rather than as kernel module--"NFS client support").  Once you check it, the "Root file system on NFS" appears, and you can check it.  The additional CONFIG entries for required for NFS root file system in .config file are shown below.  Apparently, the first line must replace the #CONFIG_NETWORK_FILESYSTEMS not set (some kind of override rule)
CONFIG_NETWORK_FILESYSTEMS=y
CONFIG_NFS_FS=y
CONFIG_NFS_V2=y
CONFIG_NFS_V3=y
# CONFIG_NFS_V3_ACL is not set
CONFIG_NFS_V4=y
# CONFIG_NFS_SWAP is not set
# CONFIG_NFS_V4_1 is not set

CONFIG_ROOT_NFS=y
CONFIG_NFS_USE_KERNEL_DNS=y
# CONFIG_NFSD is not set
CONFIG_NFS_COMMON=y

These changes can be appended to the defconfig used for the kernel config used for the kernel build (I used buildroot).

Target configuration

I explored how the Zedboard (Xilinx Zynq target) boots in another post, but to recap briefly:

  1. BootROM in the chip memory determines the boot mode (IO pins on Zynq) and loads the boot image from the boot device (in my case, SD card so far) into OCM (On Chip Memory), then starts running the FSBL.  Therefore, there is no programming involved here.
  2. FSBL (Xilinx SDK supplied first stage boot loader) loads the bitstream (also in the boot image) and programs the FPGA, then loads all ELF files in the boot partition (which contains the boot image) into respective load address in DRAM, then starts the 1st application.  In this case, there is only 1 ELF file: U-Boot, which is the SSBL (second stage boot loader for Linux).
  3. U-Boot determines the boot mode (in this case SD), and should execute the corresponding boot script (in this case sdboot).
    1. Regardless of the boot mode, a boot script should load the Linux kernel into memory.
    2. Optionally but usually for ARM, device tree binary is also loaded into a different memory address and passed to the kernel for use during boot.
    3. Also optionally, a initramfs (of the root file system) can be loaded into the memory.  Since the subject of this blog is serving RFS over NFS, this is clearly unnecessary.
  4. Linux kernel starts running the init process (PID 0), which launches all child processes.
I built U-Boot within Buildroot as well (so Buildroot for everything except the FSBL: SSBL, kernel, and root filesystem).  Unfortunately, I'd have to write some scripts to customize the default load scripts (in this case sdboot) within Buildroot.  But before automation, let's configure this manually.  By default, U-Boot waits 3 seconds before starting the bootup.  I halt it, and entered the following commands to set U-Boot environment variables:
setenv ipaddr 192.168.1.9
setenv serverip 192.168.1.2

setenv sdboot 'if mmcinfo; then run uenvboot; echo Copying Linux from SD to RAM...RTF in ext4 && fatload mmc 0 0x3000000 ${kernel_image} && fatload mmc 0 0x2A00000 ${devicetree_image} && bootm 0x3000000 - 0x2A00000; fi'

setenv bootargs 'console=ttyPS0,115200 ip=192.168.1.9:192.168.1.2:192.168.1.1:255.255.255.0 root=/dev/nfs nfsroot=192.168.1.2:/export/root/zed rw earlyprintk'

saveenv

Let's go over the above, in slow motion.  As discussed in the 1st section, the NFS server's static IP address is 192.168.1.2 in my home network.  I picked the static IP address of 192.168.1.9 for the target.  The sdboot command is straight from the "Ubuntu on Zedboard" tutorial.  The console device has to be in DTB, and earlyprintk is a good debugging option.  The NFS root is specified as /dev/nfs, with the other options trailing it.  I INTEND TO write files into the root file system, so the "rw" option for the file system is important.  Since I use static IP address for the target, I specify the gateway and netmask in the "ip" kernel boot parameter (the syntax is ip=<target IP>:<server IP>:<gateway IP>:<netmask>:<hostname>:<eth device>).  I wish I can also specify the DNS name server in the same line, but that can only be done in /etc/resolv.conf, which will require modifying the Buildroot filesystem skeleton.  I could specify DNS server ip in the U-Boot environment, but that will not get passed to the kernel.

A workaround is to reserve a static IP address for the target on the DHCP server (I use Netgear), and simply use DHCP in the bootarg:

setenv bootargs 'console=ttyPS0,115200 ip=dhcp root=/dev/nfs nfsroot=192.168.1.2:/export/root/zed rw earlyprintk'

Either way, if I let U-Boot start now, without the root file system actually copied to /export/root/zed, then the kernel will start but soon panic.  This leads us to...

Root file system

The root file system should be extracted out to the export folder; symbolic link is probably not going to work (TODO try out symbolic link).
~/work/zynq/buildroot/output/images$ mkdir /export/root/zed
~/work/zynq/buildroot/output/images$ sudo su
[sudo] password for henry: 

# tar -C /export/root/zed -xf /mnt/work/zynq/buildroot/output/images/rootfs.tar 

Extracting the tar as user root (rather than as regular user using sudo) and the no_root_squash nfs-kernel-server option were the keys to success, after many tries.