V4L2之设备注册

前言

本文主要讲的是NXP的imx8mm,源码是由NXP提供的,不同的下游厂家的开发板也应该是一样的。

引入异步注册的原因

在SOC中的视频处理可能由多个IP组成,比如csi_bridge、csi_mipi接口、具体的sensor(ov5640等),甚至更多的IP,这样就导致了V4L2的复杂性。在v4l2中的视频数据流是有方向和顺序的,在linux中引入了异步注册的机制来解决这个问题。v4l2的bridge驱动需要在所有的子设备都已经加载后,再初始化所有的子设备(比如:stream on)。异步注册的核心在于设备树中引入port接口,在子设备中有一个或者多个port接口,port接口就是子设备的纽带。

v4l2设备注册

在imx8mm中包含了三个设备
csi1_bridge :这个是SOC端的总的外设,它是和mipi控制器连接的。
mipi_csi :mipi传输的控制器,它是连接csi1_bridge 和ov5640的通道。
ov5640:外接的摄像头,是一个标准的sensor。
连接方式:csi1_bridge -> mipi_csi_1-> ov5640
其中csi1_bridge不是子设备,另外两个mipi_csi 、ov5640在系统中是子设备。

设备树

ov5640_mipi: ov5640_mipi@3c {
	compatible = "ovti,ov5640_mipi";
	...
	port {
		ov5640_mipi1_ep: endpoint {
			remote-endpoint = <&mipi1_sensor_ep>;
		};
	};
};

&mipi_csi_1 {
	#address-cells = <1>;
	#size-cells = <0>;
	status = "okay";
	port {
		mipi1_sensor_ep: endpoint1 {
			remote-endpoint = <&ov5640_mipi1_ep>;
			data-lanes = <2>;
			csis-hs-settle = <10>;
			csis-clk-settle = <0>;
			csis-wclk;
		};

		csi1_mipi_ep: endpoint2 {
			remote-endpoint = <&csi1_ep>;
		};
	};
};

&csi1_bridge {
	fsl,mipi-mode;
	status = "okay";
	port {
		csi1_ep: endpoint {
			remote-endpoint = <&csi1_mipi_ep>;
		};
	};
};

从上面的设备代码片段中可以看出:csi1_bridge 中的endpoint 连接到了mipi_csi_1 的endpoint2 ,mipi_csi_1 的endpoint1 连接到了ov5640_mipi的endpoint 。下面讲到的设备注册顺序就是根据这里来的。

所有设备注册顺序

请仔细看下面代码和注释,下面虽然只有5个函数,但却是理解v4l2中子设备注册过程的核心!

mx6s_capture.c:
 //步骤1:注册了一个视频设备。
 - video_register_device()	
 //步骤2:在notifier中注册了mipi_csi对应的异步子设备。
 - mx6sx_register_subdevs(csi_dev) 
	-> v4l2_async_notifier_register();

mxc_mipi_csi.c:
 //步骤3:首先注册了和上面匹配的子设备。
 - mipi_csis_subdev_init(&state->mipi_sd, pdev, &mipi_csis_subdev_ops) 
	-> v4l2_async_register_subdev();
 //步骤4:在notifier中注册了ov5640对应的异步子设备。	
 - mipi_csis_subdev_host(state) -> v4l2_async_notifier_register();	

ov5640.c:
 //步骤5:只注册了在上面noifier中对应的ov5640子设备。
 - v4l2_async_register_subdev(&sensor->sd);

其中,步骤2(asd)和步骤3(sd)是对应的,步骤4(asd)和步骤5(sd)是对应的,并且步骤3和步骤4是联系在一起的。可以先看下小结部分解释再回来看这里,需要补充下:v4l2_async_notifier_register()和v4l2_async_register_subdev实际上并真正注册设备,只有在它们两个注册的设备信息匹配后,才会调用v4l2_device_register_subdev()函数向系统真正注册子设备。可以想象一下,在上面的步骤中,不管是哪个步骤先执行,或者哪个步骤后执行,最终会形成csi1_bridge -> mipi_csi_1-> ov5640这样的连接方式,这就是我们想要的。

子设备注册

重要的数据结构

子设备数据结构:这里只列举出了和异步注册相关的内容。

struct v4l2_subdev {
	...
	/* 子设备链表 */
	struct list_head list;
	struct v4l2_device *v4l2_dev;
	struct list_head async_list;
	struct v4l2_async_subdev *asd;
	struct v4l2_async_notifier *notifier;
};

异步通知相关数据结构:

struct v4l2_async_notifier {
	unsigned int num_subdevs;
	struct v4l2_async_subdev **subdevs;
	struct v4l2_device *v4l2_dev;
	/* 通知链表的3种状态 */
	struct list_head waiting;
	struct list_head done;
	struct list_head list; 
	int (*bound)(struct v4l2_async_notifier *notifier,
		     struct v4l2_subdev *subdev,
		     struct v4l2_async_subdev *asd);
	...
};

驱动中定义的大涉及到整个驱动的数据结构。

struct mx6s_csi_dev {
	struct device		*dev;
	struct video_device *vdev;
	struct v4l2_subdev	*sd;
	struct v4l2_device	v4l2_dev;

	/* 核心结构体 */
	struct vb2_queue		vb2_vidq;	//video queue
	struct v4l2_ctrl_handler	ctrl_handler;

	/* 维护了3种链表 */
	struct list_head	capture;
	struct list_head	active_bufs;
	struct list_head	discard;
	
	struct v4l2_async_subdev	asd;
	struct v4l2_async_notifier	subdev_notifier;
	struct v4l2_async_subdev	*async_subdevs[2];
	...
};

重要函数

主驱动中在notifier上注册一个异步子设备。

在csi1_bridge驱动中调用了下面的函数来注册mipi_csi异步子设备:

/* 1.遍历获取所有的endpoint
 * 2.将所有的endpoint转换成async_subdev,并添加到notifier
 */
static int mx6sx_register_subdevs(struct mx6s_csi_dev *csi_dev)
{
	struct device_node *parent = csi_dev->dev->of_node;
	struct device_node *node, *port, *rem;

	/* 只解析当前设备的节点 */
	/* Attach sensors linked to csi receivers */
	for_each_available_child_of_node(parent, node) {
		/* The csi node can have only port subnode. */
		port = of_get_next_child(node, NULL);
		/* 获取和本地port关联的远程port的节点 
		 * rem: remote 的缩写
		 */
		rem = of_graph_get_remote_port_parent(port);
		csi_dev->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
		/* rem 这里代表的mipi_csi */
		csi_dev->asd.match.fwnode.fwnode = of_fwnode_handle(rem);
		/* csi_dev->asd是上面才添加的信息,也是上面代码段的主要目的 */
		csi_dev->async_subdevs[0] = &csi_dev->asd;
	}

	/* 将上面代码段中获取到的信息填充到notefier中 */
	csi_dev->subdev_notifier.subdevs = csi_dev->async_subdevs;
	csi_dev->subdev_notifier.num_subdevs = 1;
	csi_dev->subdev_notifier.bound = subdev_notifier_bound;

	/* 注册notifier */
	ret = v4l2_async_notifier_register(&csi_dev->v4l2_dev,
					&csi_dev->subdev_notifier);
	return ret;
}

下面的函数是异步通知链的核心函数。

int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,
				 struct v4l2_async_notifier *notifier)
{
	struct v4l2_subdev *sd, *tmp;
	struct v4l2_async_subdev *asd;

	notifier->v4l2_dev = v4l2_dev;
	for (i = 0; i < notifier->num_subdevs; i++) {
		asd = notifier->subdevs[i];
		switch (asd->match_type) {
		case V4L2_ASYNC_MATCH_CUSTOM:
		case V4L2_ASYNC_MATCH_DEVNAME:
		case V4L2_ASYNC_MATCH_I2C:
		case V4L2_ASYNC_MATCH_FWNODE:
			break;
		default:
			return -EINVAL;
		}
		/* 将asd的list添加到notifier的waiting中 */
		list_add_tail(&asd->list, &notifier->waiting);
	}

	/* 在subdev_list上面遍历查找sub_dev */
	list_for_each_entry_safe(sd, tmp, &subdev_list, async_list) {
		asd = v4l2_async_belongs(notifier, sd);
		/* 如果sd和asd是匹配的,将执行下面的函数 */
		ret = v4l2_async_test_notify(notifier, sd, asd);
	}

	/* Keep also completed notifiers on the list */
	/* notifier维护有3个链表,如果没有成功将对应的链表添加到notifier_list中 */
	list_add(&notifier->list, &notifier_list);
}

下面的函数主要调用了子设备注册函数和一些链表的操作。

static int v4l2_async_test_notify(struct v4l2_async_notifier *notifier,
				  struct v4l2_subdev *sd,
				  struct v4l2_async_subdev *asd)
{
	/* 如果提供了bound函数,则调用此函数 */
	if (notifier->bound) {
		ret = notifier->bound(notifier, sd, asd);
	}
	/* 在V4L2_dev设备上注册子设备 */
	ret = v4l2_device_register_subdev(notifier->v4l2_dev, sd);

	/* Remove from the waiting list */
	/* 执行到这里的时候已经匹配注册成功,需要在链表上删除 */
	list_del(&asd->list);
	sd->asd = asd;
	sd->notifier = notifier;

	/* Move from the global subdevice list to notifier's done */
	/* 移动到notifier->done链表中 */
	list_move(&sd->async_list, &notifier->done);
	...
}

下面是实际的注册函数,主要是注册调用了一些操作相关的函数。

/* 将子设备添加到v4l2设备的子设备链表上 */
int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
				struct v4l2_subdev *sd)
{
	...
	err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler, NULL);
	err = media_device_register_entity(v4l2_dev->mdev, entity);
	err = sd->internal_ops->registered(sd);
	...
	/* 添加到链表上 */
	list_add_tail(&sd->list, &v4l2_dev->subdevs);
}

小结:上面4个函数就完成了根据设备树在notifier上注册异步子设备的过程。

驱动注册子设备

/* init subdev */
static int mipi_csis_subdev_init(struct v4l2_subdev *mipi_sd,
		struct platform_device *pdev,
		const struct v4l2_subdev_ops *ops)
{
	struct csi_state *state = platform_get_drvdata(pdev);
	int ret = 0;

	v4l2_subdev_init(mipi_sd, ops);
	mipi_sd->owner = THIS_MODULE;
	snprintf(mipi_sd->name, sizeof(mipi_sd->name), "%s.%d",
		 CSIS_SUBDEV_NAME, state->index);
	mipi_sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
	mipi_sd->dev = &pdev->dev;

	state->csis_fmt = &mipi_csis_formats[0];
	state->format.code = mipi_csis_formats[0].code;
	state->format.width = MIPI_CSIS_DEF_PIX_WIDTH;
	state->format.height = MIPI_CSIS_DEF_PIX_HEIGHT;

	/* This allows to retrieve the platform device id by the host driver */
	v4l2_set_subdevdata(mipi_sd, pdev);

	/* 异步注册子设备 */
	ret = v4l2_async_register_subdev(mipi_sd);
	...
}

下面是子设备端的注册函数。

int v4l2_async_register_subdev(struct v4l2_subdev *sd)
{
	struct v4l2_async_notifier *notifier;
	...
	INIT_LIST_HEAD(&sd->async_list);
	/* 在notifier_list上遍历查找notifier,list包含有notifier上所有的成员 */
	list_for_each_entry(notifier, &notifier_list, list) {
		struct v4l2_async_subdev *asd = v4l2_async_belongs(notifier, sd);
		if (asd) {
			int ret = v4l2_async_test_notify(notifier, sd, asd);
			mutex_unlock(&list_lock);
			return ret;
		}
	}
	/* 将对应的链表添加到subdev_list中 */
	/* None matched, wait for hot-plugging */
	list_add(&sd->async_list, &subdev_list);
	...
}

后续也是调用
– v4l2_async_belongs(notifier, sd);
– v4l2_async_test_notify(notifier, sd, asd);
– v4l2_device_register_subdev(notifier->v4l2_dev, sd);
– list_add(&sd->async_list, &subdev_list);

小结
视频异步子设备注册可以分为两个部分,一部分为notifier管理的v4l2_async_subdev,另外一部分为子设备驱动的v4l2_subdev。notifier中的异步子设备都是根据设备树信息注册的。然后子设备驱动再注册自己对应的子设备。上面所涉及到的主要区别在于v4l2_async_notifier_register()和v4l2_async_register_subdev()函数。这两个函数需要仔细去分析下,原理和linux驱动中的其它总线和设备匹配非常相似。一般来说视频驱动会先解析设备树获取子设备信息后,再通过v4l2_async_notifier_register()来注册到notifier,将还没有匹配的子设备添加到waiting链表上。然后调用notifier->bound()函数来进行匹配,如果匹配成功,将调用v4l2_device_register_subdev()将subdev添加到V4L2的子设备链表上。最后调用notifier->complete()回调函数。如果匹配不成功,则将添加到v4l2自己维护的通知链表notifier_list。另一方面,在驱动中会调用v4l2_async_register_subdev()来注册子设备,将调用notifier->bound()函数来进行匹配,如果匹配成功,将调用v4l2_device_register_subdev()将subdev添加到V4L2的子设备链表上。最后调用notifier->complete()回调函数。如果匹配不成功,会将设备添加到v4l2自己维护的子设备链表subdev_list是上。

调用过程

在设备注册完成后,V4L2设备的调用过程举例如下:
在这里插入图片描述

相关内容阅读

链接: V4L2框架
链接: V4L2之设备注册
链接: V4L2之mmap()函数
链接: V4L2之events
链接: V4L2之buffer分配和映射

  • 6
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值