Arc I · Linux Driver Lab
Chapter 03

IOCTL Interface

read() and write() move data. But how does userspace send commands to a driver?

unlocked_ioctl _IO/_IOR/_IOW magic number control plane

Why this driver?

V4L2 — the camera subsystem I'm working toward — is controlled almost entirely through ioctl(). VIDIOC_S_FMT, VIDIOC_STREAMON, VIDIOC_QBUF — all ioctl calls. I needed to understand how the mechanism works before I could make sense of how V4L2 uses it.

IOCTL commands

CommandMacroDirectionWhat it does
GET_QUEUE_SIZE_IOR('e',1,int32_t)kernel → userHow many messages are waiting
GET_MAX_CAPACITY_IOR('e',2,int32_t)kernel → userMax queue depth (always 16)
CLEAR_QUEUE_IO('e',3)noneFlush all messages
RESET_DEVICE_IO('e',4)noneFull state reset

Macro encoding

The kernel packs direction, magic byte, command number, and data size into one 32-bit integer. This prevents a userspace program from accidentally calling the wrong driver's ioctl with a matching number.

#define ETX_MAGIC        'e'
#define GET_QUEUE_SIZE   _IOR(ETX_MAGIC, 1, int32_t)
#define CLEAR_QUEUE      _IO(ETX_MAGIC,  3)
MacroDirection
_IONo data transfer
_IORDriver → userspace
_IOWUserspace → driver
_IOWRBidirectional

Handler

static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int32_t val = 0;
    switch (cmd) {
    case GET_QUEUE_SIZE:
        mutex_lock(&kernel_logger.etx_mutex);
        val = kernel_logger.count;
        mutex_unlock(&kernel_logger.etx_mutex);
        if (copy_to_user((int32_t __user *)arg, &val, sizeof(val)))
            return -EFAULT;
        break;
    case CLEAR_QUEUE:
        mutex_lock(&kernel_logger.etx_mutex);
        memset(kernel_logger.kernel_buffer, 0,
               sizeof(kernel_logger.kernel_buffer));
        kernel_logger.read_indexer =
            kernel_logger.write_indexer =
            kernel_logger.count = 0;
        mutex_unlock(&kernel_logger.etx_mutex);
        break;
    default: return -EINVAL;
    }
    return 0;
}