Implementing Mutex Synchronization in Linux Kernel Device Drivers

Overview

Mutexes (mutual exclusion locks) are synchronization primitives provided by the Linux kernel to hendle concurrent accesss to shared resources. Unlike semaphores which allow multiple concurrent access, a mutex ensures that only one thread can hold the lock at any given time.

This article demonstrates how to integrate mutex synchronization into a character device driver for GPIO LED control.

Mutex Implementation in a GPIO LED Driver

Project Setup

This implementation builds upon an existing semaphore-based project. To create the new project structure:

mkdir 8_mutex
cp ./8_semaphore/* ./8_mutex/ -rf
cp ./8_semaphore/.vscode/ ./8_mutex/ -rf
cd ./8_mutex
make clean
mv semaphore.c mutex.c

Update the Makefile to reference the new source file:

obj-m := mutex.o

Driver Implementation

The following code demonstrates mutex usage with in a GPIO LED driver:

#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/mutex.h>

#define LED_DISABLE  0
#define LED_ENABLE   1
#define DEVICE_NAME  "gpioled"
#define DEVICE_COUNT 1

/* Device structure */
struct led_device {
    dev_t devid;
    int major;
    int minor;
    struct cdev cdev;
    struct class* class;
    struct device* dev;
    struct device_node* device_node;
    int gpio_num;
    struct mutex mtx;
};

static struct led_device led_dev;

static int led_open(struct inode *inode, struct file *filp)
{
    mutex_lock(&led_dev.mtx);
    filp->private_data = &led_dev;
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    int ret;
    unsigned char rx_buf[2] = {0};

    ret = copy_from_user(rx_buf, buf, count);
    if (ret != 0) {
        printk("copy_from_user failed\n");
        return ret;
    }

    if (rx_buf[0] == LED_ENABLE) {
        gpio_set_value(led_dev.gpio_num, 0);
    } else if (rx_buf[0] == LED_DISABLE) {
        gpio_set_value(led_dev.gpio_num, 1);
    }

    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    struct led_device* dev = (struct led_device*)(filp->private_data);
    mutex_unlock(&dev->mtx);
    return 0;
}

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

static int __init led_init(void)
{
    int ret;

    mutex_init(&led_dev.mtx);

    led_dev.major = 0;
    if (led_dev.major) {
        led_dev.devid = MKDEV(led_dev.major, 0);
        ret = register_chrdev_region(led_dev.devid, DEVICE_COUNT, DEVICE_NAME);
    } else {
        ret = alloc_chrdev_region(&led_dev.devid, 0, DEVICE_COUNT, DEVICE_NAME);
        led_dev.major = MAJOR(led_dev.devid);
        led_dev.minor = MINOR(led_dev.devid);
    }

    printk("Major: %d, Minor: %d\n", led_dev.major, led_dev.minor);

    if (ret < 0) {
        printk("Failed to register device number\n");
        goto alloc_devid_failed;
    }

    led_dev.cdev.owner = THIS_MODULE;
    cdev_init(&led_dev.cdev, &led_fops);
    ret = cdev_add(&led_dev.cdev, led_dev.devid, DEVICE_COUNT);
    if (ret < 0) {
        printk("Failed to add cdev\n");
        goto cdev_add_failed;
    }

    led_dev.class = class_create(THIS_MODULE, DEVICE_NAME);
    if (IS_ERR(led_dev.class)) {
        ret = PTR_ERR(led_dev.class);
        goto class_create_failed;
    }

    led_dev.dev = device_create(led_dev.class, NULL, led_dev.devid, NULL, DEVICE_NAME);
    if (IS_ERR(led_dev.dev)) {
        ret = PTR_ERR(led_dev.dev);
        goto dev_create_failed;
    }

    led_dev.device_node = of_find_node_by_path("/gpioled");
    if (!led_dev.device_node) {
        printk("Failed to find device node\n");
        goto find_node_failed;
    }

    led_dev.gpio_num = of_get_named_gpio(led_dev.device_node, "led-gpio", 0);
    if (led_dev.gpio_num < 0) {
        printk("Failed to get GPIO number\n");
        goto find_node_failed;
    }

    ret = gpio_request(led_dev.gpio_num, DEVICE_NAME);
    if (ret != 0) {
        printk("Failed to request GPIO\n");
        goto find_node_failed;
    }

    ret = gpio_direction_output(led_dev.gpio_num, 1);
    if (ret != 0) {
        printk("Failed to set GPIO direction\n");
        goto gpio_dir_failed;
    }

    return 0;

gpio_dir_failed:
    gpio_free(led_dev.gpio_num);
find_node_failed:
    device_destroy(led_dev.class, led_dev.devid);
dev_create_failed:
    class_destroy(led_dev.class);
class_create_failed:
    cdev_del(&led_dev.cdev);
cdev_add_failed:
    unregister_chrdev_region(led_dev.devid, DEVICE_COUNT);
alloc_devid_failed:
    return ret;
}

static void __exit led_exit(void)
{
    gpio_set_value(led_dev.gpio_num, 1);
    cdev_del(&led_dev.cdev);
    unregister_chrdev_region(led_dev.devid, DEVICE_COUNT);
    device_destroy(led_dev.class, led_dev.devid);
    class_destroy(led_dev.class);
    gpio_free(led_dev.gpio_num);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

Building the Module

Compile the driver from the project directory:

make

The build process invokes the kernel build system and produces a loadable kernel module. Successful compilation indicates the mutex synchronization has been properly integrated into the driver.

Key Mutex Operations

Operation Description
mutex_init() Initializes the mutex before device registration
mutex_lock() Acquires the lock in open() - blocks if already held
mutex_unlock() Releases the lock in release() - enables next access

The mutex guarantees that only one process can access the LED hardware at a time, preventing race conditions when multiple applications attempt simultaneous control.

Important Considerations

  • Single acquirer: Unlike semaphores, a mutex strictly enforces that the same thread must release the lock
  • Non-recursive: The Linux kernel mutex does not support recursive locking; attempting to lock twice from the same context causes deadlock
  • Speed: Mutexes are optimized for the common case where no contention exists, making them more efficient than semaphores for simple exclusion scenarios

Tags: Linux kernel mutex device driver Synchronization gpio

Posted on Sun, 10 May 2026 10:15:08 +0000 by uluru75