Android 6 Marshmallow over Network File System (NFS) on the Wandboard

This tutorial assumes you have already built and deployed Android 6.0.1 for the Wandboard on an SD-card, according to our preceeding blog post in this series. For quick reference, we are using the Android source 6.0.1 for the Wandboard from the official website. The source code is assumed to be extracted to /opt/android/wand, and our host system is Ubuntu 16.04.

Android system development on an SD-card can be quite cumbersome. Even though there is the possibility of pushing and pulling files via Android Debug Bridge (ADB) between the target device and the host computer, more often than not, you still need to replace the contents of the SD-card manually. In the following, we will show you how you can boot the Linux kernel via TFTP and the Android operating system over NFS.

Step 1: Setup tftp-server on the host

The TFTP server will supply the Linux kernel and the device tree files.

# Install tftp server
sudo apt-get install tftpd-hpa

# Config tftp server
sudo vim /etc/default/tftpd-hpa

In our example, we are setting the tftp root directoy to “/srv/tftpboot”, and leave the default user as “tftp”. For reference, our configuration file looks like this:

# /etc/default/tftpd-hpa


Create the tftp root directory and start the server:

# Create tftp root directory
sudo mkdir /srv/tftpboot

# Change owner of tftp server directory to the tftp user (or whichever user you specifed above)
sudo chown -R tftp /srv/tftpboot

# Start tftp server
sudo service tftpd-hpa start

# Check the status of the tftp server
sudo service tftpd-hpa status

In the same manner, you can also stop and restart the tftp server. Now copy the kernel and the device tree files to the tftp root directory

# switch to kernel directory
cd /opt/android/wand/kernel_imx

# copy kernel
sudo cp arch/arm/boot/zImage /srv/tftpboot

# copy device tree files for Wandboard Quad and/or Dual
sudo cp arch/arm/boot/dts/imx6q-wandboard.dtb /srv/tftpboot
sudo cp arch/arm/boot/dts/imx6dl-wandboard.dtb /srv/tftpboot

Step 2: Setup nfs share on the host

# install nfs server
sudo apt-get install nfs-kernel-server

# config nfs server
sudo vim /etc/exports

We will use /srv/nfsandroid as the root directory for our Android share. Our /etc/exports configuration file looks like this:

# /etc/exports: the access control list for filesystems which may be exported
#       to NFS clients.  See exports(5).
# Example for NFSv2 and NFSv3:
# /srv/homes       hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
# Example for NFSv4:
# /srv/nfs4        gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes  gss/krb5i(rw,sync,no_subtree_check)


Now create the nfs share directory, export the NFS table, and start the server:

# create nfs share directory
sudo mkdir /srv/nfsandroid

# create nfs table
sudo exportfs -a

# start nfs server
sudo service nfs-kernel-server start

We need to populate our nfs share with the Android operating system. There are multiple ways to achieve this. The approach chosen here is to extract the ramdisk (containing the root file system) to the nfs share, and then mount the Android system image inside of it. By the way, if you were to merely copy the contents of the root directory from the out directory, the system will not boot beyond init, because the permissions of the *.rc files are not correct yet.

user@host:~$ cd /srv/nfsandroid
user@host:/srv/nfsandroid$ gunzip -c  ~/opt/android/wand/out/target/product/wandboard/ramdisk.img | sudo cpio -i
user@host:/srv/nfsandroid$ sudo mount /opt/android/wand/out/target/product/wandboard/system_raw.img system

If you want to unmount system_raw.img again and get a “device is busy” error, you need to stop the NFS server beforehand with:

sudo service nfs-kernel-server stop

After setting up the bootloader environment in the next step, this should already result in a semi-successful boot. Further changes needed are detailed in Step 4.

Step 3: Configure u-boot bootloader arguments to boot from tftp/nfs server

On the Wandboard, we need an SD card with a u-boot bootloader. Remember, the SPL must to be copied at 1k offset into the SD card, and the u-boot.img placed onto the first FAT32 partition.

cd /opt/android/wand
# use the command 'lsblk' to determine the device name of the sdcard
# and replace /dev/sdX accordingly
sudo dd if=./bootable/bootloader/uboot-imx/SPL of=/dev/sdX bs=1k seek=1

Also copy the u-boot.img file to the first FAT32 partition (named BOOT in our example)

cp ./bootable/bootloader/uboot-imx/u-boot.img /media/$USER/BOOT

Startup the Wandboard with the SD-card, and in a serial console, hit enter to stop u-boot from auto-booting.

We now need to change a few u-boot environment variables. You can inspect the current variables with the “printenv” command. Note that there is already an environment variable “netboot”, along with “netargs” with the following contents:

  echo Booting from net ...;
  run netargs;
  if test ${ip_dyn} = yes; then
    setenv get_cmd dhcp;
    setenv get_cmd tftp;
  ${get_cmd} ${image};
  if test ${boot_fdt} = yes || test ${boot_fdt} = try; then
    if ${get_cmd} ${fdt_addr} ${fdtfile}; then
      bootz ${loadaddr} - ${fdt_addr};
      if test ${boot_fdt} = try; then
        echo WARN: Cannot load the DT;

  setenv bootargs console=${console},${baudrate}
  root=/dev/nfs ip=dhcp

If we run the “netboot” command, it will first set the boot arguments by running netargs. Depending on the variable “ip_dyn”, the get command for the kernel image is either set to “dhcp” or “tftp”, after which the kernel image is pulled, along with the device tree file. Finally, the zImage is booted with the bootz command. Easy, right? 🙂

As mentioned, we need to set a few environment variables to boot from our nfs server. I’ll show all the needed commands, and explain them later on:

=> setenv ipaddr
=> setenv serverip
=> setenv nfsroot /srv/nfsandroid
=> setenv ip_dyn no
=> setenv bootfile zImage
=> setenv fdtfile imx6q-wandboard.dtb
=> setenv netargs 'setenv bootargs ${bootargs_base} console=${console},${baudrate} root=/dev/nfs rw ip=dhcp nfsroot=${serverip}:${nfsroot},v3,tcp'
=> env save

The variable “ipaddr” sets the IP address of the target, and must be set to an IP address for the tftp command. If you don’t know which IP address to use, find out with the dhcp command:

=> setenv autoload no
=> dhcp
BOOTP broadcast 1
DHCP client bound to address (4ms)

Here, the variable “autoload” is temporarily set to no, so that dhcp does not automatically try to load a kernel image when it is called. You may wonder why even use tftp instead of the dhcp command for downloading our kernel image? The reason for this is that – at least for me – every time the dhcp command is used, the serverip variable is reset to So by setting “ip_dyn” to to “no”, we choose the tftp command for downloading the kernel and device tree files, which seems to work better here. (It’s very well possible that dhcp would work somehow too. In either case, tftp is fine for now)

The variable “serverip” is set to our host’s IP address. It is also read by tftp, see the official U-Boot wiki section on U-Boot Environment Variables. As mentioned, “ip_dyn” is set to “no” so that tftp instead of dhcp is used. “bootfile” is the default image to load for tftp (again, check the wiki section for more information). “fdtfile” is the name of our device tree binary on the tftp server.

We are also slightly altering “netargs” to mount the root directory as read-write (rw) and by adding ${bootargs_base}, which contains the init location, along with video arguments, etc. (This was probably just forgotten in the default settings).

Needless to say, adapt “ipaddr” and “serverip” accordingly, then save the environment.

=> env save
Saving Environment to MMC...
Writing to MMC(0)... done

If something went wrong, you may reset to the default values with “env default -a”. Otherwise, you can now manually boot via network by running netboot:

=> run netboot

Furthermore, if you inspect the “bootcmd” variable – which is run by default when the device boots up – you’ll notice that among other things it will try to find and load a ramdisk and kernel image from the the first FAT partition. However, if this fails, it will resort to “run netboot” in the end. So in case you still have a zImage and uramdisk.img on the SD card, you can simply delete or rename those files, after which it will always boot from TFTP/NFS upon rebooting.

Step 4: Changes to Android to fix NFS booting

As mentioned, we should already have a somewhat successful boot. At least, we need to adapt the fstab.freescale file. When booting from SD-card, the system folder is mounted from a separate partition. However, when booting via NFS with the above setup, we already have the system folder inside our NFS root directory. So delete or comment out the line for mounting the system

# Android fstab file.
#<src>                                                  <mnt_point>         <type>    <mnt_flags and options>                       <fs_mgr_flags>
# The filesystem that contains the filesystem checker binary (typically /system) cannot
# specify MF_CHECK, and must come before any filesystems that do specify MF_CHECK

/devices/soc0/soc.0/2100000.aips-bus/2190000.usdhc/mmc_host* auto auto defaults voldmanaged=sdcard:auto
/devices/soc0/soc.0/2100000.aips-bus/2184200.usb/ci_hdrc.1*  auto auto defaults voldmanaged=usb:auto
/devices/soc0/soc.0/2200000.sata/ata1* auto auto defaults voldmanaged=sata:auto

#/dev/block/mmcblk2p2    /system      ext4    rw,barrier=1                                                                               wait,verify
/dev/block/mmcblk2p4    /data        ext4    nosuid,nodev,nodiratime,noatime,nomblk_io_submit,noauto_da_alloc,errors=panic    wait,check
/dev/block/mmcblk2p3    /cache       ext4    nosuid,nodev,nomblk_io_submit                                                    wait,check
/dev/block/zram0 none swap defaults zramsize=314572800

Of course, this also means that we should have an SD-card with data and cache partition inserted. You may need to change the partition numbers p4 and p3 for your purpose. The above fstab file should be OK if you have set up your SD card as described in this previous post. For completeness’ sake, here is what is assumed to be on your SD card:

  • 1MB offset or more before first partition.
    The u-boot SPL is placed here at an offset of 1024 bytes.
  • Partition 1: BOOT, VFAT, ~32MB
    u-boot.img should be copied here, and will be loaded by SPL.
  • Partition 2: SYSTEM, EXT4, ~1024MB
    This holds the system partition, and is not loaded with the above fstab file.
  • Partition 3: CACHE, EXT4, ~1024MB
    Cache partition
  • Partition 4: DATA, EXT4, remaining size
    Data partition

Now we can try to boot again. You should see a fair amount of log output, but after a short time, the system will hang with the error message

nfs: server not responding, still trying
nfs: server not responding, still trying
nfs: server not responding, still trying
nfs: server not responding, timed out

Finding out what exactly goes wrong here is kind of hard, and is a great chicken-and-egg problem if I have ever seen one. Obviously, there is something wrong with the network. However, as soon as we see the error message, it’s already too late for debugging since our system with all debugging tools is only available via network. As it turns out in the end, the error stems from the “netd” service. The corresponding source code is found inside the Android source under system/netd/server/CommandListener.cpp and system/netd/server/RouteController.cpp.

When inspecting CommandListener.cpp, you can already see that something was patched here to read a system property “ro.nfs.mode”. This actually comes from a Freescale Android KK443 (KitKat) patch release. Setting this property does not fix our issue just yet, but certainly points us in the right direction. After a lot of trial & error, it turns out the problem lies within RouteController::Init function, specifically by calling flushRules() which will clear any present routing rules and in turn break our ethernet connection to the NFS server. We could simply modify the flushRules() function inside RouteController.cpp, but then we would need two different versions of netd – one for normal and one for NFS booting. Therefore, let us also use the “ro.nfs.mode” property to determine whether we should call flushRules(). A possible fix for RouteController.cpp could look like this (note that you also need to include cutils/properties.h)

// NFS fix for system/netd/server/RouteController.cpp
#include <cutils/properties.h>

static bool isNFSbooted() {
    char prop_value[PROPERTY_VALUE_MAX] = {'\0'};
    if (property_get("ro.nfs.mode", prop_value, "no")) {
        if (strcmp(prop_value, "yes") == 0)
            return true;
    return false;

int RouteController::Init(unsigned localNetId) {
    if (!isNFSbooted()){
        if (int ret = flushRules()) {
            return ret;
    if (int ret = addLegacyRouteRules()) {
        return ret;


Re-compile netd and copy the new binary to the NFS directory:

# If not done already, initialize build
$ cd /opt/android/wand
$ source build/
$ lunch wandboard-eng
# rebuild netd
$ mmm -j4 system/netd
# copy netd to NFS directory
$ sudo cp out/target/product/wandboard/system/bin/netd /srv/nfsandroid/system/bin/netd

When booting via NFS we also need to add the mentioned system property in init.freescale.rc’s on boot section.

on boot
    # Add NFS property to prevent ip rules flushing
    setprop ro.nfs.mode yes

    # emmulate battery property
    setprop sys.emulated.battery 1


That should do it, reboot und gut 😀

(There are also some more patches in the Freescale KitKat release. This one disables clearing of the interface addresses from within framework when in NFS mode. In Android 6, you would need to add those changes to inside frameworks/opt/net/ethernet, however it does not seem this patch is needed here. Another patch is for installd, but again, everything seems to work fine here without)

Bonus: Where does u-boot save environment variables on the SD card?

If you remember, we saved our u-boot environment variables to the SD card. But where exactly are those variables stored now? Have a look at the Wandboard config file to find out:

vim bootable/bootloader/uboot-imx/include/configs/wandboard.h

Search for “CONFIG_ENV” and you will find the following section:

/* FLASH and environment organization */

#define CONFIG_ENV_SIZE         (8 * 1024)

#define CONFIG_ENV_OFFSET       (6 * 64 * 1024)
#define CONFIG_SYS_MMC_ENV_DEV      0

So the environment is 8*1024 bytes = 8 KB in size. It resides in the MMC (or SD card), at an offset of 6*64*1024 bytes = 384 KB. You can double check that by saving your own variables, and then dumping the environment from the SD card with the following command:

sudo dd if=/dev/sdX of=./uboot.env bs=1024 skip=384 count=8

You can then check the contents of uboot.env with a hex editor such as “ghex”.

Leave a Reply

Your email address will not be published. Required fields are marked *