Summary

This article systematically documents how I turned a Raspberry Pi 5 into a minimal development and testing platform for the mainline Linux kernel (Vanilla Kernel) — including repository trimming strategies, custom kernel configuration, cross-compilation and automated deployment scripts, overlay removal techniques, and long-term synchronization with the mainline community. It’s aimed at geeks interested in the Linux mainline, ARM64 adaptation, kernel development, upstream contribution, or anyone who loves tinkering with the kernel.

Background and Motivation

Lately, I’ve been diving deep into Linux kernel theory and architecture, preparing to formally enter the OS kernel field. I specifically purchased a Raspberry Pi 5 as a development board for hands-on kernel work. Right away I discovered that the Pi 5 can’t run the mainline Linux kernel out of the box; it depends on the Raspberry Pi team’s maintained kernel repository.

At that time the newest branch was rpi-6.14.y (now rpi-6.16.y). Their workflow is “dynamically pull upstream code + rebase + a ton of custom patches,” involving tens of thousands of lines of changes and repeated history rewrites.

Those custom patches are indeed crucial for stable operation and full feature compatibility on Raspberry Pi, but for someone like me — purely interested in studying upstream kernel features — they become a burden:

  • Hard to synchronize with mainline; merges often conflict
  • History is rewritten frequently, making tracing and comparison difficult
  • Testing new mainline features or providing feedback isn’t straightforward

I compared with x86 development boards, which mostly run mainline kernels directly but are expensive and far less cost-effective than the Raspberry Pi 5. So I wondered: how can we smoothly run a mainline kernel on Pi 5, truly experience and test the latest kernel features at any time, and easily contribute issues or patches for the ARM64 platform? My goal was clear: with minimal patches and as close to upstream as possible, make the Pi 5 an ideal personal ARM64 platform for mainline kernel experiments and learning.

Repository Structure and Scope of Changes

My approach is simple but direct — keep only the essential drivers and patches, and track mainline for everything else. I only patch the drivers/ directory, focusing on core hardware support:

  • SD card driver: Ensure Pi boots correctly and can mount the root filesystem. Although the firmware provides some initialization, the mainline kernel still needs the driver to mount the root fs.
  • Ethernet: For SSH remote login, downloading new kernel images, remote development and debugging — one of the most important I/O channels for kernel work.
  • UART serial: Acts as the early printk console for kernel logs to observe boot and runtime status.
  • USB controller: For external device mounts or occasional offline data transfer, supporting more test scenarios.
  • Basic GPIO and power management: Hardware-critical parts for RPi5.

I remove all non-essential subsystems like Wi-Fi, Bluetooth, GPU (VideoCore), HDMI, multimedia acceleration — none are critical for kernel development and testing.

Maintain the RPi5 DTS under arch/arm64/boot/dts/ to ensure the mainline kernel correctly recognizes CPU settings and peripherals at boot.

Everything else is 1:1 synced with Linus’s mainline: aside from these very few patches, the rest of the repository matches the official mainline repo. Periodically (every month or two weeks), pull the latest mainline code, apply minimal patches, and resolve any small conflicts.

This keeps the repo minimal, easy to maintain, and lays the foundation for quick mainline follow-ups and streamlined configuration.

Example of Custom Kernel Configuration

During deployment, I prepared a custom .config file for the Pi 5 mainline kernel, aiming to trim unnecessary features to minimize build size and time while covering my development, testing, and service needs. This took several iterations of trial and error.

Phase One: Extreme Trimming, Keep Only the Core

My initial .config turned off almost everything non-essential:

  • All non-onboard NIC drivers (only keep the Pi 5 Cadence driver)
  • Virtualization support (KVM, etc.)
  • Graphics subsystem, sound cards, HDMI, multimedia acceleration
  • All network file systems
  • File systems except ext4 and SD card (mmcblk)
  • Related crypto algorithms and kernel mechanisms
  • Even cgroup

After multiple tests, while it might not be the absolute minimal, build times dropped significantly and the kernel still booted stably with the required basic functions.

Phase Two: Practical Extensions, Supporting Docker Workloads and Daily Services

As I delved deeper, I realized I needed:

  • Docker for realistic workload testing of kernel stability and compatibility
  • A LAN file server (NAS) for daily scenarios
  • A second Pi for long-term running of self-built kernels, hosting non-critical services

So the config gradually re-enabled:

  • cgroup, iptables, fuse, overlayfs, wireguard, macvlan, NFS, Samba (common modules for Docker and file services)
  • Most new features built as modules for on-demand loading, optimizing startup and runtime

This increased build time slightly, but the config remained much leaner than the default mainline and aligned well with my practical testing needs.

The config files are hosted at: https://git.bktus.com/Linux/kernel-configs/
Key configs along the timeline:

  • eric-rpi-v1_defconfig
  • eric-vanilla-c-6.13_defconfig
  • eric-vanilla-c-6.14_defconfig
  • eric-vanilla-c-6.15_defconfig
  • eric-vanilla-v1_defconfig
  • eric-vanilla-v2_defconfig
  • eric-vanilla-v3_defconfig

You can see my trimming and extension journey through these files.

Advantages and Use Cases

Close to Upstream, Quickly Test New Features

With minimal patches, you can almost “zero-cost” pull linux-next patches or Linus’s Merge-Window features directly onto Pi 5. Whether it’s a new scheduler, FS feature, virtualization and security subsystem improvements (KVM, eBPF, LSM), or Zstd compression, new memory management, you can immediately test compatibility and performance on real hardware, then provide timely feedback to the ARM64 community.

Even more importantly, by running the “vanilla” kernel, you experience the kernel’s true implementation logic, avoid upstream patches’ side-effects, and don’t mistake vendor-patch issues for mainline bugs.

Using merges instead of rebases makes history cleaner; mainline vs local diffs are clear at a glance, and merges are almost “instant pass,” letting you always keep up with mainline — e.g., pull linux-next for benchmarks or patch testing. The experience and engagement are fantastic.

This approach benefits kernel developers, upstream enthusiasts, and hardcore hackers — any bugs you find or patches you commit are in a true upstream environment, ready for kernel-ml review without vendor-patch interference.

More Efficient Maintenance and Development

  • Fewer patches, fewer conflicts: focus only on RPi5 hardware support (drivers/net/arm…, drivers/usb…, drivers/bus…, arch/arm64/boot/dts/broadcom…), minimal merge conflicts.
  • Easy debugging and rollback: if upstream introduces a regression, you only need to inspect a few patches, not thousands of lines of vendor code.
  • Simplified upstream contribution: your driver fixes and bug patches are “clean” and can be formatted into patches for the mailing list without vendor noise.

Ideal Dev/Test/Learning Platform

  • Feature exploration and validation: Pi 5 is a low-cost, mainstream ARM64 board for linux-next or any mainline branch testing, feeding real-world feedback upstream.
  • Performance tuning and driver testing: benchmarking, driver optimization, power experiments — evaluate new features on ARM64 hardware.
  • Teaching and self-learning: for students learning Linux drivers, device tree, and HAL, a minimal environment reduces “noise” and focuses on understanding mainline frameworks.

The official repo excels in “out-of-the-box multimedia and full-stack” experiences. My mainline repo focuses on “upstream sync, minimal maintenance, and dev-friendly” needs. If you just want GUI, video, or gaming, use the official. For kernel engineers, developers, or enthusiasts, the “trimmed mainline repo” lets you focus on core kernel work.

How to Use and Contribute

In theory, Pi 5 has enough power to compile its own kernel (especially with ≥4 GB RAM), and the experience is acceptable. But for serious kernel or module development with code indexing, its performance is insufficient.

I tried adding the official SSD Kit (512 GB) to fix I/O, but CPU became the bottleneck with only 4 cores! So, if you only need to build and install occasionally, on-device builds work. For frequent development and debugging, I strongly recommend cross-compilation on your x86 workstation — my 12-core station cuts compile time by several times compared to Pi 5’s 4 cores. Below I detail my cross-compile setup.

First, install the cross-compile environment and optionally ccache to speed up rebuilds:

sudo apt install bc bison flex libssl-dev make libc6-dev libncurses5-dev
sudo apt install crossbuild-essential-arm64
sudo apt install ccache

Clone the Repos and Checkout the Latest Branch

I host my configs privately, but mirror on GitHub. I regularly track Linus’s mainline and update main.

git clone https://git.bktus.com/Linux/kernel-configs.git
git clone https://github.com/saturneric/linux
cd linux
git checkout main

Note: the first command clones the config repo, so don’t use the Raspberry Pi team’s config files later (they enable many features), which likely break boot.

Simple Configuration

Choose a config file matching your kernel version, rename to .config, and copy into the kernel root:

cp ../kernel-configs/eric-vanilla-c-6.16_defconfig .config

You can trim further or enable features:

make ARCH=arm64 CROSS_COMPILE="ccache aarch64-linux-gnu-" menuconfig

Build

With the cross toolchain:

make ARCH=arm64 CROSS_COMPILE="ccache aarch64-linux-gnu-" -j$(nproc)

Build time varies; mine takes about 10 minutes.

Copy Kernel and Modules to Pi

Use my script to rsync the built kernel image and modules to the Pi’s home directory. Ensure passwordless SSH is set up:

#!/bin/bash

# ====== Configurable Parameters ======
LOCAL_KERNEL_ARTIFACTS="./kernel-artifacts"
REMOTE_HOST="[email protected]"
REMOTE_KERNEL_ARTIFACTS="/home/pi/kernel"
KERNEL_IMG="kernel-vanilla.c.img"
DTB_FILE="bcm2712d0-rpi-5-b.vanilla.c.dtb"
DTB_SOURCE="arch/arm64/boot/dts/broadcom/bcm2712d0-rpi-5-b.dtb"
# =====================================

set -e

echo ">>> Cleaning and preparing local directory <<<"
rm -rf "$LOCAL_KERNEL_ARTIFACTS"
mkdir -p "$LOCAL_KERNEL_ARTIFACTS"

echo ">>> Installing modules to local output <<<"
make ARCH=arm64 CROSS_COMPILE="aarch64-linux-gnu-" INSTALL_MOD_PATH="$LOCAL_KERNEL_ARTIFACTS" modules_install

echo ">>> Copying kernel and DTB <<<"
cp -v arch/arm64/boot/Image.gz         "$LOCAL_KERNEL_ARTIFACTS/$KERNEL_IMG"
cp -v "$DTB_SOURCE"                    "$LOCAL_KERNEL_ARTIFACTS/$DTB_FILE"

echo ">>> Uploading artifacts to Raspberry Pi <<<"
ssh "$REMOTE_HOST" "sudo mkdir -p $REMOTE_KERNEL_ARTIFACTS"
rsync -avz --progress --delete "$LOCAL_KERNEL_ARTIFACTS/" "$REMOTE_HOST:$REMOTE_KERNEL_ARTIFACTS"

echo ">>> Upload complete! Login to Pi to configure and switch kernels <<<"

Install Kernel and Modules on Pi

Run this on the Pi (with sudo) to deploy to /boot and /lib/modules. Double-check paths to avoid overwriting important files:

#!/bin/bash

# ==============================================
# RPi5 Mainline Kernel Deployment Script
# Installs kernel image, DTB, and modules to /boot and /lib/modules
# ==============================================
set -e

# ====== Configurable Parameters ======
REMOTE_KERNEL_ARTIFACTS="/home/pi/kernel"
KERNEL_IMG="kernel-vanilla.c.img"
DTB_FILE="bcm2712d0-rpi-5-b.vanilla.c.dtb"
BOOT_FIRMWARE_PATH="/boot/firmware"
LIB_MODULES_PATH="/lib/modules"

# ==============================================
echo ">>> 1. Installing kernel image and DTB <<<"
sudo cp -v "$REMOTE_KERNEL_ARTIFACTS/$KERNEL_IMG" "$BOOT_FIRMWARE_PATH/$KERNEL_IMG"
sudo cp -v "$REMOTE_KERNEL_ARTIFACTS/$DTB_FILE" "$BOOT_FIRMWARE_PATH/$DTB_FILE"

echo ">>> 2. Setting permissions <<<"
sudo chown root:root "$BOOT_FIRMWARE_PATH/$KERNEL_IMG" "$BOOT_FIRMWARE_PATH/$DTB_FILE"
sudo chmod 644 "$BOOT_FIRMWARE_PATH/$KERNEL_IMG" "$BOOT_FIRMWARE_PATH/$DTB_FILE"

echo ">>> 3. Installing kernel modules <<<"
sudo rsync -av --progress "$REMOTE_KERNEL_ARTIFACTS/lib/modules/" "$LIB_MODULES_PATH/"

echo ">>> 4. Fixing module permissions <<<"
sudo chown -R root:root "$LIB_MODULES_PATH/"
find "$LIB_MODULES_PATH/" -type d -exec sudo chmod 755 {} \;
find "$LIB_MODULES_PATH/" -type f -exec sudo chmod 644 {} \;

echo ">>> Deployment complete! Update /boot/firmware/config.txt to boot the new kernel if needed <<<"

Configure Pi Firmware to Boot the New Kernel

Edit /boot/firmware/config.txt to point to the new DTB and kernel:

# DTB
device_tree=bcm2712d0-rpi-5-b.vanilla.c.dtb

# Kernel
kernel=kernel-vanilla.c.img

Removing Raspberry Pi’s DTS Overlay Feature (Avoid Common Boot Issues)

The Pi team introduced DTS Overlay support to dynamically apply overlay fragments on top of the main DTB, flexibly enabling peripherals or parameters. However, this isn’t supported by mainline Linux. If overlays in /boot/firmware/config.txt don’t match the mainline DTB version, boot failures and device detection errors occur — a headache for pure mainline kernel work.

Approach

While overlays are flexible, mainline doesn’t support them and they introduce noise. I chose to fully remove overlay support, keeping only the main DTB. The direct side effect is missing CPU parameters normally injected by overlays, which prevents boot.

Solution: I embedded required CPU parameters directly into the main DTB. In fact, the official bcm2712d0-rpi-5-b.dtb works in a no-overlay scenario. Simply use it as the main DTB and the system boots without overlays.

How to Remove Overlays

Edit /boot/firmware/config.txt, set only device_tree, and comment out or delete all dtoverlay= lines:

# Specify main DTB (use bcm2712d0-rpi-5-b.vanilla.c.dtb)
device_tree=bcm2712d0-rpi-5-b.vanilla.c.dtb

# Do not load any overlays
#dtoverlay=xxx # comment out or delete all

# Disable other auto-detect features
camera_auto_detect=0
display_auto_detect=0

# Boot the kernel image
kernel=kernel-vanilla.c.img
  • Disable camera_auto_detect and display_auto_detect.
  • Ensure no dtoverlay= entries remain.

Reboot the Pi

After these changes, reboot. The system will load only the main DTB, no overlays, keeping your mainline environment pure. You can even remove /boot/firmware/overlays to save space and guarantee no overlays are ever applied.

Minimal config.txt Template

# Specify main DTB and mainline kernel
device_tree=bcm2712d0-rpi-5-b.vanilla.c.dtb
kernel=kernel-vanilla.c.img

# Disable overlay auto-detect
camera_auto_detect=0
display_auto_detect=0

# Other common settings
disable_fw_kms_setup=1
arm_64bit=1
disable_overscan=1
arm_boost=1

Submitting Patches or Issues

  • If you encounter any problems following this guide or find a step that doesn’t work, feel free to report an issue.
  • If you fix a compatibility bug or discover a new issue, submit an Issue, and let’s discuss improvements together.
  • If you add hardware support, performance enhancements, or port a mainline patch, use scripts/format-patch.sh to generate standardized patches and submit to the upstream kernel mailing list.

GitHub Repo: https://github.com/saturneric/linux

Long-term Maintenance Note

This repo and article are not a one-off “flash project.” I will continuously follow each major mainline release (including rc1…rcX), merging critical changes into main. Before each sync, I compile on my Debian Testing workstation and test on Pi 5 to ensure the workflow remains smooth and the repo always works.

If you’re using Pi for mainline kernel development or run into adaptation issues, let’s exchange feedback! I also look forward to your contributions in the upstream community to advance ARM64 support.