Day 7 — Cross Compile Kernel Module¶
📌 What I did¶
- Set up cross-compilation environment on Ubuntu 24.04 (WSL)
- Downloaded Raspberry Pi kernel source (matching 6.12.47)
- Applied running kernel config from Raspberry Pi
- Prepared kernel build tree for external module
- Built kernel module (
hello.ko) on WSL - Deployed module to Raspberry Pi via
scp - Successfully loaded module using
insmod
⚠️ Issues Encountered¶
1. fixdep: Exec format error¶
Cause:
- Kernel build tools were copied from Raspberry Pi (ARM)
- Attempted to execute on WSL (x86)
Fix:
👉 Rebuilt host tools for x86
2. kernelrelease mismatch¶
Initial result:
Target:
Cause:
CONFIG_LOCALVERSIONmismatch- Dirty git tree adds
+
Fix:
scripts/config --set-str LOCALVERSION "+rpt-rpi-2712"
scripts/config --disable LOCALVERSION_AUTO
make LOCALVERSION= kernelrelease
3. Module load failure (Invalid parameters)¶
Real cause from dmesg:
Cause:
- Symbol version mismatch
Module.symversnot aligned with target kernel
Fix:
- Copied
Module.symversfrom Raspberry Pi
✅ Result¶
Successfully loaded module:
Verified:
Unloaded:
🧠 Key Takeaways¶
- Cross-compilation includes host tools + target binaries
- Kernel version string must match exactly (
uname -r) LOCALVERSIONaffects module compatibility+indicates dirty tree and can break matchingModule.symversis required for symbol version alignmentvermagicmatching is necessary but not sufficient
Module Setup & Cleanup Scripts¶
setup_mydev.sh¶
#!/bin/bash
#
# @file setup_mydev.sh
# @brief Load mydev kernel module and create device node
#
# Exit immediately if any command fails (except those explicitly handled)
set -e
MODULE="mydev"
DEVICE="/dev/mydev"
echo "=== Loading module ==="
# Try to remove existing module
# - '2>/dev/null' suppresses error output (e.g., module not loaded)
# - '|| true' ensures script continues even if rmmod fails
# (important because set -e would otherwise stop execution)
sudo rmmod $MODULE 2>/dev/null || true
# Insert module
sudo insmod ./${MODULE}.ko
echo "=== Getting major number ==="
# Extract major number from /proc/devices
#
# Step-by-step:
# 1. grep $MODULE /proc/devices
# → find the line containing "mydev"
# e.g., "509 mydev"
#
# 2. awk '{print $1}'
# → extract first column (major number)
#
# 3. $( ... )
# → command substitution: store output into variable
MAJOR=$(grep $MODULE /proc/devices | awk '{print $1}')
# Check if MAJOR is empty
# -z means "string length is zero"
if [ -z "$MAJOR" ]; then
echo "ERROR: Failed to get major number"
exit 1
fi
echo "Major number: $MAJOR"
echo "=== Creating device node ==="
# If device node already exists, remove it
# This prevents mismatch if major number changed
if [ -e $DEVICE ]; then
sudo rm -f $DEVICE
fi
# Create character device node
# Syntax: mknod <path> <type> <major> <minor>
# 'c' = character device
sudo mknod $DEVICE c $MAJOR 0
# Set permissions to rw-rw-rw-
# This allows non-root users to read/write the device (for testing)
sudo chmod 666 $DEVICE
echo "=== Device ready ==="
ls -l $DEVICE
echo "=== dmesg ==="
# Show last 10 kernel log lines
dmesg | tail -n 10
# End of File
cleanup_mydev.sh¶
#!/bin/bash
#
# @file cleanup_mydev.sh
# @brief Remove mydev kernel module and device node
#
# Exit immediately if any command fails
set -e
MODULE="mydev"
DEVICE="/dev/mydev"
echo "=== Removing module ==="
sudo rmmod $MODULE
echo "=== Removing device node ==="
# Check if device node exists
# -e means "file exists"
if [ -e $DEVICE ]; then
sudo rm -f $DEVICE
fi
echo "=== Cleanup done ==="
# End of File
Test mydev using Python Script¶
test_mydev.py¶
#!/usr/bin/env python3
"""
@file test_mydev.py
@brief Test script for /dev/mydev character device
Usage:
python3 test_mydev.py basic
python3 test_mydev.py offset
python3 test_mydev.py boundary
python3 test_mydev.py busy
python3 test_mydev.py stress
"""
import os
import sys
import time
import threading
DEV_PATH = "/dev/mydev"
def check_device():
"""Ensure device exists"""
if not os.path.exists(DEV_PATH):
print(f"ERROR: {DEV_PATH} not found")
sys.exit(1)
# =========================================================
# Basic Test
# =========================================================
def test_basic():
print("=== Basic Test ===")
data = b"hello kernel"
with open(DEV_PATH, "wb") as f:
f.write(data)
with open(DEV_PATH, "rb") as f:
read_data = f.read()
print(f"Write: {data}")
print(f"Read : {read_data}")
print("PASS" if read_data == data else "FAIL")
# =========================================================
# Offset Test
# =========================================================
def test_offset():
print("=== Offset Test ===")
data = b"abcdef"
with open(DEV_PATH, "wb") as f:
f.write(data)
f = open(DEV_PATH, "rb")
r1 = f.read(3)
r2 = f.read(3)
r3 = f.read(3)
f.close()
print(f"Read1: {r1}")
print(f"Read2: {r2}")
print(f"Read3: {r3}")
if r1 == b"abc" and r2 == b"def" and r3 == b"":
print("PASS")
else:
print("FAIL")
# =========================================================
# Boundary Test
# =========================================================
def test_boundary():
print("=== Boundary Test ===")
data = b"A" * 200 # larger than BUFFER_SIZE (128)
with open(DEV_PATH, "wb") as f:
f.write(data)
with open(DEV_PATH, "rb") as f:
read_data = f.read()
print(f"Written: {len(data)} bytes")
print(f"Read : {len(read_data)} bytes")
if len(read_data) <= 128:
print("PASS (correct truncation)")
else:
print("FAIL (overflow detected)")
# =========================================================
# Busy Test
# =========================================================
def test_busy():
print("=== Busy Test ===")
fd1 = os.open(DEV_PATH, os.O_RDWR)
print("fd1 opened")
try:
fd2 = os.open(DEV_PATH, os.O_RDWR)
print("FAIL: second open should not succeed")
os.close(fd2)
except OSError as e:
print(f"PASS: second open failed as expected ({e})")
time.sleep(2)
os.close(fd1)
print("fd1 closed")
# =========================================================
# Stress Test
# =========================================================
def writer(stop_event, errors):
while not stop_event.is_set():
try:
with open(DEV_PATH, "wb") as f:
f.write(b"stress-data")
except Exception:
errors.append("write")
def reader(stop_event, errors):
while not stop_event.is_set():
try:
with open(DEV_PATH, "rb") as f:
f.read()
except Exception:
errors.append("read")
def test_stress():
print("=== Stress Test (5 sec) ===")
stop_event = threading.Event()
errors = []
threads = []
for _ in range(2):
threads.append(threading.Thread(target=writer, args=(stop_event, errors)))
threads.append(threading.Thread(target=reader, args=(stop_event, errors)))
for t in threads:
t.start()
time.sleep(5)
stop_event.set()
for t in threads:
t.join()
print("Stress test finished")
if errors:
print(f"Errors occurred: {len(errors)}")
else:
print("PASS (no userspace errors)")
print("👉 Check dmesg for kernel issues")
# =========================================================
# Main
# =========================================================
def main():
check_device()
if len(sys.argv) != 2:
print("Usage: python3 test_mydev.py [basic|offset|boundary|busy|stress]")
sys.exit(1)
cmd = sys.argv[1]
if cmd == "basic":
test_basic()
elif cmd == "offset":
test_offset()
elif cmd == "boundary":
test_boundary()
elif cmd == "busy":
test_busy()
elif cmd == "stress":
test_stress()
else:
print("Unknown command")
if __name__ == "__main__":
main()
# End of File
test_run.sh¶
sh setup_mydev.sh
chmod +x test_mydev.py
python3 test_mydev.py basic
python3 test_mydev.py offset
python3 test_mydev.py boundary
python3 test_mydev.py busy
python3 test_mydev.py stress
sh cleanup_mydev.sh
🚀 Next Steps¶
- Convert hello module to character device driver
- Add module parameters
- Learn
modprobeanddepmod - Use
dmesg -wfor real-time debugging