Raspberry Pi 5 Cross-Compile Environment Setup & Driver Development
This guide details how to set up a cross-compilation environment on an x86 Ubuntu host to build Linux kernel modules (drivers) for the Raspberry Pi 5 (ARM64).
Part 1: The Concepts
What is Cross-Compiling?
Cross-compiling is the process of creating executable code for a platform (the target) other than the one on which the compiler is running (the host).
In this scenario:
Host: Your powerful Ubuntu Computer (x86_64 architecture).
Target: Raspberry Pi 5 (ARM64/aarch64 architecture).
Host: Your powerful Ubuntu Computer (x86_64 architecture).
Target: Raspberry Pi 5 (ARM64/aarch64 architecture).
What you need
To succeed, you need four key components on your Host computer:
Toolchain: The compiler (
gcc), linker (ld), and other tools capable of generating ARM64 code.Build System: Usually
make.Kernel Source Code: The specific Linux kernel source for the Raspberry Pi.
Target Configuration:
Architecture:
arm64Triple:
aarch64-linux-gnu-(Arch: 64-bit ARM, OS: Linux, ABI: GNU).
Part 2: Host Computer Setup
1. Install Dependencies & Toolchain
First, install the necessary build tools and the cross-compiler on your Ubuntu host.
$sudo apt update
# Install build dependencies
$sudo apt install bc bison flex libssl-dev make libc6-dev libncurses5-dev git
# Install the ARM64 cross-compilation toolchain
$sudo apt install crossbuild-essential-arm64
2. Download Kernel Source
Clone the official Raspberry Pi Linux kernel.
# Clone the repository- github. (this may take a while)
# Clone the repository- github. (this may take a while)
$git clone --depth=1 https://github.com/raspberrypi/linux
$cd linux
3. Configure and Build the Kernel
# 1. Set environment variables for Cross-Compilation
$export ARCH=arm64
$export CROSS_COMPILE=aarch64-linux-gnu-
# specific naming for your kernel version
$export KERNEL=kernel_2712
# 2. Generate the default configuration for Pi 5 (BCM2712)
$make bcm2712_defconfig
# 3. Build the Kernel, Modules, and Device Tree Blobs (DTBs)
# -j12 uses 12 CPU cores to speed up the build
$make -j12 Image modules dtbs
4. Install the Kernel Modules, Kernel and Device Tree blobs
# Assume SD card partitions are mounted at mnt/boot and mnt/root
# 1. Install Kernel Modules to the SD card root filesystem
$sudo env PATH=$PATH make -j12 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_MOD_PATH=mnt/root modules_install
# 2. Install Kernel Image and Device Trees to the SD card boot partition
# 1. Install Kernel Modules to the SD card root filesystem
$sudo env PATH=$PATH make -j12 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_MOD_PATH=mnt/root modules_install
# 2. Install Kernel Image and Device Trees to the SD card boot partition
$sudo cp mnt/boot/$KERNEL.img mnt/boot/$KERNEL-backup.img
$sudo cp arch/arm64/boot/Image mnt/boot/$KERNEL.img
$sudo cp arch/arm64/boot/dts/broadcom/*.dtb mnt/boot/
$sudo cp arch/arm64/boot/dts/overlays/*.dtb* mnt/boot/overlays/
$sudo cp arch/arm64/boot/dts/overlays/README mnt/boot/overlays/
# 3. Unmount safely
$sudo umount mnt/boot
$sudo umount mnt/root
Part 3: Writing the "Hello World" Driver
Now that the environment is ready, let's write the driver on the Host Computer.1. Create a file named helloworld.c
$vi hellowrold.c
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Linda");
MODULE_DESCRIPTION("Simple insert/remove helloworld driver without any other function");
static int helloworld_init(void)
{
printk(KERN_ALERT "helloworld driver insert\n");
return 0;
}
static void helloworld_exit(void)
{
printk(KERN_ALERT "helloworld driver remove\n");
}
module_init(helloworld_init);module_exit(helloworld_exit);
2. Create a file named Makefile
#include <linux/init.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Linda");
MODULE_DESCRIPTION("Simple insert/remove helloworld driver without any other function");
static int helloworld_init(void)
{
printk(KERN_ALERT "helloworld driver insert\n");
return 0;
}
static void helloworld_exit(void)
{
printk(KERN_ALERT "helloworld driver remove\n");
}
{
printk(KERN_ALERT "helloworld driver insert\n");
return 0;
}
static void helloworld_exit(void)
{
printk(KERN_ALERT "helloworld driver remove\n");
}
module_init(helloworld_init);
module_exit(helloworld_exit);
2. Create a file named Makefile
// create Makefile that tell make how to compile helloworld.ko (driver module)
$vi Makefile
$vi Makefile
KDIR = /home/linda/fun/RB_Pi5/linux/ //the raspberry kernel source location
obj-m += helloworld.o
all:
make -C $(KDIR) M=$(PWD) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules
clean:
make -C $(KDIR) M=$(PWD) clean3. Compile
$make4. Verify
This is the most common reason for failure. The "Version Magic" of your module must match the running kernel on the Pi.//kernel header information have to be the same as the kernel you used to compile
$modinfo helloworld.ko |grep vermagic
vermagic: 6.12.66-v8-16k+ SMP preempt mod_unload modversions aarch64
Part 4: Testing on Raspberry Pi 5
obj-m += helloworld.o
all:
make -C $(KDIR) M=$(PWD) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules
all:
make -C $(KDIR) M=$(PWD) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules
clean:
make -C $(KDIR) M=$(PWD) clean
3. Compile
$make
4. Verify
This is the most common reason for failure. The "Version Magic" of your module must match the running kernel on the Pi.
//kernel header information have to be the same as the kernel you used to compile
$modinfo helloworld.ko |grep vermagic
vermagic: 6.12.66-v8-16k+ SMP preempt mod_unload modversions aarch64
Part 4: Testing on Raspberry Pi 5
1. Transfer the Driver
Copy the .ko file from Host to Target (Pi).
$scp helloworld.ko linda@10.12.xx.xxx:/home/linda/
Copy the .ko file from Host to Target (Pi).
$scp helloworld.ko linda@10.12.xx.xxx:/home/linda/
$scp helloworld.ko linda@10.12.xx.xxx:/home/linda/
2. Boot the Pi
If you installed the new kernel in Part 2, verify it is running:
$uname -r
6.12.66-v8-16k+ //this have to be the same as the driver that will be build3. Load the Module
# Insert the module
$sudo insmod helloworld.ko
# Check the kernel log
$dmesg | tail
[ 374.518816] helloworld driver insert
4. Unload the Module
If you installed the new kernel in Part 2, verify it is running:
$uname -r
6.12.66-v8-16k+ //this have to be the same as the driver that will be build
$uname -r
6.12.66-v8-16k+ //this have to be the same as the driver that will be build
3. Load the Module
# Insert the module
$sudo insmod helloworld.ko
# Check the kernel log
$dmesg | tail
[ 374.518816] helloworld driver insert
$sudo insmod helloworld.ko
# Check the kernel log
$dmesg | tail
[ 374.518816] helloworld driver insert
4. Unload the Module
# Remove the module
$sudo rmmod helloworld.ko
# Check the kernel log
$dmesg | tail
[ 477.965475] helloworld driver remove
$sudo rmmod helloworld.ko
# Check the kernel log
$dmesg | tail
[ 477.965475] helloworld driver remove
Summary
Linux drivers have a specific lifecycle managed by the kernel.
- insmod: Triggers the
module_init function. - rmmod: Triggers the
module_exit function. - dmesg: Shows the
printk output (Kernel logs).
Linux drivers have a specific lifecycle managed by the kernel.
- insmod: Triggers the
module_initfunction. - rmmod: Triggers the
module_exitfunction. - dmesg: Shows the
printkoutput (Kernel logs).
Comments