v4l2 subdev 设备节点和属性创建过程
- v4l2_subdev(子设备):通常是底层硬件组件(如摄像头传感器、ISP 模块、多路复用器等),负责原始数据采集或硬件处理,通过
v4l2_device_register_subdev注册,对应 sysfs 中的v4l-subdevX节点(如/sys/class/video4linux/v4l-subdev2)。
v4l2_subdev 内存描述和 实际设备节点的创建,是一件比较困惑的事情,如何创建自定义的v4l2_subdev 属性,来实现对设备的特殊操作,笔者就此,做了一番下面的分析探究。
下面就此分享
1 v4l2_device_register_subdev
不管是v4l2 subdev 的同步创建和异步创建,最终都是调用了v4l2_device_register_subdev这个函数,其中我们重点关注异步创建,因为视频流通路上的前后拓扑节点模块驱动 probe 的先后顺序不可控制,所以异步的注册创建显得尤为普遍。
在v4l2-async.c 文件v4l2_async_match_notify 回调中,notifier 实现对subdev描述的注册。

int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
struct v4l2_subdev *sd)
{
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_entity *entity = &sd->entity;
#endif
int err;
/* Check for valid input */
if (v4l2_dev == NULL || sd == NULL || !sd->name[0])
return -EINVAL;
/* Warn if we apparently re-register a subdev */
WARN_ON(sd->v4l2_dev != NULL);
/*
* The reason to acquire the module here is to avoid unloading
* a module of sub-device which is registered to a media
* device. To make it possible to unload modules for media
* devices that also register sub-devices, do not
* try_module_get() such sub-device owners.
*/
sd->owner_v4l2_dev = v4l2_dev->dev && v4l2_dev->dev->driver &&
sd->owner == v4l2_dev->dev->driver->owner;
if (!sd->owner_v4l2_dev && !try_module_get(sd->owner))
return -ENODEV;
sd->v4l2_dev = v4l2_dev;
if (sd->internal_ops && sd->internal_ops->registered) {
err = sd->internal_ops->registered(sd);
if (err)
goto error_module;
}
/* This just returns 0 if either of the two args is NULL */
err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler, NULL);
if (err)
goto error_unregister;
#if defined(CONFIG_MEDIA_CONTROLLER)
/* Register the entity. */
if (v4l2_dev->mdev) {
err = media_device_register_entity(v4l2_dev->mdev, entity);
if (err < 0)
goto error_unregister;
}
#endif
spin_lock(&v4l2_dev->lock);
list_add_tail(&sd->list, &v4l2_dev->subdevs);
spin_unlock(&v4l2_dev->lock);
return 0;
error_unregister:
if (sd->internal_ops && sd->internal_ops->unregistered)
sd->internal_ops->unregistered(sd);
error_module:
if (!sd->owner_v4l2_dev)
module_put(sd->owner);
sd->v4l2_dev = NULL;
return err;
}
EXPORT_SYMBOL_GPL(v4l2_device_register_subdev);
v4l2_device_register_subdev 首先判定 owner_v4l2_dev
如果owner_v4l2_dev不为真,通过try_module_get 加载驱动
绑定从属的v4l2_dev
如果sd->internal_ops和 sd->internal_ops->registered 设置,调用这个回调
通过v4l2_ctrl_add_handler 添加subdev 的控制操作集
media 框架 注册这个entity
添加到从属v4l2_dev的 subdevs链表
从上可以看出,v4l2_device_register_subdev 这个函数,只是实现一些内存中具体描述,不实现设备节点的创建。
2 实际设备节点和/sys 设备树中节点属性的创建
1)v4l2_device_register_subdev_nodes

v4l2_device_register_subdev_nodes 这个函数,其中的sd flag中如果配置了V4L2_SUBDEV_FL_HAS_DEVNODE,那么 就分配一个vdev,指向传入的v4l2_dev,v4l2的操作集v4l2_subdev_fops,系统默认的v4l2_device_release_subdev_node, 通过__video_register_device 来具体创建之。
2) __video_register_device
__video_register_device 在v4l2-dev.c,实现了实际的设备节点的创建和sys 节点和属性的初始化,是一个完整综合的过程。
int __video_register_device(struct video_device *vdev, int type, int nr,
int warn_if_nr_in_use, struct module *owner)
{
int i = 0;
int ret;
int minor_offset = 0;
int minor_cnt = VIDEO_NUM_DEVICES;
const char *name_base;
/* A minor value of -1 marks this video device as never
having been registered */
vdev->minor = -1;
/* the release callback MUST be present */
if (WARN_ON(!vdev->release))
return -EINVAL;
/* the v4l2_dev pointer MUST be present */
if (WARN_ON(!vdev->v4l2_dev))
return -EINVAL;
/* v4l2_fh support */
spin_lock_init(&vdev->fh_lock);
INIT_LIST_HEAD(&vdev->fh_list);
/* Part 1: check device type */
switch (type) {
case VFL_TYPE_GRABBER:
name_base = "video";
break;
case VFL_TYPE_VBI:
name_base = "vbi";
break;
case VFL_TYPE_RADIO:
name_base = "radio";
break;
case VFL_TYPE_SUBDEV:
name_base = "v4l-subdev";
break;
case VFL_TYPE_SDR:
/* Use device name 'swradio' because 'sdr' was already taken. */
name_base = "swradio";
break;
default:
printk(KERN_ERR "%s called with unknown type: %d\n",
__func__, type);
return -EINVAL;
}
vdev->vfl_type = type;
vdev->cdev = NULL;
if (vdev->dev_parent == NULL)
vdev->dev_parent = vdev->v4l2_dev->dev;
if (vdev->ctrl_handler == NULL)
vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
/* If the prio state pointer is NULL, then use the v4l2_device
prio state. */
if (vdev->prio == NULL)
vdev->prio = &vdev->v4l2_dev->prio;
/* Part 2: find a free minor, device node number and device index. */
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
/* Keep the ranges for the first four types for historical
* reasons.
* Newer devices (not yet in place) should use the range
* of 128-191 and just pick the first free minor there
* (new style). */
switch (type) {
case VFL_TYPE_GRABBER:
minor_offset = 0;
minor_cnt = 64;
break;
case VFL_TYPE_RADIO:
minor_offset = 64;
minor_cnt = 64;
break;
case VFL_TYPE_VBI:
minor_offset = 224;
minor_cnt = 32;
break;
default:
minor_offset = 128;
minor_cnt = 64;
break;
}
#endif
/* Pick a device node number */
mutex_lock(&videodev_lock);
nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
if (nr == minor_cnt)
nr = devnode_find(vdev, 0, minor_cnt);
if (nr == minor_cnt) {
printk(KERN_ERR "could not get a free device node number\n");
mutex_unlock(&videodev_lock);
return -ENFILE;
}
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
/* 1-on-1 mapping of device node number to minor number */
i = nr;
#else
/* The device node number and minor numbers are independent, so
we just find the first free minor number. */
for (i = 0; i < VIDEO_NUM_DEVICES; i++)
if (video_device[i] == NULL)
break;
if (i == VIDEO_NUM_DEVICES) {
mutex_unlock(&videodev_lock);
printk(KERN_ERR "could not get a free minor\n");
return -ENFILE;
}
#endif
vdev->minor = i + minor_offset;
vdev->num = nr;
devnode_set(vdev);
/* Should not happen since we thought this minor was free */
WARN_ON(video_device[vdev->minor] != NULL);
vdev->index = get_index(vdev);
video_device[vdev->minor] = vdev;
mutex_unlock(&videodev_lock);
if (vdev->ioctl_ops)
determine_valid_ioctls(vdev);
/* Part 3: Initialize the character device */
vdev->cdev = cdev_alloc();
if (vdev->cdev == NULL) {
ret = -ENOMEM;
goto cleanup;
}
vdev->cdev->ops = &v4l2_fops;
vdev->cdev->owner = owner;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
if (ret < 0) {
printk(KERN_ERR "%s: cdev_add failed\n", __func__);
kfree(vdev->cdev);
vdev->cdev = NULL;
goto cleanup;
}
/* Part 4: register the device with sysfs */
vdev->dev.class = &video_class;
vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
vdev->dev.parent = vdev->dev_parent;
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
ret = device_register(&vdev->dev);
if (ret < 0) {
printk(KERN_ERR "%s: device_register failed\n", __func__);
goto cleanup;
}
/* Register the release callback that will be called when the last
reference to the device goes away. */
vdev->dev.release = v4l2_device_release;
if (nr != -1 && nr != vdev->num && warn_if_nr_in_use)
printk(KERN_WARNING "%s: requested %s%d, got %s\n", __func__,
name_base, nr, video_device_node_name(vdev));
/* Increase v4l2_device refcount */
v4l2_device_get(vdev->v4l2_dev);
#if defined(CONFIG_MEDIA_CONTROLLER)
/* Part 5: Register the entity. */
if (vdev->v4l2_dev->mdev &&
vdev->vfl_type != VFL_TYPE_SUBDEV) {
vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
vdev->entity.name = vdev->name;
vdev->entity.info.dev.major = VIDEO_MAJOR;
vdev->entity.info.dev.minor = vdev->minor;
ret = media_device_register_entity(vdev->v4l2_dev->mdev,
&vdev->entity);
if (ret < 0)
printk(KERN_WARNING
"%s: media_device_register_entity failed\n",
__func__);
}
#endif
/* Part 6: Activate this minor. The char device can now be used. */
set_bit(V4L2_FL_REGISTERED, &vdev->flags);
return 0;
cleanup:
mutex_lock(&videodev_lock);
if (vdev->cdev)
cdev_del(vdev->cdev);
video_device[vdev->minor] = NULL;
devnode_clear(vdev);
mutex_unlock(&videodev_lock);
/* Mark this video device as never having been registered. */
vdev->minor = -1;
return ret;
}
EXPORT_SYMBOL(__video_register_device);
整个函数的过程如下
首先判定vdev 这个video_device指针有没有初始化vdev->release和vdev->v4l2_dev,
然后根据type指定name 的命名前缀,其中 我们关注的 VFL_TYPE_GRABBER,VFL_TYPE_SUBDEV 分别对应于输出流的设备节点videoX和前端的subdevY 类型。
初始化没有配置dev_parent,ctrl_handler,prio字段
通过devnode_find查找这个设备号是否已经存在,存在就退出了,不存在继续
从数组 video_device[] 找出一个空指针的元素,指向传入的dev
通过cdev_alloc 分配字符设备描述符
通过 cdev_add 创建/dev下设备节点
vdev->dev 关联 video_class
通过 device_register 注册创建/sys/class/video4linux/下 节点
配置vdev->dev.release
通过v4l2_device_get 使设备计数加1
通过 media_device_register_entity 注册有向图的entity项
flag 设置注册标志
退出
从1) 2) 可以看出,具体设备节点的创建,是 通过__video_register_device和其变体函数来实现的。
3)v4l2_device_register_subdev_nodes 的被调场景
在瑞芯微平台上,查到有cif 和isp1 这两类调用到了这个函数。
![]()
v4l2_device_register_subdev_nodes在isp1 dev.c 中

v4l2_device_register_subdev_nodes 被isp subdev的subdev_notifier_complete 调用,实现在bound 完成时机的 v4l2_dev 设备节点和链路上的subdev 的设备节点和创建。在cif 中也是同样如此。


subdev_notifier_complete 是异步回调,在解析dts endpoint 匹配后实现前后source 和sink pad 的bound
总结
所以v4l2 subdev 设备节点和属性是在被hold的v4l2_dev 的匹配异步操作过程中被创建的,v4l2_device_register_subdev是先期在内存中建立v4l2_subdev具体描述,和 v4l2_dev的从属关系,然后在后级的具体的 v4l2_dev的bound 回调中批量的来创建所有链路上的v4l2_subdev相应的各类文件系统(/dev,/sys)上的设备节点和属性。
所以我们需要创建v4l2_subdev对应设备节点的扩展属性,必须在v4l2_device_register_subdev_nodes 被调用之后,在这个之前都是无效的。
