赞
踩
一般的驱动框架中,都是分配某个结构体,然后设置注册该结构体,该结构体有个上层管理者,一般是和应用程序交互的入口,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进行注册,函数调用的层级关系如下:
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这个结构体里面的读写等函数。
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进行注册。
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)),读写时也是一样。
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函数
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函数
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此时就会被使用。
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的处理函数就会用到这些属性,这是它不同于其他函数的地方。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。