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