ioctl Linux Device Driver Tutorial: Custom Commands Done Safely
Free Linux Kernel Development Course • Free Linux Device Drivers Course
What You Will Learn
- When ioctl is genuinely the right tool, versus when sysfs would be cleaner
- How to define safe ioctl command numbers with _IOR, _IOW, and _IOWR
- How to implement unlocked_ioctl in a character device driver
- A full comparison table across all five interfaces covered in this series
When Should You Reach for ioctl?
read and write are built around a stream of bytes. Plenty of driver operations do not fit that shape at all: setting a hardware mode, querying a capability structure, resetting a device, or passing a command along with several parameters at once. Forcing those operations through read/write means inventing an ad-hoc text protocol, which is exactly the kind of mess ioctl was designed to avoid. The ioctl(2) system call lets a user-space program send a numbered command, optionally along with a pointer to a structure, directly to the driver’s ioctl handler.
| ioctl(fd, CMD, &arg) | → | VFS | → | .unlocked_ioctl |
Defining Command Numbers the Safe Way
Never hardcode raw integers as ioctl command numbers. The kernel header linux/ioctl.h provides macros that encode the direction (read, write, or both), a magic type number, a sequence number, and the size of the data structure into a single value. This lets the kernel validate the transfer size automatically and avoids collisions with other drivers’ command numbers.
#define MYDRIVER_MAGIC 'k'
struct mydriver_config {
__u32 mode;
__u32 threshold;
};
#define MYDRIVER_GET_CONFIG _IOR(MYDRIVER_MAGIC, 1, struct mydriver_config)
#define MYDRIVER_SET_CONFIG _IOW(MYDRIVER_MAGIC, 2, struct mydriver_config)
#define MYDRIVER_RESET _IO(MYDRIVER_MAGIC, 3)
Implementing unlocked_ioctl
Modern drivers implement .unlocked_ioctl rather than the older .ioctl field, which was removed years ago because it held the Big Kernel Lock implicitly. Your handler is responsible for its own locking today, typically with a driver-private mutex.
static long mydriver_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct mydriver_config cfg;
switch (cmd) {
case MYDRIVER_GET_CONFIG:
mutex_lock(&mydriver_lock);
cfg = current_config;
mutex_unlock(&mydriver_lock);
if (copy_to_user((void __user *)arg, &cfg, sizeof(cfg)))
return -EFAULT;
return 0;
case MYDRIVER_SET_CONFIG:
if (copy_from_user(&cfg, (void __user *)arg, sizeof(cfg)))
return -EFAULT;
mutex_lock(&mydriver_lock);
current_config = cfg;
mutex_unlock(&mydriver_lock);
return 0;
case MYDRIVER_RESET:
mydriver_hw_reset();
return 0;
default:
return -ENOTTY;
}
}
static const struct file_operations mydriver_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = mydriver_ioctl,
};
Returning -ENOTTY for an unrecognized command is the correct convention; it signals “inappropriate ioctl for device” to user space, matching what the standard C library expects.
Common Mistakes
- Hardcoding raw integers instead of using
_IO,_IOR,_IOW, and_IOWRmacros. - Implementing the obsolete
.ioctlfield instead of.unlocked_ioctl. - Trusting the size or contents of a user-supplied structure without validating it after
copy_from_user(). - Using ioctl for something that is really just a single configuration value, where sysfs would be simpler and more scriptable.
Full Comparison: procfs vs sysfs vs debugfs vs netlink vs ioctl
| Interface | Style | Stable ABI? | Typical Use |
|---|---|---|---|
| procfs | Pull, free-form | Partially | Process/system info |
| sysfs | Pull, one value/file | Yes | Device attributes |
| debugfs | Pull, free-form | No | Debug/bring-up dumps |
| netlink | Push, socket-based | Yes (per family) | Async events |
| ioctl | Request/response command | Yes (per command) | Custom device commands |
Summary and Key Takeaways
- Every kernel-to-user-space interface still funnels through a system call; the difference is design philosophy, not the underlying mechanism.
- Use sysfs for simple attributes, debugfs for developer-only diagnostics, netlink for asynchronous events, and ioctl for structured custom commands.
- procfs is mostly legacy for driver work; keep it for reading existing system information rather than adding new entries.
- Always validate user-supplied data at the boundary, whichever interface you use, since that boundary is where most driver security bugs originate.
That wraps up this free linux kernel development course series on kernel-to-user-space communication pathways. With procfs, sysfs, debugfs, netlink sockets, and ioctl all covered, you now have a practical decision framework for choosing the right interface the next time you sit down to write a Linux device driver.
FAQ
Q1: Is ioctl considered bad practice in modern Linux driver development?
No, it is still the right tool for structured commands that do not map to a single sysfs attribute; it is only discouraged when a simpler interface would do the same job.
Q2: Why was the old .ioctl file operation removed?
It implicitly held the Big Kernel Lock, which hurt scalability; .unlocked_ioctl requires the driver to manage its own locking instead.
Q3: What does the magic number in _IOR/_IOW mean?
It is an arbitrary character or number chosen to reduce the chance that your driver’s ioctl commands collide with another driver’s numbers.
Q4: Should I pick sysfs or ioctl for a single on/off setting?
Prefer sysfs; it is scriptable with plain shell commands and follows the standard driver model convention.
Q5: Can ioctl transfer large amounts of data efficiently?
It can, but for very large or streaming transfers, mmap-based approaches usually perform better than repeated ioctl calls.

2 Comments