Tag: Kernel Module Development

  • Linux Platform Driver Tutorial: Probe, Device Tree & Resources

    Updated October 8, 2026. This tutorial explains the Linux platform-driver model and shows a minimal driver that can bind to a matching firmware-described device. It is a learning example, not a driver for real hardware: the compatible string and binding must be supplied by a board or device-tree binding that actually describes the device.

    A platform driver does not create or discover arbitrary hardware by itself. The kernel enumerates a platform device—commonly from Device Tree, ACPI, or board setup—and the driver core matches it to a registered platform_driver. Only then does the core call probe().

    When to use a platform driver

    The platform bus is commonly used for SoC-integrated peripherals and some legacy devices with directly addressable resources. Devices on buses with their own discovery and driver models, such as PCI, USB, I2C, or SPI, should normally use that bus’s driver model instead. See the current Linux platform-device documentation before choosing the bus.

    1. Prepare a safe development environment

    Build against the headers and configuration for the target kernel. A virtual machine or QEMU test system is preferable while learning: an incorrect kernel module can crash or corrupt the running system. Do not load experimental modules on a production device.

    sudo apt update
    sudo apt install build-essential linux-headers-$(uname -r)
    ls /lib/modules/$(uname -r)/build

    On a cross-compiled embedded target, use that target’s kernel source, configuration, and toolchain rather than the headers for your development PC. Kernel APIs change over time, so the target kernel tree is the final authority.

    2. Write a minimal matching platform driver

    This example only logs when the driver binds and unbinds; it does not access registers or control hardware. The placeholder compatible value must be replaced with a real binding string already used by the target device tree. Without a matching enumerated device, the module can load successfully and probe() will not run.

    #include <linux/module.h>
    #include <linux/of.h>
    #include <linux/platform_device.h>
    
    static int example_probe(struct platform_device *pdev)
    {
        dev_info(&pdev->dev, "matched example platform device\n");
        return 0;
    }
    
    static void example_remove(struct platform_device *pdev)
    {
        dev_info(&pdev->dev, "example platform device removed\n");
    }
    
    static const struct of_device_id example_of_match[] = {
        { .compatible = "example,platform-device" },
        { }
    };
    MODULE_DEVICE_TABLE(of, example_of_match);
    
    static struct platform_driver example_driver = {
        .probe = example_probe,
        .remove = example_remove,
        .driver = {
            .name = "example-platform",
            .of_match_table = example_of_match,
        },
    };
    module_platform_driver(example_driver);
    
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("Minimal platform driver example");

    The empty final entry in the match table is required. MODULE_DEVICE_TABLE() makes the match information available to module tooling. Current upstream kernels use a void remove() callback; older kernel branches may use a different signature, so compile against the exact target headers and follow that tree’s platform_driver definition.

    3. Build the external module

    Save the source as example_platform.c and create a Makefile in the same directory:

    obj-m += example_platform.o
    KDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
    
    all:
    	$(MAKE) -C $(KDIR) M=$(PWD) modules
    
    clean:
    	$(MAKE) -C $(KDIR) M=$(PWD) clean

    Run make using the matching build tree. A successful build produces example_platform.ko. A module may still be rejected at load time if its vermagic, signing requirements, architecture, or kernel configuration do not match the running target.

    4. Describe real hardware with a binding

    For a Device Tree system, the device node is part of the board description and must follow the device’s documented binding. A node may describe resources such as registers, interrupts, clocks, resets, and power domains, but their addresses and specifiers are specific to the SoC and board. Do not copy fabricated addresses or interrupt numbers from a generic tutorial.

    For upstreamable drivers, add or update a YAML schema under Documentation/devicetree/bindings/ and validate it with the kernel’s Device Tree checks. Read the Device Tree usage model and the binding documentation for the target subsystem.

    Platform devices may also be enumerated through ACPI or board setup. An OF match table alone only matches Device Tree devices; add ACPI or platform-ID matching only when the hardware and target kernel require it.

    5. Acquire resources in probe()

    A real probe function should validate the device and acquire each resource through the device-managed APIs where appropriate. Common helpers include platform_get_resource() with devm_ioremap_resource() for a memory resource, platform_get_irq() for an IRQ, and the clock, reset, regulator, GPIO, and DMA frameworks for their respective resources. Check every return value, propagate errors, and use -EPROBE_DEFER when a required supplier is not ready yet.

    Do not dereference a missing resource, invent register layouts, or access MMIO before the resource has been reserved and mapped. Use the subsystem’s established helpers rather than raw addresses. Keep interrupt handlers short and move work that may sleep into a threaded handler or workqueue.

    6. Build, bind, and debug

    • Confirm that the device exists in the target’s firmware description and that its compatible string matches the driver table.
    • Check /sys/bus/platform/devices/ and /sys/bus/platform/drivers/ to inspect enumerated devices and registered drivers.
    • Read the exact kernel log message with dmesg or the system journal; distinguish a module-load failure from a device-match or probe failure.
    • Check the probe return value and resource errors. A deferred probe is different from a failed match.
    • Use a VM, QEMU, or a dedicated development board for module-load tests; remove the module and restore the test system if it becomes unstable.

    The official external-module build guide and platform bus documentation describe the supported build and driver-model interfaces.

    Common mistakes

    • Expecting probe() to run without an enumerated device that matches the driver.
    • Registering a fake platform device from the same driver module as a shortcut; device enumeration normally belongs to firmware or board setup.
    • Copying Device Tree register addresses, interrupt cells, or clock names from another board.
    • Ignoring errors from resource helpers or suppressing deferred-probe errors.
    • Using a remove() signature from a different kernel version or adding manual cleanup for resources already managed by devm APIs.
    • Treating a character device and a platform driver as alternatives: a platform driver binds hardware to a bus; a character-device interface is a separate userspace API that may be added when the hardware requires one.

    Further reading

    Start with the Linux platform driver model, the Device Tree usage model, and the Kbuild external-module documentation. For register-level work, continue with the documentation for the specific subsystem and board; a generic example cannot safely define hardware resources for you.

    For broader interview preparation, see our Linux device-driver interview roadmap.

  • Why Need for Kernel Programming is Essential for Beginners 10 Powerful Reasons

    Learn why Need for Kernel Programming is essential for embedded systems, device drivers, and OS optimization. A beginner-friendly guide with examples.

    When we talk about kernel programming, it might sound like something very advanced and complicated. But in reality, it’s a crucial part of how modern computers, operating systems, and devices work. Whether you’re a beginner in embedded systems or a curious software developer, understanding why kernel programming matters will give you a strong foundation.

    Let’s break it down in simple terms.

    What is the Kernel?

    Think of the kernel as the heart of an operating system. It acts as a bridge between software and hardware. Whenever your program needs to use a hardware component — like memory, CPU, disk storage, or communication interfaces — the kernel steps in to manage these requests safely and efficiently.

    In short, the kernel is responsible for:

    • Resource management (CPU scheduling, memory allocation)
    • Device control (drivers, hardware interaction)
    • System security (managing permissions and access)
    • Process management (creating, scheduling, and terminating processes)

    Why Kernel Programming is Important

    1. Better Control Over Hardware

    For embedded systems, operating systems like Linux run close to the hardware level. If you want to optimize performance, control peripherals directly, or implement custom device drivers, you need kernel programming skills.

    2. Custom Functionality

    Sometimes, the existing operating system cannot meet the exact requirements of a project. Kernel programming lets developers add custom features to the OS, enabling specific functionalities. For example, real-time systems often require kernel modifications for better timing control.

    3. Performance Optimization

    Kernel-level code runs faster because it works directly with hardware and system resources. For applications requiring high speed and low latency, kernel programming can make a huge difference.

    4. Building Device Drivers

    Device drivers are a key part of kernel programming. Without drivers, hardware devices won’t work with the OS. Writing custom drivers allows integration of new hardware with the system.

    5. Understanding System Internals

    Learning kernel programming gives you deep insight into how operating systems work internally — knowledge that is valuable for any systems programmer or embedded engineer.

    Real-Life Example

    Imagine you are developing an IoT device like a smart thermostat. The device needs to read sensor data in real time and control heating/cooling without delay. To achieve this, you might need to write a kernel module that directly interacts with hardware sensors, bypassing unnecessary overhead.

    How to Get Started with Kernel Programming

    If you’re new, here’s a simple roadmap:

    1. Learn C programming — most kernels (like Linux) are written in C.
    2. Understand operating system concepts — processes, memory management, interrupts.
    3. Explore Linux kernel source code — a treasure trove for learning.
    4. Write simple kernel modules — start with “Hello World” kernel module examples.
    5. Experiment with device drivers — build a basic driver for a peripheral.

    Linux Kernel Module Example

    Example: Simple Kernel Module (“Hello World”)

    A kernel module is a piece of code that can be loaded into the kernel at runtime. It’s often used to add features without rebuilding the kernel.

    Hello World Kernel Module Code

    // hello_module.c
    #include <linux/init.h>    // Required for init and exit macros
    #include <linux/module.h>  // Core header for loading modules
    #include <linux/kernel.h>  // Kernel log functions
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Nish");
    MODULE_DESCRIPTION("A Simple Hello World Kernel Module");
    MODULE_VERSION("1.0");
    
    // Initialization function
    static int __init hello_init(void) {
        printk(KERN_INFO "Hello, Kernel World!\n");
        return 0; // Return 0 means success
    }
    
    // Cleanup function
    static void __exit hello_exit(void) {
        printk(KERN_INFO "Goodbye, Kernel World!\n");
    }
    
    // Macros for registering functions
    module_init(hello_init);
    module_exit(hello_exit);
    

    How to Compile the Kernel Module

    1. Create a Makefile:
    obj-m += hello_module.o
    
    all:
    	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    
    clean:
    	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
    
    1. Compile the module:
    make
    
    1. Insert the module into the kernel:
    sudo insmod hello_module.ko
    
    1. Check kernel messages:
    dmesg | tail
    

    You should see:

    Hello, Kernel World!
    
    1. Remove the module:
    sudo rmmod hello_module
    

    And in dmesg:

    Goodbye, Kernel World!

    How This Code Helps Understand Kernel Programming

    • printk: Similar to printf in C, but used for kernel logs.
    • Module initialization and cleanup functions: Show how kernel modules are loaded and unloaded.
    • Macros (module_init and module_exit): Connect functions to kernel events.
    • Makefile: Demonstrates how to compile a kernel module outside the kernel tree.

    FAQs – Understanding the Need for Kernel Programming

    Q1: Do I need kernel programming for every software project?
    No. Kernel programming is mainly needed for system-level development, embedded systems, and drivers. Regular application development doesn’t require it.

    Q2: Which programming language is used for kernel programming?
    C is the most common language for kernel development. Knowledge of assembly language can also be beneficial.

    Q3: Is kernel programming difficult?
    It can be challenging, but with the right learning path, even beginners can start with simple kernel modules and gradually build expertise.

    Q4: Why is kernel programming important in embedded systems?
    Embedded systems interact closely with hardware. Kernel programming allows customization, optimization, and driver development for these systems.