User:Brendlefly62/Radxa ROCK Pi 4C Plus/Build-Install-U-Boot
There are a number of places you can find pre-built images to boot your ROCK 4c Plus. This guide describes how to set up Das U-Boot bootloader for your ROCK 4c Plus, using another arm64 device or on an amd64 crossdev platform, by building the bootloader firmware from mainline upstream source code.
Acknowledgement: This article is largely based on a similar article written for PINE64 ROCKPro64 ==> https://wiki.gentoo.org/index.php?title=PINE64_ROCKPro64/Installing_U-Boot
Prerequisites
U-Boot can be built on an ARM64 device or cross-compiled on a PC. Regardless, we will need:
- git, to check out the sources
- dtc, to compile device trees
- swig, to build U-Boot
- crossdev, since both a 64- and 32-bit ARM toolchains are required
Let's make sure these are installed:
root #
emerge --ask --update dev-vcs/git sys-apps/dtc dev-lang/swig sys-devel/crossdev
Please review the crossdev article if you don't already have it set up.
You may need to install the 32-bit toolchain, which is required to build code for Cortex-M0 MCUs in BL31:
root #
crossdev --target arm-none-eabi
If cross-compiling, install the 64-bit cross-compiler toolchain:
root #
crossdev --target aarch64-unknown-linux-gnu
Compiling
Build BL31
Before U-Boot, we need to build the BL31 ("Boot Loader stage 3-1") firmware.
Get the sources:
user $
git clone https://github.com/ARM-software/arm-trusted-firmware.git
user $
cd arm-trusted-firmware
user $
git tag
v2.9.0
Let's select the latest currently stable version:
user $
git checkout v2.9.0
The repository contains a few binaries. If this is unacceptable, they may be deleted:
user $
git rm '*.bin'
for x in $(find ./ -iname *.bin); do rm -v $x; done
To compile on ARM64, run:
user $
make -j$(nproc) PLAT=rk3399 bl31
If cross-compiling, run:
user $
make -j$(nproc) PLAT=rk3399 CROSS_COMPILE=aarch64-unknown-linux-gnu- bl31
If running distcc on the cross-compiling platform, it may be wise/necessary to disable that by temporarily removing '/usr/lib/distcc/bin' from your $PATH and turning off distcc in $FEATURES
user $
export PATH=$(echo $PATH | sed 's|/usr/lib/distcc/bin:||')
user $
MAKEOPTS="-j$(nproc) -l$(nproc)" FEATURES=$FEATURES" -distcc -distcc-pump" PLAT=rk3399 CROSS_COMPILE=aarch64-unknown-linux-gnu- make bl31
Once finished, make a copy of resulting bl31.elf
binary for safekeeping:
user $
mkdir ../rk3399-atf-2.9.0
user $
cp build/rk3399/release/bl31/bl31.elf ../rk3399-atf-2.9.0
Build U-Boot
Now we're ready to build U-Boot itself. Let's get the sources:
user $
git clone https://source.denx.de/u-boot/u-boot.git
user $
cd u-boot
user $
git tag
v2023.07.02
Check out latest currently stable version:
user $
git checkout v2023.07.02
Instead of cloning the complete u-boot repository as shown in the steps above, you may save some time and disc space by cloning branch v2023.07.02 only:
user $
git clone --depth 1 --branch v2023.07.02 https://source.denx.de/u-boot/u-boot.git
Identify and load board-specific default configuration:
user $
find ./ -iname *rk3399*_defconfig
user $
make rock-4c-plus-rk3399_defconfig
If cross-compiling, format this make command in a manner similar to what was used to make the bl31 firmware.
Similar to the Linux kernel, menuconfig
can be used to make changes to the configuration. You don't need to make any changes.
See notes below about u-boot serial console baud rate setting before concluding this step is un-needed.
user $
$make menuconfig
To compile on ARM64, run:
user $
make -j$(nproc) BL31=~/rk3399-atf-2.9.0/bl31.elf
If cross-compiling, run:
user $
make -j$(nproc) BL31=~/rk3399-atf-2.9.0/bl31.elf CROSS_COMPILE=aarch64-unknown-linux-gnu-
Again, if running distcc on the cross-compiling platform, it may be wise/necessary to disable that by temporarily removing '/usr/lib/distcc/bin' from $PATH and turning off distcc in $FEATURES
user $
export PATH=$(echo $PATH | sed 's|/usr/lib/distcc/bin:||')
user $
MAKEOPTS="-j$(nproc) -l$(nproc)" FEATURES=$FEATURES" -distcc -distcc-pump" PLAT=rk3399 CROSS_COMPILE=aarch64-unknown-linux-gnu- BL31=~/rk3399-atf-2.9.0/bl31.elf make
Make sure
BL31
is pointing to the bl31.elf
built earlier!Installing
When powered on, Rock 4c Plus will attempt to find a bootloader in the following order:
- Onboard 128MB SPI Flash - not present on 4c Plus
- Removable eMMC - empty connector, not populated by default on 4c Plus
- microSD card - i.e. this is the only real option for where to put u-boot
Once U-Boot is loaded, it could then be used to load the kernel from MMC or USB (or NVMe if provided via an external PCIe connector), or via the network. By default, after exhausting other options, u-boot will connect to the local network's dhpc server, looking for tftp service from which to retrieve a bootable image (this might be considered a security concern).
Installing on microSD/eMMC
As root, enter the u-boot build directory, and write the images to the card.
- Note that
u-boot.itb
is written starting at sector 16384. Make sure your card is partitioned such that there's no filesystem there. - Be sure to replace
/dev/mmcblk1
with the correct device!
root #
cd /path/to/u-boot
root #
dd if=idbloader.img of=/dev/mmcblk1 seek=64
root #
dd if=u-boot.itb of=/dev/mmcblk1 seek=16384
Observing boot message with serial console
Before installing a Linux operating system, check to ensure that U-Boot is booting correctly. It may be possible to use a USB-to-TTL connector (such as this one or this one), though the author has found that those are not supported by Windows 11. Instead, the author used a mini-usb FTDI board such as are commonly used to program AVR microcontrollers that lack on-board USB support (see photo) and used PuTTy software on a Windows 11 desktop PC to monitor the serial console.
Connect the USB-TTL device to pins 6 (GND), 8 (UART2_TX) and 10 (UART2_RX) on the PI2 Bus (consult the Rock 4c Plus Hardware guide for reference) to see the early boot up messages:
root #
screen /dev/ttyUSB0 1500000
- Make sure RX and TX are not backwards
Optional: Adapting baud rate
Serial communication usually requires both sides of the communication to agree on a common baud rate. By default, Rockchip uses 1500000 bauds, which may be too fast for common USB-to-serial adapters. As a consequence, you may not see any boot-messages from u-boot. A much more common and widely supported, but slower baud rate is 115200 bauds. U-Boot can be configured to use this slower baud rate.
Use menuconfig
as described in #Build_U-Boot and reconfigure the symbol BAUDRATE
to 115200
. Rebuild u-boot and copy it to the SD-card. Now start your program of choice for serial communication
Linux PC as external serial console
root #
screen /dev/ttyUSB0 115200
Window s PC for external serial console
The Radxa Rock 4c Plus web site gives additional information on serial communication.
Expected boot messages
Now put the card into the device and power on. If successful, you should be able to see output like this (click to expand):
U-Boot TPL 2023.07.02 (Sep 24 2023 - 23:39:53) lpddr4_set_rate: change freq to 400MHz 0, 1 Channel 0: LPDDR4, 400MHz BW=32 Col=10 Bk=8 CS0 Row=16 CS=1 Die BW=16 Size=2048MB Channel 1: LPDDR4, 400MHz BW=32 Col=10 Bk=8 CS0 Row=16 CS=1 Die BW=16 Size=2048MB 256B stride lpddr4_set_rate: change freq to 800MHz 1, 0 Trying to boot from BOOTROM Returning to boot ROM... U-Boot SPL 2023.07.02 (Sep 24 2023 - 23:39:53 -0400) Trying to boot from MMC1 spl_load_fit_image: Skip load 'atf-5': image size is 0! NOTICE: BL31: v2.9(release):v2.9.0-dirty NOTICE: BL31: Built : 21:42:04, Sep 24 2023 U-Boot 2023.07.02 (Sep 24 2023 - 23:39:53 -0400) SoC: Rockchip rk3399 Reset cause: POR Model: Radxa ROCK Pi 4C DRAM: 4 GiB (effective 3.9 GiB) Core: 291 devices, 29 uclasses, devicetree: separate MMC: mmc@fe310000: 2, mmc@fe320000: 1, mmc@fe330000: 0 Loading Environment from MMC... Card did not respond to voltage select! : -110 *** Warning - No block device, using default environment In: serial Out: vidconsole Err: vidconsole Model: Radxa ROCK Pi 4C can't get vref-supply: -121 rockchip_dnl_key_pressed: adc_channel_single_shot fail! Net: eth0: ethernet@fe300000 Hit any key to stop autoboot: 0 ** Booting bootflow 'mmc@fe320000.bootdev.part_1' with script Loaded and running boot.scr!
- Early output from U-Boot TPL/SPL won't be shown on HDMI, only on serial
- You can press a key to interrupt the boot process and enter U-Boot's interactive shell
- You can find the list of commands and their syntax if you run
help
Booting a Linux system
Now that U-Boot installed on the SD card, the next step is to use it to boot a Linux system.
By default, U-Boot scans the partitions of each device (except SATA, see next section) to find something it knows how to load. The default supported file systems are ext2
, ext4
and FAT
, but there are a few others you can enable in U-Boot's menuconfig
. You will want your /boot partition to be formatted with one of those filesystems.
Creating boot.scr
One thing it can load boot.scr
, which will contain commands for U-Boot to execute. This will be in your /boot partition along with your kernel and device tree binary.
First, create a file boot.cmd
. The example below includes a lot of extra stuff to facilitate exploration and debugging, but it can be significantly simplified --
echo "Loaded and running boot.scr!"
#setenv userEnv "joetooEnv.txt"
# defaults
setenv load_addr "0x9000000"
setenv overlay_error "false"
setenv rootdev "/dev/mmcblk0p1"
setenv verbosity "1"
setenv console "both"
setenv bootlogo "false"
setenv rootfstype "ext4"
if test -z "$bootargs"; then
setenv bootargs root=${rootdev} rootdelay=5
fi
# print some debugging info
echo "-----[ Initial State ]-----------------------------------"
#echo "userEnv..........: "${userEnv}
echo "load_addr........: "${load_addr}
echo "filesize.........: "${filesize}
echo "rootdev..........: "${rootdev}
echo "devtype..........: "${devtype}
echo "devnum...........: "${devnum}
echo "prefix...........: "${prefix}
echo "distro_bootpart..: "${distro_bootpart}
echo "verbosity........: "${verbosity}
echo "console..........: "${console}
echo "consoleargs......: "${consoleargs}
echo "bootlogo.........: "${bootlogo}
echo "rootfstype.......: "${rootfstype}
echo "earlycon.........: "${earlycon}
echo "overlays.........: "${overlays}
echo "overlay_prefix...: "${overlay_prefix}
echo "fdtfile..........: "${fdtfile}
echo "fdt_addr_r.......: "${fdt_addr_r}
echo "imagefile........: "${imagefile}
echo "kernel_addr_r....: "${kernel_addr_r}
echo "initrdfile.......: "${initrdfile}
echo "ramdisk_addr_r...: "${ramdisk_addr_r}
echo "partuuid.........: "${partuuid}
echo "usbstoragequirks.: "${usbstoragequirks}
echo "extraargs........: "${extraargs}
echo "extraboardargs...: "${extraboardargs}
echo "bootargs.........: "${bootargs}
echo "--------------------------------------------------------"
# Override/overwrite defaults per user's environment selections
echo "Ok - I am about to try reading joetooEnv.txt..."
if test -e ${devtype} ${devnum} ${prefix}joetooEnv.txt; then
load ${devtype} ${devnum} ${load_addr} ${prefix}joetooEnv.txt
env import -t ${load_addr} ${filesize}
else
echo "Did not find or load joetooEnv.txt"
fi
# Manage console settings
if test "${console}" = "display" || test "${console}" = "both"; then setenv consoleargs "console=tty1"; fi
if test "${console}" = "serial" || test "${console}" = "both"; then setenv consoleargs "console=ttyS2,115200 ${consoleargs}"; fi
if test "${earlycon}" = "on"; then setenv consoleargs "earlycon ${consoleargs}"; fi
if test "${bootlogo}" = "true"; then
setenv consoleargs "splash plymouth.ignore-serial-consoles ${consoleargs}"
else
setenv consoleargs "splash=verbose ${consoleargs}"
fi
# get PARTUUID of first partition on SD/eMMC the boot script was loaded from
if test "${devtype}" = "mmc"; then part uuid mmc ${devnum}:1 partuuid; fi
# update bootargs
#setenv bootargs "root=${rootdev} rootwait rootfstype=${rootfstype} ${consoleargs} consoleblank=0 loglevel=${verbosity} ubootpart=${partuuid} usb-storage.quirks=${usbstoragequirks} ${extraargs} ${extraboardargs}"
setenv bootargs "root=${rootdev} rootwait rootfstype=${rootfstype} ${consoleargs} consoleblank=0 loglevel=${verbosity} usb-storage.quirks=${usbstoragequirks} ${extraargs} ${extraboardargs}"
echo "-----[ Potentially Updated State ]-----------------------"
#echo "userEnv..........: "${userEnv}
echo "load_addr........: "${load_addr}
echo "filesize.........: "${filesize}
echo "rootdev..........: "${rootdev}
echo "devtype..........: "${devtype}
echo "devnum...........: "${devnum}
echo "prefix...........: "${prefix}
echo "distro_bootpart..: "${distro_bootpart}
echo "verbosity........: "${verbosity}
echo "console..........: "${console}
echo "consoleargs......: "${consoleargs}
echo "bootlogo.........: "${bootlogo}
echo "rootfstype.......: "${rootfstype}
echo "earlycon.........: "${earlycon}
echo "overlays.........: "${overlays}
echo "overlay_prefix...: "${overlay_prefix}
echo "fdtfile..........: "${fdtfile}
echo "fdt_addr_r.......: "${fdt_addr_r}
echo "imagefile........: "${imagefile}
echo "kernel_addr_r....: "${kernel_addr_r}
echo "initrdfile.......: "${initrdfile}
echo "ramdisk_addr_r...: "${ramdisk_addr_r}
echo "partuuid.........: "${partuuid}
echo "usbstoragequirks.: "${usbstoragequirks}
echo "extraargs........: "${extraargs}
echo "extraboardargs...: "${extraboardargs}
echo "bootargs.........: "${bootargs}
echo "-----[ now env print -a ]---------------------------------"
env print -a
# Load device tree file and kernel (to do: initramfs option)
# device tree - note that it finds the file in the "dtb" directory
echo "Loading device tree from ${devtype} ${devnum}:${distro_bootpart}..."
load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} ${prefix}dtb/${overlay_prefix}/${fdtfile}
echo "Loading initrd from ${devtype} ${devnum}:${distro_bootpart}..."
load ${devtype} ${devnum} ${ramdisk_addr_r} ${prefix}${initrdfile}
echo "Loading kernel from ${devtype} ${devnum}:${distro_bootpart}..."
load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} ${prefix}${imagefile}
# Load overlays (to do, if needed)
# Boot!
echo "Booting with arguments: ${bootargs}"
#booti ${kernel_addr_r} - ${fdt_addr_r}
booti ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}
# Recompile instruction
#
# Cross-compiling:
# cd to u-boot directory holding boot.cmd and run
# mkimage -C none -A arm64 -T script -d boot.cmd boot.scr
# then copy boot.scr, boot.cmd, and joetooEnv.txt to the /boot partition
# of your target system eMMC or microSD card
#
# If modifying an existing system (from said system)
# mkimage -C none -A arm64 -T script -d /boot/boot.cmd /boot/boot.scr
verbosity=6
earlycon=on
bootlogo=true
overlay_prefix=rockchip
fdtfile=rk3399-rock-4c-plus.dtb
imagefile=Image
initrdfile=uInitrd
rootdev=UUID=8870f459-a625-47c4-84f2-21f3a24b1818
rootfstype=ext4
extraargs=init=/sbin/init
usbstoragequirks=0x2537:0x1066:u,0x2537:0x1068:u
Be sure to change
rootdev=
to point to your actual root partition!
This can by done by UUID, as in this example, more portably than by path to block device, like "/dev/mmcblk0p1" -- but either way is validThen, compile it to boot.scr
:
root #
mkimage -C none -A arm64 -T script -d boot.cmd boot.scr
mkimage
can be used from either the bin directory in your U-Boot build directory or from dev-embedded/u-boot-tools- Create a Makefile or a shell script so you don't have to look up the command above next time you need to edit
If the /boot partition is on microSD, it should work without additional steps.