ES: Linux Kernel Modules and Dynamic Drivers

Kernel modules are loadable pieces of kernel code that extend the Linux kernel at runtime without requiring a full recompile. Dynamic kernel modules (DKMs) are the standard mechanism for adding device driver support to embedded Linux systems, and Yocto provides a structured way to build and deploy them.

The Linux kernel is monolithic — all core services run in a single executable. But it would be impractical to recompile the entire kernel every time a new hardware device needs support. Loadable Kernel Modules (LKMs) — also called Dynamic Kernel Modules (DKMs) — solve this by allowing driver code to be loaded and unloaded from the running kernel without rebooting.


The Linux Kernel Architecture

md
+-------------------------------------------+
|  User Space                               |
|  (Applications, System Programs)          |
+-------------------------------------------+
         | System Calls
         v
+-------------------------------------------+
|  Kernel Space                             |
|  +---------------------------------------+ |
|  |  Monolithic Kernel                    | |
|  |  (Process Mgmt, Memory, Filesystem,  | |
|  |   Networking, Core Drivers)           | |
|  +---------------------------------------+ |
|  |  Loadable Kernel Modules (DKMs)       | |
|  |  (loaded/unloaded at runtime)         | |
+-------------------------------------------+
         | Hardware Access
         v
+-------------------------------------------+
|  Hardware                                 |
+-------------------------------------------+

Linux uses a monolithic kernel — unlike a microkernel, even file management systems, device drivers, and networking code run in kernel space. However, the kernel extends this with Loadable Kernel Modules.


What is a Device Driver?

A device driver is software that handles communication between hardware devices and the operating system kernel.

CPUs and I/O devices operate asynchronously. Device drivers bridge this gap using two communication models:

Parallel System

Sends one word or more per operation across multiple data lines. Suitable for high-bandwidth peripherals.

Serial System

Sends one bit per operation across a few lines. Used by UART, SPI, I2C, and other serial interfaces.


Writing a Dynamic Kernel Module

A kernel module is a C source file compiled with specific kernel headers and macros.

hello.c — A Minimal Character Device Module

c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>

static dev_t hello_dev;
struct cdev hello_cdev;
static char buffer[64];

ssize_t hello_read(struct file *filp, char __user *buf,
                   size_t count, loff_t *f_pos)
{
    printk(KERN_INFO "Helloworld read (count=%d, offset=%d)\n",
           (int)count, (int)*f_pos);
    return 1;
}

struct file_operations hello_fops = {
    .owner = THIS_MODULE,
    .read  = hello_read,
};

static int __init hello_module_init(void)
{
    printk(KERN_INFO "Loading Helloworld_module.\n");
    alloc_chrdev_region(&hello_dev, 0, 1, "hello_dev");
    cdev_init(&hello_cdev, &hello_fops);
    hello_cdev.owner = THIS_MODULE;
    cdev_add(&hello_cdev, hello_dev, 1);
    return 0;
}

static void __exit hello_module_cleanup(void)
{
    printk(KERN_INFO "Cleaning-up hello_dev.\n");
    cdev_del(&hello_cdev);
    unregister_chrdev_region(hello_dev, 1);
}

module_init(hello_module_init);
module_exit(hello_module_cleanup);
MODULE_AUTHOR("Amr Abdelghafar");
MODULE_LICENSE("GPL");

Key module components:

Macro / FunctionPurpose
module_init()Register the module init function
module_exit()Register the module cleanup function
printk()Kernel-space logging (not printf)
alloc_chrdev_region()Allocate a character device number
cdev_init() / cdev_add()Register the character device
MODULE_LICENSE("GPL")Required for kernel-compatible licensing

Building a Kernel Module with Yocto

Yocto provides the module class to streamline kernel module recipes.

Directory Structure

bash
meta-raspberrypi/recipes-kernel/hello-mod/
├── files/
│   ├── hello.c
│   └── Makefile
└── hello_1.0.bb

BitBake Recipe: hello_1.0.bb

bitbake
DESCRIPTION = "hello driver"
LICENSE = "GPLv2+"
LIC_FILES_CHKSUM = "file://${BPN}.c;endline=19;md5=4866f9824d27c1cd5324fd5e84caeb6e"

inherit module

PR = "r0"

SRC_URI = "file://Makefile file://${BPN}.c"
S = "${WORKDIR}"

The inherit module line tells BitBake to use the kernel module build class, which handles compiling the module against the correct kernel headers for the target.


Integrating the Module into the Image

Step 1: Add the recipe to the build

Create the recipe in the correct layer location:

bash
cd ~/poky/sources
mkdir -p meta-raspberrypi/recipes-kernel/hello-mod
touch meta-raspberrypi/recipes-kernel/hello-mod/hello_1.0.bb

Step 2: Add to local.conf

Edit build/conf/local.conf to include the module in the image:

bash
IMAGE_INSTALL:append = " hello"

Step 3: Clean previous build

bash
bitbake -c clean rpi-basic-image

Step 4: Build the image

bash
bitbake rpi-basic-image


License Checksum Issues

If you encounter an error like LIC_FILES_CHKSUM does not match, the md5 hash of the license header in your source file differs from the one declared in the recipe. To fix it:

  1. Run the build and note the actual md5 hash from the error output
  2. Update LIC_FILES_CHKSUM in the .bb file with the correct value

Loading and Unloading Modules at Runtime

On the running target device, kernel modules can be managed with standard tools:

bash
# Load a module
insmod hello.ko

# Load with automatic dependency resolution
modprobe hello

# List loaded modules
lsmod

# Remove a module
rmmod hello

# Show module information
modinfo hello.ko


Final Thoughts

Dynamic kernel modules are the standard mechanism for device driver development in embedded Linux. They enable:

  • Adding hardware support without kernel recompilation
  • Modular, testable driver code
  • Smaller kernel binaries (modules loaded only when needed)

md
Kernel Module Development Workflow:
Write module source (.c)
    |
    v
Create Makefile with kernel build system
    |
    v
Build with Yocto (inherit module)
    |
    v
Deploy in root filesystem image
    |
    v
Load on target with insmod/modprobe
    |
    v
Test and debug with dmesg / printk

For embedded Linux engineers, understanding kernel module development opens the door to writing custom device drivers — one of the most powerful skills in the embedded software toolkit.