一、摄像头驱动 V4L2框架分析
摄像头驱动是属于字符设备驱动程序
V4L2: vidio for linux version 2,我们分析的是linux3.4.2内核。
回顾二期,怎么写驱动?
1.构造一个file_operations:.open=drv_open .read=drv_read
2.告诉内核:register_chrdev(主设备号,名字,&file_operations)
3.入口函数:调用register_chrdev
4.出口函数:卸载
一般采用register_chrdev的代替方法:分配、设置cdev,cdev_add
而对于复杂的驱动,采用分层的概念。
例如LCD驱动中分为两层:上层通用的核心层内核已经帮我们做好,即在fbmem.c
1.构造file_operations(open read write 。。)
2.注册
3.入口、出口
我们做的是硬件相关层,供上层file_operations调用
1.分配一个fb_info 结构体
2.设置
3.注册
4.硬件相关的操作
因此,对于这种复杂的驱动,我们的做法:
1.分配某个结构体
2.设置
3.注册
4.硬件相关
现在分析V4L2框架:
把usb设备接到系统前台,会有打印信息,根据打印信息在内核里找出驱动,用dmsg命令查看;
grep "Found UVC" * -nR 搜索 在uvc_driver.c里,这是个硬件相关的驱动。
分析代码,猜测V4L2 框架 肯定也是分为至少两层 。
app 调用 open read write -->调用 v4l2_fops 里的 open read write->调用硬件相关层的video_device 里提供的函数
----------------------------------------------------------------------------------------------------------
核心层:v4l2-dev.c __video_register_device
构造:v4l2_fops(.read = v4l2_read,
.write = v4l2_write,
.open = v4l2_open, 。。。。)
注册:
vdev->cdev = cdev_alloc(); //1.字符设备cdev_alloc
vdev->cdev->ops = &v4l2_fops; //2.设置fops
cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1); //3.cdev_add
----------------------------------------------------------------------------------------------------------
硬件相关层:如uvc_driver.c Found UVC ->v4l2_device_register(这个不重要)
->video_device_alloc->video_register_device(向核心层注册)
->v4l2-dev.h->__video_register_device(v4l2-dev.c)
即分配结构体 video_device (里面的函数供上层v4l2_fops调用)
设置 注册video_register_device
以vivi.c(virtual video driver )虚拟视频驱动 作为例子
1.分配一个video_device 结构体
2.设置
3.注册 video_register_device
----------------------------------------------------------------------------------------------------------
硬件相关层
从入口函数开始分析
vivi_init
vivi_create_instance
v4l2_device_register //不是主要的
下面大堆函数是用来设置音量、亮度、增益等属性,用于APP的ioctl
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);
等等...
vfd = video_device_alloc(); //分配video_device
*vfd = vivi_template; //内容设置为vivi_template
/*最底层的vivi 操作函数*/
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,
};
vfd->v4l2_dev = &dev->v4l2_dev;
video_register_device(video_device结构体vfd, 类型VFL_TYPE_GRABBER, video_nr); //向上注册
----------------------------------------------------------------------------------------------------------
核心层
__video_register_device
根据类型VFL_TYPE_GRABBER创建不同的设备节点
case VFL_TYPE_GRABBER:
name_base = "video";
根据类型VFL_TYPE_GRABBER得到不同的次设备号
case VFL_TYPE_GRABBER:
minor_offset = 0;
minor_cnt = 64;
vdev->cdev = cdev_alloc();
vdev->cdev->ops = &v4l2_fops;
cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1); //3.cdev_add
video_device[vdev->minor] = vdev;//以次设备号为下标在数组里面得到一项把vdev存进来
分析vivi.c 的open read write ioctl 过程 -
1.open过程 -
APP :open (“/dev/video0”...) -
-
drv : v4l2_fops里的v4l2_open函数 -
vdev = video_devdata(filp)//根据次设备号从数组中得到video_device---------
if (vdev->fops->open) //如果有open函数
if (video_is_registered(vdev))
ret = vdev->fops->open(filp);//调用open 函数
调用vivi.c 里的v4l2_fh_open
2.read过程
APP :read (“/dev/video0”...)
drv :v4l2_fops里的v4l2_read 函数
struct video_device *vdev = video_devdata(filp);//根据次设备号从数组中得到video_device
if (video_is_registered(vdev))
ret = vdev->fops->read(filp, buf, sz, off);//调用read 函数
调用vivi.c 里的vivi_read
2.ioctl 过程(比较复杂)
APP :ioctl(“/dev/video0”...)
drv : v4l2_fops里的v4l2_ioctl函数
struct video_device *vdev = video_devdata(filp);//根据次设备号从数组中得到video_device
if (vdev->fops->unlocked_ioctl)
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);//调用v4l2_ioctl函数
调用vivi.c 里的video_ioctl2
这个video_ioctl2做什么呢?
video_usercopy(file, cmd, arg, __video_do_ioctl);
video_usercopy:从用户空间把用户的命令复制进来,调用__video_do_ioctl
__video_do_ioctl
struct video_device *vfd = video_devdata(file);//根据次设备号从数组中得到video_device
根据用户空间APP得到的命令(cmd) 设置某些属性
switch (cmd)
怎么设置这些属性?还得由vivi.c来提供,在vivi.c 里一开始的vivi_create_instance里设置
v4l2_ctrl_handler的使用过程
分析__video_do_ioctl
二、怎么写v4l2驱动?
1.分配、设置、注册v4l2_device v4l2_device_register video_register_device
2.分配一个video_device video_device_alloc
.vfd->v4l2_dev
.fops 设置vfd的fops 里的open、read、write 被上层调用
.ioctl_ops 设置属性被上层调用
思考:APP可以通过ioctl来设置(获得)亮度等信息,在驱动程序里,谁来接收、存储、设置到硬件(提供这些信息)?
答:在驱动程序中抽象出来一个结构体v4l2_ctrl,每个Ctrl对应其中的一项(音量、亮度等等);
由v4l2_ctrl_handler来管理他们
1.初始化
v4l2_ctrl_handler_init
2.设置
v4l2_ctrl_new_std
v4l2_ctrl_new_custom
这些函数就是创建各个属性,并且放入v4l2_ctrl_handler的链表
3.跟vdev关联
dev->v4l2_dev.ctrl_handler = hdl;