Using Semaphores to Enforce Exclusive Access to an LED Device in Linux Kernel Driver

This example demonstrates how to implement a GPIO-controlled LED driver that ensures only one user process can access the LED at a time using a counting semaphore initialized to 1 (binary semaphore). The semaphore is acquired in the open() handler and released in the release() handler, effectively serializign access across multiple processes.

Driver Implementation

The following code modifies a basic GPIO LED driver to include semaphore-based mutual exclusion. The structure gpio_led now includes a struct semaphore member named sem_lock.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>

#define LED_OFF    0
#define LED_ON     1
#define DEVICE_NAME "gpioled"
#define DEVICE_COUNT 1

struct gpio_led_device {
    dev_t device_number;
    int major;
    int minor;
    struct cdev cdev;
    struct class *dev_class;
    struct device *hw_device;
    struct device_node *node;
    int gpio_pin;
    struct semaphore access_lock;  // Binary semaphore for exclusive access
};

static struct gpio_led_device led_device;

static int led_open(struct inode *inode, struct file *file)
{
    // Acquire semaphore—blocks if already held by another process
    down(&led_device.access_lock);
    file->private_data = &led_device;
    return 0;
}

static ssize_t led_write(struct file *file, const char __user *buffer, size_t len, loff_t *offset)
{
    char cmd;
    int result;

    if (copy_from_user(&cmd, buffer, sizeof(cmd)))
        return -EFAULT;

    if (cmd == LED_ON) {
        gpio_set_value(led_device.gpio_pin, 0);  // Active-low LED
    } else if (cmd == LED_OFF) {
        gpio_set_value(led_device.gpio_pin, 1);
    }

    return len;
}

static int led_release(struct inode *inode, struct file *file)
{
    struct gpio_led_device *dev = file->private_data;
    // Release semaphore to allow other processes to access
    up(&dev->access_lock);
    return 0;
}

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

static int __init led_driver_init(void)
{
    int ret;

    // Initialize semaphore to 1 (binary lock)
    sema_init(&led_device.access_lock, 1);

    // Register device number dynamically
    ret = alloc_chrdev_region(&led_device.device_number, 0, DEVICE_COUNT, DEVICE_NAME);
    if (ret < 0) {
        printk(KERN_ERR "Failed to allocate device number\n");
        return ret;
    }

    led_device.major = MAJOR(led_device.device_number);
    led_device.minor = MINOR(led_device.device_number);

    // Initialize and register character device
    cdev_init(&led_device.cdev, &led_fops);
    led_device.cdev.owner = THIS_MODULE;
    ret = cdev_add(&led_device.cdev, led_device.device_number, DEVICE_COUNT);
    if (ret < 0) {
        printk(KERN_ERR "Failed to add character device\n");
        goto unregister_region;
    }

    // Create device class and device node
    led_device.dev_class = class_create(THIS_MODULE, DEVICE_NAME);
    if (IS_ERR(led_device.dev_class)) {
        ret = PTR_ERR(led_device.dev_class);
        goto del_cdev;
    }

    led_device.hw_device = device_create(led_device.dev_class, NULL, led_device.device_number, NULL, DEVICE_NAME);
    if (IS_ERR(led_device.hw_device)) {
        ret = PTR_ERR(led_device.hw_device);
        goto destroy_class;
    }

    // Parse device tree for GPIO pin
    led_device.node = of_find_node_by_path("/gpioled");
    if (!led_device.node) {
        printk(KERN_ERR "Device tree node not found\n");
        ret = -ENODEV;
        goto destroy_device;
    }

    led_device.gpio_pin = of_get_named_gpio(led_device.node, "led-gpio", 0);
    if (led_device.gpio_pin < 0) {
        printk(KERN_ERR "Failed to get GPIO from device tree\n");
        ret = led_device.gpio_pin;
        goto destroy_device;
    }

    // Request and configure GPIO
    ret = gpio_request(led_device.gpio_pin, DEVICE_NAME);
    if (ret) {
        printk(KERN_ERR "GPIO request failed\n");
        goto destroy_device;
    }

    ret = gpio_direction_output(led_device.gpio_pin, 1); // Start with LED off
    if (ret) {
        printk(KERN_ERR "Failed to set GPIO as output\n");
        goto free_gpio;
    }

    printk(KERN_INFO "LED driver loaded: major=%d, minor=%d, GPIO=%d\n", 
           led_device.major, led_device.minor, led_device.gpio_pin);
    return 0;

free_gpio:
    gpio_free(led_device.gpio_pin);
destroy_device:
    device_destroy(led_device.dev_class, led_device.device_number);
destroy_class:
    class_destroy(led_device.dev_class);
del_cdev:
    cdev_del(&led_device.cdev);
unregister_region:
    unregister_chrdev_region(led_device.device_number, DEVICE_COUNT);
    return ret;
}

static void __exit led_driver_exit(void)
{
    // Turn off LED before cleanup
    gpio_set_value(led_device.gpio_pin, 1);

    gpio_free(led_device.gpio_pin);
    device_destroy(led_device.dev_class, led_device.device_number);
    class_destroy(led_device.dev_class);
    cdev_del(&led_device.cdev);
    unregister_chrdev_region(led_device.device_number, DEVICE_COUNT);

    printk(KERN_INFO "LED driver unloaded\n");
}

module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedded Systems Team");

Build and Test

To compile the driver, navigate to the project directory and run:

make -C /path/to/kernel/source M=$(pwd) modules

After loading the module with insmod semaphore.ko, create a device nodde (if not auto-created) and test with two concurrent processes:

echo 1 > /dev/gpioled   # Turn LED on
echo 0 > /dev/gpioled   # Turn LED off

When multiple processes attempt to access /dev/gpioled simultaneously, the semaphore ensures only one succeeds at a time—the others block until the current holder releases the lock. This prevents race conditions and guarantees atomic LED state transisions.

Tags: linux-kernel gpio Semaphore device-driver Concurrency

Posted on Tue, 16 Jun 2026 17:08:44 +0000 by Distant_storm