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的处理函数就会用到这些属性,这是它不同于其他函数的地方。

相关推荐
TeYiToKu32 分钟前
笔记整理—linux驱动开发部分(9)framebuffer驱动框架
linux·c语言·arm开发·驱动开发·笔记·嵌入式硬件·arm
dsywws35 分钟前
Linux学习笔记之时间日期和查找和解压缩指令
linux·笔记·学习
yeyuningzi43 分钟前
Debian 12环境里部署nginx步骤记录
linux·运维·服务器
上辈子杀猪这辈子学IT1 小时前
【Zookeeper集群搭建】安装zookeeper、zookeeper集群配置、zookeeper启动与关闭、zookeeper的shell命令操作
linux·hadoop·zookeeper·centos·debian
minihuabei1 小时前
linux centos 安装redis
linux·redis·centos
lldhsds2 小时前
书生大模型实战营第四期-入门岛-1. Linux前置基础
linux
wowocpp2 小时前
ubuntu 22.04 硬件配置 查看 显卡
linux·运维·ubuntu
山河君3 小时前
ubuntu使用DeepSpeech进行语音识别(包含交叉编译)
linux·ubuntu·语音识别
鹏大师运维3 小时前
【功能介绍】信创终端系统上各WPS版本的授权差异
linux·wps·授权·麒麟·国产操作系统·1024程序员节·统信uos
筱源源3 小时前
Elasticsearch-linux环境部署
linux·elasticsearch