主题:详细分析linux内核中vivv.c驱动摄像头驱动

最近学习了v4l2(video for linux two),主要是关于linux中对视频(还有其他)方面的支持。这里就分析一下内核自带的vivi.c文件,因为完全是虚拟的不涉及任何物理硬件(摄像头等),所以适合分析和了解整个v4l2框架和流程。 

说明:
1)/*  */ 为代码注释,很多时候,有一些函数比较复杂,我就在调用函数后面加上函数的实现
2)后一行比前一行缩进 代表后一行是在前一个代码里面的,被调用关系 ,有时候也用{ 、、、}
module_init(vivi_init);
vivi_init
	vivi_create_instance(i)
	{
	struct vivi_dev *dev;//申明一个vivi_dev结构体类型的变量 
	/*struct vivi_dev 
	{
		struct list_head           vivi_devlist;
		struct v4l2_device 	   v4l2_dev;//是整个v4l2树形架构的父设备(根)
		struct v4l2_ctrl_handler   ctrl_handler;//v4l2_ctrl_handler管理设备的ctrls,这些ctrls(摄像头设备)包括调节饱和度、对比度和白平衡等
		/* controls */
		struct v4l2_ctrl	   *brightness;
		struct {
			/* autogain/gain cluster */
			struct v4l2_ctrl	   *autogain;
			struct v4l2_ctrl	   *gain;
				};
		struct v4l2_ctrl	   *volume;
		/* Input Number */
		int			   input;
		/* video capture */
		struct vivi_fmt            *fmt;
		unsigned int               width, height;
		struct vb2_queue	   vb_vidq;
		enum v4l2_field		   field;
	};*/
	
	struct video_device *vfd;// video_device结构体用于在/dev目录下生成设备节点文件,把操作设备的接口暴露给用户空间
	struct v4l2_ctrl_handler *hdl;//v4l2_ctrl_handler管理设备的ctrls,这些ctrls(摄像头设备)包括调节饱和度、对比度和白平衡等
	struct vb2_queue *q;//支持流IO方式(后面会分析),驱动需要实现struct vb2_queue
	v4l2_device_register(NULL, &dev->v4l2_dev);//这里面没干啥活,就是跟着链表初始化
		int v4l2_device_register(struct device *dev(NULL), struct v4l2_device *v4l2_dev)
		{
			v4l2_dev->dev = dev(NULL);//这个后面会用到
		}
	/*
	static struct vivi_fmt formats[] = {
	{
		.name     = "4:2:2, packed, YUYV",
		.fourcc   = V4L2_PIX_FMT_YUYV,
		.depth    = 16,
	},
	*/
	dev->fmt = &formats[0];//设置格式
	dev->width = 640;//宽
	dev->height = 480;//高
	hdl = &dev->ctrl_handler;
	v4l2_ctrl_handler_init(hdl, 11);//11代表支持11种控制,该函数就是把hdl加入各种链表
	dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
	V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
	dev->v4l2_dev.ctrl_handler = hdl;
	
	/* initialize queue */
	q = &dev->vb_vidq;
	memset(q, 0, sizeof(dev->vb_vidq));
	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;//三种方式都支持,流IO,帧IO
	q->drv_priv = dev;
	q->buf_struct_size = sizeof(struct vivi_buffer);
	q->ops = &vivi_video_qops;
	q->mem_ops = &vb2_vmalloc_memops;
	vb2_queue_init(q);
	
	vfd = video_device_alloc();//动态分配一个video_device
	*vfd = vivi_template;//赋初值,vivi_template是一个实体
	vfd->debug = debug;//开调试打印
	vfd->v4l2_dev = &dev->v4l2_dev;//获取父节点,存储
	set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);//设置优先级
	
	video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);//注册video_device结构体,类型是VFL_TYPE_GRABBER, video_nr = -1,系统自动分配子设备号
	video_set_drvdata(vfd, dev);//把vivi_dev类型的dev存入video_device类型的vfd中的私有数据区中,方便后续可以找到
	dev->vfd = vfd;//作用和上面的差不多,相互记录,可以找到对方	
}

/* 分析video_register_device函数的具体实现 */
video_register_device(vfd, VFL_TYPE_GRABBER, video_nr(-1));
static inline int video_register_device(struct video_device *vdev,int type(VFL_TYPE_GRABBER), int nr(-1))
{
	return __video_register_device(vdev, type(VFL_TYPE_GRABBER), nr(-1), 1, vdev->fops->owner);
}

int __video_register_device(struct video_device *vdev, int type(VFL_TYPE_GRABBER), int nr(-1),
		int warn_if_nr_in_use(1), struct module *owner)
{
	int minor_offset = 0;
	int minor_cnt = VIDEO_NUM_DEVICES;
	const char *name_base;
	vdev->minor = -1;
	
	switch (type) 
	{
		case VFL_TYPE_GRABBER:
		name_base = "video";
		break;
	}
	vdev->vfl_type = type;
	vdev->cdev = NULL;
	if (vdev->v4l2_dev) //vdev->v4l2_dev = vfd->v4l2_dev = &dev->v4l2_dev;其中dev->v4l2_dev.ctrl_handler = hdl;
	{
		if (vdev->v4l2_dev->dev) //在v4l2_device_register函数中v4l2_dev->dev = dev(NULL);
		{
			vdev->parent = vdev->v4l2_dev->dev;
		}
		if (vdev->ctrl_handler == NULL)//dev->v4l2_dev.ctrl_handler = hdl;
		{
			vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
		}
		/* If the prio state pointer is NULL, then use the v4l2_device
		   prio state. */
		if (vdev->prio == NULL)
		{
			vdev->prio = &vdev->v4l2_dev->prio;
		}
	}
	/* Pick a device node number */
	nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);//minor_cnt = VIDEO_NUM_DEVICES(256)
上面一行代码等价于nr = devnode_find(vdev, 0 , 256);//找一个未用过的节点
	/* The device node number and minor numbers are independent, so we just find the first free minor number. */
	/* 设备节点和次设备号都独立的,所以来寻找第一个未用的设备号 */
	for (i = 0; i < VIDEO_NUM_DEVICES; i++)//在VIDEO_NUM_DEVICES(256)中找一个未用过的
		if (video_device[i] == NULL)       //找到了
			break;	
	vdev->minor = i + minor_offset;//记录次设备号,minor_offset = 0;
	vdev->num = nr;
	
	/* Part 3: Initialize the character device */
	/* 初始化字符设备 */
	vdev->cdev = cdev_alloc();//动态分配一个cdev结构体
	vdev->cdev->ops = &v4l2_fops;//操作函数集,在应用程序中调用
	/*
		v4l2_fops是在v4l2_dev.c(v4l2核心)实现
		static const struct file_operations v4l2_fops = 
		{
			.owner = THIS_MODULE,
			.read = v4l2_read,
			.write = v4l2_write,
			.open = v4l2_open,
			.get_unmapped_area = v4l2_get_unmapped_area,
			.mmap = v4l2_mmap,
			.unlocked_ioctl = v4l2_ioctl,
			.release = v4l2_release,
			.poll = v4l2_poll,
			.llseek = no_llseek,
		};
		这里举例说明一个函数,以read为例,
		static ssize_t v4l2_read(struct file *filp, char __user *buf,size_t sz, loff_t *off)
		{
		struct video_device *vdev = video_devdata(filp);//根据应用程序传入的文件找到对应的节点,
			然后在节点中找到对应的次设备号,最后再根据次设备号在video_device全局数据中找到之前存入的项
		/*	struct video_device *video_devdata(struct file *file)//代码如下:
			{
				return video_device[iminor(file->f_path.dentry->d_inode)];//其中video_device 在Part 6部分初始化的
			}
		*/
		if (!vdev->fops->read)
			return -EINVAL;
		if (vdev->lock && mutex_lock_interruptible(vdev->lock))
			return -ERESTARTSYS;
		if (video_is_registered(vdev))
			ret = vdev->fops->read(filp, buf, sz, off);
		if (vdev->lock)
			mutex_unlock(vdev->lock);
		return ret;
		}
	*/
	vdev->cdev->owner = owner;
	ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);//注册入内核
	
	/* Part 4: register the device with sysfs */
	/* 在系统文件中注册设备节点类 */
	vdev->dev.class = &video_class;
	vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
	if (vdev->parent)
		vdev->dev.parent = vdev->parent;
	dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);//name_base = "video"(part 1部分设置),vdev->num = nr(系统自动分配的);
	ret = device_register(&vdev->dev);     //在/dev下创建设备节点,因此在应用层就可以使用open read write等系统调用了,操作函数集:就是v4l2_fops里面的(vdev->cdev->ops = &v4l2_fops;)
	
#if defined(CONFIG_MEDIA_CONTROLLER)//这个宏没有被调用,所以,part 5不起作用
	/* Part 5: Register the entity. */
#endif

	/* Part 6: Activate this minor. The char device can now be used. */
	/* 激活次设备号的设备,这个字符设备可以用了 */
	set_bit(V4L2_FL_REGISTERED, &vdev->flags);//标志着以及被用过了
	video_device[vdev->minor] = vdev;//把一个video_device实体存入video_device这个全局数组中,
		后面在应用层使用open read write等操作就是根据次设备号找到数组中对应的项。例如在 Part 3中
		  vdev->cdev->ops = &v4l2_fops;中的v4l2_fops操作函数集,里面的函数就是从filp中的inode结构体中找到次设备号,
			 然后取出来,就是之前根据次设备号存进去的内容
}

========================================================================================================================
分析应用层到内核驱动的调用(以read和ioctl为例分析)

应用层:调用read()
-------------------------------------------------------------
内核层
根据__video_register_device函数中代码 part 3部分有 vdev->cdev->ops = &v4l2_fops;
为此就调用v4l2_fops中的read
	/* v4l2_fops原型如下
	static const struct file_operations v4l2_fops = 
	{
		.owner = THIS_MODULE,
		.read = v4l2_read,
		.write = v4l2_write,
		.open = v4l2_open,
		.get_unmapped_area = v4l2_get_unmapped_area,
		.mmap = v4l2_mmap,
		.unlocked_ioctl = v4l2_ioctl,
		.release = v4l2_release,
		.poll = v4l2_poll,
		.llseek = no_llseek,
	};
	*/
	
v4l2_fops中的read函数
static ssize_t v4l2_read(struct file *filp, char __user *buf,size_t sz, loff_t *off)
{
	struct video_device *vdev = video_devdata(filp);
	/*
		struct video_device *video_devdata(struct file *file)
		{
			return video_device[iminor(file->f_path.dentry->d_inode)];//分解如下
			iminor_my = iminor(file->f_path.dentry->d_inode);//根据filp找到inode,在根据inode使用函数iminor直接找到次设备号
			return video_device[iminor_my];//根据次设备号,找到之前初始化填入的内容
		}
	*/
	if (!vdev->fops->read)//判断是否为空,之前初始化了,不为空
		return -EINVAL;
	if (vdev->lock && mutex_lock_interruptible(vdev->lock))//在open调用的时候,已经解锁了
		return -ERESTARTSYS;
	if (video_is_registered(vdev))//在前面分析过了,在part6 部分被标志成已使用,set_bit(V4L2_FL_REGISTERED, &vdev->flags);//标志着以及被用过了
	/*
		static inline int video_is_registered(struct video_device *vdev)
		{
			return test_bit(V4L2_FL_REGISTERED, &vdev->flags);
		}
	*/
	ret = vdev->fops->read(filp, buf, sz, off);
	/* 这里是重点,到底是如何嵌套调用的呢? */
	1.vdev 是从本函数内上代码中video_device[iminor_my]找到,也就是之前存入的
	2.之前存入的是什么内容?
	答:是函数 __video_register_device中的vdev,也就是__video_register_device中的参数,__video_register_device(vdev,...)被
	video_register_device(vdev,....)函数调用,video_register_device在vivi_create_instance函数中 ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);这么调用
	因此,__video_register_device中的vdev就是 vfd,vivi_create_instance中vfd按照下面方式初始化
		*vfd = vivi_template;
		vfd->debug = debug;
		vfd->v4l2_dev = &dev->v4l2_dev;
	重点看一下:*vfd = vivi_template,也就是说或vdev=vivi_template,因此ret = vdev->fops->read(filp, buf, sz, off);等同于vivi_template.fops->read(filp, buf, sz, off)
	3.vivi_template.fops提供了open read write等操作函数吗?
	static struct video_device vivi_template = 
	{
		.name		= "vivi",
		.fops       = &vivi_fops,
		.ioctl_ops 	= &vivi_ioctl_ops,
		.release	= video_device_release,

		.tvnorms              = V4L2_STD_525_60,
		.current_norm         = V4L2_STD_NTSC_M,
	};
	//vivi_fops操作函数集合如下
	static const struct v4l2_file_operations vivi_fops = 
	{
		.owner		    = THIS_MODULE,
		.open           = v4l2_fh_open,
		.release        = vivi_close,
		.read           = vivi_read,
		.poll		    = vivi_poll,
		.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
		.mmap           = vivi_mmap,
	};
	
	所以vdev->fops->read(filp, buf, sz, off)最终就是调用下面的函数
	/* ------------------------------------------------------------------
		File operations for the device
	   ------------------------------------------------------------------*/
	static ssize_t vivi_read(struct file *file, char __user *data, size_t count, loff_t *ppos)
	{
		struct vivi_dev *dev = video_drvdata(file);

		dprintk(dev, 1, "read called\n");
		return vb2_read(&dev->vb_vidq, data, count, ppos,file->f_flags & O_NONBLOCK);//vb2_read是内核框架实现的关于缓冲的操作
	}
}
为此,应用层调用read函数是如何调用到内核层代码分析完毕
----------------------------------------------------------------------------------------------------------------------------------
应用层:调用ioctl()
-------------------------------------------------------------
内核层(过程和read差不多从简分析了,重点分析不一样的)
调用到 v4l2_fops->unlocked_ioctl
//在贴一遍v4l2_fops结构体,至于为什么不是调用v4l2_fops->ioctl而是v4l2_fops->unlocked_ioctl,还没搞清楚,应该就是没有ioctl,然后就只能调unlocked_ioctl了
	static const struct file_operations v4l2_fops = {
		.owner = THIS_MODULE,
		.read = v4l2_read,
		.write = v4l2_write,
		.open = v4l2_open,
		.get_unmapped_area = v4l2_get_unmapped_area,
		.mmap = v4l2_mmap,
		.unlocked_ioctl = v4l2_ioctl,
		.release = v4l2_release,
		.poll = v4l2_poll,
		.llseek = no_llseek,
	};
	
	unlocked_ioctl = v4l2_ioctl;而v4l2_ioctl是内核实现的一个函数
static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct video_device *vdev = video_devdata(filp);//同read分析,根据filp找到次设备号,然后根据次设备号在video_device[]全局数组中找到之前根据次设备填充的内容

	if (vdev->fops->unlocked_ioctl) //vdev->fops->unlocked_ioctl等价于vivi_template.vivi_fops->unlocked_ioctl
	{
		if (vdev->lock && mutex_lock_interruptible(vdev->lock))
			return -ERESTARTSYS;
		if (video_is_registered(vdev))
			ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
		if (vdev->lock)
			mutex_unlock(vdev->lock);
	} 
	else if (vdev->fops->ioctl) //vivi_template.vivi_fops里没有ioctl
	{
		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;
		
	/*	vivi_template.vivi_fops中函数集合如下
		static const struct v4l2_file_operations vivi_fops = {
			.owner		= THIS_MODULE,
			.open           = v4l2_fh_open,
			.release        = vivi_close,
			.read           = vivi_read,
			.poll		= vivi_poll,
			.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
			.mmap           = vivi_mmap,
		};
	*/
	其中video_ioctl2是内核实现的,原型如下
	long video_ioctl2(struct file *file,unsigned int cmd, unsigned long arg)
	{
		return video_usercopy(file, cmd, arg, __video_do_ioctl);//根据传入的cmd(命令),arg(参数),间接调用内核实现的另外一个函数__video_do_ioctl
	}
	
	在video_usercopy中核心点在于把应用程序的数据cmd和arg拷贝至内核空间,然后调用__video_do_ioctl函数
	long video_usercopy(struct file *file, unsigned int cmd, unsigned long arg,v4l2_kioctl func)
	{
		/* Handles IOCTL */
		err = func(file, cmd, parg);//func = __video_do_ioctl
	}
	
	//__video_do_ioctl原型如下
	static long __video_do_ioctl(struct file *file,unsigned int cmd, void *arg)
	{//这里只展示核心代码(太多了)
	
		//在此之前说一下:capabilities、priority、capture ioctls三者;
		capabilities是应用程序使用ioctl(fd,VIDIOC_QUERYBUF),用于查询摄像设备的能力
		priority是用ioctl(fd,VIDIOC_G_PRIORITY,用于查询优先级,但是一般应用层不调用
		capture ioctls是用 例如ioctl(fd,VIDIOC_ENUM_FMT),VIDIOC_ENUM_FMT是不唯一的,用于对摄像设备进行各种设置
		
		struct video_device *vfd = video_devdata(file);//vfd = vivi_template
		const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;//vfd->ioctl_ops也就是vivi_template.vivi_fops
		/*
			static struct video_device vivi_template = 
			{
				.name		= "vivi",
				.fops           = &vivi_fops,
				.ioctl_ops 	= &vivi_ioctl_ops,
				.release	= video_device_release,
				.tvnorms              = V4L2_STD_525_60,
				.current_norm         = V4L2_STD_NTSC_M,
			};
			static const struct v4l2_ioctl_ops vivi_ioctl_ops = //这里面的函数全是具有实际意义上的操作,太多不分析了
			{
				.vidioc_querycap      = vidioc_querycap,
				.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
				.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
				.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
				.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
				.vidioc_reqbufs           = vidioc_reqbufs,
				.vidioc_querybuf          = vidioc_querybuf,
				.vidioc_qbuf              = vidioc_qbuf,
				.vidioc_dqbuf             = vidioc_dqbuf,
				.vidioc_s_std             = vidioc_s_std,
				.vidioc_enum_input        = vidioc_enum_input,
				.vidioc_g_input           = vidioc_g_input,
				.vidioc_s_input           = vidioc_s_input,
				.vidioc_streamon          = vidioc_streamon,
				.vidioc_streamoff         = vidioc_streamoff,
				.vidioc_log_status        = v4l2_ctrl_log_status,
				.vidioc_subscribe_event   = v4l2_ctrl_subscribe_event,
				.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
			};
		*/
		
		switch (cmd) 
		{
			/* --- capabilities ------------------------------------------ */
			case VIDIOC_QUERYCAP:
			{
				struct v4l2_capability *cap = (struct v4l2_capability *)arg;

				if (!ops->vidioc_querycap)
					break;

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

			/* --- priority ------------------------------------------ */
			case VIDIOC_G_PRIORITY:
			{
				enum v4l2_priority *p = arg;

				if (ops->vidioc_g_priority) {
					ret = ops->vidioc_g_priority(file, fh, p);
				} else if (use_fh_prio) {
					*p = v4l2_prio_max(&vfd->v4l2_dev->prio);
					ret = 0;
				}
				break;
			}
			case VIDIOC_S_PRIORITY:
			{
				enum v4l2_priority *p = arg;

				if (!ops->vidioc_s_priority && !use_fh_prio)
					break;
				if (ops->vidioc_s_priority)
					ret = ops->vidioc_s_priority(file, fh, *p);
				else
					ret = ret_prio ? ret_prio :
						v4l2_prio_change(&vfd->v4l2_dev->prio,
									&vfh->prio, *p);
				break;
			}

			/* --- capture ioctls ---------------------------------------- */
			case VIDIOC_ENUM_FMT:
			{
				struct v4l2_fmtdesc *f = arg;

				switch (f->type) {
				case V4L2_BUF_TYPE_VIDEO_CAPTURE:
					if (likely(ops->vidioc_enum_fmt_vid_cap))
						ret = ops->vidioc_enum_fmt_vid_cap(file, fh, f);
					break;
				case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
					if (likely(ops->vidioc_enum_fmt_vid_cap_mplane))
						ret = ops->vidioc_enum_fmt_vid_cap_mplane(file,
											fh, f);
					break;
				case V4L2_BUF_TYPE_VIDEO_OVERLAY:
					if (likely(ops->vidioc_enum_fmt_vid_overlay))
						ret = ops->vidioc_enum_fmt_vid_overlay(file,
							fh, f);
					break;
				case V4L2_BUF_TYPE_VIDEO_OUTPUT:
					if (likely(ops->vidioc_enum_fmt_vid_out))
						ret = ops->vidioc_enum_fmt_vid_out(file, fh, f);
					break;
				case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
					if (likely(ops->vidioc_enum_fmt_vid_out_mplane))
						ret = ops->vidioc_enum_fmt_vid_out_mplane(file,
											fh, f);
					break;
				case V4L2_BUF_TYPE_PRIVATE:
					if (likely(ops->vidioc_enum_fmt_type_private))
						ret = ops->vidioc_enum_fmt_type_private(file,
										fh, f);
					break;
				default:
					break;
				}
			}
		}
	}
=================================================================================================================================

	

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值