V4L2 Subdevice Operations In Depth-Free Linux Device Drivers Course

V4L2 Subdevice Operations In Depth

Free Linux kernel development course — implementing core, video, and sensor operations, and calling them safely with v4l2_subdev_call.

In the last lecture of this free linux kernel development course we initialized a V4L2 sub-device and looked at the top-level v4l2_subdev_ops table. Now it’s time to actually implement callbacks inside that table, and to understand the safe, standard way sub-device operations are invoked from bridge drivers. This lecture is a deep, hands-on dive into v4l2 subdevice operationscore_ops, video_ops, and sensor_ops — and the v4l2_subdev_call() macro that ties everything together. If you’re building toward a real camera driver in this free embedded linux course, this is where the actual behaviour of your sensor gets written.

Topics Covered
v4l2_subdev_core_ops v4l2_subdev_video_ops v4l2_subdev_sensor_ops v4l2_subdev_call v4l2_device_call_all

What You Will Learn

  • The full, current-kernel field list of struct v4l2_subdev_core_ops
  • When to use video_ops versus pad_ops for streaming and format control
  • The purpose of struct v4l2_subdev_sensor_ops for skipping garbage frames
  • Why calling ops directly is unsafe, and how v4l2_subdev_call() fixes that
  • How a bridge driver can call an operation across every attached sub-device at once
  • An original demo driver implementing real core/video/sensor callbacks

Prerequisites

You should have completed the previous lecture on v4l2 subdevice initialization, and be comfortable with the ep_camsensor I2C sub-device skeleton we built there — this lecture extends it directly.

struct v4l2_subdev_core_ops: Category-Independent Callbacks

The core category holds callbacks that make sense regardless of what kind of sub-device you’re writing — a sensor, a tuner, an HDMI receiver, anything. On the current kernel, the meaningful fields are:

CallbackPurpose
log_statusDump the sub-device’s current state to the kernel log, typically via the v4l2_info() macro
load_fwTrigger loading of sub-device firmware, for hardware that needs a blob uploaded before use
ioctlHandle custom, driver-specific ioctls that don’t belong to any standard V4L2 category
compat_ioctl3232-bit compatibility shim for the custom ioctl on 64-bit kernels, built only when CONFIG_COMPAT is set
g_register / s_registerRead/write raw hardware registers for advanced debugging, gated behind CONFIG_VIDEO_ADV_DEBUG, driven by VIDIOC_DBG_G_REGISTER / VIDIOC_DBG_S_REGISTER
s_powerSwitch the sub-device between power-saving and normal operating mode
interrupt_service_routineInvoked by the bridge from IRQ context when an interrupt is attributed to this sub-device; must not sleep
subscribe_event / unsubscribe_eventRegister or remove interest in V4L2 control-change or custom events

Note the note on interrupt_service_routine: because it runs in hard IRQ context, any sub-device sitting behind a slow bus like I2C or SPI should not do real work here — it should acknowledge quickly and schedule a threaded handler or workqueue item instead, since I2C/SPI transfers can sleep. Two extra details matter in practice: the bridge driver is expected to invoke this callback via v4l2_subdev_call() (not a direct pointer call) from inside its own IRQ handler whenever the raised interrupt status could belong to this sub-device, and the callback must fill in the handled output parameter — a bridge-supplied bool * that the sub-device sets to true or false to report back whether it actually recognised and processed that interrupt.

video_ops vs pad_ops: Which One Do You Need?

This is a common point of confusion for newcomers. struct v4l2_subdev_video_ops is used when the device is accessed in classic “video mode” — no media-controller pad routing, just a simple stream on/off and format model, historically used by TV tuners, simple camera sensors, and framebuffer-like devices. struct v4l2_subdev_pad_ops exists because the media controller framework abstracts a sub-device as an entity connected to other entities through numbered pads — and once pad-based routing is involved, format negotiation and streaming naturally belong on pad_ops instead. As a rule of thumb: if your driver integrates with the media controller framework (which almost all new camera drivers do), prefer pad_ops; only reach for plain video_ops when no pad-based topology is involved.

struct v4l2_subdev_video_ops: Key Fields

struct v4l2_subdev_video_ops {
    int (*querystd)(struct v4l2_subdev *sd, v4l2_std_id *std);
    int (*s_stream)(struct v4l2_subdev *sd, int enable);
    int (*g_frame_interval)(struct v4l2_subdev *sd,
                             struct v4l2_subdev_frame_interval *interval);
    int (*s_frame_interval)(struct v4l2_subdev *sd,
                             struct v4l2_subdev_frame_interval *interval);
};
  • querystd backs the VIDIOC_QUERYSTD ioctl, used by analogue video standard detection.
  • s_stream is called when streaming starts or stops; a typical sensor driver uses this to write the register sequence that starts or halts the sensor’s pixel output.
  • g_frame_interval / s_frame_interval back the VIDIOC_SUBDEV_G_FRAME_INTERVAL / VIDIOC_SUBDEV_S_FRAME_INTERVAL ioctls, reading or setting the sub-device’s current frame interval (the inverse of frame rate).

struct v4l2_subdev_sensor_ops: Handling Garbage Frames

Real camera sensors are not perfect the instant they start streaming. Many need a short settle time before their output is trustworthy, and some always corrupt a fixed number of lines at the top of every frame (often used to embed metadata). v4l2_subdev_sensor_ops exists specifically to let a sensor driver tell the core about these quirks:

struct v4l2_subdev_sensor_ops {
    int (*g_skip_top_lines)(struct v4l2_subdev *sd, u32 *lines);
    int (*g_skip_frames)(struct v4l2_subdev *sd, u32 *frames);
};

Both callbacks are output-parameter style: fill in the value and return 0. Here’s a realistic, original example (values chosen for illustration, not copied from any real sensor’s datasheet):

#define EP_CAMSENSOR_SKIP_FRAMES 2
#define EP_CAMSENSOR_SKIP_TOP_LINES 4

static int ep_camsensor_g_skip_frames(struct v4l2_subdev *sd, u32 *frames)
{
    *frames = EP_CAMSENSOR_SKIP_FRAMES;
    return 0;
}

static int ep_camsensor_g_skip_top_lines(struct v4l2_subdev *sd, u32 *lines)
{
    *lines = EP_CAMSENSOR_SKIP_TOP_LINES;
    return 0;
}

static const struct v4l2_subdev_sensor_ops ep_camsensor_sensor_ops = {
    .g_skip_frames    = ep_camsensor_g_skip_frames,
    .g_skip_top_lines = ep_camsensor_g_skip_top_lines,
};
Ops category responsibilities
core_ops -> logging, power, custom ioctl, register debug (any sub-device) video_ops -> s_stream, frame interval (non media-controller path) pad_ops -> format/routing via pads (media-controller path) sensor_ops -> g_skip_top_lines, g_skip_frames (camera-sensor specific)

Calling Sub-device Operations Safely

Once callbacks exist, they have to be called from somewhere — typically the bridge driver. The naive way is to reach straight through the ops pointers:

err = subdev->ops->video->s_stream(subdev, 1);

This works only if you are absolutely certain subdev itself, ops->video, and s_stream are all non-NULL. In a real system with sub-devices of different capabilities, that is a fragile assumption. The kernel provides a much safer helper: the v4l2_subdev_call() macro, defined in include/media/v4l2-subdev.h:

err = v4l2_subdev_call(subdev, video, s_stream, 1);

This macro expands to logic that:

  • Returns -ENODEV immediately if subdev itself is NULL.
  • Returns -ENOIOCTLCMD if either the category (video) or the specific callback (s_stream) is NULL — meaning “this sub-device simply doesn’t support this operation,” which callers can treat as a normal, expected outcome rather than an error.
  • Otherwise calls the real callback and returns its actual result.

Always prefer v4l2_subdev_call() over a raw pointer dereference. It turns “does this sub-device support X” into a single, uniform check instead of a chain of manual NULL guards repeated at every call site.

Calling an Operation Across All Sub-devices

Sometimes a bridge driver needs to call the same operation on every sub-device attached to a v4l2_device — for example, powering all sub-devices down together, or querying an identifier from whichever one supports it. Two device-wide helpers exist for this:

/* Call an op on every sub-device; ignore results from those that don't support it */
v4l2_device_call_all(dev, 0, core, g_chip_ident, &chip);

/* Call an op on every sub-device; stop at the first real error */
err = v4l2_device_call_until_err(dev, 0, core, g_chip_ident, &chip);

v4l2_device_call_all() loops through every registered sub-device and calls the given operation, silently skipping any sub-device that doesn’t implement it (an -ENOIOCTLCMD result is simply ignored). v4l2_device_call_until_err() does the same loop but stops and returns immediately on any error other than -ENOIOCTLCMD; if every sub-device either succeeds or lacks the callback, it returns 0.

Extending ep_camsensor With Real Callbacks

Let’s continue our original ep_camsensor driver from the previous lecture and give it a genuine, working operations table:

#include <media/v4l2-subdev.h>

static int ep_camsensor_log_status(struct v4l2_subdev *sd)
{
    struct ep_camsensor *sensor = to_ep_camsensor(sd);

    v4l2_info(sd, "ep_camsensor: streaming=%d\n", sensor->streaming);
    return 0;
}

static int ep_camsensor_s_power(struct v4l2_subdev *sd, int on)
{
    struct ep_camsensor *sensor = to_ep_camsensor(sd);

    dev_info(&sensor->client->dev, "power %s\n", on ? "on" : "off");
    /* real driver: write power-control register(s) here */
    return 0;
}

static int ep_camsensor_s_stream(struct v4l2_subdev *sd, int enable)
{
    struct ep_camsensor *sensor = to_ep_camsensor(sd);

    sensor->streaming = enable;
    dev_info(&sensor->client->dev, "streaming %s\n",
             enable ? "started" : "stopped");
    /* real driver: write start/stop streaming register sequence here */
    return 0;
}

static const struct v4l2_subdev_core_ops ep_camsensor_core_ops = {
    .log_status = ep_camsensor_log_status,
    .s_power    = ep_camsensor_s_power,
};

static const struct v4l2_subdev_video_ops ep_camsensor_video_ops = {
    .s_stream = ep_camsensor_s_stream,
};

static const struct v4l2_subdev_ops ep_camsensor_ops = {
    .core   = &ep_camsensor_core_ops,
    .video  = &ep_camsensor_video_ops,
    .sensor = &ep_camsensor_sensor_ops,
};

Remember to add a bool streaming; field to struct ep_camsensor for this to compile as shown.

Build and Test

Load the updated module the same way as before, then exercise the operations from a small test module or from a bridge driver stub calling v4l2_subdev_call():

err = v4l2_subdev_call(&sensor->sd, core, s_power, 1);
err = v4l2_subdev_call(&sensor->sd, video, s_stream, 1);
err = v4l2_subdev_call(&sensor->sd, core, log_status);

Expected dmesg trace:

$ dmesg | tail -5
[  205.113004] ep_camsensor 2-0036: power on
[  205.113071] ep_camsensor 2-0036: streaming started
[  205.113098] ep_camsensor: streaming=1

Now try calling an unimplemented category, such as pad, the same way:

err = v4l2_subdev_call(&sensor->sd, pad, get_fmt, NULL, &fmt);
/* err == -ENOIOCTLCMD, no crash, no log spam */

This demonstrates exactly why v4l2_subdev_call() is the correct calling convention: the missing pad category is handled gracefully instead of dereferencing a NULL pointer.

Common Mistakes and Troubleshooting

MistakeSymptomFix
Calling ops directly via sd->ops->video->s_stream(...)NULL pointer dereference / kernel oops when a sub-device doesn’t implement that categoryAlways use v4l2_subdev_call() instead
Doing blocking I2C register writes inside interrupt_service_routineKernel warns about sleeping in atomic context, or hangs the IRQ handlerAcknowledge the interrupt quickly and defer real work to a threaded IRQ handler or workqueue
Treating -ENOIOCTLCMD from v4l2_device_call_all() as an errorBridge driver logs spurious failures for sub-devices that simply don’t support an opRemember call_all silently skips -ENOIOCTLCMD; only call_until_err treats other errors as fatal
Putting streaming-start register writes in s_power instead of s_streamSensor streams continuously even when the bridge hasn’t asked it toKeep power management and streaming as clearly separate responsibilities

Best Practices

  • Prefer v4l2_subdev_call() everywhere; never dereference the ops table directly in bridge or core code.
  • Keep core_ops callbacks category-independent — don’t sneak streaming logic into s_power or vice versa.
  • For sensors with known startup quirks, always implement sensor_ops so higher layers can automatically discard early garbage frames instead of hardcoding delays in application code.
  • Use v4l2_device_call_all() for best-effort broadcast operations, and v4l2_device_call_until_err() when a failure on any sub-device must abort the sequence.

Real-World Use Cases

Nearly every mainline sensor driver implements at minimum core.s_power and video.s_stream or the pad-based equivalent. HDMI receiver sub-devices commonly implement video.querystd equivalents and event subscription for signal-lost notifications. Sensors known for startup noise (rolling-shutter CMOS sensors especially) frequently implement sensor.g_skip_frames so applications don’t have to special-case each sensor model in userspace.

Summary / Key Takeaways

  • core_ops callbacks are category-independent and available to any sub-device type; video_ops/pad_ops handle streaming and format, with pad_ops preferred for media-controller-integrated drivers.
  • sensor_ops lets a driver declare garbage-frame and corrupted-line quirks so the core can compensate.
  • v4l2_subdev_call() is the only safe way to invoke a sub-device operation — it handles missing sub-devices, missing categories, and missing callbacks uniformly.
  • v4l2_device_call_all() and v4l2_device_call_until_err() broadcast an operation to every attached sub-device, differing only in how they treat errors.

Conclusion

With initialization and a real operations table in place, ep_camsensor is now a functioning V4L2 sub-device that can be powered, streamed, and queried safely from any bridge driver. In the next lecture of this free linux device drivers course, we’ll register this sub-device with a bridge’s v4l2_device, look at synchronous versus asynchronous registration, and take our first steps into the V4L2 controls framework.

Frequently Asked Questions

What is the difference between v4l2_subdev_video_ops and v4l2_subdev_pad_ops?

video_ops is used for classic, non-media-controller “video mode” access with simple stream on/off semantics. pad_ops is used once the sub-device is integrated with the media controller framework and exposes numbered pads for routing and format negotiation.

Why should I use v4l2_subdev_call instead of calling the ops pointer directly?

v4l2_subdev_call safely handles a NULL sub-device, a NULL category, or a NULL specific callback by returning -ENODEV or -ENOIOCTLCMD instead of crashing, and otherwise returns the real callback’s result.

What does v4l2_subdev_sensor_ops actually solve?

It lets a sensor driver tell the core how many initial frames to discard and how many lines at the top of every frame are corrupted, so higher layers can automatically compensate for known sensor startup behaviour.

What is the difference between v4l2_device_call_all and v4l2_device_call_until_err?

Both call an operation across every attached sub-device and ignore -ENOIOCTLCMD from sub-devices that don’t implement it. v4l2_device_call_until_err additionally stops and returns immediately if any sub-device returns a real error other than -ENOIOCTLCMD.

Can I do register I/O inside interrupt_service_routine?

Only if the bus access is non-blocking. I2C/SPI transfers can sleep, so sub-devices behind those buses should acknowledge quickly and defer real register work to a threaded handler or workqueue.

Is this part of a free course?

Yes — this lecture is part of Embedded Pathashala’s free Linux kernel development course, which also covers V4L2, videobuf2, and Bluetooth protocol stack topics at no cost.

Continue Your Free Linux Kernel Development Course

Next: registering sub-devices with a bridge driver and starting the V4L2 controls framework.

Next Lecture Browse Full Course Index

Leave a Reply

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