How to Build and Test a Simulated GPIO Character Driver on Linux

This tutorial walks through creating a Linux kernel character driver that simulates four GPIO devices, compiles the module, loads it to automatically generate /dev/mygpio0‑3 nodes, and provides a user‑space program to control the GPIO states via ioctl, including full build and cleanup steps.

Liangxu Linux
Liangxu Linux
Liangxu Linux
How to Build and Test a Simulated GPIO Character Driver on Linux

Example Program Goal

Write a driver module mygpio.ko that, when loaded, creates a class device mygpio and automatically creates four device nodes /dev/mygpio0 to /dev/mygpio3.

Because the tutorial runs on an x86 platform without real GPIO hardware, the driver uses the macro MYGPIO_HW_ENABLE to simulate hardware actions and prints messages with printk.

Writing the Driver

All operations are performed in the same working directory as the previous article: ~/tmp/linux-4.15/drivers/.

Create Driver Directory and Source File

$ cd linux-4.15/drivers/
$ mkdir mygpio_driver
$ cd mygpio_driver
$ touch mygpio.c

The content of mygpio.c is:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/ctype.h>
#include <linux/device.h>
#include <linux/cdev.h>

// GPIO hardware‑related macro
#define MYGPIO_HW_ENABLE

// Device name
#define MYGPIO_NAME		"mygpio"

// Number of GPIO ports
#define MYGPIO_NUMBER		4

static struct class *gpio_class;
static struct cdev gpio_cdev[MYGPIO_NUMBER];
int gpio_major = 0;
int gpio_minor = 0;

#ifdef MYGPIO_HW_ENABLE
static void gpio_hw_init(int gpio)
{
    printk("gpio_hw_init is called: %d. 
", gpio);
}
static void gpio_hw_release(int gpio)
{
    printk("gpio_hw_release is called: %d. 
", gpio);
}
static void gpio_hw_set(unsigned long gpio_no, unsigned int val)
{
    printk("gpio_hw_set is called. gpio_no = %ld, val = %d. 
", gpio_no, val);
}
#endif

static int gpio_open(struct inode *inode, struct file *file)
{
    printk("gpio_open is called. 
");
    return 0;
}

static long gpio_ioctl(struct file* file, unsigned int val, unsigned long gpio_no)
{
    printk("gpio_ioctl is called. 
");
    if (0 != val && 1 != val) {
        printk("val is NOT valid! 
");
        return 0;
    }
    if (gpio_no >= MYGPIO_NUMBER) {
        printk("dev_no is invalid! 
");
        return 0;
    }
    printk("set GPIO: %ld to %d. 
", gpio_no, val);
#ifdef MYGPIO_HW_ENABLE
    gpio_hw_set(gpio_no, val);
#endif
    return 0;
}

static const struct file_operations gpio_ops = {
    .owner = THIS_MODULE,
    .open  = gpio_open,
    .unlocked_ioctl = gpio_ioctl
};

static int __init gpio_driver_init(void)
{
    int i, devno;
    dev_t num_dev;
    printk("gpio_driver_init is called. 
");
    alloc_chrdev_region(&num_dev, gpio_minor, MYGPIO_NUMBER, MYGPIO_NAME);
    gpio_major = MAJOR(num_dev);
    printk("gpio_major = %d. 
", gpio_major);
    gpio_class = class_create(THIS_MODULE, MYGPIO_NAME);
    for (i = 0; i < MYGPIO_NUMBER; ++i) {
        devno = MKDEV(gpio_major, gpio_minor + i);
        cdev_init(&gpio_cdev[i], &gpio_ops);
        cdev_add(&gpio_cdev[i], devno, 1);
        device_create(gpio_class, NULL, devno, NULL, MYGPIO_NAME"%d", i);
    }
#ifdef MYGPIO_HW_ENABLE
    for (i = 0; i < MYGPIO_NUMBER; ++i) {
        gpio_hw_init(i);
    }
#endif
    return 0;
}

static void __exit gpio_driver_exit(void)
{
    int i;
    printk("gpio_driver_exit is called. 
");
    for (i = 0; i < MYGPIO_NUMBER; ++i) {
        cdev_del(&gpio_cdev[i]);
        device_destroy(gpio_class, MKDEV(gpio_major, gpio_minor + i));
    }
    class_destroy(gpio_class);
#ifdef MYGPIO_HW_ENABLE
    for (i = 0; i < MYGPIO_NUMBER; ++i) {
        gpio_hw_release(i);
    }
#endif
    unregister_chrdev_region(MKDEV(gpio_major, gpio_minor), MYGPIO_NUMBER);
}

MODULE_LICENSE("GPL");
module_init(gpio_driver_init);
module_exit(gpio_driver_exit);

The code adds the MYGPIO_HW_ENABLE macro to guard hardware‑related functions, making the driver compile even without real GPIO hardware.

Create Makefile

ifneq ($(KERNELRELEASE),)
    obj-m := mygpio.o
else
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
    default:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    clean:
        $(MAKE) -C $(KERNEL_PATH) M=$(PWD) clean
endif

Compile the Driver Module

$ make

The resulting mygpio.ko file is the loadable kernel module.

Load the Driver Module

$ sudo insmod mygpio.ko

During loading, module_init() runs gpio_driver_init(), which registers a dynamic major number (e.g., 244) and creates the four device nodes. Verify with dmesg and cat /proc/devices.

Application Program

The user‑space program resides in ~/tmp/App/app_mygpio:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#define MY_GPIO_NUMBER		4

char gpio_name[MY_GPIO_NUMBER][16] = {
    "/dev/mygpio0",
    "/dev/mygpio1",
    "/dev/mygpio2",
    "/dev/mygpio3"
};

int main(int argc, char *argv[])
{
    int fd, gpio_no, val;
    if (3 != argc) {
        printf("Usage: ./app_gpio gpio_no value 
");
        return -1;
    }
    gpio_no = atoi(argv[1]);
    val = atoi(argv[2]);
    assert(gpio_no < MY_GPIO_NUMBER);
    assert(0 == val || 1 == val);
    if ((fd = open(gpio_name[gpio_no], O_RDWR | O_NDELAY)) < 0) {
        printf("%s: open failed! 
", gpio_name[gpio_no]);
        return -1;
    }
    printf("%s: open success! 
", gpio_name[gpio_no]);
    ioctl(fd, val, gpio_no);
    close(fd);
    return 0;
}

Compile with: $ gcc app_mygpio.c -o app_mygpio Run the program with two arguments: the GPIO device index (0‑3) and the desired state (0 or 1), e.g.: $ sudo ./app_mygpio 0 1 Check dmesg to confirm that the driver printed a message like “set GPIO: 0 to 1”. Repeating with value 0 toggles the simulated GPIO back.

Unload the Driver Module

$ sudo rmmod mygpio

After removal, /proc/devices no longer lists the major number, and dmesg shows that gpio_driver_exit() cleaned up the device nodes and class.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CMakefilekernel driverioctlGPIOcharacter device
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.