This document describes my procedure for building and running a mainline Linux kernel for the single-board computer (SBC) Rock 4C Plus by Radxa.
Support for Rock 4C Plus has been upstreamed since Linux 6.0, but it is still difficult to find a single online resource that describes all the steps needed to run a mainline kernel on the SBC.
The Rock 4C Plus Starter Kit sold by Okdo contains an SD card reader which is very useful.
If you do not want to overwrite the preloaded SD card, you have to buy a couple of additional SD cards.
We also need a USB-TTL converter to connect to the UART of the Rock. The Rock UART uses a very high speed of 1500000, rather than the conventional speed of 115200. Therefore it is important to get a high-quality converter that supports this speed.
We need to build three components:
- The Linux kernel;
- U-Boot, a bootloader that loads Linux;
- ATF the trusted firmware.
All three components are open-source. However, booting the Rock still requires a piece of firmware blobs from Radxa, which is for DDR memory training.
We begin from a Debian container.
podman pull debian:stable-slim
podman create -ti --name rock_linux debian:stable-slim
podman start -ai rock_linuxFirst we disable automatic installation of recommended and suggested packages by apt.
Most of them are unnecessary.
cd /etc/apt/apt.conf.d/
echo "APT::Install-Recommends \"false\";" > no-rec
echo "APT::Install-Suggests \"false\";" >> no-rec
apt-config dump | grep "APT::Install-"Install nano and wget which will be very useful.
Wget requires ca-certificates to make HTTPS connections.
apt-get update
apt-get install nano wget ca-certificatesAssume we are using x86 hosts. We need to install a cross compiler for Aarch64. Building the ATF additionally requires a cross compiler for 32-bit ARM because part of the code will run on an M0 processor. For our current purpose, a bare-metal compiler is sufficient. However Debian does not package bare-metal compilers for Aarch64. Therefore we shall use the bare-metal toolchain provided by ARM.
cd ~
# Link to the latest version can be found at https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
wget https://developer.arm.com/-/media/Files/downloads/gnu/13.3.rel1/binrel/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-elf.tar.xz
wget https://developer.arm.com/-/media/Files/downloads/gnu/13.3.rel1/binrel/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi.tar.xz
apt-get install xz-utils
tar -C /opt -xJf arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-elf.tar.xz
tar -C /opt -xJf arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi.tar.xz
cd /opt
mv arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-elf aarch64-none-elf
mv arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi arm-none-eabiWe now install git and download the sources for the kernel, U-Boot, and ATF.
cd ~
apt-get install git
git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git -b v6.6.44 --depth 1
git clone https://source.denx.de/u-boot/u-boot.git -b v2024.07 --depth 1
git clone https://github.com/ARM-software/arm-trusted-firmware.git -b lts-v2.10.4 --depth 1To build the kernel we need to install the following packages:
apt-get install gcc flex bison bc libncurses-dev libssl-devIf you plan to make an initramfs (not really necessary for the Rock), install also cpio and zstd.
apt-get install cpio zstdTo build U-Boot we additionally need to install:
apt-get install libgnutls28-dev libpython3-dev python3 python3-setuptools python3-pyelftools swig uuid-dev`Building ATF does not need additional packages, but remember to install the 32-bit ARM cross compiler.
Building the kernel requires setting the following environment variables:
export ARCH=arm64
export CROSS_COMPILE=/opt/aarch64-none-elf/bin/aarch64-none-elf-
export KCFLAGS="-march=armv8-a+crc+crypto -mtune=cortex-a72.cortex-a53"Execute make defconfig to get the default config.
Then execute make menuconfig to disable unneeded features.
My current kernel config is at: https://gist.github.com/CharlieQiu2017/2c4966564e8519c361e1861101aadff0 It enables only the basic I/O and networking functionalities.
After saving the config, execute make -j4 to build the kernel with 4 parallel threads.
After building the kernel, there are two important output files:
arch/arm64/boot/Image: This is the kernel image file;arch/arm64/boot/dts/rockchip/rk3399-rock-4c-plus.dtb: This is the device tree blob.
I tried to build the mainline ATF following https://opensource.rock-chips.com/wiki_ATF and https://trustedfirmware-a.readthedocs.io/en/latest/plat/rockchip.html, but it resulted in a boot loop. Then I followed https://stikonas.eu/wordpress/2019/09/15/blobless-boot-with-rockpro64/ and deleted binary blobs from ATF. This seemed to do the trick.
find . -name '*.bin' -exec rm -rf {} \;We also need to set the following environment variables:
unset BL31 # BL31 must NOT be set
export CROSS_COMPILE=/opt/aarch64-none-elf/bin/aarch64-none-elf-
export M0_CROSS_COMPILE=/opt/arm-none-eabi/bin/arm-none-eabi-Invoke make PLAT=rk3399 DEBUG=0 -j4 bl31 to build ATF.
After building, there is one important output file:
build/rk3399/release/bl31/bl31.elf: This is the BL31 executable, which implements the PSCI interface.
It appears that the DDR training firmware from Radxa is no longer needed for the latest version of U-Boot.
Therefore, we only need to copy the bl31.elf file from ATF to firmware/bl31.elf.
We also need to set these environment variables:
export CROSS_COMPILE=/opt/aarch64-none-elf/bin/aarch64-none-elf-
export BL31=firmware/bl31.elfExecute make rock-4c-plus-rk3399_defconfig to use the default config.
Execute make -j4 to build U-Boot.
After building U-Boot, there are two important output files:
idbloader.img: The "first stage loader";u-boot.itb: The "second stage loader".
The default partitioning scheme for Rockchip devices is described at http://opensource.rock-chips.com/wiki_Partitions. However, it wastes too much space. Therefore we will use an alternative partitioning scheme consisting of only 4 partitions:
- The first stage bootloader is placed from 32KiB to 4MiB;
- The second stage bootloader is placed from 8MiB to 12MiB;
- The kernel is placed in an EFI partition from 12MiB to 20MiB;
- The remaining space is used as rootfs.
Insert an empty SD card into the SD card reader.
On my laptop, the SD card shows up as a block device /dev/sda.
We will use parted to partition the card.
The other tools like fdisk, cfdisk cannot create non-aligned partitions.
This is necessary because the first stage bootloader partition is hard-coded to be located at 32KiB.
# Outside podman container, since we have to access low-level block devices
sudo parted /dev/sda
# Inside parted
mktable gpt
unit s
mkpart primary 64 8191
mkpart primary 16384 24575
mkpart primary 24576 40959
mkpart primary 40960 59404287
toggle 3 boot
quit
# Outside parted
sudo mkfs.vfat -F 12 /dev/sda3
sudo mkfs.vfat /dev/sda4My SD card reports 59406336 sectors. I made the last partition end at the last MB boundary.
Execute sudo blkid /dev/sda4 to get the PARTUUID of the rootfs partition.
This will be important in the next step.
Copy the kernel Image file, device tree blob rk3399-rock-4c-plus.dtb, U-Boot files idbloader.img and u-boot.itb to the host.
First, flash the U-Boot files:
sudo dd if=idbloader.img of=/dev/sda1
sudo dd if=u-boot.itb of=/dev/sda2Next, mount and flash the EFI system partition:
mkdir sd_boot
sudo mount -t vfat /dev/sda3 sd_boot
sudo mkdir sd_boot/extlinux
sudo mkdir sd_boot/rockchip
sudo cp Image sd_boot/
sudo cp rk3399-rock-4c-plus.dtb sd_boot/rockchip/Inside sd_boot/extlinux, create a file extlinux.conf with the following content:
default linux-6.6.44
label linux-6.6.44
kernel /Image
append console=ttyS2,1500000 earlycon root=PARTUUID=$(rootfs_uuid)
fdtdir ../
Here $(rootfs_uuid) is the PARTUUID of the root partition obtained in the last step (without the double quotes).
The device tree of Rock 4C Plus reports 4 serial consoles.
However, the first one is connected to the bluetooth device.
The second and fourth ones are disabled by default.
Therefore, we use ttyS2 which is the third serial console.
We will take a shortcut and simply use the rootfs of Alpine Linux.
Mount the root partitions. Download the rootfs of Alpine Linux and extract it to the root partition. Unmount and eject the SD card.
Insert the SD card into the Rock. Connect the USB-TTL converter to the Rock.
- Connect
RXDto pin 8; - Connect
TXDto pin 10; - Connect
GNDto any black pin. Do not connect other pins, especiallyVCC.
On the laptop host, use microcom to connect to the UART:
sudo microcom -s 1500000 -p /dev/ttyUSB0Power on the Rock. It should boot into Alpine Linux userspace.