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.