Advent of Embedded Linux — Day 4: Writing Your First Kernel Module

Day 4 — Writing Your First Kernel Module — Virtual Temperature Sensor Driver

Overview

Today, you’ll write your first Linux kernel module — a virtual temperature sensor driver that provides temperature readings through the sysfs interface. Unlike a simple “Hello World” driver, this module demonstrates concepts like creating sysfs attributes, generating random data, and integrating with the kernel’s kobject framework.

This tutorial uses Buildroot and QEMU, and this will be our last post focusing on QEMU-based development. Future posts in this series will move to actual hardware, so this is your final chance to experiment in a safe virtual environment!

Writing kernel modules is essential for embedded linux development. Whether you’re creating drivers for custom sensors, implementing protocol handlers, or adding system functionality, understanding how to write loadable kernel modules opens up a world of possibilities.

What You Will Do

Prerequisites

Step-by-Step Guide

Step 1: Understanding the Driver Code

Let’s start by examining the complete driver code. Create a file called mocktemp.c with the following content:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/random.h>

static struct kobject *mock_kobj;

static ssize_t temp_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
    int temp_value;
    
    temp_value = 30 + (get_random_u32() % 51);
    
    return sysfs_emit(buf, "%d\n", temp_value);
}

static struct kobj_attribute temp_attr = __ATTR_RO(temp);

static int __init mock_init(void)
{
    int ret;
    
    mock_kobj = kobject_create_and_add("mocktemp", kernel_kobj);
    if (!mock_kobj)
        return -ENOMEM;

    ret = sysfs_create_file(mock_kobj, &temp_attr.attr);
    if (ret) {
        kobject_put(mock_kobj);
        return ret;
    }
    
    return 0;
}

static void __exit mock_exit(void)
{
    sysfs_remove_file(mock_kobj, &temp_attr.attr);
    kobject_put(mock_kobj);
}

module_init(mock_init);
module_exit(mock_exit);
MODULE_LICENSE("<Your License>");
MODULE_AUTHOR("<Your Author Name>");
MODULE_DESCRIPTION("Mock Temperature Sensor Driver");

Code Explanation

Let’s break down this driver step by step:

Header Files:

Global Variables:

static struct kobject *mock_kobj;

Temperature Reading Function:

static ssize_t temp_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
    int temp_value;
    
    temp_value = 30 + (get_random_u32() % 51);
    
    return sysfs_emit(buf, "%d\n", temp_value);
}

This function is called whenever someone reads from /sys/kernel/mocktemp/temp:

Sysfs Attribute Definition:

static struct kobj_attribute temp_attr = __ATTR_RO(temp);

Module Initialization:

static int __init mock_init(void)
{
    int ret;
    
    mock_kobj = kobject_create_and_add("mocktemp", kernel_kobj);
    if (!mock_kobj)
        return -ENOMEM;

    ret = sysfs_create_file(mock_kobj, &temp_attr.attr);
    if (ret) {
        kobject_put(mock_kobj);
        return ret;
    }
    
    return 0;
}

Module Cleanup:

static void __exit mock_exit(void)
{
    sysfs_remove_file(mock_kobj, &temp_attr.attr);
    kobject_put(mock_kobj);
}

Module Macros:

module_init(mock_init);
module_exit(mock_exit);
MODULE_LICENSE("<Your License>");
MODULE_AUTHOR("<Your Author Name>");
MODULE_DESCRIPTION("Mock Temperature Sensor Driver");

Step 2: Creating the Buildroot Package

Since Buildroot package creation was covered on day 2, we’ll just skip over the basics. However, you’ll need to create the package structure if it doesn’t exist:

The driver should be packaged so that:

For reference, your Buildroot package would typically include:

My mocktemp.mk looks like this:

MOCKTEMP_VERSION = 1.0
MOCKTEMP_SITE = package/mocktemp/src
MOCKTEMP_SITE_METHOD = local

# Use the kernel module infrastructure
$(eval $(kernel-module))
$(eval $(generic-package))

After building Buildroot with the package enabled, the module might be at:

/lib/modules/6.12.47/updates/mocktemp.ko

Step 3: Building with Buildroot

Assuming you’ve already set up your Buildroot environment:

cd ~/buildroot

# Configure for RISC-V QEMU (if not already done)
make qemu_riscv64_virt_defconfig

# Open menuconfig to enable your package
make menuconfig

Navigate to your package location in menuconfig and enable it. I usually suggest searching by the package name itself to make your life easy :)

After enabling:

# Build everything (this will take some time if rebuilding)
make

Buildroot will compile your kernel module along with the kernel and root filesystem.

Step 4: Booting QEMU

After the build completes, boot QEMU using the convenient script that Buildroot generates:

cd output/images

# Boot QEMU (the script handles all the parameters)
./start_qemu.sh

Wait for the system to boot and log in (usually root with no password, or check Buildroot documentation).

Step 5: Loading the Driver

Once you’re logged into the QEMU system, load the kernel module:

# Load the module
insmod /lib/modules/6.12.47/updates/mocktemp.ko

# Verify it loaded successfully
lsmod | grep mocktemp

# Check kernel messages
dmesg | tail

If everything worked correctly, you should see the module in lsmod output. If there were errors, check dmesg for details.

Step 6: Testing the Sysfs Interface

Now that the driver is loaded, the sysfs interface should be available:

# Check that the directory was created
ls -la /sys/kernel/mocktemp/

# Read the temperature
cat /sys/kernel/mocktemp/temp

# Read it a few more times to see different values
cat /sys/kernel/mocktemp/temp
cat /sys/kernel/mocktemp/temp

Each time you read from /sys/kernel/mocktemp/temp, you should get a different temperature value (since we’re using random numbers). The values will be in the range 30-80°C.

Step 7: Understanding Sysfs

Sysfs is a virtual filesystem that provides a view of the kernel’s device model. Our driver creates:

/sys/kernel/mocktemp/
└── temp    (read-only file containing temperature)

When you cat the file, the kernel:

  1. Recognizes it’s a sysfs attribute
  2. Calls our temp_show() function
  3. Our function generates a random temperature
  4. The value is formatted and returned as file content

Step 8: Unloading the Driver

When you’re done testing, unload the module:

# Unload the module
rmmod mocktemp

# Verify it's gone
lsmod | grep mocktemp

# Check that sysfs entry is removed
ls /sys/kernel/mocktemp/
# Should show: "No such file or directory"

The mock_exit() function handles cleanup, removing the sysfs entries.

Expected Outcome

After completing this tutorial, you should have:

  1. A working kernel module that creates a sysfs interface
  2. Understanding of how kernel modules are structured
  3. Experience with kobject and sysfs APIs
  4. A virtual temperature sensor accessible at /sys/kernel/mocktemp/temp
  5. Ability to load and unload kernel modules dynamically

When you read from /sys/kernel/mocktemp/temp, you should see:

67

Or any value between 30-80, changing each time you read it.

Troubleshooting

Module won’t load:

Sysfs entry not appearing:

Permission denied reading temp:

Buildroot build fails:

Key Concepts Learned

What’s Next?

This was our last QEMU-focused post! In upcoming days, we’ll move to real hardware and explore:

The concepts you’ve learned here (sysfs, kobjects, module structure) will directly apply when working with real hardware drivers.

Extras