In this article, we will explore U-Boot Bootloader, a commonly used bootloader in Embedded systems. Number of different Embedded boards, development kits like Raspberry Pi, NVIDIA Jetson supports & actively build their boot-up, board bring up process using U-Boot. U-Boot is Open Source under GPL-2.0-or-later License.
Usually, there are 6 distinct stages of boot process, each explained in brief in Linux Boot Process article. Embedded Linux boot process similar to standard Linux to much extent, but due to the variations in hardware and the board specifics like multi-stage bootloaders, it becomes somewhat different in bootloader section and pre-dominant use of u-Boot bootloader in embedded systems also needs to be addressed. To find out overview of Embedded Linux Boot Process see this article.
U-Boot Reference Material:
Building U-Boot for Raspberry Pi:
- U-Boot is not an official or default bootloader for Raspberry Pi. RPi uses multi-stage booting process, we can add another stage of U-Boot to this boot process to customize the boot process as per our requirements.
- I have used U-Boot stage with default RPi standard boot process to support OTA (Over-the-Air) Update mechanism on RPi 4 board.
- U-Boot main repository itself supports multiple Raspberry Pi boards, look at the /u-boot/configs/ folder.
Checkout U-Boot source code:
$ git clone git://git.denx.de/u-boot.git
- You can even download the particular release version from U-Boot repository Tags.
Install the ARM Compiler for Cross-compilation:
- There are various ARM cross-compiler toolchains like GCC, Linaro, even Raspberry Pi itself provides a compiler in their repository: raspberrypi/tools.
- We will use GCC toolchain available as a standard Ubuntu package as follows:
- for 32-bit:
$ sudo apt update $ sudo apt install gcc-arm-linux-gnueabi $ export CROSS_COMPILE=arm-linux-gnueabi-
- for 64-bit:
$ sudo apt update $ sudo apt install gcc-aarch64-linux-gnu $ export CROSS_COMPILE=aarch64-linux-gnu-
- Note: Every time you start a new terminal to compile U-Boot, run the export command first.
Compile the U-Boot source:
- Configs and DTBs for particular to boards:
-
Important:
- Raspberry Pi Kernel depends on Device Tree Binary(DTB) to understand and work with different peripherals during the boot process.
- Every Raspberry Pi board and the Compute Modules comes with different DTBs and DTB Overlays.
- One
config
file from/u-boot/configs/
folder corresponds to a particular DTB, as mentioned in config parameter:CONFIG_DEFAULT_DEVICE_TREE
. - By default, config files supports for Raspberry Pi development boards DTBs.
- While compiling, U-Boot for any Raspberry Pi Compute Modules, like CM3 or CM4, we need to modify the default DTB mentioned in the config file corresponding to particular version of Raspberry Pi to the Compute Module compatible DTB.
- Look for different
config
files for various supported boards under/u-boot/configs/
directory in checked-out repository. - There are different
config
files for different Raspberry Pi versions and for either 32/64-bit builds. Select appropriate config file. - for RPi 4, 32-Bit build:
$ make rpi_4_32b_defconfig
- for RPi 3, 64-Bit build:
$ make rpi_3_defconfig
- for RPI CM3, 32-Bit build:
$ vim /u-boot/configs/rpi_3_32b_defconfig
--> Update following config parameter:
CONFIG_DEFAULT_DEVICE_TREE=bcm2837-rpi-cm3-io3.dts
$ make rpi_3_32b_defconfig
- This creates a hidden
.config
file in the/u-boot/
folder. You can view it to observe it’s contents and modify, if necessary. -
But be careful, the modifications in
.config
file, will be overwritten by above commands. -
Compile the source code:
$ cd /u-boot/ $ make
- This will create
u-boot
andu-boot.bin
binaries in the/u-boot/
folder.
- This will create
Verify U-Boot Binaries:
$ file u-boot
u-boot: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, with debug_info, not stripped
$ file u-boot.bin
u-boot.bin: COM executable for DOS
Testing U-Boot on Raspberry Pi:
- Copy the compiled
u-boot.bin
binary to the Boot Partition of the Raspberry Pi SD card. - Modify Raspberry Pi boot flow to add
u-boot
bootloader stage as the last stage of normal RPi Boot process, before starting the kernel.-
Add
kernel=u-boot.bin
in theconfig.txt
file and save this file. - With this, the
start.elf
file loads theu-boot.bin
instead of the kernel. - Now, it’s
u-boot
bootloaders responsibility to properly load the Linux Kernel + DTBs in memory and start the kernel. -
Back-up the original
config.txt
file, before any edits.$ mount /dev/sdb1 /mnt/tmp $ cp u-boot.bin /mnt/tmp/ $ echo 'kernel=u-boot.bin' > /mnt/tmp/config.txt $ umount /mnt/tmp
-
Add
U-Boot Command Prompt:
- After above changes, boot the Raspberry Pi board and press any keyboard keys to halt the
u-boot
Autoboot. - Before Autobooting the kernel,
u-boot
is by default configured to wait for few seconds for user interruption. - If user presses any keyboard keys, during this wait period,
u-boot
presents the command prompts for user to enter the u-boot commands.
Check U-Boot version:
- Use
version
command on theu-boot
command prompt to get the version of theu-boot
binary.
<U-Boot> version
Booting the Kernel:
- For kernels that need Device Tree Binaries:
<U-Boot> setenv fdtfile bcm2710-rpi-cm3.dtb
<U-Boot> mmc dev 0
<U-Boot> fatload mmc 0:1 ${kernel_addr_r} zImage
<U-Boot> fatload mmc 0:1 ${fdt_addr_r} ${fdtfile}
<U-Boot> setenv bootargs earlyprintk console=tty0 console=ttyAMA0 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait noinitrd
<U-Boot> bootz ${kernel_addr_r} - ${fdt_addr_r}
- Here, we are setting the Device Tree Binary to be used as
fdtfile
variable. - We are using FAT boot partition to load the kernel
zImage
and DTB file to memory. -
kernel_addr_r
&fdt_addr_r
are the RAM addreses for kernel & DTB file respectively. - We are then setting the boot arguments with
bootargs
variable, enabling the kernel prints, output consoles, and defining theroot
device partition and it’s type. - Then with
bootz
command we are starting the kernel. -
bootz
command needs the kernel to be inzImage
format only.