V4L2 Async Connection Data Structures-Free Linux Device Drivers Course

PREV_LEC | NEXT_LEC

V4L2 Async Connection Data Structures

A lecture from EmbeddedPathashala’s free Linux kernel development course — how the async core represents a sub-device it hasn’t seen yet

Earlier in this free linux kernel development course we introduced why V4L2 async exists: sub-devices and bridge drivers probe independently, in no guaranteed order, so the kernel needs a way to describe “a sub-device I expect to show up eventually” before it actually does. This lecture goes one level deeper into that mechanism — the exact data structures the async core uses to represent a pending sub-device, how matching criteria are expressed, and how the API evolved from the older struct v4l2_async_subdev model to today’s struct v4l2_async_connection. This is core material for anyone writing bridge drivers in our free embedded systems course.

v4l2_async_notifier v4l2_async_connection fwnode matching free linux device drivers course

What You Will Learn

  • The role of struct v4l2_async_notifier and its current field layout
  • How a single pending sub-device is represented via struct v4l2_async_connection and struct v4l2_async_match_desc
  • Why the old struct v4l2_async_subdev match union (device-name, I2C, custom callback) was trimmed down to just fwnode and I2C matching
  • The three-step call sequence a bridge driver uses to register a notifier: init, add connections, register

Prerequisites

  • Comfortable with the fwnode graph API and V4L2 media bus parsing from earlier lectures in this series
  • Basic platform driver probe()/remove() flow

Why the Async Core Needs Its Own Bookkeeping

A bridge driver (say, a CSI capture controller) usually knows, from its device tree port nodes, that it needs a camera sensor sub-device to function — but that sensor driver might not have probed yet; its I2C bus might still be initializing, or it might be waiting on a regulator. The bridge driver cannot simply call into a struct v4l2_subdev that doesn’t exist yet. What it can do is register a description of the sub-device it is waiting for, and let the V4L2 async core notify it later once a real sub-device shows up and matches that description.

That description — the “I’m waiting for something that looks like this” object — is exactly what struct v4l2_async_connection represents.

Async Registration at a Glance

Bridge probe() → v4l2_async_nf_init(&notifier) → allocate empty waiting list
For each port endpoint → v4l2_async_nf_add_fwnode_remote(&notifier, ep, …) → new v4l2_async_connection appended
v4l2_async_nf_register(v4l2_dev, &notifier) → core starts matching against subdevs as they probe

struct v4l2_async_notifier — the Waiting Room

Every bridge driver that needs one or more sub-devices owns a notifier. On current kernels, declared in include/media/v4l2-async.h, it looks like this:

struct v4l2_async_notifier {
    const struct v4l2_async_notifier_operations *ops;
    struct v4l2_device *v4l2_dev;
    struct v4l2_subdev *sd;
    struct v4l2_async_notifier *parent;
    struct list_head waiting_list;
    struct list_head done_list;
    struct list_head notifier_entry;
};
  • ops — the bound/complete/unbind/destroy callback set (covered fully in the next lecture)
  • v4l2_dev — the parent v4l2_device of the bridge driver that owns this notifier
  • sd — set only when a sub-device driver, not a bridge, registered this notifier (a “sub-device notifier”)
  • parent — links a sub-device notifier back to the notifier that is ultimately responsible for it
  • waiting_list — connections still waiting for a matching sub-device to probe
  • done_list — connections that have already matched and bound
  • notifier_entry — links this notifier into the async core’s internal registry of all active notifiers

Note the rename from the older waiting/done/list field names (still seen in books and older kernel trees) to waiting_list/done_list/notifier_entry — the concepts are identical, only the names were clarified.

struct v4l2_async_connection — One Pending Sub-Device

Where the notifier is the waiting room, a connection is one ticket in it — one specific sub-device the bridge is waiting to match:

struct v4l2_async_connection {
    struct v4l2_async_match_desc match;
    struct v4l2_async_notifier *notifier;
    struct list_head asc_entry;
    struct list_head asc_subdev_entry;
    struct v4l2_subdev *sd;
};

The matching criteria itself lives in a separate, smaller structure:

enum v4l2_async_match_type {
    V4L2_ASYNC_MATCH_TYPE_I2C,
    V4L2_ASYNC_MATCH_TYPE_FWNODE,
};

struct v4l2_async_match_desc {
    enum v4l2_async_match_type type;
    union {
        struct fwnode_handle *fwnode;
        struct {
            int adapter_id;
            unsigned short address;
        } i2c;
    };
};

Compare this with the structure the book’s original chapter documents, struct v4l2_async_subdev, which predates this split:

/* Older kernels — superseded by v4l2_async_connection above */
struct v4l2_async_subdev {
    enum v4l2_async_match_type match_type;
    union {
        struct fwnode_handle *fwnode;
        const char *device_name;
        struct {
            int adapter_id;
            unsigned short address;
        } i2c;
        struct {
            bool (*match)(struct device *, struct v4l2_async_subdev *);
            void *priv;
        } custom;
    } match;
    struct list_head list;
};

What Actually Changed

Old (v4l2_async_subdev)Current (v4l2_async_connection)Why
match_type had 4 options: DEVNAME, FWNODE, I2C, CUSTOMmatch.type has 2 options: I2C, FWNODEDEVNAME and CUSTOM matching saw little real-world use and complicated the core’s matching loop for no real benefit — nearly every modern driver matches by fwnode
Matching data embedded directly in the subdev structMatching data pulled into its own v4l2_async_match_descCleaner separation between “who am I matching” and “what connection state am I tracking”
One list fieldasc_entry (waiting/done list) plus asc_subdev_entry (per-subdev connection list)A single sub-device can now be associated with more than one async connection at once

Functionally, the fwnode-matching path you will use in almost every driver is unchanged in spirit: a bridge driver still hands the async core a fwnode_handle for a remote endpoint, and the core still compares it against the fwnode of every sub-device that probes.

Registering Connections: the Three-Step Sequence

A bridge driver follows the same three steps regardless of how many sub-devices it needs:

  1. Initialize the notifier with v4l2_async_nf_init(&notifier, v4l2_dev) — this sets up the internal lists and takes no sub-device information yet.
  2. Add a connection for each port/endpoint using one of the helper functions:
    • v4l2_async_nf_add_fwnode() — match by the fwnode of a local endpoint
    • v4l2_async_nf_add_fwnode_remote() — walks to the remote endpoint’s fwnode first, then matches on that (the common case for camera sensors)
    • v4l2_async_nf_add_i2c() — match by I2C adapter ID and address instead of fwnode
  3. Register the fully-populated notifier with v4l2_async_nf_register(v4l2_dev, &notifier), at which point the async core begins actively matching it against every sub-device that probes from that point on — and against any that already probed earlier.

Original Demo: an ep_bridge Driver That Registers a Connection

Here is ep_bridge, a minimal bridge platform driver that walks its port node, grabs the remote sensor endpoint, and registers exactly one fwnode-matched async connection.

#include <linux/module.h>
#include <linux/platform_device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-async.h>
#include <media/v4l2-fwnode.h>

struct ep_bridge {
    struct v4l2_device v4l2_dev;
    struct v4l2_async_notifier notifier;
};

static int ep_bridge_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct ep_bridge *eb;
    struct fwnode_handle *ep;
    struct v4l2_async_connection *asc;
    int ret;

    eb = devm_kzalloc(dev, sizeof(*eb), GFP_KERNEL);
    if (!eb)
        return -ENOMEM;

    ret = v4l2_device_register(dev, &eb->v4l2_dev);
    if (ret)
        return ret;

    v4l2_async_nf_init(&eb->notifier, &eb->v4l2_dev);

    ep = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
    if (!ep) {
        dev_err(dev, "no endpoint found\n");
        ret = -ENODEV;
        goto err_unregister;
    }

    asc = v4l2_async_nf_add_fwnode_remote(&eb->notifier, ep,
                                           struct v4l2_async_connection);
    fwnode_handle_put(ep);
    if (IS_ERR(asc)) {
        ret = PTR_ERR(asc);
        goto err_unregister;
    }

    ret = v4l2_async_nf_register(&eb->notifier);
    if (ret)
        goto err_cleanup;

    platform_set_drvdata(pdev, eb);
    dev_info(dev, "ep_bridge: notifier registered, waiting for sensor\n");
    return 0;

err_cleanup:
    v4l2_async_nf_cleanup(&eb->notifier);
err_unregister:
    v4l2_device_unregister(&eb->v4l2_dev);
    return ret;
}

static void ep_bridge_remove(struct platform_device *pdev)
{
    struct ep_bridge *eb = platform_get_drvdata(pdev);

    v4l2_async_nf_unregister(&eb->notifier);
    v4l2_async_nf_cleanup(&eb->notifier);
    v4l2_device_unregister(&eb->v4l2_dev);
}

static const struct of_device_id ep_bridge_of_match[] = {
    { .compatible = "ep,bridge" },
    { }
};
MODULE_DEVICE_TABLE(of, ep_bridge_of_match);

static struct platform_driver ep_bridge_driver = {
    .probe = ep_bridge_probe,
    .remove = ep_bridge_remove,
    .driver = {
        .name = "ep_bridge",
        .of_match_table = ep_bridge_of_match,
    },
};
module_platform_driver(ep_bridge_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("EmbeddedPathashala demo: V4L2 async connection registration");

Build, Load, and Expected Output

make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
sudo insmod ep_bridge.ko
dmesg | tail -n 3
[    8.220110] ep_bridge ep_bridge@0: ep_bridge: notifier registered, waiting for sensor
[    8.220145] ep_bridge: probe of ep_bridge@0 succeeded

At this point nothing has “bound” yet — the connection simply sits in notifier.waiting_list until a matching sub-device probes. We cover exactly what happens next — the matching algorithm and the bound/complete/unbind callbacks — in the next lecture.

Common Mistakes and Troubleshooting

  • Calling nf_register() before adding any connections: the notifier will register successfully but its .complete() callback fires immediately with zero sub-devices, which usually is not what you want.
  • Forgetting v4l2_async_nf_cleanup() on error paths: connections allocated by the add_* helpers are only freed by cleanup — a failed probe that skips it leaks memory.
  • Reaching for I2C matching by habit: fwnode matching is almost always preferable for new drivers since it works uniformly across device tree and ACPI, whereas I2C matching only helps when no proper endpoint graph exists.
  • Expecting the old DEVNAME or CUSTOM match types: both were removed from the current API — device-name matching should be replaced with a device-tree-based fwnode match wherever possible.

Best Practices

  • Embed the notifier inside your driver’s private state struct rather than allocating it separately — its lifetime is tied to the bridge device anyway.
  • Always pair v4l2_async_nf_register() with v4l2_async_nf_unregister() plus v4l2_async_nf_cleanup() in your remove path.
  • Prefer v4l2_async_nf_add_fwnode_remote() over manually calling fwnode_graph_get_remote_endpoint() yourself — it handles the reference counting correctly.

Summary and Key Takeaways

  • struct v4l2_async_notifier is the bridge driver’s waiting room; struct v4l2_async_connection is one ticket in it, describing a sub-device the bridge expects to appear.
  • Matching criteria live in struct v4l2_async_match_desc, now limited to fwnode and I2C matching — the old device-name and custom-callback options were removed.
  • Registration always follows init → add connection(s) → register, mirrored by unregister → cleanup on teardown.

You now understand exactly how a bridge driver describes the sub-device it’s waiting for. The remaining piece — how the async core actually performs the match and calls back into your driver — is the subject of the next lecture in this free linux kernel development course.

Frequently Asked Questions

What replaced struct v4l2_async_subdev in current kernels?

struct v4l2_async_connection, with its matching criteria split out into struct v4l2_async_match_desc.

Why were DEVNAME and CUSTOM match types removed?

They saw little real-world adoption compared to fwnode and I2C matching, and simplifying the match union reduced complexity in the async core’s matching loop.

What is the difference between v4l2_async_nf_add_fwnode() and v4l2_async_nf_add_fwnode_remote()

add_fwnode() matches on the fwnode you pass directly; add_fwnode_remote() first walks to the remote endpoint of the fwnode you pass, which is what you want when matching a sensor from the bridge’s own port node.

Can a bridge driver wait for more than one sub-device?

Yes — call the relevant add_* helper once per port/endpoint before registering the notifier; the core tracks each as a separate connection in the waiting list.

Is this part of a free course?

Yes, this lecture is part of EmbeddedPathashala’s free linux kernel development course and free embedded systems course.

Continue the Free Linux Kernel Development Course

Next: how the async core matches connections and calls bound/complete/unbind on your driver.

Browse All Lectures Join the Free Course

PREV_LEC | NEXT_LEC

Leave a Reply

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