linux V4L2子系统——v4l2架构(2)之v4l2_device

linux V4L2子系统——v4l2架构(2)之v4l2_device

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

文章目录

  • linux V4L2子系统——v4l2架构(2)之v4l2_device
    • v4l2_device结构体概述
    • 主要函数解析
      • v4l2_device_register函数
      • 其他相关函数

v4l2_device结构体概述

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

V4L2主设备实例使用struct v4l2_device结构体表示,v4l2_device是V4L2子系统的入口,管理着V4L2子系统的主设备和从设备。简单设备可以仅分配这个结构体,但在大多数情况下,都会将这个结构体嵌入到一个更大的结构体中。需要与媒体框架整合的驱动必须手动设置dev->driver_data,指向包含v4l2_device结构体实例的驱动特定设备结构体。这可以在注册V4L2设备实例前通过dev_set_drvdata()函数完成。同时必须设置v4l2_device结构体的mdev域,指向适当的初始化并注册过的media_device实例。

在文件 drivers/media/platform/sunxi/sun6i-csi/sun6i-csi.c 中的 sun6i_csi_probe 函数将调用 sun6i_csi_v4l2_init 函数中的第一个参数为 struct sun6i_csi ,此为驱动自定义的结构体,那就是它了,那么它的定义如下:

// 源码:drivers/media/platform/sunxi/sun6i-csi/sun6i-csi.c
struct sun6i_csi {
	struct device			*dev;
	struct v4l2_ctrl_handler	ctrl_handler;
	struct v4l2_device		v4l2_dev;
	struct media_device		media_dev;

	struct v4l2_async_notifier	notifier;

	/* video port settings */
	struct v4l2_fwnode_endpoint	v4l2_ep;

	struct sun6i_csi_config		config;

	struct sun6i_video		video;
};

主要函数解析

// 源码:include/media/v4l2-device.h
    // 注册v4l2_device结构体,并初始化v4l2_device结构体
    // dev-父设备结构体指针,若为NULL,在注册之前设备名称name必须被设置,
    // v4l2_dev-v4l2_device结构体指针
    // 返回值-0成功,小于0-失败
    int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)

    // 卸载注册的v4l2_device结构体
    // v4l2_dev-v4l2_device结构体指针
    void v4l2_device_unregister(struct v4l2_device *v4l2_dev)

    // 设置设备名称,填充v4l2_device结构体中的name成员
    // v4l2_dev-v4l2_device结构体指针
    // basename-设备名称基本字符串
    // instance-设备计数,调用v4l2_device_set_name后会自加1
    // 返回值-返回设备计数自加1的值
    int v4l2_device_set_name(struct v4l2_device *v4l2_dev, 
            const char *basename, atomic_t *instance)

    // 热插拔设备断开时调用此函数
    // v4l2_dev-v4l2_device结构体指针
    void v4l2_device_disconnect(struct v4l2_device *v4l2_dev);

v4l2_device_register函数

// 源码:drivers/media/platform/sunxi/sun6i-csi/sun6i-csi.c
static int sun6i_csi_v4l2_init(struct sun6i_csi *csi)
{
	int ret;

......
	ret = v4l2_ctrl_handler_init(&csi->ctrl_handler, 0);
	if (ret) {
		dev_err(csi->dev, "V4L2 controls handler init failed (%d)\n",
			ret);
		goto clean_media;
	}
    
	csi->v4l2_dev.mdev = &csi->media_dev;
	csi->v4l2_dev.ctrl_handler = &csi->ctrl_handler;
	ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
	if (ret) {
		dev_err(csi->dev, "V4L2 device registration failed (%d)\n",
			ret);
		goto free_ctrl;
	}

......
}

v4l2的设备注册其实没有注册设备或者设备驱动,只是将v4l2的大结构体与其他设备进行捆绑,
并保存v4l2设备对象到当前设备的driverdata中。此函数的作用就是将当前设备驱动和v4l2对象进行捆绑,便于挂接子设备

如下面是v4l2注册设备的过程。

//源码:drivers/media/v4l2-core/v4l2-device.c

 // 注册v4l2_device结构体,并初始化v4l2_device结构体
// dev-父设备结构体指针,若为NULL,在注册之前设备名称name必须被设置,
// v4l2_dev-v4l2_device结构体指针
// 返回值-0成功,小于0-失败
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
	if (v4l2_dev == NULL)
		return -EINVAL;

	INIT_LIST_HEAD(&v4l2_dev->subdevs);	//初始化subdev链表
	spin_lock_init(&v4l2_dev->lock);
	v4l2_prio_init(&v4l2_dev->prio);
	kref_init(&v4l2_dev->ref);			//初始化v4l2_dev计数值为1
	// 上面都是做一些初始化工作

	get_device(dev);

	// 下面将当前设备的对象,赋值给v4l2_dev->dev中,
	// 这样的话当前的设备就成了v4l2设备,
	// 由于当前设备已经注册过了设备文件,
	// 所以后续不需要在重新注册设备文件。
	v4l2_dev->dev = dev;
	if (dev == NULL) {		// 条件检查
		/* If dev == NULL, then name must be filled in by the caller */
		if (WARN_ON(!v4l2_dev->name[0]))
			return -EINVAL;
		return 0;
	}

	/* Set name to driver name + device name if it is empty. */
	/* 如果v4l2设备的名字为空,则会将当前设备的名字拷贝为v4l2设备中。*/
	if (!v4l2_dev->name[0])
		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
			dev->driver->name, dev_name(dev));

	// 下面是非常重要的一步,即将v4l2设备对象,保存到dev设备中,
	// 这个dev可以是platform、char、block等设,
	// 我们可以使用dev_get_drvdata(dev)获取到v4l2对象。
	if (!dev_get_drvdata(dev))
		dev_set_drvdata(dev, v4l2_dev);
	return 0;
}

使用v4l2_device_register注册v4l2_device结构体。如果v4l2_dev->name为空,则它将被设置为从dev中衍生出的值(为了更加精确,形式为驱动名后跟bus_id)。如果在调用v4l2_device_register前已经设置好了,则不会被修改。如果dev为NULL,则必须在调用v4l2_device_register前设置v4l2_dev->name。可以基于驱动名和驱动的全局atomic_t类型的实例编号,通过 v4l2_device_set_name() 设置name。这样会生成类似ivtv0、ivtv1等名字。若驱动名以数字结尾,则会在编号和驱动名间插入一个破折号,如:cx18-0、cx18-1等。dev参数通常是一个指向pci_dev、usb_interface或platform_device的指针,很少使其为NULL,除非是一个ISA设备或者当一个设备创建了多个PCI设备,使得v4l2_dev无法与一个特定的父设备关联。

使用v4l2_device_unregister卸载v4l2_device结构体。如果dev->driver_data域指向 v4l2_dev,将会被重置为NULL。主设备注销的同时也会自动注销所有子设备。如果你有一个热插拔设备(如USB设备),则当断开发生时,父设备将无效。由于v4l2_device有一个指向父设备的指针必须被清除,同时标志父设备
已消失,所以必须调用v4l2_device_disconnect函数清理v4l2_device中指向父设备的dev指针。

v4l2_device_disconnect 并不注销主设备,因此依然要调用 v4l2_device_unregister 函数注销主设备。

其他相关函数

//源码:drivers/media/v4l2-core/v4l2-device.c

// 设置设备名称,填充v4l2_device结构体中的name成员
// v4l2_dev-v4l2_device结构体指针
// basename-设备名称基本字符串
// instance-设备计数,调用v4l2_device_set_name后会自加1
// 返回值-返回设备计数自加1的值
int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename,
						atomic_t *instance)
{
	int num = atomic_inc_return(instance) - 1;
	int len = strlen(basename);

	if (basename[len - 1] >= '0' && basename[len - 1] <= '9')
		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
				"%s-%d", basename, num);
	else
		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
				"%s%d", basename, num);
	return num;
}

//源码:drivers/media/v4l2-core/v4l2-device.c

 // 热插拔设备断开时调用此函数 
 // v4l2_dev-v4l2_device结构体指针
void v4l2_device_disconnect(struct v4l2_device *v4l2_dev)
{
	if (v4l2_dev->dev == NULL)
		return;

	if (dev_get_drvdata(v4l2_dev->dev) == v4l2_dev)
		dev_set_drvdata(v4l2_dev->dev, NULL);
	put_device(v4l2_dev->dev);
	v4l2_dev->dev = NULL;
}
EXPORT_SYMBOL_GPL(v4l2_device_disconnect);
//源码:drivers/media/v4l2-core/v4l2-device.c

// 卸载注册的v4l2_device结构体
// v4l2_dev-v4l2_device结构体指针
void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
{
	struct v4l2_subdev *sd, *next;

	/* Just return if v4l2_dev is NULL or if it was already
	 * unregistered before. */
	if (v4l2_dev == NULL || !v4l2_dev->name[0])
		return;
	v4l2_device_disconnect(v4l2_dev);

	/* Unregister subdevs */
	list_for_each_entry_safe(sd, next, &v4l2_dev->subdevs, list) {
		v4l2_device_unregister_subdev(sd);
		if (sd->flags & V4L2_SUBDEV_FL_IS_I2C)
			v4l2_i2c_subdev_unregister(sd);
		else if (sd->flags & V4L2_SUBDEV_FL_IS_SPI)
			v4l2_spi_subdev_unregister(sd);
	}
	/* Mark as unregistered, thus preventing duplicate unregistrations */
	v4l2_dev->name[0] = '\0';
}
EXPORT_SYMBOL_GPL(v4l2_device_unregister);

你可能感兴趣的:(#,Linux,v4l2,v4l2,video,linux,v4l2)