第一章 UVC驱动之虚拟视频驱动
1.1视频驱动的整体框架
内核的V4L2 摄像头驱动的设计采用分离分层的思想,整个框架分为四大部分:应用空间、内核核心层、硬件驱动相关层;具体硬件设备。
视频驱动的整体框架见下图:
图1.1 V4L2驱动框架
Figure 1.1 V4L2 driver framework
1.2视频驱动代码流程分析
1.2.1 虚拟视频驱动分析myuvc.c
Linux内核版本: Linux-3.4.2
虚拟视频驱动代码路径: Linux-3.4.2/drivers/media/video/myuvc.c
myuvc驱动涉及文件:
myuvc.c 驱动的具体实现
v4l2-common.c
v4L2-dev.c video_register_device(struct video_device *vdev...);
v4L2-device.c v4l2_device_register(struct device *dev,struct v4l2_device *v4l2_device);
videobuf_core.c
videobuf_vmalloc.c
分析一个驱动程序最快捷的方法就是从驱动的入口(模块加载函数)函数开始,在驱动的入口函数里大部分就是分配、设置和注册该驱动模型的核心结构体,虚拟视频驱动程序也不例外。
1.2.1.1入口函数:
static int __init myuvc_init(void)
{
……
ret = myuvc_create_instance(i); /*创建设备*/
……
}
此入口清晰明了,直接封装了一个函数来调用myuvc_create_instance(i);这个函数才是重点内容。分析此函数要抓住一下三大要点:
1.分配video_device
2.设置这个结构体
3.注册:video_register_device
1.2.1.2分析myuvc_create_instance(i)函数:
static int __init myuvc_create_instance(int inst)
{
……
/* 1:只是用于初始化一些东西,比如自旋锁、引用计数 */
ret = v4l2_device_register(NULL, &dev->v4l2_dev);
……
/* 添加一个新标准的cortl */
dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
/* 设置亮度属性 最大值255; 最小值0;默认值127 */
dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
……
/* 添加一个客户自定义的cortl */
dev->button = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_button, NULL);
dev->int32 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int32, NULL);
……
/* 2:分配video_device 设备 */
vfd = video_device_alloc();
/*:3:设置video_device
/* 设置video_device
* vfd = myuvc_template;等价于:
* vfd->fops = &myuvc_fops, --->v4l2_file_operations
* vfd->ioctl_ops = &myuvc_ioctl_ops, ---> v4l2_ioctl_ops
*/
*vfd = myuvc_template;
vfd->debug = debug;
/*来自ret = v4l2_device_register(NULL, &dev->v4l2_dev);*/
vfd->v4l2_dev = &dev->v4l2_dev;
……
/* 注册video_device
* 根据类型VFL_TYPE_GRABBER 自动创建不同的设备节点
*/
ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr); ……
}
myuvc_create_instance函数的框架其是也很简单明了,就是围绕了三个步骤:分配、设置、注册核心结构体。
在此函数当中,重点在于对video_device结构体的设置,现在就来重点分析它到底做了哪些设置?下面就来看看
在分配video_device 设备vfd = video_device_alloc();之后,把vfd的内容设置为vfd = myuvc_template;。而myuvc_template实例的内容如下图所示:
此对象实体有myuvc_fops和myuvc_ioctl_ops,分别是v4l2_file_operations和v4l2_ioctl_ops结构体的实体。
myuvc_fops内容如下:
myuvc_ioctl_ops内容如下:
从以上的myuvc_fops和myuvc_ioctl_ops内容知道了,此次时把文件操作函数集和驱动的控制接口绑定到了video_device的fops和ioctl_ops成员当中,以后应用程序调用open、read、write、ioctl等一系列标准函数时,最终将会调用到这里绑定的两个函数操作集合。
接下来我们就仔细的分析video_register_device这个注册函数,看看此函数到底做了些什么工作?
1.2.1.3分析video_register_device函数:
由于此函数涉及到大量的代码调用流程,因此下面直接利用代码的调用关系进行分析:
video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
__video_register_device(vdev, type, nr, 1, vdev->fops->owner);
各种参数判断
/* 根据类型得到设备的名字
* 比如video0 video1 .......
*/
switch (type) {
case VFL_TYPE_GRABBER:
name_base = "video";
break;
……
}
/* 也根据不同的类型设置次设备号 */
switch (type) {
case VFL_TYPE_GRABBER:
minor_offset = 0;
minor_cnt = 64;
break;
……
}
……
/* 分配字符设备核心结构 */
vdev->cdev = cdev_alloc();
/* 设置字符设备的file_operations */
vdev->cdev->ops = &v4l2_fops;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
/* 注册字符设备 */
ret = device_register(&vdev->dev);
/* 以次设备号为数组下标,
* 把video_device 存入video_device数组
*/
video_device[vdev->minor] = vdev; video_register_device函数的框架也很简单了,核心还是分配、设置、注册字符设备驱动核心结构体,这个函数位于核心层v4l2_dev.c里面。在此函数里面也要设置一个核心结构体并且注意到此次也有一个file_operations v4l2_fops 文件操作函数集,而在上一层的myuvc_create_instance函数里面也有一个v4l2_file_operations myuvc_fops文件操作函数集。那么这两个文件操作函数集合到底有什么联系?关系就是核心层的v4l2_fops文件操作函数集将会通过某种方式去调用到驱动相关层的myuvc_fops文件操作函数集。下面就以open、ioctl等函数为例子解析。
1:open过程
假设应用程序去open一个摄像头设备“/dev/videoX”得到一个文件句柄,了解过Linux系统编程的人就会明白,此文件句柄里面会有文件设备的主设备号、次设备号等等信息。此时,通过次设备号最终会导致驱动核心层的open函数被调用,也就是v4l2_fops.open : v4l2_open函数。
App: fd=open("/dev/video0",....)
Drv: v4l2_fops.open
即:static int v4l2_open(struct inode *inode, struct file *filp)
/* 定义一个video_device 结构体 */
struct video_device *vdev;
/* 根据次设备号从数组中得到video_device
* video_device 是在核心层的video_register_device
* 过程以次设备号为小标存入video_device数组
*/
vdev = video_devdata(filp);
……
/* 如果 video_device 里面的video_device. fops有提供
* open函数就调用它的open函数也就是
* v4l2_fh_open函数,此时完成了核心层
* 到硬件驱动层
*/
if (vdev->fops->open)
2. ioctl
app: ioctl
drv: v4l2_fops.unlocked_ioctl
v4l2_ioctl
/* 根据次设备号从数组中得到video_device
* video_device 是在核心层的video_register_device
* 过程以次设备号为小标存入video_device数组
*/
struct video_device *vdev = video_devdata(filp);
/* 如果 video_device 里面的video_device. fops有提供
* unlocked_ioctl 就调用它的unlocked_ioctl即video_ioctl2
*/
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
video_ioctl2
/* 根据应用空间传递来的参数
* 去调用__video_do_ioctl 函数
*/
video_usercopy(file, cmd, arg, __video_do_ioctl);
__video_do_ioctl
// 根据次设备号从数组中得到video_device
struct video_device *vfd = video_devdata(file);
根据APP传入的cmd来获得、设置"某些属性" case VIDIOC_QUERYCAP: capabilities
{ …… }
case VIDIOC_G_PRIORITY: 优先级
{……}
……
问题:这些cmd在哪里添加设置进去的?
答:在驱动相关代码的vivi_create_instance函数当中:
//设置cotrl属性用于app的ioctrl
/* 添加一个新标准的iocotrl */
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,
/* 设置亮度属性 最大值255; 最小值0;默认值127 */
V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_CONTRAST, 0, 255, 1, 16);
……
/* 添加一个客户自定义的cortl */
dev->button = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_button, NULL);
dev->int32 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int32, NULL);
dev->int64 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int64, NULL);
1.1视频驱动的整体框架
内核的V4L2 摄像头驱动的设计采用分离分层的思想,整个框架分为四大部分:应用空间、内核核心层、硬件驱动相关层;具体硬件设备。
视频驱动的整体框架见下图:
图1.1 V4L2驱动框架
Figure 1.1 V4L2 driver framework
1.2视频驱动代码流程分析
1.2.1 虚拟视频驱动分析myuvc.c
Linux内核版本: Linux-3.4.2
虚拟视频驱动代码路径: Linux-3.4.2/drivers/media/video/myuvc.c
myuvc驱动涉及文件:
myuvc.c 驱动的具体实现
v4l2-common.c
v4L2-dev.c video_register_device(struct video_device *vdev...);
v4L2-device.c v4l2_device_register(struct device *dev,struct v4l2_device *v4l2_device);
videobuf_core.c
videobuf_vmalloc.c
分析一个驱动程序最快捷的方法就是从驱动的入口(模块加载函数)函数开始,在驱动的入口函数里大部分就是分配、设置和注册该驱动模型的核心结构体,虚拟视频驱动程序也不例外。
1.2.1.1入口函数:
static int __init myuvc_init(void)
{
……
ret = myuvc_create_instance(i); /*创建设备*/
……
}
此入口清晰明了,直接封装了一个函数来调用myuvc_create_instance(i);这个函数才是重点内容。分析此函数要抓住一下三大要点:
1.分配video_device
2.设置这个结构体
3.注册:video_register_device
1.2.1.2分析myuvc_create_instance(i)函数:
static int __init myuvc_create_instance(int inst)
{
……
/* 1:只是用于初始化一些东西,比如自旋锁、引用计数 */
ret = v4l2_device_register(NULL, &dev->v4l2_dev);
……
/* 添加一个新标准的cortl */
dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
/* 设置亮度属性 最大值255; 最小值0;默认值127 */
dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
……
/* 添加一个客户自定义的cortl */
dev->button = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_button, NULL);
dev->int32 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int32, NULL);
……
/* 2:分配video_device 设备 */
vfd = video_device_alloc();
/*:3:设置video_device
/* 设置video_device
* vfd = myuvc_template;等价于:
* vfd->fops = &myuvc_fops, --->v4l2_file_operations
* vfd->ioctl_ops = &myuvc_ioctl_ops, ---> v4l2_ioctl_ops
*/
*vfd = myuvc_template;
vfd->debug = debug;
/*来自ret = v4l2_device_register(NULL, &dev->v4l2_dev);*/
vfd->v4l2_dev = &dev->v4l2_dev;
……
/* 注册video_device
* 根据类型VFL_TYPE_GRABBER 自动创建不同的设备节点
*/
ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr); ……
}
myuvc_create_instance函数的框架其是也很简单明了,就是围绕了三个步骤:分配、设置、注册核心结构体。
在此函数当中,重点在于对video_device结构体的设置,现在就来重点分析它到底做了哪些设置?下面就来看看
在分配video_device 设备vfd = video_device_alloc();之后,把vfd的内容设置为vfd = myuvc_template;。而myuvc_template实例的内容如下图所示:
此对象实体有myuvc_fops和myuvc_ioctl_ops,分别是v4l2_file_operations和v4l2_ioctl_ops结构体的实体。
myuvc_fops内容如下:
myuvc_ioctl_ops内容如下:
从以上的myuvc_fops和myuvc_ioctl_ops内容知道了,此次时把文件操作函数集和驱动的控制接口绑定到了video_device的fops和ioctl_ops成员当中,以后应用程序调用open、read、write、ioctl等一系列标准函数时,最终将会调用到这里绑定的两个函数操作集合。
接下来我们就仔细的分析video_register_device这个注册函数,看看此函数到底做了些什么工作?
1.2.1.3分析video_register_device函数:
由于此函数涉及到大量的代码调用流程,因此下面直接利用代码的调用关系进行分析:
video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
__video_register_device(vdev, type, nr, 1, vdev->fops->owner);
各种参数判断
/* 根据类型得到设备的名字
* 比如video0 video1 .......
*/
switch (type) {
case VFL_TYPE_GRABBER:
name_base = "video";
break;
……
}
/* 也根据不同的类型设置次设备号 */
switch (type) {
case VFL_TYPE_GRABBER:
minor_offset = 0;
minor_cnt = 64;
break;
……
}
……
/* 分配字符设备核心结构 */
vdev->cdev = cdev_alloc();
/* 设置字符设备的file_operations */
vdev->cdev->ops = &v4l2_fops;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
/* 注册字符设备 */
ret = device_register(&vdev->dev);
/* 以次设备号为数组下标,
* 把video_device 存入video_device数组
*/
video_device[vdev->minor] = vdev; video_register_device函数的框架也很简单了,核心还是分配、设置、注册字符设备驱动核心结构体,这个函数位于核心层v4l2_dev.c里面。在此函数里面也要设置一个核心结构体并且注意到此次也有一个file_operations v4l2_fops 文件操作函数集,而在上一层的myuvc_create_instance函数里面也有一个v4l2_file_operations myuvc_fops文件操作函数集。那么这两个文件操作函数集合到底有什么联系?关系就是核心层的v4l2_fops文件操作函数集将会通过某种方式去调用到驱动相关层的myuvc_fops文件操作函数集。下面就以open、ioctl等函数为例子解析。
1:open过程
假设应用程序去open一个摄像头设备“/dev/videoX”得到一个文件句柄,了解过Linux系统编程的人就会明白,此文件句柄里面会有文件设备的主设备号、次设备号等等信息。此时,通过次设备号最终会导致驱动核心层的open函数被调用,也就是v4l2_fops.open : v4l2_open函数。
App: fd=open("/dev/video0",....)
Drv: v4l2_fops.open
即:static int v4l2_open(struct inode *inode, struct file *filp)
/* 定义一个video_device 结构体 */
struct video_device *vdev;
/* 根据次设备号从数组中得到video_device
* video_device 是在核心层的video_register_device
* 过程以次设备号为小标存入video_device数组
*/
vdev = video_devdata(filp);
……
/* 如果 video_device 里面的video_device. fops有提供
* open函数就调用它的open函数也就是
* v4l2_fh_open函数,此时完成了核心层
* 到硬件驱动层
*/
if (vdev->fops->open)
2. ioctl
app: ioctl
drv: v4l2_fops.unlocked_ioctl
v4l2_ioctl
/* 根据次设备号从数组中得到video_device
* video_device 是在核心层的video_register_device
* 过程以次设备号为小标存入video_device数组
*/
struct video_device *vdev = video_devdata(filp);
/* 如果 video_device 里面的video_device. fops有提供
* unlocked_ioctl 就调用它的unlocked_ioctl即video_ioctl2
*/
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
video_ioctl2
/* 根据应用空间传递来的参数
* 去调用__video_do_ioctl 函数
*/
video_usercopy(file, cmd, arg, __video_do_ioctl);
__video_do_ioctl
// 根据次设备号从数组中得到video_device
struct video_device *vfd = video_devdata(file);
根据APP传入的cmd来获得、设置"某些属性" case VIDIOC_QUERYCAP: capabilities
{ …… }
case VIDIOC_G_PRIORITY: 优先级
{……}
……
问题:这些cmd在哪里添加设置进去的?
答:在驱动相关代码的vivi_create_instance函数当中:
//设置cotrl属性用于app的ioctrl
/* 添加一个新标准的iocotrl */
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,
/* 设置亮度属性 最大值255; 最小值0;默认值127 */
V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_CONTRAST, 0, 255, 1, 16);
……
/* 添加一个客户自定义的cortl */
dev->button = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_button, NULL);
dev->int32 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int32, NULL);
dev->int64 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int64, NULL);