Back | Next | Contents
Building Embedded Linux - Full Custom
U-Boot is a universal bootloader. Truly. It is used in almost every known embedded device out there and the DE10-Nano is no exception. Here we will build a U-Boot image to be used for the DE10-Nano.
This step will also generate the Secondary Program Loader (SPL) along with the bootloader. To understand how all the pieces fit together, refer to this table originally shared on Stack Overflow. We will generate both steps 2 and 3.
+--------+----------------+----------------+----------+
| Boot | Terminology #1 | Terminology #2 | Actual |
| stage | | | program |
| number | | | name |
+--------+----------------+----------------+----------+
| 1 | Primary | - | ROM code |
| | Program | | |
| | Loader | | |
| | | | |
| 2 | Secondary | 1st stage | u-boot |
| | Program | bootloader | SPL |
| | Loader (SPL) | | |
| | | | |
| 3 | - | 2nd stage | u-boot |
| | | bootloader | |
| | | | |
| 4 | - | - | kernel |
| | | | |
+--------+----------------+----------------+----------+
There are two source repositories for U-Boot - the official U-Boot repo and the altera fork of the U-Boot repo. You can use either of them and honestly, I don't know if any difference exists. For this guide, we will be using the official U-Boot repo because a patch had been submitted for it and it worked fine when I tested it.
Clone the repository:
cd $DEWD
git clone https://github.com/u-boot/u-boot.git
List all the tags and select a release that you want to use. For this guide, I used the latest stable release v2021.07
:
cd $DEWD/u-boot
# List all available tags.
git tag
# Checkout the desired release.
git checkout v2021.07
Here we will make a few changes to the source code to make it work more nicely with our DE10-Nano, viz.,
- Configure U-Boot to flash FPGA automatically at boot time.
- Assign a permanent mac address to the ethernet device.
These steps are optional i.e. you can skip them and your bootloader will work great and you will only lose these 2 capabilities. If you prefer to get something up and running asap, then you can skip directly to Part 2 and come back to it later when you feel you need it.
We want to keep our changes separate from the branch synced with the repo. So let's create a new branch:
git checkout -b v2021.07_mine_fpga_boot_mac
One of the features of U-Boot is the ability to flash the FPGA with a binary design at boot time. If you have a design that you want running on the FPGA every time you turn on the device, this is a very useful feature. How to use this is explained in another section, but for now, we need to add some commands that will do this automatically.
We will edit the following file for this:
cd $DEWD/u-boot
nano include/config_distro_bootcmd.h
Towards the end of the file, look for the following lines:
BOOT_TARGET_DEVICES(BOOTENV_DEV) \
\
"distro_bootcmd=" BOOTENV_SET_SCSI_NEED_INIT \
BOOTENV_SET_NVME_NEED_INIT \
BOOTENV_SET_IDE_NEED_INIT \
BOOTENV_SET_VIRTIO_NEED_INIT \
"for target in ${boot_targets}; do " \
"run bootcmd_${target}; " \
"done\0"
#ifndef CONFIG_BOOTCOMMAND
#define CONFIG_BOOTCOMMAND "run distro_bootcmd"
#endif
We will modify the variable distro_bootcmd
to load the FPGA design from the FAT partition. So modify it to look like this:
BOOT_TARGET_DEVICES(BOOTENV_DEV) \
\
"distro_bootcmd= " \
"if test -e mmc 0:1 u-boot.scr; then " \
"echo --- Found u-boot.scr ---; " \
"fatload mmc 0:1 0x2000000 u-boot.scr; " \
"source 0x2000000; " \
"elif test -e mmc 0:1 soc_system.rbf; then " \
"echo --- Programming FPGA ---; " \
"fatload mmc 0:1 0x2000000 soc_system.rbf; " \
"fpga load 0 0x2000000 0x700000; " \
"else " \
"echo u-boot.scr and soc_system.rbf not found in fat.; " \
"fi; " \
BOOTENV_SET_SCSI_NEED_INIT \
BOOTENV_SET_NVME_NEED_INIT \
BOOTENV_SET_IDE_NEED_INIT \
BOOTENV_SET_VIRTIO_NEED_INIT \
"for target in ${boot_targets}; do " \
"run bootcmd_${target}; " \
"done\0"
#ifndef CONFIG_BOOTCOMMAND
#define CONFIG_BOOTCOMMAND "run distro_bootcmd"
#endif
Quick explanation of what we're doing here. distro_bootcmd
sets up the u-boot sequence of commands and at the end launches the kernel with run bootcm_${target}
.
We're modifying this command to do two things.
-
First, we check to see if a u-boot script file exists on the fat partition called
u-boot.scr
. If it does we load it into memory at0x2000000
and then run it using thesource
command.Having the check for
u-boot.scr
is helpful to override the default sequence of commands by creating a boot script image fileu-boot.scr
and saving it in the fat partition. This way we don't have to compile u-boot and burn it to the sd card every time we need some custom configuration. This may come in handy in the future. -
Next, if the script file is not found, then we check if
soc_system.rbf
exists on the fat partition. If it does then, we load the design namedsoc_system.rbf
into memory address0x2000000
and in the next step, copy0x700000
bytes from the memory location0x2000000
into the FPGA. I obtained these numbers by looking at this article (Section titled "Writing the boot script") and by looking up in the source code.Presumably,
0x2000000
is the RAM address where we want to copy the contents of the binary and0x700000
is the size of the binary in bytes. But where does0x700000
or7MB
come from? Well, if we look in the Cyclone V Device Data Sheet on page 78, we see a table showing the size of.rbf
configuration files in bits. Here is a screenshot:The de10-nano uses a Cyclone V 5CSEBA6U23I7 and I've highlighted that above. So converting that from bits to MB, we get about
6.68MB
which is close to the7MB
above.
After making the changes, save the file and exit. Then commit it to the branch.
git add .
git commit -m "Load FPGA on boot."
By default, the ethernet device on the DE10-Nano isn't assigned a Mac address. So it assigns a random one every time you reboot the device. Which in turn leads to a different IP address every time, which makes sshing into it a bit tedious. You could run the commands to assign the mac address at boot time as shown in the appendix, but then you'll have to do that every time you make changes to the SD Card. By adding it to the source code, you avoid both these problems.
Let's generate a random mac address first. You can get it online if you like, but U-Boot already ships with one out of the box. Let's use that:
cd $DEWD/u-boot
# Compile the mac address generator.
make -C tools gen_eth_addr
# Run it!
tools/gen_eth_addr
You should get an address that looks like 56:6b:20:e9:4a:47
. Copy this and save it somewhere.
Now let's assign this to our device. To do this, open the following file in a text editor. I am using nano
but you can use vim
, gedit
or any other editor:
cd $DEWD/u-boot
nano include/configs/socfpga_common.h
Scroll down to the section that has the following lines:
#ifndef CONFIG_EXTRA_ENV_SETTINGS
#define CONFIG_EXTRA_ENV_SETTINGS \
"fdtfile=" CONFIG_DEFAULT_FDT_FILE "\0" \
"bootm_size=0xa000000\0" \
"kernel_addr_r="__stringify(CONFIG_SYS_LOAD_ADDR)"\0" \
"fdt_addr_r=0x02000000\0" \
"scriptaddr=0x02100000\0" \
"pxefile_addr_r=0x02200000\0" \
"ramdisk_addr_r=0x02300000\0" \
"socfpga_legacy_reset_compat=1\0" \
BOOTENV
#endif
Modify this to add the U-Boot environment variable ethaddr
as shown below. Don't forget the \0
at the end as well as the \
or it won't work.
#ifndef CONFIG_EXTRA_ENV_SETTINGS
#define CONFIG_EXTRA_ENV_SETTINGS \
"fdtfile=" CONFIG_DEFAULT_FDT_FILE "\0" \
"bootm_size=0xa000000\0" \
"kernel_addr_r="__stringify(CONFIG_SYS_LOAD_ADDR)"\0" \
"fdt_addr_r=0x02000000\0" \
"scriptaddr=0x02100000\0" \
"pxefile_addr_r=0x02200000\0" \
"ramdisk_addr_r=0x02300000\0" \
"socfpga_legacy_reset_compat=1\0" \
"ethaddr=56:6b:20:e9:4a:47\0" \
BOOTENV
#endif
You can save the file and exit. We're done with this step. Let's commit our changes:
git add .
git commit -m "Added mac address"
Note: To those familiar with version management, saving hard coded mac addresses, passwords and any other kind of sensitive data in a repository is a definite no-no. But since this is for hobby use and on my own personal router, and I only have one DE10-Nano board, it should be fine. If you are doing this for your company, you might not want to hard code the mac address in the header file, it is not scalable. Instead, you should follow the steps in the Appendix which show how to set the mac address in the U-Boot console on first boot. Also, you shouldn't rely on the random mac generator and you should actually buy some valid mac addresses. Refer to this link for more info.
U-Boot has a number of pre-built configurations in the configs
folder. To view all the available ones for altera, run the following command:
cd $DEWD/u-boot
ls -l configs/socfpga*
We will be using socfpga_de10_nano_defconfig
.
Prepare the default config:
make ARCH=arm socfpga_de10_nano_defconfig
The defaults should be fine. But should you choose to fine tune the config, you can run the following and update them:
make ARCH=arm menuconfig
Now we can build U-Boot. Run the following command:
make ARCH=arm -j 24
Once the compilation completes, it should have generated the file u-boot-with-spl.sfp
. This is the bootloader combined with the secondary program loader (spl).
Official U-Boot repository - The README has most of the instructions.
Building embedded linux for the Terasic DE10-Nano - This page is again a very useful reference.
If you skipped the section on hardcoding the mac address, you can change it at boot time as well. When the device boots, you can interrupt autoboot and enter the following commands to set the mac address. Note that this particular environment variable can only be set once and once set, cannot be changed:
setenv ethaddr 56:6b:20:e9:4a:47
saveenv
Next | Building the Kernel
Back | The Basics
Building Embedded Linux - Full Custom | Table of Contents