linux V4L2子系统——v4l2架构(4)之v4l2_subdev

备注:
 1. Kernel版本:5.4
 2. 使用工具:Source Insight 4.0
 3. 参考博客:
(1)Linux V4L2子系统分析(一)
(2)linux v4l2 学习之-v4l2设备注册过程及各个设备之间的联系

文章目录

  • linux V4L2子系统——v4l2架构(4)之v4l2_subdev
    • 概述
    • 主要结构体介绍
    • v4l2_subdev初始化
    • v4l2_subdev注册与释放
      • v4l2_device_register_subdev()注册过程
      • v4l2_device_register_subdev_nodes()注册设备节点
      • v4l2_subdev释放过程
    • 示例——OV7251

概述

V4L2从设备使用struct v4l2_subdev结构体表示。一个V4L2主设备可能对应多个V4L2从设备,所有主设备对应的从设备都挂到v4l2_device结构体的subdevs链表中。对于视频设备,从设备就是摄像头,通常情况下是I2C设备,主设备可通过I2C总线控制从设备,例如控制摄像头的焦距、闪光灯等。

主要结构体介绍

详见:linux V4L2子系统——v4l2的结构体(3)之v4l2_subdev

v4l2_subdev初始化

使用 v4l2_subdev_init 初始化v4l2_subdev结构体。然后必须用一个唯一的名字初始化 subdev->name,同时初始化模块的owner域。若从设备是I2C设备,则可使用 v4l2_i2c_subdev_init 函数进行初始;若从设备是SPI设备,则可使用 v4l2_spi_subdev_init 函数进行初始化。该函数内部会调用 v4l2_subdev_init ,同时设置flags、owner、dev、name等成员。

以从设备是I2C设备为例——v4l2_i2c_subdev_init

// 源码: drivers/media/i2c/v4l2-i2c.cvoid v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client,const struct v4l2_subdev_ops *ops){v4l2_subdev_init(sd, ops); // 初始化 v4l2_subdevsd->flags |= V4L2_SUBDEV_FL_IS_I2C; // 配置 I2C flag/* the owner is the same as the i2c_client's driver owner */sd->owner = client->dev.driver->owner;sd->dev = &client->dev;/* i2c_client and v4l2_subdev point to one another */v4l2_set_subdevdata(sd, client);i2c_set_clientdata(client, sd);// 配置 v4l2_subdev 设备名v4l2_i2c_subdev_set_name(sd, client, NULL, NULL);}EXPORT_SYMBOL_GPL(v4l2_i2c_subdev_init);
// 源码: drivers/media/v4l2-core/v4l2-subdev.cvoid v4l2_subdev_init(struct v4l2_subdev *sd, const struct v4l2_subdev_ops *ops){INIT_LIST_HEAD(&sd->list);// 初始化 v4l2_subdev 挂载链表BUG_ON(!ops);sd->ops = ops;// 初始化 v4l2_subdev_opssd->v4l2_dev = NULL;sd->flags = 0;sd->name[0] = '\0';sd->grp_id = 0;// 设置默认组为0sd->dev_priv = NULL;sd->host_priv = NULL;#if defined(CONFIG_MEDIA_CONTROLLER)sd->entity.name = sd->name;sd->entity.obj_type = MEDIA_ENTITY_TYPE_V4L2_SUBDEV;sd->entity.function = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN;#endif}EXPORT_SYMBOL(v4l2_subdev_init);

若需同媒体框架整合,必须调用 media_entity_init 初始化 v4l2_subdev 结构体中的 media_entity 结构体(entity域)。pads数组必须预先初始化。无须手动设置media_entity的type和name域,但如有必要,revision域必须初始化。当(任何)从设备节点被打开/关闭,对entity的引用将被自动获取/释放。在从设备被注销之后,使用 media_entity_cleanup 清理 media_entity 结构体。

如果从设备驱动趋向于处理视频并整合进了媒体框架,必须使用 v4l2_subdev_pad_ops 替代v4l2_subdev_video_ops 实现格式相关的功能。这种情况下,子设备驱动应该设置 link_validate 域,以提供它自身的链接验证函数。链接验证函数应对管道(两端链接的都是V4L2从设备)中的每个链接调用。驱动还要负责验证子设备和视频节点间格式配置的正确性。如果link_validate操作没有设置,默认的 v4l2_subdev_link_validate_default 函数将会被调用。这个函数保证宽、高和媒体总线像素格式在链接的收发两端都一致。子设备驱动除了它们自己的检测外,也可以自由使用这个函数以执行上面提到的检查。

v4l2_subdev注册与释放

v4l2_device_register_subdev()注册过程

//源码:divers/media/v4l2-core/v4l2-device.cint v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,struct v4l2_subdev *sd){#if defined(CONFIG_MEDIA_CONTROLLER)//获取到子设备的入口对象,方面后面注册到media_device上面struct media_entity *entity = &sd->entity;#endifint err;/* Check for valid input */if (!v4l2_dev || !sd || sd->v4l2_dev || !sd->name[0])return -EINVAL;/* * 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;//绑定v4l2_dev,下面v4l2_dev一般为系统根v4l2_device设备。sd->v4l2_dev = v4l2_dev;/* This just returns 0 if either of the two args is NULL */// 印证了之前说的,子设备的ctrl_handler都会// 挂载到根设备v4l2_device的ctrl_handler上面err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler,NULL, true);if (err)goto error_module;#if defined(CONFIG_MEDIA_CONTROLLER)/* Register the entity. */if (v4l2_dev->mdev) {err = media_device_register_entity(v4l2_dev->mdev, entity);if (err internal_ops && sd->internal_ops->registered) {err = sd->internal_ops->registered(sd);if (err)goto error_unregister;}spin_lock(&v4l2_dev->lock);// 将子设备链接到跟设备的,subdevs链表上list_add_tail(&sd->list, &v4l2_dev->subdevs);spin_unlock(&v4l2_dev->lock);return 0;error_unregister:#if defined(CONFIG_MEDIA_CONTROLLER)media_device_unregister_entity(entity);#endiferror_module:if (!sd->owner_v4l2_dev)module_put(sd->owner);sd->v4l2_dev = NULL;return err;}
    1. 获取到子设备的entity对象,并将当前根设备对象赋值给subdev->v4l2_dev域。
    1. 将子设备的ctrl_handler对象,添加到根设备的v4l2_dev->ctrl_handler域。
    1. 将子设备的entity注册到根设备上的media_device设备中。
    1. 将当前 v4l2_subdev 添加到 v4l2_dev 中的 subdevs 链表中。

v4l2_device_register_subdev_nodes()注册设备节点

//源码:divers/media/v4l2-core/v4l2-device.cint v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev){struct video_device *vdev;struct v4l2_subdev *sd;int err;/* Register a device node for every subdev marked with the * V4L2_SUBDEV_FL_HAS_DEVNODE flag. */// 遍历 v4l2_dev 下的 subdevs 链表,查找 subdevlist_for_each_entry(sd, &v4l2_dev->subdevs, list) {// 检查当前 subdev 是否有创建设备节点的标志if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE))continue;// 当前 subdev 是否已创建设备节点if (sd->devnode)continue;// 申请 video_devicevdev = kzalloc(sizeof(*vdev), GFP_KERNEL);if (!vdev) {err = -ENOMEM;goto clean_up;}// 初始化 video_devicevideo_set_drvdata(vdev, sd);strscpy(vdev->name, sd->name, sizeof(vdev->name));vdev->dev_parent = sd->dev;vdev->v4l2_dev = v4l2_dev;vdev->fops = &v4l2_subdev_fops;vdev->release = v4l2_device_release_subdev_node;vdev->ctrl_handler = sd->ctrl_handler;// 注册 video_device,类型为 VFL_TYPE_SUBDEVerr = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1,sd->owner);if (err devnode = vdev;#if defined(CONFIG_MEDIA_CONTROLLER)sd->entity.info.dev.major = VIDEO_MAJOR;sd->entity.info.dev.minor = vdev->minor;/* Interface is created by __video_register_device() */if (vdev->v4l2_dev->mdev) {struct media_link *link;link = media_create_intf_link(&sd->entity,&vdev->intf_devnode->intf,MEDIA_LNK_FL_ENABLED |MEDIA_LNK_FL_IMMUTABLE);if (!link) {err = -ENOMEM;goto clean_up;}}#endif}return 0;clean_up:list_for_each_entry(sd, &v4l2_dev->subdevs, list) {if (!sd->devnode)break;video_unregister_device(sd->devnode);}return err;}

v4l2_subdev释放过程

//源码:divers/media/v4l2-core/v4l2-device.cvoid v4l2_device_unregister_subdev(struct v4l2_subdev *sd){struct v4l2_device *v4l2_dev;/* return if it isn't registered */if (sd == NULL || sd->v4l2_dev == NULL)return;v4l2_dev = sd->v4l2_dev;spin_lock(&v4l2_dev->lock);list_del(&sd->list);// 移除当前 subdev 节点spin_unlock(&v4l2_dev->lock);if (sd->internal_ops && sd->internal_ops->unregistered)sd->internal_ops->unregistered(sd);sd->v4l2_dev = NULL;#if defined(CONFIG_MEDIA_CONTROLLER)if (v4l2_dev->mdev) {/* * No need to explicitly remove links, as both pads and * links are removed by the function below, in the right order */media_device_unregister_entity(&sd->entity);}#endif// 若注册 video_device 设备节点,则unregister video_deviceif (sd->devnode)video_unregister_device(sd->devnode);elsev4l2_subdev_release(sd);// 释放 subdev}

示例——OV7251

//源码:drivers/media/i2c/ov7251.cstatic int ov7251_probe(struct i2c_client *client){struct device *dev = &client->dev;struct fwnode_handle *endpoint;struct ov7251 *ov7251;u8 chip_id_high, chip_id_low, chip_rev;int ret;ov7251 = devm_kzalloc(dev, sizeof(struct ov7251), GFP_KERNEL);if (!ov7251)return -ENOMEM;......// 初始化 ctrl_handlerv4l2_ctrl_handler_init(&ov7251->ctrls, 7);ov7251->ctrls.lock = &ov7251->lock;v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops,V4L2_CID_HFLIP, 0, 1, 1, 0);v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops,V4L2_CID_VFLIP, 0, 1, 1, 0);ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, V4L2_CID_EXPOSURE, 1, 32, 1, 32);ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, V4L2_CID_GAIN, 16, 1023, 1, 16);v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, V4L2_CID_TEST_PATTERN, ARRAY_SIZE(ov7251_test_pattern_menu) - 1, 0, 0, ov7251_test_pattern_menu);ov7251->pixel_clock = v4l2_ctrl_new_std(&ov7251->ctrls,&ov7251_ctrl_ops,V4L2_CID_PIXEL_RATE,1, INT_MAX, 1, 1);ov7251->link_freq = v4l2_ctrl_new_int_menu(&ov7251->ctrls, &ov7251_ctrl_ops, V4L2_CID_LINK_FREQ, ARRAY_SIZE(link_freq) - 1, 0, link_freq);if (ov7251->link_freq)ov7251->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;ov7251->sd.ctrl_handler = &ov7251->ctrls;if (ov7251->ctrls.error) {dev_err(dev, "%s: control initialization error %d\n",__func__, ov7251->ctrls.error);ret = ov7251->ctrls.error;goto free_ctrl;}v4l2_i2c_subdev_init(&ov7251->sd, client, &ov7251_subdev_ops);ov7251->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;ov7251->pad.flags = MEDIA_PAD_FL_SOURCE;ov7251->sd.dev = &client->dev;ov7251->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;ret = media_entity_pads_init(&ov7251->sd.entity, 1, &ov7251->pad);if (ret sd);if (ret sd, NULL);return 0;......return ret;}
//源码:drivers/media/i2c/ov7251.cstatic const struct v4l2_subdev_core_ops ov7251_core_ops = {.s_power = ov7251_s_power,};static const struct v4l2_subdev_video_ops ov7251_video_ops = {.s_stream = ov7251_s_stream,.g_frame_interval = ov7251_get_frame_interval,.s_frame_interval = ov7251_set_frame_interval,};static const struct v4l2_subdev_pad_ops ov7251_subdev_pad_ops = {.init_cfg = ov7251_entity_init_cfg,.enum_mbus_code = ov7251_enum_mbus_code,.enum_frame_size = ov7251_enum_frame_size,.enum_frame_interval = ov7251_enum_frame_ival,.get_fmt = ov7251_get_format,.set_fmt = ov7251_set_format,.get_selection = ov7251_get_selection,};static const struct v4l2_subdev_ops ov7251_subdev_ops = {.core = &ov7251_core_ops,.video = &ov7251_video_ops,.pad = &ov7251_subdev_pad_ops,};