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

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

概述

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.c
void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client,
			  const struct v4l2_subdev_ops *ops)
{
	v4l2_subdev_init(sd, ops); // 初始化 v4l2_subdev
	sd->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.c
void 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_ops
	sd->v4l2_dev = NULL;
	sd->flags = 0;
	sd->name[0] = '\0';
	sd->grp_id = 0;				// 设置默认组为0
	sd->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.c

int 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;
#endif
	int 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 < 0)
			goto error_module;
	}
#endif

	// 这里对应具体的子设备,可以发现调用了registered()回调,
	// 如果有需要可以在对应的设备驱动中实现
	if (sd->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);
#endif
error_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.c

int 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 链表,查找 subdev
	list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
		// 检查当前 subdev 是否有创建设备节点的标志
		if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE))
			continue;

		// 当前 subdev 是否已创建设备节点
		if (sd->devnode)
			continue;

		// 申请 video_device
		vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
		if (!vdev) {
			err = -ENOMEM;
			goto clean_up;
		}

		// 初始化 video_device
		video_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_SUBDEV
		err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1,
					      sd->owner);
		if (err < 0) {
			kfree(vdev);
			goto clean_up;
		}
		sd->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.c

void 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_device
	if (sd->devnode)
		video_unregister_device(sd->devnode);
	else
		v4l2_subdev_release(sd);	// 释放 subdev
}

示例——OV7251

//源码:drivers/media/i2c/ov7251.c

static 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_handler
	v4l2_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 < 0) {
		dev_err(dev, "could not register media entity\n");
		goto free_ctrl;
	}

......

    // 异步注册 v4l2_subdev
	ret = v4l2_async_register_subdev(&ov7251->sd);
	if (ret < 0) {
		dev_err(dev, "could not register v4l2 device\n");
		goto free_entity;
	}

	ov7251_entity_init_cfg(&ov7251->sd, NULL);

	return 0;

......

	return ret;
}
//源码:drivers/media/i2c/ov7251.c
static 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,
};
Logo

更多推荐