Welcome back to EmbeddedPathashala’s free Linux kernel development course.
In the previous lecture of this free linux device drivers course we introduced
the videobuf2 (vb2) framework and looked at the individual fields of struct vb2_buffer.
In this lecture we finish the buffer lifecycle by walking through every state a vb2 buffer can be
in, the exact kernel API that moves a buffer between those states, and how the vb2_queue
structure ties a whole set of buffers together into one streaming pipeline. By the end you will be
able to reason about buffer ownership in any V4L2 capture driver — whether it belongs to
userspace, to videobuf2, or to your hardware.
What You Will Learn
By the end of this free linux kernel development course lecture you will:
Prerequisites
This lecture continues directly from the earlier lectures of this free linux device drivers course. You should already be comfortable with:
The vb2 Buffer State Machine, Explained
Every buffer that videobuf2 manages is, at any given instant, owned by exactly one of three
parties: userspace, the vb2 core itself, or your driver’s hardware queue. The framework encodes
this ownership as a single enum value stored in the buffer’s state field, and the
entire point of the state machine is to make that ownership transfer explicit and race-free.
A capture driver never touches buffer memory it does not currently own, and vb2 refuses ioctls
that would violate the ownership rules — for example, you cannot QBUF a buffer
that is already queued.
Think of it as a relay race with three runners: userspace, videobuf2, and your driver’s DMA engine.
The baton (the buffer) is only ever in one runner’s hand. Userspace hands the baton off by calling
VIDIOC_QBUF. videobuf2 briefly holds it while it may call your buf_prepare
callback, then hands it to your driver by invoking buf_queue. Your driver programs the
DMA engine and, once the hardware signals frame-complete (usually in an interrupt handler), hands the
baton back to videobuf2 by calling vb2_buffer_done(). Userspace finally reclaims it with
VIDIOC_DQBUF.
The Complete State Enum on Current Kernels
The old textbooks that circulate around this topic usually list only four states. Current mainline
kernel has seven, because request-API support (used for atomic multi-buffer configuration, common on
modern ISP-based camera drivers) added an extra state. Here is the up-to-date enum, as defined in
include/media/videobuf2-core.h:
enum vb2_buffer_state {
VB2_BUF_STATE_DEQUEUED, /* buffer is under userspace control */
VB2_BUF_STATE_IN_REQUEST, /* buffer is queued inside a media request */
VB2_BUF_STATE_PREPARING, /* buffer is being prepared by videobuf2 */
VB2_BUF_STATE_QUEUED, /* buffer is in videobuf2, not yet in driver */
VB2_BUF_STATE_ACTIVE, /* buffer is in the driver / hardware queue */
VB2_BUF_STATE_DONE, /* driver finished, waiting for userspace */
VB2_BUF_STATE_ERROR, /* same as DONE, but the capture failed */
};
vb2_buffer_done() — Returning a Buffer From Hardware
This is the single most important function call inside any V4L2 capture driver’s interrupt or workqueue handler. It is how your driver tells videobuf2 “the DMA engine is finished with this buffer, please give it back to userspace.” The current signature is:
void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state);
The state argument must be one of three values: VB2_BUF_STATE_DONE on a
successful capture, VB2_BUF_STATE_ERROR if the hardware reported a corrupted frame or
a DMA fault, or VB2_BUF_STATE_QUEUED if you want to hand the buffer straight back into
the driver’s own active queue without userspace ever seeing it (used during error recovery). Any
other value passed here is treated as a driver bug and videobuf2 will downgrade it to
VB2_BUF_STATE_ERROR and print a warning.
struct vb2_queue: The Container That Owns All the Buffers
A single vb2_buffer only makes sense in the context of the queue it belongs to. The
vb2_queue structure is what your driver actually allocates, embeds inside its private
device structure, and initializes once at probe time. It is the object videobuf2 uses to track how
many buffers exist, which memory model userspace picked, and which callback table (your
vb2_ops) should run at each stage of the handoff we just described. A trimmed, field-by-field
view of the fields you will set in a typical capture driver on current kernels:
struct vb2_queue {
unsigned int type; /* V4L2_BUF_TYPE_VIDEO_CAPTURE, etc. */
unsigned int io_modes; /* VB2_MMAP | VB2_USERPTR | VB2_DMABUF */
void *drv_priv; /* pointer back to your private device data */
unsigned int buf_struct_size; /* sizeof(your custom buffer struct) */
u32 timestamp_flags;
const struct vb2_ops *ops; /* your queue_setup/buf_queue/... table */
const struct vb2_mem_ops *mem_ops; /* usually &vb2_dma_contig_memops or
&vb2_vmalloc_memops */
struct mutex *lock; /* serializes ioctls against this queue */
enum v4l2_buf_type ...;
/* ... additional fields for min_buffers_needed, gfp_flags, dma_dir, etc. */
};
Two fields deserve special attention for anyone new to this framework. buf_struct_size
tells videobuf2 how large each buffer allocation should be, because most drivers embed
struct vb2_buffer as the first member of their own larger buffer struct (to attach a
DMA scatter-gather list or a physical address, for example). ops points to your
vb2_ops table — the callbacks queue_setup, buf_prepare,
buf_queue, start_streaming, and stop_streaming are what
actually drive the state transitions shown in the diagram above.
| vb2_ops Callback | Called When | Typical Driver Action |
|---|---|---|
| queue_setup | VIDIOC_REQBUFS | Report how many buffers/planes and their sizes |
| buf_prepare | Before a buffer becomes QUEUED | Validate buffer, map DMA addresses |
| buf_queue | Buffer moves QUEUED -> ACTIVE | Push buffer onto driver’s hardware list |
| start_streaming | VIDIOC_STREAMON | Arm DMA engine, enable capture interrupt |
| stop_streaming | VIDIOC_STREAMOFF | Disable hardware, return all buffers via vb2_buffer_done() |
Hands-On: Extending ep_vcap With a Working Buffer Queue
We continue building on ep_vcap, the original demo capture driver from this free
embedded linux course series. Here we add a minimal vb2_ops table and a simulated
“hardware done” path using a kernel timer instead of real DMA hardware, so you can run and observe
the full state machine on any Linux machine without a capture card.
#include <linux/module.h>
#include <linux/timer.h>
#include <media/videobuf2-core.h>
#include <media/videobuf2-vmalloc.h>
struct ep_vcap_buffer {
struct vb2_buffer vb; /* must be first member */
struct list_head list;
};
struct ep_vcap_dev {
struct vb2_queue queue;
struct list_head active_list;
spinlock_t qlock;
struct timer_list fake_dma_timer;
};
static int ep_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
unsigned int *nplanes, unsigned int sizes[],
struct device *alloc_devs[])
{
if (*nplanes)
return sizes[0] < PAGE_SIZE ? -EINVAL : 0;
*nplanes = 1;
sizes[0] = PAGE_SIZE; /* one fake "frame" per buffer for this demo */
return 0;
}
static void ep_buf_queue(struct vb2_buffer *vb)
{
struct ep_vcap_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
struct ep_vcap_buffer *ebuf =
container_of(vb, struct ep_vcap_buffer, vb);
unsigned long flags;
spin_lock_irqsave(&dev->qlock, flags);
list_add_tail(&ebuf->list, &dev->active_list);
spin_unlock_irqrestore(&dev->qlock, flags);
/* buffer is now VB2_BUF_STATE_ACTIVE; a real driver would kick DMA here */
mod_timer(&dev->fake_dma_timer, jiffies + msecs_to_jiffies(33));
}
/* Simulated "frame complete" interrupt, using a timer instead of real IRQ */
static void ep_fake_dma_complete(struct timer_list *t)
{
struct ep_vcap_dev *dev = from_timer(dev, t, fake_dma_timer);
struct ep_vcap_buffer *ebuf;
unsigned long flags;
spin_lock_irqsave(&dev->qlock, flags);
ebuf = list_first_entry_or_null(&dev->active_list,
struct ep_vcap_buffer, list);
if (ebuf)
list_del(&ebuf->list);
spin_unlock_irqrestore(&dev->qlock, flags);
if (ebuf) {
ebuf->vb.timestamp = ktime_get_ns();
vb2_buffer_done(&ebuf->vb, VB2_BUF_STATE_DONE);
pr_info("ep_vcap: buffer index %u marked DONE\n", ebuf->vb.index);
}
}
static const struct vb2_ops ep_vcap_qops = {
.queue_setup = ep_queue_setup,
.buf_queue = ep_buf_queue,
.wait_prepare = vb2_ops_wait_prepare,
.wait_finish = vb2_ops_wait_finish,
};
Notice how ep_buf_queue() is exactly the moment a buffer transitions from
QUEUED to ACTIVE — videobuf2 calls it for you, you never set
vb->state by hand. Likewise, calling vb2_buffer_done() is the only
correct way to move a buffer to DONE; setting the state field directly is a common
beginner mistake that we cover below.
Building and Loading the Module
Build against your running kernel’s headers as usual for any out-of-tree module in this free linux kernel development course:
$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
$ sudo insmod ep_vcap.ko
$ sudo v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=3
Expected dmesg Output
Watch buffers cycle through ACTIVE to DONE in real time:
$ dmesg -w
[ 912.113045] ep_vcap: probe successful, video0 registered
[ 914.220981] ep_vcap: buffer index 0 marked DONE
[ 914.253912] ep_vcap: buffer index 1 marked DONE
[ 914.287004] ep_vcap: buffer index 2 marked DONE
[ 914.290117] ep_vcap: streaming stopped, 0 buffers left ACTIVE
Real-World Use Cases
Every mainline V4L2 capture driver you will read in the kernel tree — UVC webcam drivers,
SoC camera bridge drivers, HDMI capture bridges — follows exactly this same state machine.
Learning it once here means you can open any of those drivers and immediately locate the
buf_queue callback to see what triggers hardware DMA, and the interrupt handler to see
where vb2_buffer_done() is called. This is also the exact mechanism that powers
zero-copy pipelines between a capture driver and a downstream encoder using DMABUF, which is why
understanding buffer ownership matters well beyond simple webcam drivers.
Common Mistakes and Troubleshooting
Setting vb->state directly instead of calling vb2_buffer_done()
This bypasses cache synchronization and internal bookkeeping videobuf2 performs on your behalf, leading to stale frame data reaching userspace.
Forgetting to drain the ACTIVE list in stop_streaming
Every buffer your driver is holding must be returned with vb2_buffer_done(vb, VB2_BUF_STATE_ERROR) before stop_streaming returns, or VIDIOC_STREAMOFF will hang.
Racing buf_queue against the interrupt handler
Always protect the active buffer list with a spinlock, since buf_queue runs in process context while your completion handler often runs in interrupt or softirq context.
Best Practices, Performance and Security Considerations
Summary and Key Takeaways
In this lecture of our free linux device drivers course you learned that every vb2 buffer moves
through a well-defined ownership chain — DEQUEUED, PREPARING, QUEUED, ACTIVE, and finally DONE
or ERROR — and that vb2_buffer_done() is the only correct way for a driver to hand
a buffer back to videobuf2. You also saw how struct vb2_queue ties buffers, memory
operations, and your vb2_ops callback table together, and built a working demo driver
that exercises the full cycle without needing real capture hardware. This state machine is the
backbone of every V4L2 capture driver in the mainline kernel, making it one of the most valuable
concepts in this free linux kernel development course.
Frequently Asked Questions
What is the difference between VB2_BUF_STATE_QUEUED and VB2_BUF_STATE_ACTIVE?
QUEUED means videobuf2 core has the buffer but your driver has not yet been handed it via buf_queue(). ACTIVE means your driver’s buf_queue() callback has run and the buffer is currently owned by your hardware/DMA engine.
Can I call vb2_buffer_done() from interrupt context?
Yes, this is the normal case for real capture hardware — most drivers call it directly from the frame-complete interrupt handler or from a tasklet/workqueue scheduled by that handler.
What happens if I never call vb2_buffer_done() for an ACTIVE buffer?
The buffer stays stuck in ACTIVE forever. VIDIOC_STREAMOFF and VIDIOC_DQBUF will hang waiting for it, since videobuf2 cannot reclaim a buffer your driver still owns.
Why was VB2_BUF_STATE_IN_REQUEST added?
It supports the media request API, which lets userspace bundle a buffer together with a full set of controls that must be applied atomically — common on modern ISP pipelines with per-frame exposure/gain changes.
Is struct vb2_queue the same as struct video_device?
No. video_device represents the /dev/videoN character device node itself, while vb2_queue is the buffer-management object your driver embeds and initializes to handle streaming I/O for that device.
Do I need to write my own buf_prepare callback?
Only if you need extra validation or DMA mapping beyond what videobuf2 already does by default — many simple drivers omit it entirely and rely on queue_setup and buf_queue alone.
Where can I practice this without real capture hardware?
The vivid virtual video driver in the mainline kernel (drivers/media/test-drivers/vivid) is the best sandbox — it implements this exact state machine against fully simulated video data.
Continue the Free Linux Kernel Development Course
Next up: configuring vb2_queue io_modes, memory ops, and streaming control ioctls in detail.
Next Lecture Browse Full Course Index