回顾:
怎么写分层驱动:
1、分配某结构体
2、设置
3、注册
4、硬件相关操作
V4L2框架可参考文章:
http://blog.csdn.net/lizuobin2/article/details/53000720
http://blog.csdn.net/lanbing510/article/details/24204655?locationNum=15&fps=1
一. V4L2框架: video for linux version 2
大致猜测:
至少分为两层:
第一层:核心层: v4l2_dev.c
1、编写file_operations结构体
2、注册
然后:
cdev_alloc
cdev->ops = v4l2_fops
cdev_add
第二层:硬件相关层:
1、分配video_device
2、设置
3、注册vido_register_device
uvc_driver.c
v4l2_device_register 这个其实并不重要
video_device_alloc
video_register_device 这个重要
虚拟视频驱动vivi.c分析:
1.分配 video_device
2.设置
3.注册:video_register_device
ViVi分析:
vivi_init
vivi_create_instance
v4l2_device_register // 不是主要, 只是用于初始化一些东西,比如自旋锁、引用计数 、并没有注册什么东西
video_device_alloc //分配vido_device设备,分配好后进行设置
// 设置
1. vfd:
.fops = &vivi_fops, //Garmen:这个fops 指的是 :v4l2_file_oprations结构体
.ioctl_ops = &vivi_ioctl_ops,
.release = video_device_release,
2.
vfd->v4l2_dev = &dev->v4l2_dev; //Garmen:dev->v4l2_dev这个结构体,实际上就是前面v4l2_device_register函数得到的结构体!
3. 设置"ctrl属性"(用于APP的ioctl):
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); //亮度 0:亮度Min,255:亮度Max,127:默认
dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_CONTRAST, 0, 255, 1, 16);
Garmen:进行好上面的分配之后,然后进行注册!
video_register_device(video_device, type:VFL_TYPE_GRABBER, nr) //Garmen: 根据这个类型type:VFL_TYPE_GRABBER,创建不同的设备节点;不同类型,次设备号不同
__video_register_device
vdev->cdev = cdev_alloc(); //Garmen: 从这里我们可以看到,不管驱动框架多么复杂,最后还是有cdev_alloc();和cdev_add;
vdev->cdev->ops = &v4l2_fops; //猜测:v4l2_fops 可能就是上面第1点里面的.fops
cdev_add
video_device[vdev->minor] = vdev; //Garmen:以次设备号为下标,将vdev结构体存入video_device,当我们调用open函数的时候就反方向操作 Garmen:对应了问1里面的问题; if (vdev->ctrl_handler == NULL) vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
附上vivi.c 中vivi_init里的vivi_create_instance函数(这是主要函数)
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;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name),
"%s-%03d", VIVI_MODULE_NAME, inst);
ret = v4l2_device_register(NULL, &dev->v4l2_dev);
if (ret)
goto free_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);
dev->hue = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_HUE, -128, 127, 1, 0);
dev->autogain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
dev->gain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_GAIN, 0, 255, 1, 100);
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);
dev->boolean = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_boolean, NULL);
dev->menu = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_menu, NULL);
dev->string = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_string, NULL);
dev->bitmask = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_bitmask, NULL);
if (hdl->error) {
ret = hdl->error;
goto unreg_dev;
}
v4l2_ctrl_auto_cluster(2, &dev->autogain, 0, true);
dev->v4l2_dev.ctrl_handler = hdl;
/* initialize locks */
spin_lock_init(&dev->slock);
/* initialize queue */
q = &dev->vb_vidq;
memset(q, 0, sizeof(dev->vb_vidq));
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
q->drv_priv = dev;
q->buf_struct_size = sizeof(struct vivi_buffer);
q->ops = &vivi_video_qops;
q->mem_ops = &vb2_vmalloc_memops;
vb2_queue_init(q);
mutex_init(&dev->mutex);
/* init video dma queues */
INIT_LIST_HEAD(&dev->vidq.active);
init_waitqueue_head(&dev->vidq.wq);
ret = -ENOMEM;
vfd = video_device_alloc();
if (!vfd)
goto unreg_dev;
*vfd = vivi_template;
vfd->debug = debug;
vfd->v4l2_dev = &dev->v4l2_dev;
set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
/*
* Provide a mutex to v4l2 core. It will be used to protect
* all fops and v4l2 ioctls.
*/
vfd->lock = &dev->mutex;
ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
if (ret < 0)
goto rel_vdev;
video_set_drvdata(vfd, dev);
/* Now that everything is fine, let's add it to device list */
list_add_tail(&dev->vivi_devlist, &vivi_devlist);
if (video_nr != -1)
video_nr++;
dev->vfd = vfd;
v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n",
video_device_node_name(vfd));
return 0;
rel_vdev:
video_device_release(vfd);
unreg_dev:
v4l2_ctrl_handler_free(hdl);
v4l2_device_unregister(&dev->v4l2_dev);
free_dev:
kfree(dev);
return ret;
}
对于一个驱动程序,你要想彻底的弄清楚它,就是要去分析它的open,read,write,ioctl过程
分析vivi.c的open,read,write,ioctl过程
1. open
app: open("/dev/video0",....) ---------------------------------------------------
drv: v4l2_fops.v4l2_open
vdev = video_devdata(filp); // 根据次设备号从数组中得到video_device
ret = vdev->fops->open(filp);
vivi_ioctl_ops.open //转到了我们硬件相关层所提供的open函数了
v4l2_fh_open
2. read
app: read .... ---------------------------------------------------
drv: v4l2_fops.v4l2_read //调用file_operations v4l2_fops
struct video_device *vdev = video_devdata(filp); //首先根据次设备号,从数组里面得到之前注册的video_device结构体
ret = vdev->fops->read(filp, buf, sz, off); //然后调用它的fops里的read函数;
3. ioctl
app: ioctl ----------------------------------------------------
drv: v4l2_fops.unlocked_ioctl
v4l2_ioctl
struct video_device *vdev = video_devdata(filp);
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
video_ioctl2 //分析了vivi_fops中的ioctl函数;
video_usercopy(file, cmd, arg, __video_do_ioctl);
__video_do_ioctl
struct video_device *vfd = video_devdata(file);
根据APP传入的cmd来获得、设置"某些属性"
//Garmen:发现大概是做了这些事情:从用户空间,根据这些参数,把用户空间的参数复制进来,
然后调用 __video_do_ioctl这个参数 ----------------------------------------------------
4、v4l2_ctrl_handler的使用过程:
__video_do_ioctl
struct video_device *vfd = video_devdata(file);
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) // 问1:在哪设置? 答1:在video_register_device
ret = v4l2_queryctrl(vfd->ctrl_handler, p);
// 根据ID在ctrl_handler里找到v4l2_ctrl,返回它的值
Garmen:我们在vivi.c里面,所有创建的v4l2_ctrl,都有一个ID!
Garmen:小结
怎么写V4L2驱动:
实际上并没有脱离字符设备驱动程序的框架!
1、分配、设置、注册 V4L2_device (实际上并不重要,但是它里面提供了一些辅助信息,比如自旋锁、引用计数 )
它的关键函数是:v4l2_device_register,得到一个结构体v4l2_device
2、分配:vido_device,调用vido_device_alloc函数
3、设置:
A:vfd,它里面有一些结构体,比如v4l2_dev,我们将它指向V4L2_device,我们以后会引用到里面的一些结构体
B : vfd里面还有很多结构体,例如vfd.fops,fops里面有open、read、write函数,被上层的V4l2_fops调用
ioctl_ops 调用
C : App可以通过ioctl来设置亮度等信息,驱动程序里,谁来接收/存储/设置到硬件
App可以通过ioctl来获得亮度等信息,驱动程序里,谁来提供这些信息呢?
我们用V4l2_ctrl
问:谁来管理?
答:V4l2_ctrl_handle
该函数主要要完成的事情
①:初始化V4l2_ctrl_handler_init,进行V4l2_ctrl_handle初始化
②:设置;添加了类似V4l2_Ctrl_new_std、V4l2_Ctrl_new_custom的各项函数
问:这些函数干嘛用呢?
答:创建各个V4l2_Ctrl,并且放入V4l2_ctrl_handle这个链表
③:跟vdev进行关联
v4l2_dev.ctrl_handler = hdl;