linux-3.4.2 的v4l2驱动框架分析

一般的驱动框架中,都是分配某个结构体,然后设置注册该结构体,该结构体有个上层管理者,一般是和应用程序交互的入口,V4l2框架框是否也是如此呢,下面进行源码分析。

首先uvc_driver.c里分配了uvc_driver结构体,然后在init函数里进行了注册,当外接的摄像头id和uvc_driver结构体的id匹配时,就会调用uvc_driver里的proble函数,在proble函数里最终依次调用了v4l2_device_register、 video_device_alloc、video_register_device,这里video_register_device才是重要的,在uvc_register_video里分配了video_device结构体并用video_register_device进行注册,函数调用的层级关系如下:

c 复制代码
uvc_probe
	v4l2_device_register
	uvc_register_chains
		uvc_register_terms
			uvc_register_video
				video_device_alloc
				video_register_device

static int uvc_register_video(struct uvc_device *dev,
		struct uvc_streaming *stream)
{
	struct video_device *vdev;
	int ret;

	/* Initialize the streaming interface with default streaming
	 * parameters.
	 */
	ret = uvc_video_init(stream);
	if (ret < 0) {
		uvc_printk(KERN_ERR, "Failed to initialize the device "
			"(%d).\n", ret);
		return ret;
	}

	uvc_debugfs_init_stream(stream);

	/* Register the device with V4L. */
	vdev = video_device_alloc();
	if (vdev == NULL) {
		uvc_printk(KERN_ERR, "Failed to allocate video device (%d).\n",
			   ret);
		return -ENOMEM;
	}

	/* We already hold a reference to dev->udev. The video device will be
	 * unregistered before the reference is released, so we don't need to
	 * get another one.
	 */
	vdev->v4l2_dev = &dev->vdev;
	vdev->fops = &uvc_fops;
	vdev->release = uvc_release;
	strlcpy(vdev->name, dev->name, sizeof vdev->name);

	/* Set the driver data before calling video_register_device, otherwise
	 * uvc_v4l2_open might race us.
	 */
	stream->vdev = vdev;
	video_set_drvdata(vdev, stream);

	ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
	if (ret < 0) {
		uvc_printk(KERN_ERR, "Failed to register video device (%d).\n",
			   ret);
		stream->vdev = NULL;
		video_device_release(vdev);
		return ret;
	}

	atomic_inc(&dev->nstreams);
	return 0;
}

video_register_device调用__video_register_device,__video_register_device是v4l2-dev.c里的函数,这里面会设置video_device 的cdev属性,cdev包含了一个file_operations结构体,并这这里被设置为v4l2_fops,这里其实就是字符设备驱动那一套,name_base 就是设备文件名的一部分,然后通过cdev_add和device_register进行添加注册,最终应用程序就是操作v4l2_fops这个结构体里面的读写等函数。

c 复制代码
int __video_register_device(struct video_device *vdev, int type, int nr,
		int warn_if_nr_in_use, struct module *owner)
{
    ...
     switch (type) {
	case VFL_TYPE_GRABBER:
		name_base = "video";
		break;
	case VFL_TYPE_VBI:
		name_base = "vbi";
		break;
	case VFL_TYPE_RADIO:
		name_base = "radio";
		break;
	case VFL_TYPE_SUBDEV:
		name_base = "v4l-subdev";
		break;
	default:
		printk(KERN_ERR "%s called with unknown type: %d\n",
		       __func__, type);
		return -EINVAL;
	}
    ...
	vdev->cdev->ops = &v4l2_fops;
	vdev->cdev->owner = owner;
	ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
	if (ret < 0) {
		printk(KERN_ERR "%s: cdev_add failed\n", __func__);
		kfree(vdev->cdev);
		vdev->cdev = NULL;
		goto cleanup;
	}

	/* 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);
	ret = device_register(&vdev->dev);
    ....
	return ret;
}

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,
#ifdef CONFIG_COMPAT
	.compat_ioctl = v4l2_compat_ioctl32,
#endif
	.release = v4l2_release,
	.poll = v4l2_poll,
	.llseek = no_llseek,
};

///

以虚拟视频驱动vivi.c为例进行分析

初始化函数里调用了vivi_create_instance,vivi_create_instance里首先调用了v4l2_device_register,该函数主要对dev->v4l2_dev进行了初始化,并提供了锁等机制,v4l2_dev主要为video_device服务的,后面会把它赋值给video_device的v4l2_dev。

然后是初始化ctrl_handler这个结构体,通过v4l2_ctrl_new_std构造一个个新的属性来设置ctrl_handler,这些属性就像形成了一个链表,主要是为了给应用程序调用ioctl的时候使用,构造完成后把链表头hdl赋值给dev->v4l2_dev.ctrl_handler,因为后面dev->v4l2_dev赋值给了video_device的v4l2_dev,所以video_device就和这个链表进行了关联。

vivi.c驱动也是使用的V4l2框架,在构造完ctrl_handler链表后,通过video_device_alloc分配了video_device ,只不过这里把vivi_template赋值给了这个video_device ,然后设置了debug 和v4l2_dev 这些属性,最终调用v4l2-dev.c里的__video_register_device进行注册。

c 复制代码
static int __init vivi_init(void)
{
	...
	ret = vivi_create_instance(i);
	...
	return ret;
}

static int __init vivi_create_instance(int inst)
{
	struct vivi_dev *dev;
	struct video_device *vfd;
	struct v4l2_ctrl_handler *hdl;
	struct vb2_queue *q;
	int ret;
     ret = v4l2_device_register(NULL, &dev->v4l2_dev);
	...

	dev->fmt = &formats[0];
	dev->width = 640;
	dev->height = 480;
	hdl = &dev->ctrl_handler;
	v4l2_ctrl_handler_init(hdl, 11);
	dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
			V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
	dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
			V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
	dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
			V4L2_CID_CONTRAST, 0, 255, 1, 16);
	dev->saturation = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
			V4L2_CID_SATURATION, 0, 255, 1, 127);
	...
	v4l2_ctrl_auto_cluster(2, &dev->autogain, 0, true);
	dev->v4l2_dev.ctrl_handler = hdl;

	
	vfd = video_device_alloc();
	if (!vfd)
		goto unreg_dev;

	*vfd = vivi_template;
	vfd->debug = debug;
	vfd->v4l2_dev = &dev->v4l2_dev;
	...

	ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
    ...
	return ret;
}

//v4l2-device.c
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
	if (v4l2_dev == NULL)
		return -EINVAL;

	INIT_LIST_HEAD(&v4l2_dev->subdevs);
	spin_lock_init(&v4l2_dev->lock);
	mutex_init(&v4l2_dev->ioctl_lock);
	v4l2_prio_init(&v4l2_dev->prio);
	kref_init(&v4l2_dev->ref);
	get_device(dev);
	v4l2_dev->dev = dev;
	if (dev == NULL) {
		/* If dev == NULL, then name must be filled in by the caller */
		WARN_ON(!v4l2_dev->name[0]);
		return 0;
	}

	/* Set name to driver name + device name if it is empty. */
	if (!v4l2_dev->name[0])
		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
			dev->driver->name, dev_name(dev));
	if (!dev_get_drvdata(dev))
		dev_set_drvdata(dev, v4l2_dev);
	return 0;
}

应用程序是如何调用的呢

应用程序其实就是操作的__video_register_device这个函数创建的设备文件,也就是执行v4l2_fops 这个结构体里面的函数,例如当打开文件时,会调用v4l2_open这个函数,而v4l2_open会通过video_devdata函数去video_device这个数组里找到对应的video_device ,video_device 是在

__video_register_device注册时存放到数组里的。v4l2_open取到video_device进行相关判断后就会调用其fops里的对应open函数(vdev->fops->open(filp)),读写时也是一样。

c 复制代码
int __video_register_device(struct video_device *vdev, int type, int nr,
		int warn_if_nr_in_use, struct module *owner)
{
	...
	vdev->cdev->ops = &v4l2_fops;
    if (vdev->v4l2_dev) {
		
		if (vdev->ctrl_handler == NULL)
		//这里会把之前的ctrl_handler给vdev->ctrl_handler,方便ioctrl的调用
			vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
		
	}
	..
	video_device[vdev->minor] = vdev;
	...
}

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,
#ifdef CONFIG_COMPAT
	.compat_ioctl = v4l2_compat_ioctl32,
#endif
	.release = v4l2_release,
	.poll = v4l2_poll,
	.llseek = no_llseek,
};

static int v4l2_open(struct inode *inode, struct file *filp)
{
	struct video_device *vdev;
	
	vdev = video_devdata(filp);
	...
	mutex_unlock(&videodev_lock);
	if (vdev->fops->open) {
		...
		if (video_is_registered(vdev))
			ret = vdev->fops->open(filp);
		...
	}
	return ret;
}

struct video_device *video_devdata(struct file *file)
{
	return video_device[iminor(file->f_path.dentry->d_inode)];
}

ioctrl是如何调用的呢,其实也是和读写类似,应用程序调用ioctrl实际上就是调用v4l2_fops里的unlocked_ioctl (v4l2_ioctl),v4l2_ioctl也是从数组里取出video_device判断后调用video_device的fops的unlocked_ioctl函数

c 复制代码
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,
#ifdef CONFIG_COMPAT
	.compat_ioctl = v4l2_compat_ioctl32,
#endif
	.release = v4l2_release,
	.poll = v4l2_poll,
	.llseek = no_llseek,
};

static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct video_device *vdev = video_devdata(filp);
	
    ...
	if (vdev->fops->unlocked_ioctl) {
		...
		if (video_is_registered(vdev))
			ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
			
			}
	...	
}

vivi_template 赋值给了video_device ,所以就是调用vivi_template 里fops的unlocked_ioctl函数(video_ioctl2),video_ioctl2最终会调用video_usercopy,video_usercopy又会调用__video_do_ioctl函数

c 复制代码
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_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,
};

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

所以ioctl最终会调用到__video_do_ioctl,__video_do_ioctl也会从数组里面取到video_device ,然后通过判断命令参数cmd会进行相关属性设置,而在vivi.c里构造的ctrl_handler此时就会被使用。

c 复制代码
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;
	void *fh = file->private_data;
	struct v4l2_fh *vfh = NULL;
	int use_fh_prio = 0;
	long ret_prio = 0;
	long ret = -ENOTTY;

	...

	switch (cmd) {

	/* --- controls ---------------------------------------------- */
	case VIDIOC_QUERYCTRL:
	{
		struct v4l2_queryctrl *p = arg;

		if (vfh && vfh->ctrl_handler)
			ret = v4l2_queryctrl(vfh->ctrl_handler, p);
		else if (vfd->ctrl_handler)
			ret = v4l2_queryctrl(vfd->ctrl_handler, p);
		else if (ops->vidioc_queryctrl)
			ret = ops->vidioc_queryctrl(file, fh, p);
		else
			break;
		if (!ret)
			dbgarg(cmd, "id=0x%x, type=%d, name=%s, min/max=%d/%d, "
					"step=%d, default=%d, flags=0x%08x\n",
					p->id, p->type, p->name,
					p->minimum, p->maximum,
					p->step, p->default_value, p->flags);
		else
			dbgarg(cmd, "id=0x%x\n", p->id);
		break;
	}
	...
	default:
		if (!ops->vidioc_default)
			break;
		ret = ops->vidioc_default(file, fh, ret_prio >= 0, cmd, arg);
		break;
	} /* switch */

	if (vfd->debug & V4L2_DEBUG_IOCTL_ARG) {
		if (ret < 0) {
			v4l_print_ioctl(vfd->name, cmd);
			printk(KERN_CONT " error %ld\n", ret);
		}
	}

	return ret;
}

总结:其实V4l2框架总体上也可分为两大层,一层是和硬件交互真正被调用的(对应vivi.c中vivi_template这个结构体),一层是上面的管理层(对应v4l2-dev.c中v4l2_fops这个结构体),应用程序执行时是调用管理层中v4l2_fops里的函数,而v4l2_fops又会调用vivi_template里fops对应的函数

具体过程为:在驱动程序(拿vivi.c来说)中分配一个video_device,然后把vivi_template的值给了video_device,接下来对video_device进行其他属性相关设置后会调用v4l2-dev.c中的__video_register_device进行注册,__video_register_device中会设置video_device的cdev的ops属性为v4l2_fops,同时会用字符设备那一套把video_device进行添加和注册,也就是会添加一个字符设备文件,最后以video_device的次设备号为下标把video_device放在一个数组里面。

应用程序调用时,其实就是去打开__video_register_device里注册的字符设备文件,本质上就是去调用v4l2_fops里的那些函数,例如当打开一个文件时,就会调用v4l2_fops的open函数,open函数里会通过打开的文件属性获得数组下标找到video_device,在进行相关判断后会调用video_device的fops的open函数,也就是vivi_template的fops的open函数,其他读写函数调用也是如此。所以V4l2框架总体上分为两大层,上层是和应该程序对接的,下层才是真正硬件操作函数,上层的操作最终会映射到下层对应的操作函数。

另外,对于ioctl来说,它的操作稍微复杂,但总体上调用流程和读写等函数差不多。因为ioctl一般就用来设置属性,例如分辨率和声音等,而这些属性在video_device注册时就进行了设置,然后再和video_device关联,最终ioctl的处理函数就会用到这些属性,这是它不同于其他函数的地方。

相关推荐
M78佐菲7 小时前
ARM学习笔记(五)
linux·arm开发·笔记·嵌入式硬件·学习
H_oRIZoN_8 小时前
Linux入门DAY45 IMX6ULL 裸机开发笔记:汇编点灯、C 语言点灯、GIC 中断梳理
linux·单片机·嵌入式硬件
离凌寒9 小时前
一、关于linux开发板上移植ubuntu根文件系统并配置桌面环境笔记记录
驱动开发·单片机
临期冰淇淋9 小时前
Unisoc 展锐平台Camera摄像头分辨率适配
linux·c语言·驱动开发
Rsingstarzengjx9 小时前
第二十八章 Linux 并发与竞争实验
驱动开发·嵌入式硬件·stm32mp157
流浪0019 小时前
Linux系统篇36——线程(一) 线程的概念、本质和Linux的实现方式
linux·面试·操作系统·线程
从零开始的嵌入式之旅10 小时前
day46
linux·arm开发·笔记·嵌入式硬件
虚无的纽扣10 小时前
【Linux】将进程知识与基础IO融会贯通——简单自定义Shell的实现
linux·ubuntu
傲世仙尊10 小时前
预处理详解-define宏井号条件编译一篇过
linux·c语言
忆挽篱笙歌10 小时前
linux基本指令
linux