Implementing IOCTL in a Linux Character Device Driver-Linux Device Driver Training in Hyderabad

Implementing IOCTL in a Linux Character Device Driver
Free Linux Device Drivers Course — Kernel 6.x Compatible Example

← Previous Lecture  |  Next Lecture →

In this lesson of our free Linux kernel development course, we implement a Linux ioctl character driver from scratch, targeting modern 6.x kernels. You will learn how to define custom ioctl commands with the _IO macro family, register a handler in file_operations, and safely exchange data with user space. This is a hands-on continuation of the previous lesson on the Linux ioctl system call basics.

Linux ioctl character driver Free Linux Device Drivers Course Kernel Module Programming copy_to_user / copy_from_user

What You Will Learn

  • How to define custom ioctl commands safely with _IO macros
  • How to register an unlocked_ioctl handler in a character driver
  • How to move data safely with copy_to_user and copy_from_user
  • How to register a character device on kernel 6.x using the modern class_create() API

Prerequisites

  • Completed the previous lesson on the Linux ioctl system call fundamentals
  • Comfortable writing and inserting a basic loadable kernel module (LKM)
  • A Linux VM or board running a 6.x kernel with headers installed

Step 1: Defining Custom IOCTL Commands

Every Linux ioctl character driver needs a shared header file that defines its custom command numbers. Never invent these numbers by hand — always build them using the _IO, _IOR, _IOW, or _IOWR macros from <linux/ioctl.h>. These macros pack a magic number, a sequence number, a direction, and a size into a single unsigned long, which drastically reduces the chance of two unrelated drivers colliding on the same command value.

/* epdrv_ioctl.h - shared between kernel driver and user app */
#ifndef EPDRV_IOCTL_H
#define EPDRV_IOCTL_H

#include <linux/ioctl.h>

#define EPDRV_MAGIC   'E'

#define EPDRV_SET_VALUE   _IOW(EPDRV_MAGIC, 1, int)
#define EPDRV_GET_VALUE   _IOR(EPDRV_MAGIC, 2, int)
#define EPDRV_RESET       _IO(EPDRV_MAGIC,  3)

#endif
Macro Data Direction Typical Use
_IO No data Simple trigger commands, e.g. reset
_IOW User to kernel Setting a configuration value
_IOR Kernel to user Reading a status or value
_IOWR Both directions Combined read/modify/write commands

Step 2: Writing the Driver’s IOCTL Handler

With the command numbers defined, the next step of this Linux ioctl character driver is the handler function itself. It receives the file pointer, the command, and the argument, then switches on the command to decide what to do.

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include "epdrv_ioctl.h"

static int epdrv_value;

static long epdrv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int tmp;

    switch (cmd) {
    case EPDRV_SET_VALUE:
        if (copy_from_user(&tmp, (int __user *)arg, sizeof(tmp)))
            return -EFAULT;
        epdrv_value = tmp;
        break;

    case EPDRV_GET_VALUE:
        tmp = epdrv_value;
        if (copy_to_user((int __user *)arg, &tmp, sizeof(tmp)))
            return -EFAULT;
        break;

    case EPDRV_RESET:
        epdrv_value = 0;
        break;

    default:
        return -ENOTTY;
    }

    return 0;
}
Inside the IOCTL Handler: Command Dispatch
cmd == EPDRV_SET_VALUE
copy_from_user()
cmd == EPDRV_GET_VALUE
copy_to_user()
cmd == EPDRV_RESET
no data transfer
unknown cmd
return -ENOTTY

Notice that the default case returns -ENOTTY rather than silently ignoring an unrecognized command — this is the standard kernel convention for “inappropriate ioctl for device” and helps user-space code detect mismatched driver versions.

Step 3: Registering the Handler and Creating the Device Node

The handler is wired up through the file_operations structure using .unlocked_ioctl, and the character device is registered using the modern alloc_chrdev_region/cdev/class_create sequence.

static int epdrv_open(struct inode *inode, struct file *filp) { return 0; }
static int epdrv_release(struct inode *inode, struct file *filp) { return 0; }

static const struct file_operations epdrv_fops = {
    .owner          = THIS_MODULE,
    .open           = epdrv_open,
    .release        = epdrv_release,
    .unlocked_ioctl = epdrv_ioctl,
};

static struct cdev epdrv_cdev;
static struct class *epdrv_class;
static dev_t epdrv_devno;

static int __init epdrv_init(void)
{
    int ret = alloc_chrdev_region(&epdrv_devno, 0, 1, "epdrv");
    if (ret)
        return ret;

    cdev_init(&epdrv_cdev, &epdrv_fops);
    ret = cdev_add(&epdrv_cdev, epdrv_devno, 1);
    if (ret)
        goto unregister;

    /* class_create() takes only the class name on kernel 6.4+ */
    epdrv_class = class_create("epdrv_class");
    if (IS_ERR(epdrv_class)) {
        ret = PTR_ERR(epdrv_class);
        goto del_cdev;
    }

    device_create(epdrv_class, NULL, epdrv_devno, NULL, "epdrv0");
    return 0;

del_cdev:
    cdev_del(&epdrv_cdev);
unregister:
    unregister_chrdev_region(epdrv_devno, 1);
    return ret;
}

static void __exit epdrv_exit(void)
{
    device_destroy(epdrv_class, epdrv_devno);
    class_destroy(epdrv_class);
    cdev_del(&epdrv_cdev);
    unregister_chrdev_region(epdrv_devno, 1);
}

module_init(epdrv_init);
module_exit(epdrv_exit);
MODULE_LICENSE("GPL");
Kernel Version Note

class_create() dropped its owner/THIS_MODULE parameter starting with Linux kernel 6.4. If you are following older tutorials online for your Linux ioctl character driver, watch out for this exact mismatch — it’s one of the most common build errors reported on recent kernels.

Common Mistakes When Writing an IOCTL Handler

Mistake Consequence
Dereferencing the arg pointer directly Kernel oops or security hole; always go through copy_to_user/copy_from_user
Not checking copy_to_user/copy_from_user return value Silent data corruption on partial copy failures
Forgetting the default case in the switch statement Driver silently succeeds on unsupported commands instead of returning -ENOTTY

Best Practices

  • Keep the ioctl command header shared and versioned between kernel and user space
  • Protect shared driver state (like epdrv_value above) with a mutex if multiple processes can open the device concurrently
  • Return standard errno values (-EFAULT, -ENOTTY, -EINVAL) so user space can react appropriately
  • Avoid large structures passed by value through ioctl; prefer pointers with explicit size fields

Performance Considerations

The Linux ioctl system call itself has very low overhead — it is a direct syscall trap into the driver. The real performance cost usually comes from copy_to_user/copy_from_user when transferring large buffers. For high-frequency, high-volume data transfer, ioctl is not the right tool; consider mmap-based shared memory or a dedicated read/write path instead, and reserve ioctl for control-plane operations.

Summary and Key Takeaways

  • Define ioctl commands with _IO/_IOR/_IOW/_IOWR, never with raw numbers
  • Hook .unlocked_ioctl in file_operations on modern kernels
  • Always use copy_to_user/copy_from_user for data exchange
  • Return -ENOTTY for unrecognized commands
  • class_create() takes only a name argument from kernel 6.4 onward

Conclusion

You now have a complete, kernel 6.x-compatible Linux ioctl character driver that safely exchanges data with user space through custom commands. In the next lesson of this free Linux device drivers course, we will write the matching user-space application, build it, and test the full round trip end to end.

Frequently Asked Questions

Q1. Why use _IOW/_IOR macros instead of plain integers for ioctl commands?

The macros encode direction, magic number, and size into the command value, which helps the kernel and drivers detect mismatched or colliding commands.

Q2. What does -ENOTTY mean in an ioctl handler?

It signals “inappropriate ioctl for device” — the standard response for a command your driver does not recognize.

Q3. Do I need locking inside my ioctl handler?

If more than one process can access the device concurrently and the handler touches shared state, yes — protect it with a mutex or spinlock as appropriate.

Q4. Why did my class_create() call fail to compile?

Kernel 6.4 removed the owner/THIS_MODULE argument from class_create(). Check your kernel version and update the call signature accordingly.

Q5. Is ioctl suitable for transferring large amounts of data?

Not ideally. Use it for control operations; for bulk data transfer, prefer read/write or mmap-based approaches.

← Previous Lecture  |  Next Lecture →

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *