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 operations — core_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.
What You Will Learn
- The full, current-kernel field list of
struct v4l2_subdev_core_ops - When to use
video_opsversuspad_opsfor streaming and format control - The purpose of
struct v4l2_subdev_sensor_opsfor 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:
| Callback | Purpose |
|---|---|
log_status | Dump the sub-device’s current state to the kernel log, typically via the v4l2_info() macro |
load_fw | Trigger loading of sub-device firmware, for hardware that needs a blob uploaded before use |
ioctl | Handle custom, driver-specific ioctls that don’t belong to any standard V4L2 category |
compat_ioctl32 | 32-bit compatibility shim for the custom ioctl on 64-bit kernels, built only when CONFIG_COMPAT is set |
g_register / s_register | Read/write raw hardware registers for advanced debugging, gated behind CONFIG_VIDEO_ADV_DEBUG, driven by VIDIOC_DBG_G_REGISTER / VIDIOC_DBG_S_REGISTER |
s_power | Switch the sub-device between power-saving and normal operating mode |
interrupt_service_routine | Invoked by the bridge from IRQ context when an interrupt is attributed to this sub-device; must not sleep |
subscribe_event / unsubscribe_event | Register 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);
};
querystdbacks theVIDIOC_QUERYSTDioctl, used by analogue video standard detection.s_streamis 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_intervalback theVIDIOC_SUBDEV_G_FRAME_INTERVAL/VIDIOC_SUBDEV_S_FRAME_INTERVALioctls, 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,
};
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
-ENODEVimmediately ifsubdevitself isNULL. - Returns
-ENOIOCTLCMDif either the category (video) or the specific callback (s_stream) isNULL— 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
| Mistake | Symptom | Fix |
|---|---|---|
Calling ops directly via sd->ops->video->s_stream(...) | NULL pointer dereference / kernel oops when a sub-device doesn’t implement that category | Always use v4l2_subdev_call() instead |
Doing blocking I2C register writes inside interrupt_service_routine | Kernel warns about sleeping in atomic context, or hangs the IRQ handler | Acknowledge the interrupt quickly and defer real work to a threaded IRQ handler or workqueue |
Treating -ENOIOCTLCMD from v4l2_device_call_all() as an error | Bridge driver logs spurious failures for sub-devices that simply don’t support an op | Remember 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_stream | Sensor streams continuously even when the bridge hasn’t asked it to | Keep 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_opscallbacks category-independent — don’t sneak streaming logic intos_poweror vice versa. - For sensors with known startup quirks, always implement
sensor_opsso 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, andv4l2_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_opscallbacks are category-independent and available to any sub-device type;video_ops/pad_opshandle streaming and format, withpad_opspreferred for media-controller-integrated drivers.sensor_opslets 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()andv4l2_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