camera驅動框架分析(下)


sensor的驅動

v4l2_i2c_new_subdev_board先用client = i2c_new_device(adapter, info);創建info對應的i2c_client對象(代表着一個i2c client),並進行驅動匹配。匹配就會觸發i2c sensor驅動的probe調用。現在進入到目錄drivers/media/i2c/soc_camera/,我們還是看OV2640驅動吧,畢竟前面的板級文件里只有在定義了CONFIG_SOC_CAMERA_OV2640宏才會編譯進去。

static struct i2c_driver ov2640_i2c_driver = {
	.driver = {
		.name = "ov2640",
	},
	.probe    = ov2640_probe,
	.remove   = ov2640_remove,
	.id_table = ov2640_id,
};

module_i2c_driver(ov2640_i2c_driver);

直接看ov2640_probe。這是一個i2c sensor驅動該做的事情。同樣,直接將說明插入到代碼中:

static int ov2640_probe(struct i2c_client *client,
			const struct i2c_device_id *did)
{
	struct ov2640_priv	*priv;
	struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
	struct i2c_adapter	*adapter = to_i2c_adapter(client->dev.parent);
	int			ret;

	if (!ssdd) {
		dev_err(&adapter->dev,
			"OV2640: Missing platform_data for driver\n");
		return -EINVAL;
	}

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
		dev_err(&adapter->dev,
			"OV2640: I2C-Adapter doesn't support SMBUS\n");
		return -EIO;
	}

	priv = devm_kzalloc(&client->dev, sizeof(struct ov2640_priv), GFP_KERNEL);
	if (!priv) {
		dev_err(&adapter->dev,
			"Failed to allocate memory for private data!\n");
		return -ENOMEM;
	}

	v4l2_i2c_subdev_init(&priv->subdev, client, &ov2640_subdev_ops);//這里就是初始化v4l2框架提供的v4l2_subdev,前面說了i2c sensor在v4l2框架里是小弟,host才是老大,小弟用v4l2_subdev來描述,老大用v4l2_device來描述。這里僅僅是初始化,怎么和老大綁定起來的事情在前面還沒講完的soc_camera_probe里,后面會講解

	//這部分就是添加該sensor支持的ioctl了,采用v4l2提供的現有機制。我們會發現小弟sensor會有自己的ctrl handler,camera也有自己的ctrl handler,sensor和camera的關系屬於包含關系,一般camera都包括了sensor。camera是整體,sensor是部分	
	v4l2_ctrl_handler_init(&priv->hdl, 2);
	v4l2_ctrl_new_std(&priv->hdl, &ov2640_ctrl_ops,
			V4L2_CID_VFLIP, 0, 1, 1, 0);
	v4l2_ctrl_new_std(&priv->hdl, &ov2640_ctrl_ops,
			V4L2_CID_HFLIP, 0, 1, 1, 0);
	priv->subdev.ctrl_handler = &priv->hdl;
	if (priv->hdl.error)
		return priv->hdl.error;

	priv->clk = v4l2_clk_get(&client->dev, "mclk");//這里就是獲取之前在soc_camera_i2c_init里注冊的時鍾啦
	if (IS_ERR(priv->clk)) {
		ret = PTR_ERR(priv->clk);
		goto eclkget;
	}

	ret = ov2640_video_probe(client);//這里就是初始化sensor了,通過i2c總線進行配置,不同的sensor配置方法不一樣,這就不繼續分析了。不過有一點要說明,就是前面不是注冊了很多ioctl么,里面都會有默認值,但是分析到現在(也就是設備啟動到現在),硬件還沒有初始化到默認值,於是在ov2640_video_probe里調用v4l2_ctrl_handler_setup來將所有的ioctl執行一遍,進行初始化
	if (ret) {
		v4l2_clk_put(priv->clk);
eclkget:
		v4l2_ctrl_handler_free(&priv->hdl);
	} else {
		dev_info(&adapter->dev, "OV2640 Probed\n");
	}

	return ret;
}

從這里我們可以看到ov2640_probe主要就是分配了自己的數據結構,當然里面嵌入了嵌入v4l2框架的v4l2_subdev以及自己ioctl的支持v4l2_ctrl_handler等數據結構,並對這些數據結構進行相應的初始化以及將其綁定到i2c_clientov2640_probe執行完后,我們前面的i2c_new_device就可以返回了。v4l2_i2c_new_subdev_board在執行完i2c_new_device后,用sd = i2c_get_clientdata(client);獲取i2c sensor驅動里自己的數據結構里的v4l2_subdev。並調用v4l2_device_register_subdevv4l2_subdev與老大v4l2_device進行綁定。綁定的過程中,會將sensor里的所有的ioctl操作添加到icd的ctrl里去。這樣icd導出給應用的設備文件的某些ioctl的操作,會引起i2c sensor的ioctl也可以得到調用到。

分析到這里,基本就結束了。總結一下,camera通過通用的camera驅動將其先放到全局的鏈表中,然后camera host驅動會去探測屬於它的camera驅動,然后將camera添加進來。添加的過程中,會觸發camera里的sensor驅動進行匹配並初始化sensor,同時會將sensor驅動里的ioctl操作集合通過v4l2提供的ctrl機制添加到camera里來,最終通過video_device字符設備導出給應用層操作。下面通過幾個情景來進一步了解內部的整個流程

導出給應用層的設備文件相關信息

前文已經說過,soc_camera_probe里面會調用video_dev_create創建並初始化了video_device並通過soc_camera_probe_finish間接(調用soc_camera_video_start->video_register_device)注冊了video_device,通過__video_register_device分析可以知道,設備名及設備后綴的確立是根據類別以及注冊的先后順序來的(應用層通過設備節點/dev/videoX打開video4linux devices。/dev/videoX是一個字符設備,主設備號81,次設備號: (0~63)分配給capture設備,64127分配給radio設備,223255分配給VBI設備,128~191分配給其他類型的)。需要注意里面有一句vdev->dev_parent = vdev->v4l2_dev->dev;也就是說video_device的父是v4l2_device。還需要注意的是里面有一句vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;它是不會執行的,因為在video_dev_create里已經將其初始化了(vdev->ctrl_handler = &icd->ctrl_handler;),前文也已經說明,v4l2_device的ctrl集合同時包含了i2c sensor的ctrl集合。還需要注意注冊的時候操作集合設置為v4l2_fops,后面分析會用到它。

open操作流程

open的設備文件當然就是上面小節所說的設備文件。open操作最終會導向到字符設備注冊時提供的ops,在這里也就是v4l2_fops。因此我們直接分析v4l2_fops里面的open吧!至於怎么導向到這里的過程,屬於字符設備驅動范疇,這里不進行說明。先貼一下v4l2_open代碼:

static int v4l2_open(struct inode *inode, struct file *filp)
{
	struct video_device *vdev;
	int ret = 0;

	/* Check if the video device is available */
	mutex_lock(&videodev_lock);
	vdev = video_devdata(filp);
	/* return ENODEV if the video device has already been removed. */
	if (vdev == NULL || !video_is_registered(vdev)) {
		mutex_unlock(&videodev_lock);
		return -ENODEV;
	}
	/* and increase the device refcount */
	video_get(vdev);
	mutex_unlock(&videodev_lock);
	if (vdev->fops->open) {
		if (video_is_registered(vdev))
			ret = vdev->fops->open(filp);
		else
			ret = -ENODEV;
	}

	if (vdev->debug)
		printk(KERN_DEBUG "%s: open (%d)\n",
			video_device_node_name(vdev), ret);
	/* decrease the refcount in case of an error */
	if (ret)
		video_put(vdev);
	return ret;
}
先通過video_devdata拿到video_device對象指針。這個內部實現是通過video_device注冊的時候,會根據子設備號為索引將其放入到一個全局的video_device數組中,因此v4l2_open的時候,只需要根據當前的設備文件的子設備號為索引再到video_device里取出來即可。v4l2_open的核心操作還是通過:
if (vdev->fops->open) {
		if (video_is_registered(vdev))
			ret = vdev->fops->open(filp);
		else
			ret = -ENODEV;
	}
將open的操作最終導向到video_device的回調函數集合里的open中去,其實就是回調到通用設備驅動實現的open。該回調操作集合在video_dev_create里面初始化為soc_camera_fops。因此最終調用到了soc_camera_fops里面的open,即soc_camera_open。它主要做了兩件事情,第一,file->private_data = icd;將icd存放file->private_data中,這樣在之后的read、write、ioctl時可以直接從private_data拿icd了,免去了video_get_drvdata獲取的麻煩。第二,這件事只在第一次open該設備文件的時候會調用,主要代碼如下:
	/* Now we really have to activate the camera */
	if (icd->use_count == 1) {
		struct soc_camera_desc *sdesc = to_soc_camera_desc(icd);
		/* Restore parameters before the last close() per V4L2 API */
		struct v4l2_format f = {
			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
			.fmt.pix = {
				.width		= icd->user_width,
				.height		= icd->user_height,
				.field		= icd->field,
				.colorspace	= icd->colorspace,
				.pixelformat	=
					icd->current_fmt->host_fmt->fourcc,
			},
		};

		/* The camera could have been already on, try to reset */
		if (sdesc->subdev_desc.reset)
			sdesc->subdev_desc.reset(icd->pdev);

		ret = soc_camera_add_device(icd);
		if (ret < 0) {
			dev_err(icd->pdev, "Couldn't activate the camera: %d\n", ret);
			goto eiciadd;
		}

		ret = __soc_camera_power_on(icd);
		if (ret < 0)
			goto epower;

		pm_runtime_enable(&icd->vdev->dev);
		ret = pm_runtime_resume(&icd->vdev->dev);
		if (ret < 0 && ret != -ENOSYS)
			goto eresume;

		/*
		 * Try to configure with default parameters. Notice: this is the
		 * very first open, so, we cannot race against other calls,
		 * apart from someone else calling open() simultaneously, but
		 * .host_lock is protecting us against it.
		 */
		ret = soc_camera_set_fmt(icd, &f);
		if (ret < 0)
			goto esfmt;

		if (ici->ops->init_videobuf) {
			ici->ops->init_videobuf(&icd->vb_vidq, icd);
		} else {
			ret = ici->ops->init_videobuf2(&icd->vb2_vidq, icd);
			if (ret < 0)
				goto einitvb;
		}
		v4l2_ctrl_handler_setup(&icd->ctrl_handler);
	}

看第一行注釋其實就知道它是要做什么了,主要是激活設備,所謂的設備就是camera啦。首先復位設備,這個在板級相關文件里通過camera_link來實現reset的回調,可能會為NULL。其次,調用soc_camera_add_device來開啟clk及host的add回調。然后調用__soc_camera_power_on來讓i2c sensor控制power on,然后就是最重要的,調用ici->ops->init_videobuf或者ici->ops->init_videobuf2,即host實現的init_videobuf來初始化video buffer queue,最后調用v4l2_ctrl_handler_setup來將所有的ioctl執行一遍,進行初始化,這里用icd的ctrl集合。總的來說,open操作所做的主要是各種初始化准備工作,過程中會回調host及i2c sensor驅動實現的一些api。

close操作

close操作和open操作類似,最終會回調到soc_camera_close,它主要是做一些去初始化操作,不過因此可能多次打開了設備文件,所以它只在使用技術為0的時候徹底去初始化open所做的工作。

ioctl操作

這部分是應用操作的關鍵。應用層透過ioctl並基於v4l2提供的支持,來配置設備。對應到這里,就是配置sensor和camera host以及v4l2相關的東西。先從網上引入一張v4l2 ioctl相關的圖片,好有個大體的了解:
v4l2 ioctl

下面從正常使用v4l2獲取視頻的流程來分析ioctl

一般在open設備文件后,我們會先獲取該設備的能力集合,通過如下代碼(以下所有代碼僅僅是為了展示,正式代碼應該要做出錯檢查什么的哈)

Struct v4l2_capability cap;                                                    
                                                                                
ioctl(fd, VIDIOC_QUERYCAP, &cap);      

這里使用v4l2頭文件里定義的命令宏VIDIOC_QUERYCAP來獲取設備的能力集合。我們看看獲取的過程吧!和open操作類似,ioctl操作最終會導向到字符設備注冊時提供的v4l2_fops。因此我們直接分析v4l2_fops里面的v4l2_ioctl吧!同樣,采用代碼行內注釋方式:

static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct video_device *vdev = video_devdata(filp);
	int ret = -ENODEV;

	if (vdev->fops->unlocked_ioctl) {//如果video_device的回調函數集合里有實現unlocked_ioctl,那么進入這個里面,通用驅動
					//soc_camera實現了unlocked_ioctl,即video_ioctl2。因此,我們會進入到這個里面
		struct mutex *lock = v4l2_ioctl_get_lock(vdev, cmd);

		if (lock && mutex_lock_interruptible(lock))
			return -ERESTARTSYS;
		if (video_is_registered(vdev))
			ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);//這里就是調用通用驅動soc_camera實現的video_ioctl2了,后面會詳細分析這個函數
		if (lock)
			mutex_unlock(lock);
	} else if (vdev->fops->ioctl) {
		/* This code path is a replacement for the BKL. It is a major
		 * hack but it will have to do for those drivers that are not
		 * yet converted to use unlocked_ioctl.
		 *
		 * There are two options: if the driver implements struct
		 * v4l2_device, then the lock defined there is used to
		 * serialize the ioctls. Otherwise the v4l2 core lock defined
		 * below is used. This lock is really bad since it serializes
		 * completely independent devices.
		 *
		 * Both variants suffer from the same problem: if the driver
		 * sleeps, then it blocks all ioctls since the lock is still
		 * held. This is very common for VIDIOC_DQBUF since that
		 * normally waits for a frame to arrive. As a result any other
		 * ioctl calls will proceed very, very slowly since each call
		 * will have to wait for the VIDIOC_QBUF to finish. Things that
		 * should take 0.01s may now take 10-20 seconds.
		 *
		 * The workaround is to *not* take the lock for VIDIOC_DQBUF.
		 * This actually works OK for videobuf-based drivers, since
		 * videobuf will take its own internal lock.
		 */
		static DEFINE_MUTEX(v4l2_ioctl_mutex);
		struct mutex *m = vdev->v4l2_dev ?
			&vdev->v4l2_dev->ioctl_lock : &v4l2_ioctl_mutex;

		if (cmd != VIDIOC_DQBUF && mutex_lock_interruptible(m))
			return -ERESTARTSYS;
		if (video_is_registered(vdev))
			ret = vdev->fops->ioctl(filp, cmd, arg);
		if (cmd != VIDIOC_DQBUF)
			mutex_unlock(m);
	} else
		ret = -ENOTTY;

	return ret;
}

現在繼續看video_ioctl2

long video_ioctl2(struct file *file,
	       unsigned int cmd, unsigned long arg)
{
	return video_usercopy(file, cmd, arg, __video_do_ioctl);
}

這個函數簡單的不能再簡單了,不過之所以簡單,是因為video_usercopy幫忙做了很多事情。它實現了用戶空間和內核空間傳輸傳遞的實現,因此在__video_do_ioctl不用再考慮這些了。下面繼續看__video_do_ioctl,同樣,采用代碼行內注釋方式:

static long __video_do_ioctl(struct file *file,
		unsigned int cmd, void *arg)
{
	struct video_device *vfd = video_devdata(file);
	const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
	bool write_only = false;
	struct v4l2_ioctl_info default_info;
	const struct v4l2_ioctl_info *info;
	void *fh = file->private_data;
	struct v4l2_fh *vfh = NULL;
	int use_fh_prio = 0;
	int debug = vfd->debug;
	long ret = -ENOTTY;

	if (ops == NULL) {
		pr_warn("%s: has no ioctl_ops.\n",
				video_device_node_name(vfd));
		return ret;
	}

	if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) {//這個不會進入,因為soc_camera根本就沒設置它,所以直接忽略吧
		vfh = file->private_data;
		use_fh_prio = test_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
	}

	if (v4l2_is_known_ioctl(cmd)) {//這里是判斷是不是已知的ioctl命令調用,我們后面會看看,都有哪些已知調用
		info = &v4l2_ioctls[_IOC_NR(cmd)];

	        if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) &&//檢測ioctl是否有效
		    !((info->flags & INFO_FL_CTRL) && vfh && vfh->ctrl_handler))
			goto done;

		if (use_fh_prio && (info->flags & INFO_FL_PRIO)) {//優先級檢測,暫時也忽略吧
			ret = v4l2_prio_check(vfd->prio, vfh->prio);
			if (ret)
				goto done;
		}
	} else {//如果不是已知ioctl調用
		default_info.ioctl = cmd;
		default_info.flags = 0;
		default_info.debug = v4l_print_default;
		info = &default_info;
	}

	write_only = _IOC_DIR(cmd) == _IOC_WRITE;
	if (info->flags & INFO_FL_STD) {//如果設置了INFO_FL_STD(后面分析v4l2_is_known_ioctl的時候,會知道哪些ioctl會設置它,哪些不會設置)
		typedef int (*vidioc_op)(struct file *file, void *fh, void *p);
		const void *p = vfd->ioctl_ops;
		const vidioc_op *vidioc = p + info->u.offset;

		ret = (*vidioc)(file, fh, arg);
	} else if (info->flags & INFO_FL_FUNC) {//如果設置了INFO_FL_FUNC(后面分析v4l2_is_known_ioctl的時候,會知道哪些ioctl會設置它,哪些不會設置)
		ret = info->u.func(ops, file, fh, arg);
	} else if (!ops->vidioc_default) {
		ret = -ENOTTY;
	} else {//如果不是已知ioctl且ops->vidioc_default不為NULL
		ret = ops->vidioc_default(file, fh,
			use_fh_prio ? v4l2_prio_check(vfd->prio, vfh->prio) >= 0 : 0,
			cmd, arg);
	}

done:
	if (debug) {
		v4l_printk_ioctl(video_device_node_name(vfd), cmd);
		if (ret < 0)
			pr_cont(": error %ld", ret);
		if (debug == V4L2_DEBUG_IOCTL)
			pr_cont("\n");
		else if (_IOC_DIR(cmd) == _IOC_NONE)
			info->debug(arg, write_only);
		else {
			pr_cont(": ");
			info->debug(arg, write_only);
		}
	}

	return ret;
}

下面看看v4l2_is_known_ioctl吧!繼續代碼內注釋,你懂得!

#define IOCTL_INFO_STD(_ioctl, _vidioc, _debug, _flags)			\
	[_IOC_NR(_ioctl)] = {						\
		.ioctl = _ioctl,					\
		.flags = _flags | INFO_FL_STD,				\
		.name = #_ioctl,					\
		.u.offset = offsetof(struct v4l2_ioctl_ops, _vidioc),	\
		.debug = _debug,					\
	}

#define IOCTL_INFO_FNC(_ioctl, _func, _debug, _flags)			\
	[_IOC_NR(_ioctl)] = {						\
		.ioctl = _ioctl,					\
		.flags = _flags | INFO_FL_FUNC,				\
		.name = #_ioctl,					\
		.u.func = _func,					\
		.debug = _debug,					\
	}

static struct v4l2_ioctl_info v4l2_ioctls[] = {
	IOCTL_INFO_FNC(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0),
	IOCTL_INFO_FNC(VIDIOC_ENUM_FMT, v4l_enum_fmt, v4l_print_fmtdesc, INFO_FL_CLEAR(v4l2_fmtdesc, type)),
	IOCTL_INFO_FNC(VIDIOC_G_FMT, v4l_g_fmt, v4l_print_format, INFO_FL_CLEAR(v4l2_format, type)),
	IOCTL_INFO_FNC(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO),
	IOCTL_INFO_FNC(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO_FNC(VIDIOC_QUERYBUF, v4l_querybuf, v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)),
	IOCTL_INFO_STD(VIDIOC_G_FBUF, vidioc_g_fbuf, v4l_print_framebuffer, 0),
	IOCTL_INFO_STD(VIDIOC_S_FBUF, vidioc_s_fbuf, v4l_print_framebuffer, INFO_FL_PRIO),
	IOCTL_INFO_FNC(VIDIOC_OVERLAY, v4l_overlay, v4l_print_u32, INFO_FL_PRIO),
	IOCTL_INFO_FNC(VIDIOC_QBUF, v4l_qbuf, v4l_print_buffer, INFO_FL_QUEUE),
	IOCTL_INFO_STD(VIDIOC_EXPBUF, vidioc_expbuf, v4l_print_exportbuffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_exportbuffer, flags)),
	IOCTL_INFO_FNC(VIDIOC_DQBUF, v4l_dqbuf, v4l_print_buffer, INFO_FL_QUEUE),
	IOCTL_INFO_FNC(VIDIOC_STREAMON, v4l_streamon, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO_FNC(VIDIOC_STREAMOFF, v4l_streamoff, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO_FNC(VIDIOC_G_PARM, v4l_g_parm, v4l_print_streamparm, INFO_FL_CLEAR(v4l2_streamparm, type)),
	IOCTL_INFO_FNC(VIDIOC_S_PARM, v4l_s_parm, v4l_print_streamparm, INFO_FL_PRIO),
	IOCTL_INFO_STD(VIDIOC_G_STD, vidioc_g_std, v4l_print_std, 0),
	IOCTL_INFO_FNC(VIDIOC_S_STD, v4l_s_std, v4l_print_std, INFO_FL_PRIO),
	IOCTL_INFO_FNC(VIDIOC_ENUMSTD, v4l_enumstd, v4l_print_standard, INFO_FL_CLEAR(v4l2_standard, index)),
	IOCTL_INFO_FNC(VIDIOC_ENUMINPUT, v4l_enuminput, v4l_print_enuminput, INFO_FL_CLEAR(v4l2_input, index)),
	IOCTL_INFO_FNC(VIDIOC_G_CTRL, v4l_g_ctrl, v4l_print_control, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_control, id)),
	IOCTL_INFO_FNC(VIDIOC_S_CTRL, v4l_s_ctrl, v4l_print_control, INFO_FL_PRIO | INFO_FL_CTRL),
	IOCTL_INFO_FNC(VIDIOC_G_TUNER, v4l_g_tuner, v4l_print_tuner, INFO_FL_CLEAR(v4l2_tuner, index)),
	IOCTL_INFO_FNC(VIDIOC_S_TUNER, v4l_s_tuner, v4l_print_tuner, INFO_FL_PRIO),
	IOCTL_INFO_STD(VIDIOC_G_AUDIO, vidioc_g_audio, v4l_print_audio, 0),
	IOCTL_INFO_STD(VIDIOC_S_AUDIO, vidioc_s_audio, v4l_print_audio, INFO_FL_PRIO),
	IOCTL_INFO_FNC(VIDIOC_QUERYCTRL, v4l_queryctrl, v4l_print_queryctrl, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_queryctrl, id)),
	IOCTL_INFO_FNC(VIDIOC_QUERYMENU, v4l_querymenu, v4l_print_querymenu, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_querymenu, index)),
	IOCTL_INFO_STD(VIDIOC_G_INPUT, vidioc_g_input, v4l_print_u32, 0),
	IOCTL_INFO_FNC(VIDIOC_S_INPUT, v4l_s_input, v4l_print_u32, INFO_FL_PRIO),
	IOCTL_INFO_STD(VIDIOC_G_EDID, vidioc_g_edid, v4l_print_edid, INFO_FL_CLEAR(v4l2_edid, edid)),
	IOCTL_INFO_STD(VIDIOC_S_EDID, vidioc_s_edid, v4l_print_edid, INFO_FL_PRIO | INFO_FL_CLEAR(v4l2_edid, edid)),
	IOCTL_INFO_STD(VIDIOC_G_OUTPUT, vidioc_g_output, v4l_print_u32, 0),
	IOCTL_INFO_FNC(VIDIOC_S_OUTPUT, v4l_s_output, v4l_print_u32, INFO_FL_PRIO),
	IOCTL_INFO_FNC(VIDIOC_ENUMOUTPUT, v4l_enumoutput, v4l_print_enumoutput, INFO_FL_CLEAR(v4l2_output, index)),
	IOCTL_INFO_STD(VIDIOC_G_AUDOUT, vidioc_g_audout, v4l_print_audioout, 0),
	IOCTL_INFO_STD(VIDIOC_S_AUDOUT, vidioc_s_audout, v4l_print_audioout, INFO_FL_PRIO),
	IOCTL_INFO_FNC(VIDIOC_G_MODULATOR, v4l_g_modulator, v4l_print_modulator, INFO_FL_CLEAR(v4l2_modulator, index)),
	IOCTL_INFO_STD(VIDIOC_S_MODULATOR, vidioc_s_modulator, v4l_print_modulator, INFO_FL_PRIO),
	IOCTL_INFO_FNC(VIDIOC_G_FREQUENCY, v4l_g_frequency, v4l_print_frequency, INFO_FL_CLEAR(v4l2_frequency, tuner)),
	IOCTL_INFO_FNC(VIDIOC_S_FREQUENCY, v4l_s_frequency, v4l_print_frequency, INFO_FL_PRIO),
	IOCTL_INFO_FNC(VIDIOC_CROPCAP, v4l_cropcap, v4l_print_cropcap, INFO_FL_CLEAR(v4l2_cropcap, type)),
	IOCTL_INFO_FNC(VIDIOC_G_CROP, v4l_g_crop, v4l_print_crop, INFO_FL_CLEAR(v4l2_crop, type)),
	IOCTL_INFO_FNC(VIDIOC_S_CROP, v4l_s_crop, v4l_print_crop, INFO_FL_PRIO),
	IOCTL_INFO_STD(VIDIOC_G_SELECTION, vidioc_g_selection, v4l_print_selection, 0),
	IOCTL_INFO_STD(VIDIOC_S_SELECTION, vidioc_s_selection, v4l_print_selection, INFO_FL_PRIO),
	IOCTL_INFO_STD(VIDIOC_G_JPEGCOMP, vidioc_g_jpegcomp, v4l_print_jpegcompression, 0),
	IOCTL_INFO_STD(VIDIOC_S_JPEGCOMP, vidioc_s_jpegcomp, v4l_print_jpegcompression, INFO_FL_PRIO),
	IOCTL_INFO_FNC(VIDIOC_QUERYSTD, v4l_querystd, v4l_print_std, 0),
	IOCTL_INFO_FNC(VIDIOC_TRY_FMT, v4l_try_fmt, v4l_print_format, 0),
	IOCTL_INFO_STD(VIDIOC_ENUMAUDIO, vidioc_enumaudio, v4l_print_audio, INFO_FL_CLEAR(v4l2_audio, index)),
	IOCTL_INFO_STD(VIDIOC_ENUMAUDOUT, vidioc_enumaudout, v4l_print_audioout, INFO_FL_CLEAR(v4l2_audioout, index)),
	IOCTL_INFO_FNC(VIDIOC_G_PRIORITY, v4l_g_priority, v4l_print_u32, 0),
	IOCTL_INFO_FNC(VIDIOC_S_PRIORITY, v4l_s_priority, v4l_print_u32, INFO_FL_PRIO),
	IOCTL_INFO_FNC(VIDIOC_G_SLICED_VBI_CAP, v4l_g_sliced_vbi_cap, v4l_print_sliced_vbi_cap, INFO_FL_CLEAR(v4l2_sliced_vbi_cap, type)),
	IOCTL_INFO_FNC(VIDIOC_LOG_STATUS, v4l_log_status, v4l_print_newline, 0),
	IOCTL_INFO_FNC(VIDIOC_G_EXT_CTRLS, v4l_g_ext_ctrls, v4l_print_ext_controls, INFO_FL_CTRL),
	IOCTL_INFO_FNC(VIDIOC_S_EXT_CTRLS, v4l_s_ext_ctrls, v4l_print_ext_controls, INFO_FL_PRIO | INFO_FL_CTRL),
	IOCTL_INFO_FNC(VIDIOC_TRY_EXT_CTRLS, v4l_try_ext_ctrls, v4l_print_ext_controls, INFO_FL_CTRL),
	IOCTL_INFO_STD(VIDIOC_ENUM_FRAMESIZES, vidioc_enum_framesizes, v4l_print_frmsizeenum, INFO_FL_CLEAR(v4l2_frmsizeenum, pixel_format)),
	IOCTL_INFO_STD(VIDIOC_ENUM_FRAMEINTERVALS, vidioc_enum_frameintervals, v4l_print_frmivalenum, INFO_FL_CLEAR(v4l2_frmivalenum, height)),
	IOCTL_INFO_STD(VIDIOC_G_ENC_INDEX, vidioc_g_enc_index, v4l_print_enc_idx, 0),
	IOCTL_INFO_STD(VIDIOC_ENCODER_CMD, vidioc_encoder_cmd, v4l_print_encoder_cmd, INFO_FL_PRIO | INFO_FL_CLEAR(v4l2_encoder_cmd, flags)),
	IOCTL_INFO_STD(VIDIOC_TRY_ENCODER_CMD, vidioc_try_encoder_cmd, v4l_print_encoder_cmd, INFO_FL_CLEAR(v4l2_encoder_cmd, flags)),
	IOCTL_INFO_STD(VIDIOC_DECODER_CMD, vidioc_decoder_cmd, v4l_print_decoder_cmd, INFO_FL_PRIO),
	IOCTL_INFO_STD(VIDIOC_TRY_DECODER_CMD, vidioc_try_decoder_cmd, v4l_print_decoder_cmd, 0),
	IOCTL_INFO_FNC(VIDIOC_DBG_S_REGISTER, v4l_dbg_s_register, v4l_print_dbg_register, 0),
	IOCTL_INFO_FNC(VIDIOC_DBG_G_REGISTER, v4l_dbg_g_register, v4l_print_dbg_register, 0),
	IOCTL_INFO_FNC(VIDIOC_S_HW_FREQ_SEEK, v4l_s_hw_freq_seek, v4l_print_hw_freq_seek, INFO_FL_PRIO),
	IOCTL_INFO_STD(VIDIOC_S_DV_TIMINGS, vidioc_s_dv_timings, v4l_print_dv_timings, INFO_FL_PRIO),
	IOCTL_INFO_STD(VIDIOC_G_DV_TIMINGS, vidioc_g_dv_timings, v4l_print_dv_timings, 0),
	IOCTL_INFO_FNC(VIDIOC_DQEVENT, v4l_dqevent, v4l_print_event, 0),
	IOCTL_INFO_FNC(VIDIOC_SUBSCRIBE_EVENT, v4l_subscribe_event, v4l_print_event_subscription, 0),
	IOCTL_INFO_FNC(VIDIOC_UNSUBSCRIBE_EVENT, v4l_unsubscribe_event, v4l_print_event_subscription, 0),
	IOCTL_INFO_FNC(VIDIOC_CREATE_BUFS, v4l_create_bufs, v4l_print_create_buffers, INFO_FL_PRIO | INFO_FL_QUEUE),
	IOCTL_INFO_FNC(VIDIOC_PREPARE_BUF, v4l_prepare_buf, v4l_print_buffer, INFO_FL_QUEUE),
	IOCTL_INFO_STD(VIDIOC_ENUM_DV_TIMINGS, vidioc_enum_dv_timings, v4l_print_enum_dv_timings, 0),
	IOCTL_INFO_STD(VIDIOC_QUERY_DV_TIMINGS, vidioc_query_dv_timings, v4l_print_dv_timings, 0),
	IOCTL_INFO_STD(VIDIOC_DV_TIMINGS_CAP, vidioc_dv_timings_cap, v4l_print_dv_timings_cap, INFO_FL_CLEAR(v4l2_dv_timings_cap, type)),
	IOCTL_INFO_FNC(VIDIOC_ENUM_FREQ_BANDS, v4l_enum_freq_bands, v4l_print_freq_band, 0),
	IOCTL_INFO_FNC(VIDIOC_DBG_G_CHIP_INFO, v4l_dbg_g_chip_info, v4l_print_dbg_chip_info, INFO_FL_CLEAR(v4l2_dbg_chip_info, match)),
};
#define V4L2_IOCTLS ARRAY_SIZE(v4l2_ioctls)

bool v4l2_is_known_ioctl(unsigned int cmd)
{
	if (_IOC_NR(cmd) >= V4L2_IOCTLS)//如果該命令對應的數量超過了v4l2_ioctls的大小(linux ioctl
					//的cmd有一套機制的,它由方向、類型、序號、負載數據大小組成,所以可以通過cmd提取出該cmd對應的序號),
					//那么就不是已知命令咯。v4l2根據視頻這類設備的實際情況,默認定義了一些命令
		return false;
	return v4l2_ioctls[_IOC_NR(cmd)].ioctl == cmd;//進一步判斷從cmd里提取出的序號上的成員的cmd與它是否完全相等,相等,才表示確實是已知命令啦
}

需要補充下,IOCTL_INFO_FNC定義了INFO_FL_FUNC flag,而IOCTL_INFO_STD定義了INFO_FL_STD flag,不同的命令又會再附加一些flag,比如INFO_FL_PRIOINFO_FL_CTRLINFO_FL_QUEUE等等。用IOCTL_INFO_FNC定義的命令條目,存放的是函數指針,而INFO_FL_STD定義的命令條目,存放的是相對於結構體v4l2_ioctl_ops的偏移。v4l2_ioctl_ops相信都布魔人啦,就是video_device設備的ioctl_ops啦,對應到我們這里,就是soc_camera_ioctl_ops。總結一下,有些ioctl,會直接回調v4l2_ioctls數組里面對應條目初始化時填寫的函數,有些則會回調video_device設備的ioctl_ops里面的函數,具體哪個函數,則是通過v4l2_ioctls數組里面對應條目初始化時填寫的函數偏移決定。

經過上面的層層分析,我們知道:

IOCTL_INFO_FNC(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0),

最終會被使用,看到它是用IOCTL_INFO_FNC定義的,於是我們就知道了v4l_querycap函數會被回調。下面看v4l_querycap

static int v4l_querycap(const struct v4l2_ioctl_ops *ops,
				struct file *file, void *fh, void *arg)
{
	struct v4l2_capability *cap = (struct v4l2_capability *)arg;

	cap->version = LINUX_VERSION_CODE;
	return ops->vidioc_querycap(file, fh, cap);
}

代碼很清晰,其中,ops就是__video_do_ioctl通過ops = vfd->ioctl_ops;video_device拿到的,對應到我們這里,就是soc_camera_ioctl_ops啦!函數將應用層的ioctl轉移到soc_camera_ioctl_ops里面的vidioc_querycap了。對應代碼:

static int soc_camera_querycap(struct file *file, void  *priv,
			       struct v4l2_capability *cap)
{
	struct soc_camera_device *icd = file->private_data;
	struct soc_camera_host *ici = to_soc_camera_host(icd->parent);

	WARN_ON(priv != file->private_data);

	strlcpy(cap->driver, ici->drv_name, sizeof(cap->driver));
	return ici->ops->querycap(ici, cap);
}

看到這里,估計要罵人了。或許都在想,為什么不直接調用啊,這樣一層一層回調多麻煩,對吧!這里將ioctl又轉移到ici->ops->querycap了。我認為這就是高手與菜鳥之間的差別了。菜鳥寫的代碼沒有層次感,擴展性差,模塊之間緊耦合等等。我們看到v4l_querycap屬於v4l2架構層,它負責填cap->version,因為這個是它可以完成而別的模塊不應該填寫的,不然就重復了,對吧。而soc_camera_querycap負責填充了cap->driversoc_camera_querycap是屬於通用soc_camera驅動層,它當然可以填寫cap->driver咯,不然每個host驅動都要自己填寫cap->driver,重復,對吧!v4l2框架是只與soc_camera打交道的(因此是它使用v4l2機制注冊了video_device,v4l2就認video_device),它不用知道camera host的任何信息,camera host是由soc_camera來負責的。層次是很清晰的。我們繼續看ici->ops->querycap,即isi_soc_camera_host_ops里的isi_camera_querycap

static int isi_camera_querycap(struct soc_camera_host *ici,
			       struct v4l2_capability *cap)
{
	strcpy(cap->driver, "atmel-isi");
	strcpy(cap->card, "Atmel Image Sensor Interface");
	cap->capabilities = (V4L2_CAP_VIDEO_CAPTURE |
				V4L2_CAP_STREAMING);
	return 0;
}

設備的能力集,當然只有host最清楚了,所以才由host填capabilities。不過我們看到第一行代碼貌似是多余的,也許寫該驅動的人沒像我這樣分析過吧!^_^

分析到這里,總算把VIDIOC_QUERYCAP分析完了。后面分析其他命令的時候,就不會再向上面一樣一步一步分析啦,因為都是相同的路線,最多就是這個進if,那個進else,這個直接callback,那個通過偏移callback啦!

在獲取VIDIOC_QUERYCAP后,一般會設置視頻捕獲格式,通過如下代碼(以下所有代碼僅僅是為了展示,正式代碼應該要做出錯檢查什么的哈)

fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;                                          
fmt.fmt.pix.width       = 640;                                                  
fmt.fmt.pix.height      = 480;                                                  
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;  //像素格式                         
fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;                                
ioctl(fd, VIDIOC_S_FMT, &fmt);

直接看IOCTL_INFO_FNC(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO),再看v4l_s_fmt

static int v4l_s_fmt(const struct v4l2_ioctl_ops *ops,
				struct file *file, void *fh, void *arg)
{
	struct v4l2_format *p = arg;
	struct video_device *vfd = video_devdata(file);
	bool is_vid = vfd->vfl_type == VFL_TYPE_GRABBER;
	bool is_sdr = vfd->vfl_type == VFL_TYPE_SDR;
	bool is_rx = vfd->vfl_dir != VFL_DIR_TX;
	bool is_tx = vfd->vfl_dir != VFL_DIR_RX;

	switch (p->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
		if (unlikely(!is_rx || !is_vid || !ops->vidioc_s_fmt_vid_cap))
			break;
		CLEAR_AFTER_FIELD(p, fmt.pix);
		return ops->vidioc_s_fmt_vid_cap(file, fh, arg);
	......
	......
	}
	return -EINVAL;
}

最終又調用到soc_camera_ioctl_ops里的vidioc_s_fmt_vid_cap了,即soc_camera_s_fmt_vid_cap。我想應該能想到最終會調用到host的callback吧。soc_camera_set_fmt->(ici->ops->set_fmt(icd, f));

在設置了視頻捕獲格式后,就是向驅動申請緩沖區了,通過如下代碼(以下所有代碼僅僅是為了展示,正式代碼應該要做出錯檢查什么的哈)

Struct v4l2_requestbuffers req;                                                
req.count = 4;                                                    
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;                                          
req.memory = V4L2_MEMORY_MMAP;                                                   
ioctl(fd, VIDIOC_REQBUFS, &req);

直接看IOCTL_INFO_FNC(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),再看v4l_reqbufs:

static int v4l_reqbufs(const struct v4l2_ioctl_ops *ops,
				struct file *file, void *fh, void *arg)
{
	struct v4l2_requestbuffers *p = arg;
	int ret = check_fmt(file, p->type);

	if (ret)
		return ret;

	CLEAR_AFTER_FIELD(p, memory);

	return ops->vidioc_reqbufs(file, fh, p);
}

還是一樣的,先回調到soc_camera層的vidioc_reqbufs,即soc_camera_reqbufs,最終調用到host初始化buffer相關callback了:

static int soc_camera_reqbufs(struct file *file, void *priv,
			      struct v4l2_requestbuffers *p)
{
	int ret;
	struct soc_camera_device *icd = file->private_data;
	struct soc_camera_host *ici = to_soc_camera_host(icd->parent);

	WARN_ON(priv != file->private_data);

	if (icd->streamer && icd->streamer != file)
		return -EBUSY;

	if (ici->ops->init_videobuf) {
		ret = videobuf_reqbufs(&icd->vb_vidq, p);
		if (ret < 0)
			return ret;

		ret = ici->ops->reqbufs(icd, p);
	} else {
		ret = vb2_reqbufs(&icd->vb2_vidq, p);
	}

	if (!ret && !icd->streamer)
		icd->streamer = file;

	return ret;
}

注意,v4l2使用的buffer我們可以用v4l2提供的現有的videobuf2機制就可以了。

請求完buffer,應用層一般會獲取它請求的buffer,然后對其做mmap:

獲取每個緩沖區的信息,映射到用戶空間                                            
structbuffer {                                                                  
        void  *start;                                                           
        size_t length;                                                          
} *buffers;
                                                              
buffers = calloc(req.count, sizeof(*buffers));                                  

for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {                        
	struct  v4l2_buffer buf;                                                        
	buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;                                  
	buf.memory      = V4L2_MEMORY_MMAP;                                             
	buf.index       = n_buffers;                                                    
	
	if (-1 == ioctl(fd, VIDIOC_QUERYBUF, & buf))                                    
		errno_exit("VIDIOC_QUERYBUF"); 
                          
	buffers[n_buffers].length = buf.length;
	buffers[n_buffers].start=                                                       
		mmap(NULL /* start anywhere */,                                         
		buf.length,                                                             
		PROT_READ | PROT_WRITE /* required */,                                  
		MAP_SHARED /* recommended */,                                           
		fd, buf.m.offset);                                                      
}    

VIDIOC_QUERYBUF對應的回調函數是v4l_querybuf,還是回調soc_cameravidioc_querybuf,即soc_camera_querybuf,它內部的實現就部分析了,和請求buffer的思想是一樣的。

下面看mmap的實現,對應的video_device的mmap是soc_camera_mmap,內部的實現還是和上面的一樣,如果使用自己的buffer實現,就走自己的路,如果是用v4l2提供的現有的機制實現,那么就繼續調用相應的api即可。

緩沖區mmap好后,我們剩下的就是將發命令將buffer放入到視頻采集的buffer隊列中去,並開啟流,好讓底層去填充它:

for (i =0; i < n_buffers; ++i) {                                                
    struct v4l2_buffer buf;                                                     
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;                                     
    buf.memory = V4L2_MEMORY_MMAP;                                              
    buf.index = i;                                                              
    if (-1 == ioctl(fd, VIDIOC_QBUF, &buf))                                    
          errno_exit("VIDIOC_QBUF");                                            
 } 
                                                                             
 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;                                            
 ioctl(fd, VIDIOC_STREAMON, & type);

VIDIOC_QBUF對應的回調函數是v4l_qbuf,還是回調soc_cameravidioc_qbuf,即soc_camera_qbuf,它內部的實現就部分析了,和上面類似。

VIDIOC_STREAMON對應的回調函數是v4l_streamon,還是回調soc_cameravidioc_streamon,即soc_camera_streamon,它內部的實現就部分析了,和上面類似,不過需要額外做一些事情,比如讓sensor開始工作,對吧!

static int soc_camera_streamon(struct file *file, void *priv,
			       enum v4l2_buf_type i)
{
	struct soc_camera_device *icd = file->private_data;
	struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
	int ret;

	WARN_ON(priv != file->private_data);

	if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE)
		return -EINVAL;

	if (icd->streamer != file)
		return -EBUSY;

	/* This calls buf_queue from host driver's videobuf_queue_ops */
	if (ici->ops->init_videobuf)
		ret = videobuf_streamon(&icd->vb_vidq);
	else
		ret = vb2_streamon(&icd->vb2_vidq, i);

	if (!ret)
		v4l2_subdev_call(sd, video, s_stream, 1);

	return ret;
}

這里會調用v4l2_subdev的video類ops的s_stream。如果看懂了前文的相關內容,應該知道v4l2_subdev對應的ops就是i2c sensor驅動實現的函數集!看文件ov2640.c

static struct v4l2_subdev_video_ops ov2640_subdev_video_ops = {
	.s_stream	= ov2640_s_stream,
	.g_mbus_fmt	= ov2640_g_fmt,
	.s_mbus_fmt	= ov2640_s_fmt,
	.try_mbus_fmt	= ov2640_try_fmt,
	.cropcap	= ov2640_cropcap,
	.g_crop		= ov2640_g_crop,
	.enum_mbus_fmt	= ov2640_enum_fmt,
	.g_mbus_config	= ov2640_g_mbus_config,
};

static struct v4l2_subdev_ops ov2640_subdev_ops = {
	.core	= &ov2640_subdev_core_ops,
	.video	= &ov2640_subdev_video_ops,
};

於是,這里就是調用ov2640_s_stream了。

總結

  基本上都分析完了。我突然想起另外一個問題,那就是v4l2提供的ctrl機制怎么調用的呢!i2c sensor里都添加了這些ctrl啊,對吧!這個其實是通過命令VIDIOC_G_CTRLVIDIOC_S_CTRL來調用的。我們可以通過它們來配置sensor的一些參數,比如曝光、白平衡等。我認為這篇博文應該可以幫助要寫sensor驅動或者host驅動的人吧_

基本分析完了吧!!!!!!   有什么漏掉的,歡迎大家指出!

2015年6月


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM