Skip to content

IOCTL and Control Plane

1. What is ioctl?

ioctl (input/output control) is a system call used to send control commands from user space to kernel drivers.

Unlike read() / write() which handle data transfer, ioctl() is used for:

  • Configuration
  • Status query
  • Device-specific control operations

2. IOCTL Design Philosophy

Command-based (NOT message-based)

Linux ioctl follows a command-per-struct model:

  • Each command has its own data structure
  • No global message container
  • No embedded "cmd" field inside struct

Correct design:

#define MYGPIO_IOC_SET_DEBOUNCE _IOW(MYGPIO_IOC_MAGIC, 1, struct mygpio_ioc_debounce)
#define MYGPIO_IOC_GET_STATUS   _IOR(MYGPIO_IOC_MAGIC, 2, struct mygpio_ioc_status)
#define MYGPIO_IOC_CLR_EVENTS   _IO(MYGPIO_IOC_MAGIC, 3)

Avoid:

  • giant union-based container
  • message-style multiplexing

3. IOCTL Command Encoding

Linux provides macros:

Macro Direction Description
_IO none no data
_IOR read kernel → user
_IOW write user → kernel
_IOWR both bidirectional

Example:

#define MYGPIO_IOC_MAGIC 'M'

struct mygpio_ioc_debounce {
    __u32 debounce_ms;
};

#define MYGPIO_IOC_SET_DEBOUNCE \
    _IOW(MYGPIO_IOC_MAGIC, 1, struct mygpio_ioc_debounce)

4. User ↔ Kernel Memory Handling

User pointer must NEVER be dereferenced directly.

Use:

copy_from_user()
copy_to_user()

Example:

if (copy_from_user(&cfg, argp, sizeof(cfg)))
    return -EFAULT;

5. ABI (Application Binary Interface)

IOCTL interface is a stable ABI:

  • Structure layout must not change arbitrarily
  • Use fixed-size types (__u32, etc.)
  • Consider versioning

Example:

#define MYGPIO_ABI_VERSION 1

6. Control Plane vs Data Plane

Data Plane

Handles continuous data flow:

  • read()
  • poll()
  • event queue

Control Plane

Handles configuration and queries:

  • ioctl()

7. Locking Strategy

Different data requires different locks:

Data Type Lock
Event queue spinlock
Runtime config mutex
ISR path spinlock only

Example:

mutex_lock(&mydev->ctrl_lock);
mydev->debounce_ms = cfg.debounce_ms;
mutex_unlock(&mydev->ctrl_lock);

8. Snapshot Design

GET_STATUS returns a snapshot:

  • Not fully atomic across all fields
  • Acceptable trade-off

Example:

mutex_lock(&ctrl_lock);
copy runtime state
mutex_unlock();

spin_lock();
copy queue state
spin_unlock();

9. Error Handling

Common return values:

Error Meaning
-ENOTTY unknown ioctl
-EFAULT invalid user pointer
-EINVAL invalid argument

10. Debug Logging

Use:

  • dev_dbg() → frequent operations
  • dev_info() → important state change

Example:

dev_info(dev, "Debounce set to %u ms\n", val);

Summary

Key principles:

  • ioctl is command-based
  • ABI must be stable
  • separate control plane from data plane
  • proper locking is critical
  • snapshot is acceptable design trade-off