(二) V4L2引入(含浅析UVC)


title: V4L2引入(含浅析UVC)
date: 2019/4/23 19:00:00
toc: true

V4L2引入(含浅析UVC)

基本框架

  • V4L2全名是video for linux 2之前还有个老版本v4l,也就是video for linux 1.0版本

  • V4L2不仅仅用于摄像头,也用于视频输出接口,收音机接口等,完整的框架可以参考这里

基本框架图如下:摘录自 Linux摄像头驱动1——vivid

mark

代码入手

我们插入USB,使用dmesg查看usb的输出信息

uvcvideo: Found UVC 1.00 device USB 2.0 Camera (05a3:9310)

搜索内核源码可以找到相关函数

cd linux-3.4.2/
cd drivers/
$ grep "Found UVC" *  -nR
media/video/uvc/uvc_driver.c:1848:      uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s 

这里就涉及到UVC这个名词了,所谓的UVC也就是usb video class类的驱动,也就是USB硬件相关的驱动,也就是应该会向上注册这个驱动程序

UVC流程简述

这里简单分析下UVC驱动的流程,详细分析到USB摄像头那里,对于UVC驱动也是一个驱动,从入口函数分析下

static int __init uvc_init(void)
{
	int ret;

	uvc_debugfs_init();

	ret = usb_register(&uvc_driver.driver);
	if (ret < 0) {
		uvc_debugfs_cleanup();
		return ret;
	}

	printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")
");
	return 0;
}

注册了一个结构体,看下这个结构体的成员

struct uvc_driver uvc_driver = {
	.driver = {
		.name		= "uvcvideo",
		.probe		= uvc_probe,
		.disconnect	= uvc_disconnect,
		.suspend	= uvc_suspend,
		.resume		= uvc_resume,
		.reset_resume	= uvc_reset_resume,
		.id_table	= uvc_ids,
		.supports_autosuspend = 1,
	},
};

按照以往的经验,应该就是先匹配id_table,然后执行probe来初始化,查看下这个probe,深入分析最后是调用cdev注册了一个 v4l2_fopsfile_operation,也就是说最终app的操作函数就是这个啦.

v4l2_fops > 具体的驱动

简单流程一览,抓住关键注册函数是uvc_register_chains,而v4l2_device_register并不重要在整体上

// driversmediavideouvcuvc_driver.c
uvc_probe
	v4l2_device_register  //这个不重要,只是进行一些初始化
	..
	uvc_register_chains  //这里才是实际的注册v4l2管理结构
		>uvc_register_terms
			>uvc_register_video(dev, stream)
				>video_device_alloc						//分配
    			>vdev->v4l2_dev = &dev->vdev;
----			>vdev->fops = &uvc_fops;		//这个是最终的读写函数
				>vdev->release = uvc_release;
				>video_set_drvdata(vdev, stream);		// 设置
				>video_register_device(vdev, VFL_TYPE_GRABBER, -1) //注册
					// includemediav4l2-dev.h
					static inline int __must_check video_register_device(struct video_device *vdev,
					int type, int nr)
					{
						return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
					}
					
----这个实际的		__video_register_device 注册函数是在  driversmediavideov4l2-dev.c	也就是它为下层提供注册接口的	
__video_register_device
	>case VFL_TYPE_GRABBER:
			name_base = "video";
	> 获得一个空的次设备号	
		for (i = 0; i < VIDEO_NUM_DEVICES; i++)
			if (video_device[i] == NULL) break;
	> 这里就有字符设备驱动的了
	>vdev->cdev = cdev_alloc();
	>vdev->cdev->ops = &v4l2_fops;  //这个也就是具体的 /dev/video的fileoperation
	>vdev->cdev->owner = owner;
	>ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
	

调用流程

appopen,read,write 最终就会调用到vdev->cdev->ops=v4l2_fops

// driversmediavideov4l2-dev.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,
};

open

vdev = video_devdata(filp);  //return video_device[iminor(file->f_path.dentry->d_inode)];
	>vdev->fops->open(filp)

read

这里调用的最终就是vdev->fops->read

video_device *vdev = video_devdata(filp);
					> return video_device[iminor(file->f_path.dentry->d_inode)];
vdev->fops->read(filp, buf, sz, off)

这个是在uvc_register_video中设置的

vdev->fops = &uvc_fops;

const struct v4l2_file_operations uvc_fops = {
	.owner		= THIS_MODULE,
	.open		= uvc_v4l2_open,
	.release	= uvc_v4l2_release,
	.unlocked_ioctl	= uvc_v4l2_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl32	= uvc_v4l2_compat_ioctl32,
#endif
	.read		= uvc_v4l2_read,
	.mmap		= uvc_v4l2_mmap,
	.poll		= uvc_v4l2_poll,
#ifndef CONFIG_MMU
	.get_unmapped_area = uvc_v4l2_get_unmapped_area,
#endif
};

v4l2_ioctl

也就是调用了`uvc_v4l2_ioctl>uvc_v4l2_do_ioctl

struct video_device *vdev = video_devdata(filp);
vdev->fops->unlocked_ioctl(filp, cmd, arg)

videodev_init(字符设备驱动注册)

这个是v4l2-dev.c的入口,这里就是常规的字符设备驱动,这里使用了主设备号81

static int __init videodev_init(void)
{
	dev_t dev = MKDEV(VIDEO_MAJOR, 0);
	int ret;

	printk(KERN_INFO "Linux video capture interface: v2.00
");
	ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);
	if (ret < 0) {
		printk(KERN_WARNING "videodev: unable to get major %d
",
				VIDEO_MAJOR);
		return ret;
	}

	ret = class_register(&video_class);
	if (ret < 0) {
		unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);
		printk(KERN_WARNING "video_dev: class_register failed
");
		return -EIO;
	}

	return 0;
}
原文地址:https://www.cnblogs.com/zongzi10010/p/10764119.html