Second-Stage Bootloader (U-Boot)
In this section, as the preparation to boot Linux, a bootloader called “Das U-Boot”, which is capable of booting Linux, will be explained.
Das U-Boot
Das U-Boot (The Universal Bootloader) is a bootloader which is capable of booting Linux OS. Also, as the name “universal” suggests, this bootloader is compatible with various platforms, and it allows user to utilize boot script and environment variables.
Preparation
To configure/compile U-Boot and Linux kernel for ARM processors, Cross Compiler and toolchain are needed. Cross Compiler compiles applications and generates the executables for different platform. For example, Cross Compiler runs on x86–64 platform, and generates executables for ARM processors.
If you have Xilinx SDK, Cross Compiler and toolchain are already installed. Add the path to those files by adding these two lines in ~/.bashrc (in Linux).
PATH+=":/opt/Xilinx/SDK/<SDK version>/bin”
PATH+=”:/opt/Xilinx/SDK/<SDK version>/gnu/aarch32/lin/gcc-arm-linux-gnueabi/bin”
First line is not the path to the Cross Compiler, but later time, we would need some tools in SDK bin. In Windows, add the path to the corresponding directory by customizing “System Environment Variable” settings.
Configuring U-Boot
Clone Xlilinx U-Boot repository from here.
To Cross Compile this for ARM, add environment variables using “export” command like below;
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
Before compiling U-Boot, it should be configured properly for the targeted platform. Build configuration files are in “configs” directory. Look for the best suiting config file. In this example, zynq_zed_defconfig will be used.
make zynq_zed_defconfig
You can simply build after this, but you can also change some default environment variables and some configurations by using “menuconfig” like Linux kernel configuration.
make menuconfig
The variables configured here will be used as “default” environment variables. But, as explained later, it can be overwritten by configurations in “uEnv.txt”.
Compiling U-Boot
-j <number of threads> option would be recommended to optimize performance. It will use number of threads specified for compiling.
make -j9
You can also refer to Xilinx Wiki for compiling U-Boot.
Generating boot image
As explained in previous section, boot image can be generated using Xilinx SDK GUI, but if you add path to SDK binaries, boot image can be generated via command line (path is added in previous paragraph).
Before generating boot image, .bif file (Boot Image File) should be prepared.
//arch = zynq; split = false; format = BIN
the_ROM_image:
{
[bootloader]./FSBL.elf
./default_ps_wrapper.bit
./u-boot.elf
}
Save this file as “boot.bif”. As explained in previous section, 3 files are necessary to create boot image;
- First-Stage Bootloader executable
- Hardware bitstream
- Second-Stage Bootloader executable
You can copy those 3 files in the same directory, but in that case, you have to copy it every time you change the design. I would recommend using “link” for those files. use the command below to create links;
ln -s <target file full path> ./
Then, it will create link for that target file. This is not applicable for Windows.
Create boot image using “bootgen”.
bootgen -image boot.bif -o i BOOT.bin
Then, copy “BOOT.bin” to boot partition in SD card.
Command line interface in U-Boot
U-Boot uses “hush (hyper utility shell)” as a command line interface. Also, U-Boot uses a lot of Environment variables. Environment variables can be values, or commands. If an environment variable is a command, then you can execute that command by typing “run <environment variable>”.
We will look at some important environment variables. To do so, we will play around with actual U-Boot. Insert the SD card which has “BOOT.bin” in boot partition, and turn on Zedboard. Open serial terminal on your development PC (/dev/ttyACM0, baud rate: 115200. If Windows, use Putty or TeraTerm to connect to the device). If U-Boot is successfully booted, it will prompt “Zynq>” via serial console.
To look at all the default environment variables, you can simply type
printenv
It will show huge list of environment variables. But the most important variables are those two;
- preboot
- bootcmd
“preboot” variable is executed before U-Boot shows the prompt to user. “printenv preboot” command will show what the value of this variable is.
Zynq> printenv preboot
preboot=
if test $modeboot = sdboot && env run sd_uEnvtxt_existence_test; then
if env run loadbootenv; then
env run importbootenv;
fi;
fi;
New lines are added manually above for visibility. This environment variable also refers to a lot of other environment variables. But basically, what it does is;
- Check if the boot mode is “SD boot” (test $modeboot = sdboot).
- If so, then check if “uEnv.txt” exists in SD (env run sd_uEnvtxt_existence_test).
- If “uEnv.txt” exists in SD, then load contents into main memory (env run loadbootenv).
- If it’s successfully loaded, import environment variables from memory (env run importbootenv).
Those modeboot, sd_uEnvtxt_existence_test, loadbootenv and importbootenv are also environment variables. You can also do, “printenv loadbootenv” (for example) to look at what the values of those variables are.
What important the most here is, you can overwrite environment variables by defining variables in uEnv.txt. uEnv.txt is just a plain text file. You can put it in the same directory as BOOT.bin, and edit it as needed.
bootcmd variable is default boot command. U-Boot will wait for user input until timeout count, which is specified as “bootdelay” environment variable, exceeds. If there’s no user input during this time, then U-Boot will execute “run bootcmd”.
default bootcmd does a lot of things. It will be explained in later section, but for now, the focus is on the simplest way to boot Linux OS.
Simple boot command
To boot the operating system, what bootloader does is quite simple;
- Open executable, data file or file system image (ramdisk) on boot device.
- Load the contents of the file into targeted address in main memory.
- Start executing the kernel from main memory, with bootargs.
“bootargs” is also an environment variable, which can be overridden by uEnv.txt, or syslinux, and is passed to the Linux kernel. Step 1 and 2 are done in one command like below;
load <boot device> <device number>:<partition number> <load address> <file name>
For example, if the boot device is SD card, boot partition is partition 1, load address is 0x03000000, and loading Linux kernel image, then the command would be like below;
load mmc 0:1 0x03000000 uImage
Then, it will open “uImage” and load it to the targeted address 0x03000000.
To boot from main memory, use command like below;
bootm <kernel address> <ramdisk address> <device tree address>
But for now, since we don’t have any of those files above, how to boot Linux kernel using U-Boot will be explained in next section.