Linux LED Driver Framework with Hardware Abstraction

Character Device Driver Foundations

Developing a Linux character device driver generally follows a standard sequence of operations:

  • Acquire a major device number, either statically assigned or dynamically allocated by the kernel.
  • Declare a file_operations structure to define the driver's supported interactions.
  • Implement the requisite handlers (such as open, read, write, and release) and bind them to the file_operations instance.
  • Register the driver with the kernel using register_chrdev.
  • Provide an entry function triggered upon module insertion to execute the registration routine.
  • Provide an exit function triggered upon module removal to invoke unregister_chrdev and clean up resources.
  • Automatically generate device nodes in /dev using class_create and device_create to expose device information to userspace.

Driver Layering for Multi-Board Support

To design an LED driver that seamlessly supports different hardware platforms, a layered architecture is essential. The implementation must be divided into a hardware-independent upper layer (the core driver logic) and a hardware-dependent lower layer (board-specific operations).

By adopting an object-oriented approach, we abstract the hadrware interactions into a shared interface:

struct led_hw_ops {
    int (*setup)(int id); /* Initialize the specified LED, id represents the LED index */
    int (*set_state)(int id, bool is_on); /* Control the LED state, id is the LED index, is_on determines active status */
};

Each board-specific module implements its own version of the led_hw_ops structure, which the generic upper-layer driver queries and invokes.

Layered Implementation Code

led_core.c (Hardware-Independent Layer)

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include "led_hw_ops.h"

#define MAX_LEDS 2

static int dev_major;
static struct class *led_dev_class;
static struct led_hw_ops *hw_callbacks;

static int led_open(struct inode *inode, struct file *filp)
{
    int minor_id = iminor(inode);
    hw_callbacks->setup(minor_id);
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *user_buf, size_t count, loff_t *f_pos)
{
    unsigned char state_val;
    int minor_id = iminor(file_inode(filp));

    if (copy_from_user(&state_val, user_buf, 1))
        return -EFAULT;

    hw_callbacks->set_state(minor_id, state_val);
    return 1;
}

static struct file_operations led_fops = {
    .owner   = THIS_MODULE,
    .open    = led_open,
    .write   = led_write,
};

static int __init led_core_init(void)
{
    int i;
    dev_major = register_chrdev(0, "generic_led", &led_fops);

    led_dev_class = class_create(THIS_MODULE, "led_class");
    if (IS_ERR(led_dev_class)) {
        unregister_chrdev(dev_major, "generic_led");
        return PTR_ERR(led_dev_class);
    }

    for (i = 0; i < MAX_LEDS; i++) {
        device_create(led_dev_class, NULL, MKDEV(dev_major, i), NULL, "myled%d", i);
    }
    
    hw_callbacks = fetch_led_hw_ops();
    return 0;
}

static void __exit led_core_exit(void)
{
    int i;
    for (i = 0; i < MAX_LEDS; i++) {
        device_destroy(led_dev_class, MKDEV(dev_major, i));
    }
    class_destroy(led_dev_class);
    unregister_chrdev(dev_major, "generic_led");
}

module_init(led_core_init);
module_exit(led_core_exit);
MODULE_LICENSE("GPL");

led_hw_ops.h (Abstraction Header)

#ifndef _LED_HW_OPS_H
#define _LED_HW_OPS_H

#include <linux/types.h>

struct led_hw_ops {
    int (*setup)(int id);
    int (*set_state)(int id, bool is_on);
};

struct led_hw_ops *fetch_led_hw_ops(void);

#endif

board_impl.c (Hardware-Dependent Layer)

#include <linux/module.h>
#include <linux/kernel.h>
#include "led_hw_ops.h"

static int demo_led_setup(int id)
{
    printk(KERN_INFO "Initializing LED %d on demo board\n", id);
    return 0;
}

static int demo_led_set_state(int id, bool is_on)
{
    printk(KERN_INFO "Setting LED %d state to %s\n", id, is_on ? "ACTIVE" : "INACTIVE");
    return 0;
}

static struct led_hw_ops demo_ops = {
    .setup     = demo_led_setup,
    .set_state = demo_led_set_state,
};

struct led_hw_ops *fetch_led_hw_ops(void)
{
    return &demo_ops;
}

app_test.c (Userspace Testing Tool)

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int dev_fd;
    unsigned char brightness;

    if (argc != 3) {
        fprintf(stderr, "Usage: %s <device_node> <on|off>\n", argv[0]);
        return EXIT_FAILURE;
    }

    dev_fd = open(argv[1], O_RDWR);
    if (dev_fd < 0) {
        perror("Failed to open device");
        return EXIT_FAILURE;
    }

    if (strcmp(argv[2], "on") == 0) {
        brightness = 1;
    } else {
        brightness = 0;
    }

    write(dev_fd, &brightness, 1);
    close(dev_fd);

    return EXIT_SUCCESS;
}

Tags: Linux kernel device driver Character Device Hardware Abstraction LED

Posted on Mon, 06 Jul 2026 17:19:02 +0000 by Stressed