Arc I · Linux Driver Lab
Chapter 06

Workqueue + Poll — The Full Pipeline

Everything comes together. This is the camera frame pipeline in miniature.

full pipeline workqueue poll O_NONBLOCK spinlock

Why this driver?

Drivers 01–05 each taught one concept. This one combines all of them into a single working pipeline: a kernel timer generates events, a workqueue processes them safely, a ring buffer stores them, and userspace can read via blocking read, non-blocking read, or poll(). It's a minimal but real model of how a camera driver feeds frames to an application.

The full data flow

timer_callback()          /* softirq — fires every 1000ms */
  └─ schedule_work()
       └─ workqueue_fn()  /* process context */
            ├─ spin_lock_irqsave
            ├─ snprintf → ring buffer
            ├─ advance write_indexer, update count
            ├─ spin_unlock_irqrestore
            └─ wake_up_interruptible
                 ├─ unblocks blocking read()
                 └─ signals poll() waiters

Poll with spinlock

Unlike driver 02 which used mutex, here poll() must use a spinlock — the workqueue holds a spinlock when updating count, so anything that reads count must use the same lock.

static __poll_t etx_poll(struct file *filp, poll_table *wait)
{
    unsigned long flags;
    __poll_t mask = 0;
    poll_wait(filp, &etx_wait_queue, wait);
    spin_lock_irqsave(&kernel_logger.lock, flags);
    if (kernel_logger.count > 0)
        mask = POLLIN | POLLRDNORM;
    spin_unlock_irqrestore(&kernel_logger.lock, flags);
    return mask;
}
The rule: every access to shared state must use the same lock, in every context. If workqueue uses spinlock, poll() uses spinlock too. Mixing lock types on the same data is a bug.

What three access patterns look like

PatternWhat userspace doesWhat the driver does
Blocking readCalls read(), goes to sleepWakes it when data arrives
Non-blocking readCalls read() with O_NONBLOCKReturns EAGAIN if empty
poll()Waits on fd with timeoutReturns POLLIN when buffer has data