In this article, I will document the process of porting the latest Linux to an Arria 10 board, the Reflex CES R329, and start it using TFTP.

Starting the board

Hook the BLASTER micro-usb port to your computer. Power-on the board and connect to it using picocom:

picocom -b 115200 /dev/ttyUSB0

Your board will boot to Linux. Before it does, interrupt the boot process by pressing on any key. You’re now in U-boot. The printenv command will show you the saved environment variables. By default, U-boot run the bootcmd command. We can see that it fetches the kernel (zImage) and the device tree (linuxDT.dtb) from the on-board eMMC. As that board features no SD-card slot, and to preserve the eMMC, let’s setup TFTP as our boot process.

TFTP

Setting up the TFTP server is beyond the scope of this document. On my distribution (Arch Linux), it’s just a matter of activating the tftpd service and putting the files in /srv/tftp. Refer to your distribution’s documentation for further instructions.

systemctl start tftpd.service

On the client side (i.e. in U-boot), we want to use the tftp command. We can get some inspiration from examining the output of printenv, particularly:

bootimage=zImage
fdtaddr=0x100
fdtimage=linuxDT.dtb
ipaddr=192.168.1.155
loadaddr=0x8000
rxcethload=dhcp ${bootimage} ; tftp ${fdtaddr} ${fdtimage} ;
rxcethboot=setenv bootargs console=ttyS0,115200 ip=192.168.1.155::192.168.1.59:255.255.255.0::eth0:none root=/dev/nfs rw nfsroot=192.168.1.81:/tftpboot/linaro-nano,nolock,rsize=4096,wsize=4096;bootz ${loadaddr} - ${fdtaddr}
rxcmmcload=mmc rescan;${mmcloadcmd} mmc 0:${mmcloadpart} ${loadaddr} ${bootimage};${mmcloadcmd} mmc 0:${mmcloadpart} ${fdtaddr} ${fdtimage}
serverip=192.168.1.54

In our case, the commands we want to use are:

tftp ${fdtaddr} ${fdtimage}
tftp ${loadaddr} ${bootimage}
bootz ${loadaddr} - ${fdtaddr}

Also notice how ip addresses are hardcoded in variables.

Let’s create a variable with our command.

tftpboot=tftp ${fdtaddr} ${fdtimage}; tftp ${loadaddr} ${bootimage}; bootz ${loadaddr} - ${fdtaddr}
saveenv

Now let’s test our TFTP infrastructure. Connect the board to an ethernet interface on your computer and set a static ip address. Use the same IP as referenced by the serverip variable in U-boot. Create a dummy device tree and kernel image in your TFTP server folder.

ip addr add 192.168.1.54/24 dev eth0
touch /srv/tftp/linuxDT.dtb
touch /srv/tftp/zImage

Of course, the boot process will fail. But TFTP should be able to download the files.

SOCFPGA_ARRIA10 # run tftpboot
Speed: 100, full duplex
Using dwmac.ff802000 device
TFTP from server 192.168.1.54; our IP address is 192.168.1.155
Filename 'linuxDT.dtb'.
Load address: 0x100
Loading: #
0 Bytes/s
done
Speed: 100, full duplex
Using dwmac.ff802000 device
TFTP from server 192.168.1.54; our IP address is 192.168.1.155
Filename 'zImage'.
Load address: 0x8000
Loading: #
0 Bytes/s
done
Bad Linux ARM zImage magic!

Compiling Linux

This is the easy part. You can use the same defconfig file for all the SoCFPGA boards, a.k.a socfpga_defconfig. The result is that compiling Linux for SoCFPGAs is as easy as typing a few commands.

git clone https://github.com/torvalds/linux.git
cd linux
make ARCH=arm socfpga_defconfig
make ARCH=arm CROSS_COMPILE=/opt/toolchain/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- -j8

Of course, I’m assuming that you have some familiarity with cross compilation and that you have an adequate toolchain installed. Linaro provides some.

Let’s try running our newly-created kernel. We will use an in-tree device tree as our first test, let’s say ./arch/arm/boot/dts/socfpga_arria10_socdk_qspi.dtb. Copy this dtb and the zImage in your /srv/tftp folder and run the tftpboot command in U-boot.

SOCFPGA_ARRIA10 # run tftpboot
Speed: 100, full duplex
Using dwmac.ff802000 device
TFTP from server 192.168.1.54; our IP address is 192.168.1.155
Filename 'linuxDT.dtb'.
Load address: 0x100
Loading: ##
1.5 MiB/s
done
Bytes transferred = 17126 (42e6 hex)
Speed: 100, full duplex
Using dwmac.ff802000 device
TFTP from server 192.168.1.54; our IP address is 192.168.1.155
Filename 'zImage'.
Load address: 0x8000
Loading: #################################################################
#################################################################
#################################################################
#################################################################
################################################################
2.2 MiB/s
done
Bytes transferred = 4742176 (485c20 hex)
Kernel image @ 0x008000 [ 0x000000 - 0x485c20 ]
## Flattened Device Tree blob at 00000100
Booting using the fdt blob at 0x000100
Loading Device Tree to 01ff8000, end 01fff2e5 ... OK

Starting kernel ...

The boot process is stuck at this point. Something looks wrong.

A good idea at this point is to enable EARLY_PRINTK in the kernel config. Run make ARCH=arm menuconfig and enable Kernel hacking -> Kernel low-level debugging functions and Kernel hacking -> Early printk. This will allow the kernel to output early messages in the boot process to the serial console (way before the serial driver is initialized).

In our case, the problem lays in the device tree. The UART0 node is disabled.

Creating the device tree

The old-fashioned way to do that was to run sopc2dts, an open-source tool ported by Altera that took an .xml file describing the board as its input. This file was meant to be given by the vendor. Unfortunately, these .xml files are hard to find and the process is rather cumbersome. Generated device trees must oftentimes be corrected by the user.

The “modern” way to proceed is to take the device tree from a similar board as a starting point and complete it/adapt it to our board. This also ensures that we remain up-to-date with the latest Linux sources. That’s exactly what we’ll be doing here.

In our case, to have a proper boot sequence, all we need is to include socfpga_arria10.dtsi, add some line copied from socfpga_arria10_socdk.dtsi (which is Altera’s offical platform for the Arria 10) and activate the uart0 node. Here is the resulting dts file:

/*
* Copyright (C) 2016 Intel. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see http://www.gnu.org/licenses/.
*/
 
/dts-v1/;
#include "socfpga_arria10.dtsi"

/ {
    model = "Altera SOCFPGA Arria 10";
    compatible = "altr,socfpga-arria10", "altr,socfpga";

    soc {
        clkmgr@ffd04000 {
            clocks {
                osc1 {
                    clock-frequency = 25000000;
                };
            };
        };
    };
};
 
uart0 {
    status = "okay";
};

Compile this and place it in your TFTP server folder.

make ARCH=arm CROSS_COMPILE=/opt/toolchain/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- reflex_ces_r329.dtb

The boot sequence now runs well, only crashing because of the missing root filesystem.

Feel free to improve the device tree by peeking into socfpga_arria10.dtsi and activating the needed nodes.

Adding the rootfs

We will use the filesystem already present on the eMMC. To do so, we first need to pass the right arguments to the kernel in order for it to boot properly. As before, we will get our inspiration from the already-defined rxcmmcload variable.

setenv bootargs console=ttyS0,115200 root=/dev/mmcblk0p3 ext3 ro rootwait
saveenv

Linux now hangs forever, waiting for the eMMC to become available. We need to add mmc information in our device tree. The mmc node is defined in arch/arm/boot/dts/socfpga_arria10.dtsi and is disabled by default. Let’s just enable it in reflex_ces_r329.dts. Moreover, as the official Altera devkit has no eMMC but an SD Card, its mmc definition reflects that. It results in Linux waiting for the SD card detect signal to be raised (when one inserts an SD card in the slot). We need to add the “broken-cd” property as we don’t have that card detect signal, the eMMC being roughly an on-board-soldered SD card.

mmc {
    status = "okay";
    broken-cd;
};

We can now boot up to the prompt. There are still some errors tied to the ethernet controller.

Ethernet support

Activating the ethernet controller gmac0 does not work. Having a look in what U-boot outputs during bootup gives us an explanation.

 
SOCFPGA_ARRIA10 # run tftpboot 
Speed: 100, full duplex 
Using dwmac.ff802000 device

The ethernet wired on the board is tied to gmac1, which is mapped at 0xff802000 and disabled. gmac0 is mapped at 0xff800000. Let’s rectify this.

gmac1 {
    status = "okay";
};

Conclusion

Here is the full listing of reflex_ces_r329.dts for your convenience.

/*
* Copyright (C) 2016 Intel. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see http://www.gnu.org/licenses/.
*/
 
/dts-v1/;
#include "socfpga_arria10.dtsi"

/ {
    model = "Altera SOCFPGA Arria 10";
    compatible = "altr,socfpga-arria10", "altr,socfpga";

    soc {
        clkmgr@ffd04000 {
            clocks {
                osc1 {
                    clock-frequency = 25000000;
                };
            };
        };
    };
};
 
uart0 {
    status = "okay";
};

mmc {
    status = "okay";
    broken-cd;
};

gmac1 {
    status = "okay";
};