Raspberry Pi 4 Model B Das U-Boot
Setup a Raspberry Pi 4 Model B to boot using Das U-Boot
Das U-Boot is both a first-stage and second-stage bootloader that is very flexible and suitable for embedded systems. Below is a brief summary of setting up a Raspberry Pi 4 Model B (RPi4) to boot using U-Boot.
First iteration
Goal: boot into U-Boot and allow access to the U-Boot boot prompt on a serial console, but don’t boot a kernel.
UART’s
The RPi4 has primary and secondary UART. By default:
- the primary UART is the mini UART (UART1) and it is disabled
- the secondary UART is the first PL011 and it is used by the bluetooth module
To allow serial console access, the primary UART must be enabled using
“enable_uart=1” in config.txt. There is a compromise in doing this in that it
disables variable core clock speed and locks the core clock at a low speed of
250MHz. You can add “force_turbo=1”, but that locks it at the highest speed and
results in high power usage. For now, we will live with the lower speed. In
future iterations, we’ll talk about alternatives. For more details about the
serial port config see this bitscope summary
and the official documentation.
Build Boot Files
Below is a script that will build a directory named boot with all of the required files. See comments in the script for explanation:
#!/bin/bash
# exit on first error
set -e
# install pre-requisite packages. Assumes a base install of Debian Bullseye
sudo apt install -y git dosfstools gcc-aarch64-linux-gnu make flex bison bc libssl-dev
# clone a recent release of u-boot
git clone --depth=1 --branch=v2023.01 https://source.denx.de/u-boot/u-boot.git
# cross compile for 64 bit arm architecture using RPi4 config
cd u-boot/
CROSS_COMPILE=aarch64-linux-gnu- make rpi_4_defconfig
CROSS_COMPILE=aarch64-linux-gnu- make -j6
# create our boot directory and copy u-boot.bin into it
cd ..
mkdir boot
cp u-boot/u-boot.bin boot/
# clone a recent release of the Raspberry Pi firmware and copy boot files
git clone --depth=1 --branch=1.20230106 https://github.com/raspberrypi/firmware.git
cp firmware/boot/{bcm2711-rpi-4-b.dtb,fixup4.dat,start4.elf} boot/
# create a simple config.txt file
cat << EOF > boot/config.txt
# Enable mini UART
enable_uart=1
# Run in 64-bit mode
arm_64bit=1
# Use Das U-Boot
kernel=u-boot.bin
EOF
The end result looks like:
boot/
|-- bcm2711-rpi-4-b.dtb
|-- config.txt
|-- fixup4.dat
|-- start4.elf
`-- u-boot.bin
Copy to and microSD Card
Here is a helper script (mkboot.sh) for creating the microSD card with our boot files:
#!/bin/bash
# exit on any error
set -e
if [ $# -ne 1 ]; then
echo "no disk device specified"
exit 1
fi
# make a 100MiB partition
echo 'start=2048, size=204800, type=b'|sfdisk "$1"
# format the partition
part="${1}1" # normal /dev/sda1 format
if [[ $1 == /dev/mmcblk* ]]; then
part="${1}p1"
fi
mkfs.vfat -F 32 -n BOOT "$part"
# mount the partion, copy boot files, then unmount
mount "$part" /mnt/
cp -r boot/* /mnt/
umount /mnt/
Run the mkboot.sh script as root with the microSD card device as an argument to overwrite the microSD card with a single partition containing the contents of ./boot/. Example with /dev/sda:
sudo ./mkboot.sh /dev/sda
WARNING: make sure you specify the correct device as this will overwrite every thing on the disk.
Then insert the microSD card in the Raspberry Pi.
Serial Connection
Use a 3.3V FTDI USB to serial adapter such as this.
Attach the RX from the adapter to pin 8 on the Raspberry Pi header and attach the TX to pin 10 (i.e., attach RX of one to TX of the other and vice versa). Also attach ground from the adapter to a ground pin (either pin 6 or 14 will work).
When you plug in the USB adapter you should see a new device /dev/ttyUSB0. To connect to the port, use minicom:
sudo apt install -y minicom
minicom -D /dev/ttyUSB0 -b 115200
To exit minicom, pres ctrl-a then z then x.
Power Up
Plug a USB-C power cord into the Raspberry Pi to power it on and boot. You should see something like this in minicom:
Welcome to minicom 2.8
OPTIONS: I18n
Port /dev/ttyUSB0, 20:26:14
Press CTRL-A Z for help on special keys
U-Boot 2023.01 (Jan 26 2023 - 02:24:13 +0000)
DRAM: 948 MiB (effective 7.9 GiB)
RPI 4 Model B (0xd03114)
Core: 209 devices, 16 uclasses, devicetree: board
MMC: mmcnr@7e300000: 1, mmc@7e340000: 0
Loading Environment from FAT... Unable to read "uboot.env" from mmc0:1...
In: serial
Out: vidconsole
Err: vidconsole
Net: eth0: ethernet@7d580000
PCIe BRCM: link up, 5.0 Gbps x1 (SSC)
starting USB...
Bus xhci_pci: Register 5000420 NbrPorts 5
Starting the controller
USB XHCI 1.00
scanning bus xhci_pci for devices... 2 USB Device(s) found
scanning usb for storage devices... 0 Storage Device(s) found
Hit any key to stop autoboot: 0
U-Boot>
Second Iteration
Ok, we were able to boot into U-Boot, but now lets actually boot a kernel and a root filesystem using an initramfs. We won’t actually use an init system, but just boot into a shell using “rdinit=/bin/sh” boot argument.
This time we will disable the bluetooth module so we can use the PL011 UART for the serial console and don’t have to lock the core clock speed. We do this by adding “dtoverlay=disable-bt” to the config.txt and copying the disable-bt.dtbo file into an overlays directory in our boot directory.
#!/bin/bash
# exit on first error
set -e
# install pre-requisite packages. Assumes a base install of Debian Bullseye
sudo apt install -y git dosfstools gcc-aarch64-linux-gnu make flex bison bc libssl-dev fakeroot wget cpio
if ! [ -d u-boot ]; then
# clone a recent release of u-boot
git clone --depth=1 --branch=v2023.01 https://source.denx.de/u-boot/u-boot.git
cd u-boot/
# cross compile for 64 bit arm architecture using RPi4 config
CROSS_COMPILE=aarch64-linux-gnu- make rpi_4_defconfig
CROSS_COMPILE=aarch64-linux-gnu- make -j6
cd ..
fi
if ! [ -d firmware ]; then
# clone a recent release of the Raspberry Pi firmware
git clone --depth=1 --branch=1.20230106 https://github.com/raspberrypi/firmware.git
fi
if ! [ -d rootfs ]; then
# download and extract an alpine linux root filesystem
wget https://dl-cdn.alpinelinux.org/alpine/v3.17/releases/aarch64/alpine-minirootfs-3.17.1-aarch64.tar.gz
mkdir rootfs
tar xf alpine-minirootfs-*-aarch64.tar.gz -C rootfs
fi
# create initramfs
cd rootfs && find . | fakeroot cpio -ov -H newc | gzip > ../initramfs.igz && cd ..
# create a u-boot boot script. Put EOF in double quotes so shell variables are not expanded.
# note: fdt_addr is the address of the device tree passed by start4.elf.
cat << "EOF" > boot.txt
fatload mmc 0:1 ${kernel_addr_r} Image
fatload mmc 0:1 ${ramdisk_addr_r} uRamdisk
booti ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr}
EOF
# create our boot directory and copy files into it
rm -rf boot
mkdir -p boot/overlays
cp u-boot/u-boot.bin boot/
cp firmware/boot/{bcm2711-rpi-4-b.dtb,fixup4.dat,start4.elf} boot/
cp firmware/boot/overlays/disable-bt.dtbo boot/overlays/
# uncompress kernel (U-Boot can boot compressed but takes special handling)
zcat -S .img firmware/boot/kernel8.img > boot/Image
# use the mkimage tool to prepare the initramfs and boot script for u-boot
./u-boot/tools/mkimage -A arm64 -O linux -T script -C none -d boot.txt boot/boot.scr
./u-boot/tools/mkimage -A arm64 -O linux -T ramdisk -d initramfs.igz boot/uRamdisk
# create a simple config.txt file
cat << EOF > boot/config.txt
# Disable bluetooth so we can user serial console
dtoverlay=disable-bt
# Run in 64-bit mode
arm_64bit=1
# Use Das U-Boot
kernel=u-boot.bin
EOF
# Kernel cmdline. start4.elf combines this with bootargs from the device
# tree file:
# chosen {
# bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1";
# };
# The combined args are used to boot the kernel. If "setenv bootargs ..."
# is used in the u-boot boot script, it will override the device tree bootargs
# and the bootargs in cmdline.txt.
# ref: https://stackoverflow.com/questions/48801998/passing-bootargs-via-chosen-node-in-device-tree-not-working-for-beaglebone-black
echo "console=tty1 console=serial0,115200 rdinit=/bin/sh" > boot/cmdline.txt
Third Iteration
This iteration is similar to the last one, but we will use the newer Flat Image Tree (FIT) U-Boot image format. It is a newer format that lets you bundle multiple resources into one image (e.g., kernel, fdt, and initramfs). It also has other features such as integrity checking, etc. Ref: docs.
A couple things we have to do differently to support this:
- add “CONFIG_FIT=y” to the u-boot config when compiling
- install device-tree-compiler because mkimage relies on dtc being available
Here is the script:
#!/bin/bash
# exit on first error
set -e
# install pre-requisite packages. Assumes a base install of Debian Bullseye
sudo apt install -y git dosfstools gcc-aarch64-linux-gnu make flex bison bc libssl-dev fakeroot wget cpio device-tree-compiler
if ! [ -d u-boot ]; then
# clone a recent release of u-boot
git clone --depth=1 --branch=v2023.01 https://source.denx.de/u-boot/u-boot.git
cd u-boot/
# cross compile for 64 bit arm architecture using RPi4 config
echo "CONFIG_FIT=y" >> configs/rpi_4_defconfig
CROSS_COMPILE=aarch64-linux-gnu- make rpi_4_defconfig
CROSS_COMPILE=aarch64-linux-gnu- make -j6
cd ..
fi
if ! [ -d firmware ]; then
# clone a recent release of the Raspberry Pi firmware
git clone --depth=1 --branch=1.20230106 https://github.com/raspberrypi/firmware.git
fi
if ! [ -d rootfs ]; then
# download and extract an alpine linux root filesystem
wget https://dl-cdn.alpinelinux.org/alpine/v3.17/releases/aarch64/alpine-minirootfs-3.17.1-aarch64.tar.gz
mkdir rootfs
tar xf alpine-minirootfs-*-aarch64.tar.gz -C rootfs
fi
# create initramfs
cd rootfs && find . | fakeroot cpio -ov -H newc | gzip > ../initramfs.cpio.gz && cd ..
# create a u-boot boot script. Put EOF in double quotes so shell variables are not expanded.
# note: fdt_addr is the address of the device tree passed by start4.elf.
cat << "EOF" > boot.txt
setenv bootargs coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1 console=ttyAMA0,115200 rdinit=/bin/sh
fatload mmc 0:1 2800000 image.itb
bootm 2800000
EOF
cat << "EOF" > image.its
/dts-v1/;
/ {
description = "U-Boot fitImage for Raspberry Pi 4 Model B kernel";
#address-cells = <1>;
images {
kernel {
description = "Linux Kernel";
data = /incbin/("./firmware/boot/kernel8.img");
type = "kernel";
arch = "arm64";
os = "linux";
compression = "gzip";
load = <0x80000>;
entry = <0x80000>;
hash {
algo = "sha1";
};
};
fdt-1 {
description = "Flattened Device Tree blob";
data = /incbin/("./firmware/boot/bcm2711-rpi-4-b.dtb");
type = "flat_dt";
arch = "arm64";
compression = "none";
load = <0x02600000>;
hash {
algo = "sha1";
};
};
fdt-2 {
description = "Flattened Device Tree blob";
data = /incbin/("./firmware/boot/overlays/disable-bt.dtbo");
type = "flat_dt";
arch = "arm64";
compression = "none";
load = <0x02680000>;
hash {
algo = "sha1";
};
};
ramdisk {
description = "ramdisk";
data = /incbin/("./initramfs.cpio.gz");
type = "ramdisk";
arch = "arm64";
os = "linux";
hash {
algo = "sha1";
};
};
};
configurations {
default = "conf";
conf {
description = "Boot Linux kernel with FDT blob + ramdisk";
kernel = "kernel";
fdt = "fdt-1", "fdt-2";
ramdisk = "ramdisk";
hash {
algo = "sha1";
};
};
};
};
EOF
# create our boot directory and copy files into it
rm -rf boot
mkdir -p boot/overlays
cp u-boot/u-boot.bin boot/
cp firmware/boot/overlays/disable-bt.dtbo boot/overlays/
cp firmware/boot/{bcm2711-rpi-4-b.dtb,fixup4.dat,start4.elf} boot/
# use the mkimage tool to prepare the boot script for u-boot
./u-boot/tools/mkimage -A arm64 -O linux -T script -C none -d boot.txt boot/boot.scr
./u-boot/tools/mkimage -f image.its boot/image.itb
# create a simple config.txt file
cat << EOF > boot/config.txt
# Disable bluetooth so we can user serial console
dtoverlay=disable-bt
# Run in 64-bit mode
arm_64bit=1
# Use Das U-Boot
kernel=u-boot.bin
EOF
Conclusion
This post documented the process of using U-Boot to boot a Linux kernel on a Raspberry Pi 4 Model B. We also explored options for using the serial console and the compromises between those options (locked clock speed vs no bluetooth).