Understand Linux V4L2 Video Device-Free Linux Device Drivers Course

PREV_LEC  |  NEXT_LEC

Understand Linux V4L2 Video Device
Free Linux Kernel Development Course — V4L2 & Video Capture Device Drivers, Lecture 2
Chapter: V4L2 Video Capture Drivers
Level: Intermediate
Kernel: 6.x (latest stable)

In the previous lecture of this free Linux kernel development course, we registered a struct v4l2_device — the top-level object that represents one physical piece of video hardware and owns every sub-device attached to it. But v4l2_device never talks to user space directly. That job belongs to a second, equally important structure: struct video_device. This lecture is a deep, field-by-field walkthrough of struct video_device, how it creates /dev/videoX nodes, and how a bridge driver registers and releases it on the current Linux kernel. As always in this free embedded Linux course, every driver example below is original and written against the latest stable V4L2 API — not copied from any book.

What You Will Learn

  • Why struct video_device exists and how it differs from struct v4l2_device
  • Every field of video_device explained in plain language
  • How vfl_type and vfl_dir decide what kind of /dev node gets created
  • How the V4L2 core wraps your driver’s file operations before the VFS ever sees them
  • How to register and cleanly unregister a video device node with video_register_device()
  • A complete, original demo driver you can build and load on a real Linux machine

Prerequisites

  • Lecture 1 of this chapter: v4l2_device registration basics
  • Comfort with character device drivers, platform_driver, and kernel modules
  • A Linux machine (or VM) with kernel headers installed, and the media subsystem enabled in your .config

Two Structures, Two Jobs: v4l2_device vs video_device

It helps to think of the V4L2 object model as a small company. struct v4l2_device is the manager — there is exactly one per physical device, and it keeps a list of every sub-device (sensor, tuner, encoder) reporting to it. struct video_device is the receptionist — it is the object that actually faces the outside world. Every time an application calls open(), ioctl(), mmap(), or read() on /dev/video0, it is video_device that answers. A single bridge driver can register several video_device instances (capture, output, VBI) that all point back to the same one v4l2_device.

V4L2 Object Relationship
struct v4l2_device (one per physical card)
    |– struct video_device “video0” -> creates /dev/video0
    |– struct video_device “video1” -> creates /dev/video1
    |– struct v4l2_subdev “sensor” -> no /dev node, internal only

Field-by-Field: struct video_device

Below is what actually matters in struct video_device on a current kernel, grouped by purpose rather than by declaration order — that grouping makes the structure far easier to remember than reading it top to bottom.

Media framework integration

  • entity — wraps this video device as a node inside the media controller graph, only compiled in when CONFIG_MEDIA_CONTROLLER is set.
  • intf_devnode — the media interface device node used by user-space topology tools such as media-ctl.
  • pipe — the streaming pipeline this entity currently belongs to, used to prevent conflicting concurrent uses of the same hardware path.

Talking to user space

  • fops — a pointer to your driver’s struct v4l2_file_operations (open, release, read, write, poll, mmap). This is a V4L2-specific subset of the normal file_operations table; the inode argument is dropped because V4L2 never needs it.
  • device_caps — a bitmask (V4L2_CAP_VIDEO_CAPTURE, V4L2_CAP_STREAMING, and so on) reported back to user space through VIDIOC_QUERYCAP.
  • ioctl_ops — a pointer to struct v4l2_ioctl_ops, the table of callbacks that implement every VIDIOC_* ioctl your device supports.

Kernel plumbing

  • dev and cdev — the embedded struct device and the character device object backing /dev/videoX. You never touch these by hand; the core owns them.
  • v4l2_dev — back-pointer to the parent v4l2_device this node belongs to. Must be set before registration.
  • dev_parent — the physical bus device (platform/USB/PCI). If left NULL, the core copies it from v4l2_dev->dev.
  • lock — an optional struct mutex *. If you set it, the core automatically takes this lock around your unlocked_ioctl callback, saving you from hand-rolling that locking yourself.
  • release — a mandatory callback the core calls once the last reference to this video_device drops. Forgetting to set it is one of the most common bugs in new drivers (see Troubleshooting below).

Device identity and streaming

  • name — a fixed 32-byte device name shown to user space.
  • vfl_type — which family of node to create: VFL_TYPE_GRABBER (videoX), VFL_TYPE_VBI (vbiX), VFL_TYPE_RADIO (radioX), VFL_TYPE_SDR, VFL_TYPE_TOUCH, or VFL_TYPE_SUBDEV for internal sub-devices with no user-facing node.
  • vfl_dir — direction of data flow: VFL_DIR_RX for capture, VFL_DIR_TX for output, or VFL_DIR_M2M for memory-to-memory devices such as scalers and codecs that consume and produce buffers in the same operation.
  • minor, num, index — the assigned minor number, the trailing digit in /dev/videoX, and an optional driver-assigned index used to build predictable udev rules.
  • queue — pointer to the struct vb2_queue that manages capture buffers via videobuf2. Only bridge drivers that actually move data set this; a pure sub-device leaves it NULL.
  • ctrl_handler — the V4L2 control handler for this node, normally inherited from v4l2_dev->ctrl_handler unless the driver needs a private one.
  • prio — tracks which open file handle currently “owns” the device for priority-sensitive ioctls like VIDIOC_S_PRIORITY. NULL falls back to the priority state in the parent v4l2_device.

How the Core Wraps Your File Operations

A subtlety that trips up many newcomers to this free Linux device drivers course: the cdev registered for /dev/videoX does not point directly at your vdev->fops. It points at a generic, internal table owned by the V4L2 core. That internal table performs sanity checks — is the device still registered, is the requested callback actually implemented — and only then forwards the call into your driver’s fops. This is why, for example, calling mmap() on a node whose driver never implemented .mmap fails cleanly with -ENOTTY instead of crashing the kernel.

Open() Call Path
User space: open(“/dev/video0”)
    -> VFS
    -> V4L2 core wrapper (sanity checks, refcounting)
    -> vdev->fops->open() (your driver code runs here)

Registering and Unregistering a video_device

Once every field above is filled in, you hand the structure to the core with:

int video_register_device(struct video_device *vdev,
                           enum vfl_devnode_type type,
                           int nr);

type picks the device family (usually VFL_TYPE_GRABBER for a capture node) and nr requests a specific trailing number; pass -1 to let the core allocate the next free one. Internally this macro calls __video_register_device(), which assigns a minor number, creates the character device, and exposes it under /dev. The function assumes the structure was zero-initialized beforehand, so always allocate it with kzalloc(), never a plain stack struct.

When the driver is removed, the mirror call is:

void video_unregister_device(struct video_device *vdev);

This immediately blocks new opens on the node, but if an application still has it open, the actual memory is only freed later, when your release callback finally runs.

Original Demo: A Minimal Video Capture Skeleton

The driver below is intentionally minimal — it registers a real /dev/videoX node with correct video_device setup but skips actual DMA/streaming, which we cover later in this chapter. It is written from scratch for this free embedded systems course.

// ep_video_skel.c - minimal V4L2 video_device demo driver
#include <linux/module.h>
#include <linux/platform_device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>

struct ep_video_dev {
	struct v4l2_device v4l2_dev;
	struct video_device vdev;
};

static int ep_video_open(struct file *file)
{
	pr_info("ep_video_skel: device opened\n");
	return 0;
}

static int ep_video_release(struct file *file)
{
	pr_info("ep_video_skel: device released\n");
	return 0;
}

static const struct v4l2_file_operations ep_video_fops = {
	.owner   = THIS_MODULE,
	.open    = ep_video_open,
	.release = ep_video_release,
};

static int ep_vidioc_querycap(struct file *file, void *priv,
			       struct v4l2_capability *cap)
{
	strscpy(cap->driver, "ep_video_skel", sizeof(cap->driver));
	strscpy(cap->card, "EP Video Skeleton", sizeof(cap->card));
	return 0;
}

static const struct v4l2_ioctl_ops ep_ioctl_ops = {
	.vidioc_querycap = ep_vidioc_querycap,
};

static void ep_video_device_release(struct video_device *vdev)
{
	struct ep_video_dev *epv =
		container_of(vdev, struct ep_video_dev, vdev);

	v4l2_device_unregister(&epv->v4l2_dev);
	kfree(epv);
}

static int ep_video_probe(struct platform_device *pdev)
{
	struct ep_video_dev *epv;
	int ret;

	epv = kzalloc(sizeof(*epv), GFP_KERNEL);
	if (!epv)
		return -ENOMEM;

	ret = v4l2_device_register(&pdev->dev, &epv->v4l2_dev);
	if (ret) {
		kfree(epv);
		return ret;
	}

	epv->vdev.v4l2_dev   = &epv->v4l2_dev;
	epv->vdev.fops       = &ep_video_fops;
	epv->vdev.ioctl_ops  = &ep_ioctl_ops;
	epv->vdev.release    = ep_video_device_release;
	epv->vdev.vfl_type   = VFL_TYPE_GRABBER;
	epv->vdev.vfl_dir    = VFL_DIR_RX;
	strscpy(epv->vdev.name, "ep_video_skel", sizeof(epv->vdev.name));

	video_set_drvdata(&epv->vdev, epv);

	ret = video_register_device(&epv->vdev, VFL_TYPE_GRABBER, -1);
	if (ret) {
		v4l2_device_unregister(&epv->v4l2_dev);
		kfree(epv);
		return ret;
	}

	platform_set_drvdata(pdev, epv);
	dev_info(&pdev->dev, "ep_video_skel registered as %s\n",
		 video_device_node_name(&epv->vdev));
	return 0;
}

static void ep_video_remove(struct platform_device *pdev)
{
	struct ep_video_dev *epv = platform_get_drvdata(pdev);

	video_unregister_device(&epv->vdev);
}

static struct platform_driver ep_video_driver = {
	.probe  = ep_video_probe,
	.remove = ep_video_remove,
	.driver = {
		.name = "ep_video_skel",
	},
};

module_platform_driver(ep_video_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala minimal V4L2 video_device demo");

Building and Loading

# Makefile
obj-m += ep_video_skel.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
$ make
$ sudo insmod ep_video_skel.ko

Because this demo registers as a platform_driver with no matching device, bind it manually for testing, then check the result:

$ echo ep_video_skel | sudo tee /sys/bus/platform/devices/.../driver_override
$ ls /dev/video*
/dev/video0

$ v4l2-ctl --device=/dev/video0 --info
Driver Info:
	Driver name      : ep_video_skel
	Card type        : EP Video Skeleton
	Bus info         : platform:ep_video_skel
$ dmesg | tail -3
[  812.221093] ep_video_skel: device opened
[  812.221390] ep_video_skel ep_video_skel: ep_video_skel registered as video0

Unloading cleanly triggers the release chain we wired up:

$ sudo rmmod ep_video_skel
$ dmesg | tail -1
[  902.114820] ep_video_skel: device released

vfl_type vs vfl_dir: A Quick Comparison

FieldPurposeCommon values
vfl_typeWhich device family / node prefixVFL_TYPE_GRABBER, VFL_TYPE_VBI, VFL_TYPE_RADIO, VFL_TYPE_SDR, VFL_TYPE_TOUCH, VFL_TYPE_SUBDEV
vfl_dirDirection of the data pathVFL_DIR_RX (capture), VFL_DIR_TX (output), VFL_DIR_M2M (memory-to-memory)

Common Mistakes and Troubleshooting

  • Missing release callback — registration fails outright, or worse, the kernel warns loudly on module unload because it cannot free a video_device with no way to release its container structure.
  • Stack-allocated video_device — always kzalloc() it; the registration code explicitly assumes the memory was zeroed.
  • Setting v4l2_dev after calling video_register_device() — the pointer must be valid before registration, since the core reads it immediately to build the device name and control handler defaults.
  • Confusing vfl_type with vfl_dir — a capture-only USB camera is VFL_TYPE_GRABBER + VFL_DIR_RX, not the other way around.
  • Forgetting video_unregister_device() in remove() — leaves a dangling character device that can crash user space tools still holding it open.

Best Practices

  • Always zero-allocate video_device and embed it inside your own driver-private structure, as shown in the demo.
  • Set device_caps accurately — user-space applications like GStreamer and ffmpeg query this field to decide which ioctls to even attempt.
  • Keep the release callback symmetrical with your probe: undo every allocation and unregister the parent v4l2_device there, not in remove().
  • Prefer video_register_device() over the internal __video_register_device(); the latter is core-only.

Summary

In this lecture of our free Linux kernel development course, you learned that struct video_device is the object that actually exposes /dev/videoX to user space, sitting one level below v4l2_device in the object hierarchy. You now know what each field controls, how the V4L2 core wraps your file operations for safety, and how video_register_device() / video_unregister_device() manage the node’s lifetime. The next lecture builds on this skeleton by wiring up videobuf2 for real streaming.

FAQ

What is the difference between v4l2_device and video_device?

v4l2_device represents one physical piece of hardware and owns a list of sub-devices. video_device is the object that creates a specific /dev/videoX node and handles user-space calls for it. One v4l2_device can own several video_device nodes.

Do I need videobuf2 to register a video_device?

No. Registration only requires fops, ioctl_ops, vfl_type, vfl_dir, and a release callback. The queue field for videobuf2 is only required once you implement actual buffer streaming.

What happens if I never set the release callback?

The kernel will refuse to free the video_device cleanly and will print a warning when the last reference is dropped, since it has no way to release the container structure you embedded it in.

Can one driver register multiple video_device nodes?

Yes. This is common for devices with separate capture and output paths, each getting its own video_device that shares the same parent v4l2_device.

What does VFL_TYPE_SUBDEV mean?

It marks a video_device that represents an internal V4L2 sub-device rather than a user-facing capture or output node, typically used for exposing sub-device controls without a full /dev/videoX interface.

Why does my /dev/video node number change between boots?

If you pass -1 as nr to video_register_device(), the core assigns the next free minor number, which can shift depending on registration order. Use udev rules keyed on the name or index field for a stable symlink.

Is this content specific to any vendor SoC?

No. Everything in this free embedded Linux course is written against the mainline V4L2 core API and applies to any bridge driver, regardless of vendor.

free linux kernel development course free linux device drivers course V4L2 video_device video_register_device free embedded linux course

Continue the Free Linux Kernel Development Course

Next: wiring up videobuf2 for real V4L2 streaming.

Next Lecture Course Index

PREV_LEC  |  NEXT_LEC

Leave a Reply

Your email address will not be published. Required fields are marked *