read() and write() move data. But how does userspace send commands to a 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.
| Command | Macro | Direction | What it does |
|---|---|---|---|
GET_QUEUE_SIZE | _IOR('e',1,int32_t) | kernel → user | How many messages are waiting |
GET_MAX_CAPACITY | _IOR('e',2,int32_t) | kernel → user | Max queue depth (always 16) |
CLEAR_QUEUE | _IO('e',3) | none | Flush all messages |
RESET_DEVICE | _IO('e',4) | none | Full state reset |
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)| Macro | Direction |
|---|---|
_IO | No data transfer |
_IOR | Driver → userspace |
_IOW | Userspace → driver |
_IOWR | Bidirectional |
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;
}