Cross-Compiling the Linux Kernel for Raspberry Pi-Free Linux Kernel Development Course

Previous Lecture                                                                                                                                                                                                     Next Lecture

Cross-Compiling the Linux Kernel for Raspberry Pi
Chapter 3 · Tutorial 9 — Free Linux Kernel Development Course · EmbeddedPathashala
🎯 Kernel 6.x Updated
⚙️ ARM Cross-Compile
🛠️ Hands-On Steps
📋 Interview Q&A

What you will learn:

Cross-compilation basics
Cross-toolchain setup
ARM kernel build
Raspberry Pi kernel 6.x
ARCH and CROSS_COMPILE variables
Kernel config for embedded targets
dtb device tree blobs
Deploying kernel to target

Why This Topic Matters

In the previous tutorials of Chapter 3, you learned how to build the Linux kernel for your own x86_64 machine.
That was building for the same architecture you are sitting on. Now things get more interesting.

In the real embedded world — whether you are working on a Raspberry Pi, an industrial ARM board, or a custom SoC — the device you are targeting is almost never powerful enough to compile its own kernel quickly. You need to build on a fast host machine (your laptop or desktop) and produce binaries that run on a completely different CPU architecture. This is called cross-compilation, and it is a fundamental skill every embedded Linux engineer must have.

We use the Raspberry Pi as our example target because it is affordable, widely available, and has excellent community support. The concepts here apply equally to any ARM-based embedded target.

1. What Is Cross-Compilation?

When you compile a C program on your laptop and run it on the same laptop, that is called native compilation. The compiler produces instructions for the same CPU it is running on.

Cross-compilation is when your host machine (where you build) and your target machine (where the program runs) have different CPU architectures. The compiler on your x86_64 host produces binary code that runs on the ARM processor of the Raspberry Pi — not on your host itself.

Native Compilation vs Cross-Compilation
Aspect Native Compilation Cross-Compilation
Host CPU x86_64 x86_64
Target CPU x86_64 (same) ARM (different)
Compiler used gcc (system gcc) arm-linux-gnueabihf-gcc
Output binary runs on Your machine Raspberry Pi
Build speed Fast Fast on host (much faster than building on the Pi itself)

Why not just build on the Raspberry Pi itself? You can, but the Pi is slow for compilation. Building the full kernel on a Pi 4 can take 30–60 minutes or more. The same build on a modern x86_64 machine takes 5–10 minutes. For professional embedded development, cross-compilation is always the preferred approach.

2. Big Picture — Three Steps to Cross-Build the Kernel

Cross-Compilation Workflow
📥
Step 1
Get RPi Kernel Source
Clone from raspberrypi/linux
🔧
Step 2
Install Cross-Toolchain
arm-linux-gnueabihf
🏗️
Step 3
Configure & Build
make ARCH=arm CROSS_COMPILE=…

We will walk through each of these three steps in detail. All commands are shown for a modern Ubuntu/Debian host running on x86_64, updated for kernel 6.x.

3. Step 1 — Getting the Raspberry Pi Kernel Source

The official Raspberry Pi kernel is maintained in its own GitHub repository. Unlike the vanilla mainline kernel from kernel.org, the Raspberry Pi foundation maintains a fork with Raspberry Pi-specific drivers, overlays, and device tree files. As of 2024–2025, the Raspberry Pi kernel 6.x is available and actively maintained.

3.1 Set Up Your Workspace

First, create a dedicated staging directory. Using an environment variable keeps your commands clean and avoids hard-coding paths everywhere.

# Create a staging directory for our RPi cross-build work
export RPI_STG=~/rpi_work

# Create subdirectories: one for kernel source, one for toolchain
mkdir -p ${RPI_STG}/kernel_rpi ${RPI_STG}/rpi_tools

# Verify
ls ${RPI_STG}
💡 Disk Space Check: The RPi kernel source tree takes roughly 900 MB to 1.2 GB. The cross-toolchain adds another 1–2 GB. Make sure you have at least 5 GB free to be comfortable.

3.2 Clone the Raspberry Pi Kernel Source (Kernel 6.x)

The Raspberry Pi foundation maintains branches named by kernel version. For kernel 6.6 (the current LTS), the branch is rpi-6.6.y. We use --depth=1 to fetch only the latest commit, which dramatically reduces download size and time.

cd ${RPI_STG}/kernel_rpi

# Clone the RPi kernel 6.6 LTS branch (shallow clone for speed)
git clone --depth=1 \
    --branch rpi-6.6.y \
    https://github.com/raspberrypi/linux.git

# This creates: ${RPI_STG}/kernel_rpi/linux/

Once the clone completes, let us verify we have the right version:

cd ${RPI_STG}/kernel_rpi/linux

# Check the top 5 lines of the Makefile
head -n5 Makefile

# Expected output (similar to):
# SPDX-License-Identifier: GPL-2.0
# VERSION = 6
# PATCHLEVEL = 6
# SUBLEVEL = xx
# EXTRAVERSION =

Raspberry Pi Kernel Branch Naming Convention
Branch Name Kernel Version LTS? Status (2025)
rpi-6.6.y Linux 6.6.x ✅ Yes Recommended
rpi-6.1.y Linux 6.1.x ✅ Yes Active
rpi-5.15.y Linux 5.15.x ✅ Yes Older LTS
rpi-5.4.y Linux 5.4.x ✅ Yes EOL — avoid for new projects

4. Step 2 — Installing the Cross-Toolchain

A cross-toolchain is a set of tools — compiler, linker, assembler, standard library — that run on your x86_64 host but produce output for your ARM target. Think of it as a translation kit: you speak x86, but it writes ARM.

What Is Inside a Cross-Toolchain?
🖊️
Cross-Compiler
arm-linux-gnueabihf-gcc
Compiles C/C++ → ARM binary
🔗
Cross-Linker
arm-linux-gnueabihf-ld
Links object files → executable
📚
C Library
glibc (gnueabihf variant)
ARM-targeted standard library
🔬
Debugger
arm-linux-gnueabihf-gdb
Debug ARM binaries from host

4.1 Understanding the Toolchain Prefix

The toolchain prefix encodes a lot of information. Let us break it down:

Decoding: arm-linux-gnueabihf-
arm
Target CPU architecture
linux
Target OS kernel
gnueabihf
ABI: GNU, EABI, Hard-Float
gcc / ld / …
Actual tool name
eabi = Embedded ABI (calling convention for embedded ARM)  |
hf = Hard Float (uses FPU hardware registers for floating point, faster than soft-float)

4.2 Installing the Toolchain (Ubuntu/Debian)

The simplest method is installing from your distribution’s package manager. This is sufficient for most kernel builds.

# Update package lists
sudo apt update

# Install the ARM 32-bit cross-compiler (for RPi 1, 2, 3, Zero)
sudo apt install -y gcc-arm-linux-gnueabihf

# If you also need 64-bit ARM (RPi 3, 4, 5 in 64-bit mode)
sudo apt install -y gcc-aarch64-linux-gnu

# Verify the compiler is working
arm-linux-gnueabihf-gcc --version
# arm-linux-gnueabihf-gcc (Ubuntu ...) 13.x.x

4.3 Which Toolchain for Which Raspberry Pi?

Raspberry Pi Model → Correct Cross-Toolchain
RPi Model CPU Default Kernel Mode Toolchain Prefix
Pi Zero, Pi 1 ARM1176 (ARMv6) 32-bit arm-linux-gnueabihf-
Pi 2, Pi 3 Cortex-A7/A53 (ARMv7/v8) 32-bit (default), 64-bit available arm-linux-gnueabihf-
Pi 4 Cortex-A72 (ARMv8) 64-bit (Raspberry Pi OS 64-bit default) aarch64-linux-gnu-
Pi 5 Cortex-A76 (ARMv8.2) 64-bit aarch64-linux-gnu-
📌 Note for 2025: Raspberry Pi OS now defaults to 64-bit for Pi 4 and Pi 5. If you are targeting a Pi 4 or Pi 5 with the official 64-bit OS, use aarch64-linux-gnu-gcc as your cross-compiler and set ARCH=arm64 in the build command. For Pi Zero and older boards, stick with arm-linux-gnueabihf- and ARCH=arm.

5. Step 3 — Configuring and Building the Kernel

This is where everything comes together. The kernel build system uses two important environment variables for cross-compilation:

Two Critical Build Variables
ARCH=arm
Tells the kernel Makefile which architecture we are building for. This selects the right source directories, Kconfig options, and build rules.
Examples: arm, arm64, x86, mips, riscv
CROSS_COMPILE=arm-linux-gnueabihf-
Tells the Makefile the prefix to prepend to every tool name. So gcc becomes arm-linux-gnueabihf-gcc, ld becomes arm-linux-gnueabihf-ld, and so on.
Note the trailing dash — that is required!

5.1 Configure the Kernel

The Raspberry Pi kernel ships with default configuration files for each board family. Using these as your starting point is the correct approach — you do not need to configure every kernel option from scratch.

cd ${RPI_STG}/kernel_rpi/linux

# --- For Pi 2, Pi 3, Pi Zero 2W (32-bit, ARMv7/v8 in 32-bit mode) ---
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig

# --- For Pi 4 (32-bit mode) ---
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2711_defconfig

# --- For Pi 4, Pi 5 (64-bit mode) ---
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2711_defconfig

# --- For Pi 5 (64-bit) ---
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2712_defconfig

After running defconfig, a .config file is created in the kernel source root. This is your kernel configuration. You can optionally fine-tune it:

# Launch the menu-based configuration interface
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
💡 Tip: For your first cross-build, skip menuconfig and go with the defconfig directly. You can always refine the configuration later once you have a working base build.

5.2 Build the Kernel

Now trigger the actual compilation. The -j$(nproc) flag uses all available CPU cores to parallelize the build — this is what makes cross-compilation on a host machine so much faster than building on the Pi itself.

# For 32-bit ARM (Pi 2, Pi 3, Pi Zero)
make -j$(nproc) \
    ARCH=arm \
    CROSS_COMPILE=arm-linux-gnueabihf- \
    zImage modules dtbs

# For 64-bit ARM (Pi 4, Pi 5)
make -j$(nproc) \
    ARCH=arm64 \
    CROSS_COMPILE=aarch64-linux-gnu- \
    Image modules dtbs

Build Outputs Explained
Target Output File What It Is
zImage arch/arm/boot/zImage Compressed kernel image for 32-bit ARM
Image arch/arm64/boot/Image Uncompressed kernel image for 64-bit ARM
modules Various .ko files Loadable kernel modules (device drivers, etc.)
dtbs arch/arm/boot/dts/*.dtb Device Tree Blobs — hardware description for each RPi board variant

5.3 Install Modules

After the build completes, install the compiled modules into a staging directory. This directory will later be copied to the Raspberry Pi.

# Create a staging output directory
mkdir -p /tmp/rpi_kernel_output

# Install modules into the staging directory
make ARCH=arm \
     CROSS_COMPILE=arm-linux-gnueabihf- \
     INSTALL_MOD_PATH=/tmp/rpi_kernel_output \
     modules_install

This creates a directory structure under /tmp/rpi_kernel_output/lib/modules/ with all the .ko files organized by kernel version.

6. Deploying the New Kernel to the Raspberry Pi

After the build is done on your host, you need to copy the output files to the Raspberry Pi’s SD card. The Raspberry Pi boots from a FAT32 partition (usually the first partition, mounted as /boot or /boot/firmware).

Raspberry Pi SD Card Partition Layout
Partition 1
FAT32 /boot
~512 MB
Partition 2
ext4 / (root filesystem)
Rest of card
The kernel image and DTB files go to Partition 1 (FAT32).
Kernel modules (.ko files) go to Partition 2 under /lib/modules/.

6.1 Copy Files to the SD Card

Insert the RPi SD card into your host machine (via a card reader). It typically mounts automatically. Adjust the mount path to match your system.

# Assume SD card boot partition is mounted at /media/$USER/bootfs
# and root partition is mounted at /media/$USER/rootfs

BOOT_PART=/media/$USER/bootfs
ROOT_PART=/media/$USER/rootfs

# Copy kernel image (32-bit example)
cp ${RPI_STG}/kernel_rpi/linux/arch/arm/boot/zImage ${BOOT_PART}/kernel7.img

# Copy device tree blobs
cp ${RPI_STG}/kernel_rpi/linux/arch/arm/boot/dts/broadcom/*.dtb ${BOOT_PART}/

# Copy device tree overlays
cp -r ${RPI_STG}/kernel_rpi/linux/arch/arm/boot/dts/overlays/*.dtb* ${BOOT_PART}/overlays/

# Copy kernel modules to root filesystem
sudo cp -r /tmp/rpi_kernel_output/lib/modules/ ${ROOT_PART}/lib/

📌 Kernel filename on the boot partition:

  • kernel.img — Pi Zero, Pi 1 (ARMv6)
  • kernel7.img — Pi 2, Pi 3 (ARMv7)
  • kernel7l.img — Pi 4 (ARMv8 in 32-bit LPAE mode)
  • kernel8.img — Pi 3, Pi 4, Pi 5 (64-bit)

7. Practical Tips You Will Actually Use

7.1 Export Variables Once, Use Everywhere

Instead of typing ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- before every command, export them as environment variables once in your session:

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

# Now you can type shorter make commands
make bcm2709_defconfig
make -j$(nproc) zImage modules dtbs

7.2 Save and Restore Your Kernel Config

If you spend time customizing menuconfig, always back up your .config:

# Save the current config as a defconfig for later reuse
make savedefconfig

# This creates a minimal defconfig file called 'defconfig'
# Store it safely
cp defconfig ~/my_rpi_defconfig.conf

7.3 Check the Toolchain Is Generating ARM Code

After building, you can verify the kernel image is really an ARM binary:

# Check ELF header of the vmlinux file
file vmlinux

# Expected output should say something like:
# vmlinux: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV)...
# or for 64-bit:
# vmlinux: ELF 64-bit LSB pie executable, ARM aarch64...

7.4 Build Time Estimates

Kernel 6.x Build Time Comparison
Build Method Machine Approximate Build Time
Cross-compile on x86_64 host Modern 8-core laptop 5–10 minutes
Native build on the Pi itself Raspberry Pi 4 (4 cores) 45–90 minutes
Native build on the Pi itself Raspberry Pi 3 (4 cores) 2–4 hours
Cross-compile inside a VM 4-core VM on the same laptop 10–25 minutes (slower than native host)

This is why cross-compilation is not optional in professional embedded development — it saves enormous amounts of time over the course of a project.

8. Complete Workflow — All Commands in One Place

Here is a clean, end-to-end script summarizing everything covered in this tutorial. This is for a Pi 4 (32-bit mode) with kernel 6.6:

#!/bin/bash
# ======================================================
# Cross-compile Linux kernel 6.6 for Raspberry Pi 4
# Host: Ubuntu 22.04 or 24.04 x86_64
# ======================================================

# --- Setup ---
export RPI_STG=~/rpi_work
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

mkdir -p ${RPI_STG}/kernel_rpi /tmp/rpi_output

# --- Step 1: Get kernel source ---
cd ${RPI_STG}/kernel_rpi
git clone --depth=1 --branch rpi-6.6.y \
    https://github.com/raspberrypi/linux.git
cd linux

# --- Step 2: Install toolchain ---
sudo apt install -y gcc-arm-linux-gnueabihf

# --- Step 3: Configure ---
make bcm2711_defconfig

# --- Step 4: Build ---
make -j$(nproc) zImage modules dtbs

# --- Step 5: Install modules ---
make INSTALL_MOD_PATH=/tmp/rpi_output modules_install

echo "Build complete! Output in /tmp/rpi_output"
echo "Kernel: arch/arm/boot/zImage"
echo "DTBs:   arch/arm/boot/dts/broadcom/"

📋 Interview Questions — Cross-Compilation & Embedded Kernel Build
Q1. What is cross-compilation and why is it needed in embedded Linux development?
Cross-compilation is the process of building software on one architecture (the host) to run on a different architecture (the target). It is necessary in embedded development because target devices — like microcontrollers or single-board computers — often have limited CPU power and RAM, making native compilation either impractical or extremely slow. A modern x86_64 workstation can cross-compile an ARM kernel in minutes versus hours on the target itself.
Q2. What do the ARCH and CROSS_COMPILE variables do in the kernel Makefile?
ARCH tells the kernel build system which architecture to target, causing it to include the correct architecture-specific source files and Kconfig options. CROSS_COMPILE specifies the prefix for all toolchain programs — so the Makefile calls arm-linux-gnueabihf-gcc instead of the host gcc, ensuring all compiled code targets the ARM architecture rather than x86_64.
Q3. What is a Device Tree Blob (DTB) and why does the Raspberry Pi need one?
A Device Tree Blob is a binary file that describes the hardware topology of a board to the Linux kernel — things like which peripherals exist, their memory-mapped addresses, interrupt lines, and clock connections. ARM-based boards like the Raspberry Pi do not have mechanisms to auto-discover peripherals the way a PC discovers PCI devices, so the bootloader passes a DTB to the kernel at boot time. The kernel reads it to know what hardware is present without having that information hard-coded.
Q4. What does the “hf” in arm-linux-gnueabihf mean, and why does it matter?
“hf” stands for Hard Float. It means the toolchain passes floating-point function arguments and return values in hardware floating-point registers (VFP/NEON), which is much faster than the soft-float alternative where the CPU emulates floating-point in software. On modern ARM Cortex-A processors which all have FPU hardware, always use the “hf” variant unless you have a specific reason not to (such as targeting a CPU without a hardware FPU).
Q5. What is the difference between zImage and Image in ARM kernel builds?
zImage is a self-decompressing compressed kernel image used in 32-bit ARM systems. It is smaller to store on the boot medium and decompresses itself into RAM at boot time. Image is a raw, uncompressed kernel binary used in 64-bit ARM (AArch64) systems. In AArch64, the bootloader (like U-Boot or the RPi firmware) typically handles any decompression externally, so the kernel itself is kept as a plain binary.
Q6. If you want to use all CPU cores on your host for the kernel build, what flag do you use?
The -j$(nproc) flag. nproc is a shell command that returns the number of available processor cores. Passing this to make via -j enables parallel compilation — each core works on a different source file simultaneously. For a kernel with thousands of source files, this reduces build time dramatically, often by a factor of 4–8x compared to a single-threaded build.
Q7. After cross-compiling the kernel, where do the .ko module files get installed, and how do you set the installation path?
By default, make modules_install installs modules to /lib/modules/<kernel-version>/ on the host. For cross-builds destined for a target device, you override this with INSTALL_MOD_PATH=/some/staging/dir. This places the modules under /some/staging/dir/lib/modules/, which you then copy to the target’s root filesystem — ensuring you do not accidentally overwrite your host’s own kernel modules.
Q8. What is the difference between ARCH=arm and ARCH=arm64 for Raspberry Pi builds?
ARCH=arm builds a 32-bit ARMv7/ARMv8 kernel in 32-bit compatibility mode, producing code that runs on all Raspberry Pi models starting from Pi 2. ARCH=arm64 builds a true 64-bit AArch64 kernel, which can only run on Pi 3 and newer (which have 64-bit capable Cortex-A53/A72/A76 CPUs). The 64-bit kernel can address more RAM, has a larger virtual address space, and can use 64-bit CPU registers natively — but will not boot on a Pi Zero or Pi 1.

Chapter 3 — What We Have Covered So Far
Ch3 T1–T4
Kernel build overview, prerequisites, downloading source
Ch3 T5–T7
Kernel configuration, make targets, build system internals
Ch3 T8
Installing and booting the custom x86_64 kernel
Ch3 T9 ← You are here
Cross-compiling the kernel for Raspberry Pi (ARM target)

Keep Learning — Free Embedded Linux Courses at EmbeddedPathashala

This is part of our completely free Linux Kernel Programming series. No fees, no sign-up walls — just solid technical content for engineering students and working engineers.

Previous Lectures                                                                                                                                                                                                    Next Lectures

Leave a Reply

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