There is a video on YouTube that accompanies this blog post and encourage watching it if any of the points are not completely clear.

From time to time you may find yourself in a situation where you need to build/rebuild a linux kernel. In my case I am working on a Linux Gentoo build for the Raspberry Pi. The main reason for the rebuild is that I would like to use a different file system, btrfs, for the root filesystem as opposed to ext4.

The commands referenced below are all summarized in an easy to copy/paste text file that is available here (insert link here).

While the Raspberry Pi kernel does have support for btrfs that support is provided by a module (which exists in the root filesystem). In order to mount that root filesystem (which is a btrfs filesystem) we need btrfs filesystem support built into the kernel so it is available before the root filesystem is mounted.

Long story short, the kernel needs to be rebuilt with btrfs filesystem support built in.

With that let’s get right into the kernel rebuild. I created a set of copy/paste notes that can be directly copy/pasted into a terminal window to provide an easy way to document and repeat the procedure.

First of all, I am starting off with the latest (as of 2025-01-11) Raspberry Pi OS copied onto a 64G sd card. I highly recommend using an external hard disk for this process as not to hammer the sd card and prematurely wear it out with excessive writes.

For reference here is what was used for this:
+ Raspberry Pi 5 (8GB model)
+ 64GB micro sd card
+ external USB3 hard disk (4TB)
+ Raspberry Pi OS
+ 2024-11-19-raspios-bookworm-arm64-full.img.xz
+ Raspberry Pi OS with desktop and recommended software
+ Release date: November 19th 2024
+ System: 64-bit
+ Kernel version: 6.6
+ Debian version: 12 (bookworm)
+ Size: 2,955MB
+ Show SHA256 file integrity hash:
+ Release notes
+ Linux kernel 6.6.69 (Raspberry Pi modifications) (as of 2025-01-10)
+ https://github.com/raspberrypi/linux

Here is the complete script that was used for this project available for download.
The script is not intended to be directly executed, copy/paste each part in sequence.

First of all to start off with a baseline which is a fresh creation of an sd card followed with any updates available. The only part of this process that does not take place on the Raspberry Pi is the initial creation of the sd card. The sd card can be created using any method on any OS such as Windows. Once the sd card is created and the Raspberry Pi is booting we can move to the next step.

For Linux (and maybe MacOS with some changes) an sd card image can be written to an sd card with the following command (replacing /dev/target with the device such as /dev/sda):

wget https://downloads.raspberrypi.com/raspios_full_arm64/images/raspios_full_arm64-2024-11-19/2024-11-19-raspios-bookworm-arm64-full.img.xz

xzcat 2024-11-19-raspios-bookworm-arm64-full.img.xz | sudo dd of=/dev/target

Be sure to startup the Raspberry Pi and perform all the initial setup required and possibly update it as well. Then enable SSH if desired.

Now that we have an sd card created and a working Raspberry Pi operating system we can proceed. The following steps can be performed in an ssh session to the Raspberry Pi (recommended) or directly on the Raspberry Pi.

From here forward all these commands are performed either on the Raspberry Pi itself or through an SSH connection to the Raspberry Pi.

To begin change to root (or super user) to eliminate frustrations. This may not be a popular option with some and if this is the case then just append sudo when required.

sudo su

Setup the environment variables. Variables are used to allow easy changes to paths and other options without having to do a find/replace and possibly miss something important.

An external hard disk is highly recommended as not to hammer the sd card with excessive writes and cause premature burn out on write endurance. The external hard disk should be formatted in ext4 (or another Linux permission compatible format). The below command can format the external hard disk, but be careful about the target and double check. Replace /dev/target with your device, for example /dev/sda1 and be sure to dismount it first (if mounted).

umount /dev/target partition
mkfs.ext4 /dev/targetpartition

After formatting and unmounting any other partitions mounted, simply disconnecting and reconnecting the USB cable should mount the external hard disk. Find out where the disk as been mounted with the mount command and set the below WORKROOT variable accordingly.

If using an external hard disk is not an option, or if the rootfs is on something like an NVMe device, then adjust the below WORKROOT to something like /home/pi instead.

The BOOTDIR and KERNELMODULESDIR variables are for final kernel installation if desired, but not required. Up until the final commands to update the boot partition no system changes are being performed and everything is self contained inside the WORKDIR (outside of the build tools installed through apt, which can be removed after).

For ease of organization a date stamp is applied to many of the folders and archive files created. Feel free to fix it to a desired value, or append the time to it. This is adjusted with the DATESTAMP variable below. By default it will retrieve the current datetime when that command is executed.

If at anytime the terminal window is closed or paused, be sure to execute the below variables commands again. It is recommended to perform everything here in one sitting (or one session). The actual build (compile) can be started and let run overnight and then continued the next morning if desired.

This compile will work best on a Raspberry Pi 5 (fastest) but should have no issue running it on an older Raspberry Pi that has 64-bit support.

NOW_YMDHMS=$(date +"%Y%m%d%H%M%S")
NOW_YMD=$(date +"%Y%m%d")
DATESTAMP=${NOW_YMD}
WORKROOT=/media/pi/e65e2c06-9254-4d71-b75e-3e407c807acf
WORKDIR=${WORKROOT}/kernel_build_${DATESTAMP}
BOOTDIR=/boot/firmware
KERNELMODULESDIR=/lib/modules

Next, prepare the work directory by executing the below commands.
This will erase any files present in the work directory specified in the WORKDIR variable.

rm -rf ${WORKDIR}
mkdir ${WORKDIR}
cd ${WORKDIR}

Next, install the build tools required by executing the following apt install command. Some tools may already be present, in this case they will updated if a newer version is available.

apt install git bc bison flex libssl-dev make libc6-dev libncurses5-dev

Next will be a large download, the Linux kernel source tree.
Be prepared for this to take several minutes to download.
This is the only item required to be downloaded and require an internet connection.
The internet collection can be disconnected after the download is complete.
Any previous download is deleted first.

rm -rf ${WORKDIR}/linux
git clone --depth=1 https://github.com/raspberrypi/linux

Next, we would like to know the version of this Linux kernel so the next commands will parse that out from the Makefile and make it available to later parts of this script.

#kernel version found in kernel Makefile, at the top (VERSION, PATCHLEVEL, SUBLEVEL)
filename="${WORKDIR}/linux/Makefile"
KERNEL_VERSION=$(grep -m 1 VERSION $filename | sed 's/^.*= //g')
KERNEL_VERSION=${KERNEL_VERSION}.
KERNEL_VERSION=${KERNEL_VERSION}$(grep -m 1 PATCHLEVEL $filename | sed 's/^.*= //g')
KERNEL_VERSION=${KERNEL_VERSION}.
KERNEL_VERSION=${KERNEL_VERSION}$(grep -m 1 SUBLEVEL $filename | sed 's/^.*= //g')
echo "KERNEL_VERSION="${KERNEL_VERSION}

While not required we can archive the download so we have an untouched copy for possible future use.
Yes, there is a dot (.) on the end of the command, it is important, don’t forget it.

*** archive downloaded files for possible use later
tar -jcvpf ${WORKDIR}/linux_${KERNEL_VERSION}_${DATESTAMP}.tar.bz2 --directory=${WORKDIR}/linux .

There are two kernel configurations used for the Raspberry Pi (64-bit).
kernel8 (bcm2711_defconfig) for 64-bit supporting Raspberry Pi 4 and earlier.
kernel_2712 (bcm2712_defconfig) for the Raspberry Pi 5.
Only the necessary flavor needs to be built, but it is recommended to build both to update the kernel for the Raspberry Pi 5 and also previous models.

Let’s setup for the kernel8 build below.

#Architecture 64-bit
#Models
# Raspberry Pi 3
# Raspberry Pi Compute Module 3
# Raspberry Pi 3+
# Raspberry Pi Compute Module 3+
# Raspberry Pi Zero 2 W
# Raspberry Pi 4
# Raspberry Pi 400
# Raspberry Pi Compute Module 4
# Raspberry Pi Compute Module 4S
cd ${WORKDIR}/linux
KERNEL=kernel8
make bcm2711_defconfig

This will setup the sources and configure them with the Raspberry Pi default options.
At this point we can edit and customize the kernel settings.
This may be desired for supporting new hardware, or enabling some kernel feature.
There are hundreds of options to configure and explaining how to configure the Linux kernel is beyond the scope of this current post, but there is a lot of information out there on it.

In my case I would like make one change to the kernel. I would like to use a BTRFS root filesystem for a current project. I will be installing Linux Gentoo (a build from source flavor of Linux) on the Raspberry Pi soon and would like to use BTRFS (as opposed to ext4) for the root filesystem. While the Raspberry Pi kernel default configuration has BTRFS support enabled it is built as a module. Since the module resides in the root filesystem the kernel can not access it without first mounting the root filesystem which contains it. A slight chicken-egg problem and to resolve this we will choose to have BTRFS support built directly into the kernel (which increases kernel size slightly but necessary to mount the root filesystem at boot time).

If the above explanation did not make sense don’t worry, it is not important to understand to complete the kernel build, it is simply a reason I have to change a kernel option.

In order to make the above change (only if desired) we will use menuconfig to perform the change.

make menuconfig

This will compile a couple files then display a text based menu system to allow kernel changes, and what I am aiming to change is under the following:
File systems -> Btrfs filesystem support
Place the cursor over the “M” that indicates “build as module” and press “y” on the keyboard to have BTRFS support build directly into the kernel.

Once this change is done, navigate (with the left/right cursor keys) to “Exit” and continue to “Exit” until a prompt is displayed to save change. Choose “yes” here and the new kernel configuration is changed and ready.

Next, kick off the build.
This will take some time, a very long time, can expect an hour or more.
Come back when done and the prompt returns.
This below command “make” will begin the compile process and generate “Image.gz” along with the kernel loadable modules (drivers) and dtbs (overlay files).

make -j6 Image.gz modules dtbs

After a long compile our prompt returns and we will copy out the kernel modules for archive using the below command. Again, the existing folder will be deleted if it exists. Normally the below command will directly install the kernel modules into the root filesystem but we are altering the destination with the INSTALL_MOD_PATH option.

rm -rf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_modules
make -j6 INSTALL_MOD_PATH=${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_modules modules_install

Archive the following three results from the build (to save our work for future use).
+ kernel modules
+ kernel and kernel config
* dtbs overlays

tar -jcvpf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_modules_${DATESTAMP}.tar.bz2 --directory=${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_modules/lib/modules/ .
rm -rf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}
mkdir -p ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}
cp arch/arm64/boot/Image.gz ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}/${KERNEL}.img
cp .config ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}/${KERNEL}.config
tar -jcvpf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_${DATESTAMP}.tar.bz2 --directory=${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}/ .
rm -rf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb
mkdir -p ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb
cp arch/arm64/boot/dts/broadcom/*.dtb ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb/
mkdir -p ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb/overlays
cp arch/arm64/boot/dts/overlays/*.dtb* ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb/overlays/
cp arch/arm64/boot/dts/overlays/README ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb/overlays/
tar -jcvpf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb_${DATESTAMP}.tar.bz2 --directory=${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb/ .

We then repeat the above process for the kernel_2712 (bcm2712_defconfig) configuration.
This is simply a repeat with just a change to the KERNEL variable.

Configure the build with default options.

#Architecture 64-bit
#Models
# Raspberry Pi 5
cd ${WORKDIR}/linux
KERNEL=kernel_2712
make bcm2712_defconfig

Make a change to the kernel to enable BTRFS support (only if desired).

make menuconfig

Change the following:
File systems -> Btrfs filesystem support
Navigate to “Exit” and continue to “Exit” until presented with the save dialog.
Choose “yes” and the configuration is saved ready for building.

Kick off the build.
Again, this will take a while.

make -j6 Image.gz modules dtbs

Prepare the kernel modules for this build.

rm -rf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_modules
make -j6 INSTALL_MOD_PATH=${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_modules modules_install

Archive the following three results from the build (to save our work for future use).
+ kernel modules
+ kernel and kernel config
* dtbs overlays

tar -jcvpf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_modules_${DATESTAMP}.tar.bz2 --directory=${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_modules/lib/modules/ .
rm -rf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}
mkdir -p ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}
cp arch/arm64/boot/Image.gz ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}/${KERNEL}.img
cp .config ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}/${KERNEL}.config
tar -jcvpf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_${DATESTAMP}.tar.bz2 --directory=${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}/ .
rm -rf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb
mkdir -p ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb
cp arch/arm64/boot/dts/broadcom/*.dtb ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb/
mkdir -p ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb/overlays
cp arch/arm64/boot/dts/overlays/*.dtb* ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb/overlays/
cp arch/arm64/boot/dts/overlays/README ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb/overlays/
tar -jcvpf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb_${DATESTAMP}.tar.bz2 --directory=${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb/ .

Congratulations, if you made it this far you now have a shiny new kernel awaiting installation into the boot partition, which we will perform next.

While not required it is never a bad idea to backup the current boot partition incase we make a mistake.
The below command will do that.

#backup the current bootfs if needed
tar -jcvpf ${WORKDIR}/bootfs_${DATESTAMP}.tar.bz2 --directory=${BOOTDIR} .

If it is desired to restore the boot partition from this backup then the below commands will be useful.
Be careful with the rm -rf command, it will wipe all the contents from the boot partition (rendering the Raspberry Pi unable to boot until the files are restored).

Double check the variable BOOTDIR is correct, it is at the top of this document.

#restore the bootfs from backup if needed
#first blow away the bootfs
rm -rf ${BOOTDIR}/*
#verify the current bootfs is empty
ls -la ${BOOTDIR}
#next, restore the bootfs from backup
tar xjf ${WORKDIR}/bootfs_${DATESTAMP}.tar.bz2 -C ${BOOTDIR}
#verify the current bootfs is now populated
ls -la ${BOOTDIR}

The final step is to copy our new kernel (and related support files) to the boot and root filesystems.

Copy the kernel8 kernel and related files over.

#copy the new kernel to the bootfs
#for kernel8 (raspberry pi 3, 3a, 3a+, 3b, 3b+, 4, cm4, 400, zero, 2w
KERNEL=kernel8
tar xjf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_${DATESTAMP}.tar.bz2 -C ${BOOTDIR} ./${KERNEL}.img

#dtb files should be same for all kernels
tar xjf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb_${DATESTAMP}.tar.bz2 -C ${BOOTDIR}

#optional but recommended to retain a copy of the kernel configuration
tar xjf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_${DATESTAMP}.tar.bz2 -C ${BOOTDIR} ./${KERNEL}.config

#copy kernel modules to the rootfs
tar xpjf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_modules_${DATESTAMP}.tar.bz2 -C ${KERNELMODULESDIR}

Copy over the kernel_2712 kernel and related files.
Notice the dtb files copy command is commented out as it should be redundant and not required.

#copy the new kernel to the bootfs
#for kernel_2712 (raspberry pi 5)
KERNEL=kernel_2712
tar xjf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_${DATESTAMP}.tar.bz2 -C ${BOOTDIR} ./${KERNEL}.img

#dtb files should be same for all kernels
#tar xjf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_dtb_${DATESTAMP}.tar.bz2 -C ${BOOTDIR}

#optional but recommended to retain a copy of the kernel configuration
tar xjf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_${DATESTAMP}.tar.bz2 -C ${BOOTDIR} ./${KERNEL}.config

#copy kernel modules to the rootfs
tar xpjf ${WORKDIR}/linux_${KERNEL_VERSION}_${KERNEL}_modules_${DATESTAMP}.tar.bz2 -C ${KERNELMODULESDIR}

That’s it! The kernel and related files should have been updated and all that remains is to perform a reboot and if everything went ok there should be no issues and the new shiny kernel version can be verified with the following command on the Raspberry Pi.

cat /proc/version

In my case, this was the output.

pi@raspberrypi:~ $ cat /proc/version
Linux version 6.6.70-v8-16k+ (root@raspberrypi) (gcc (Debian 12.2.0-14) 12.2.0, GNU ld (GNU Binutils for Debian) 2.40) #2 SMP PREEMPT Sun Jan 12 03:38:56 JST 2025

This will conclude this post but stay tuned for the next post which will be installing Gentoo on the Raspberry Pi. This kernel rebuild was a pre requisite for the larger Gentoo install project.

Leave a Reply

Your email address will not be published. Required fields are marked *