V4L2
的核心是 v4l2-dev.c
它向上提供统一的文件操作接口 v4l2_fops
,向下
提供 video_device
注册接口 register_video_device
,作为一个具体的驱动
,需要做的工作就是分配、设置、注册一个 video_device.框架很简单,复杂的是视频设备相关众多的 ioctl阻塞操作: 是指在执行设备操作时,若不能获得资源,则挂起进程
直到满足操作条件后再进行操作。被挂起的进程进入休眠(不占用cpu资源),被从调度器移走,直到条件满足。在设备驱动中,阻塞的实现通常是通过等待队列。
非阻塞操作: 如果使用非阻塞模式调用视频设备,在不能进行设备操作时,并不挂起
或者放弃,或者不停地查询
,直到可以进行操作(一直占用CPU资源
)。即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF
)里的东西返回给应用程序。
非阻塞应用程序通常使用 select
系统调用查询是否可以对设备进行无阻塞的访问,最终会引发设备驱动中 poll
函数执行。
(建议 V4L2
编程中使用阻塞方式打开一个设备文件,除非你能保重开始采集数据时队列里的n块缓存已有数据存在。)
int cameraFd = open(“/dev/video0″, O_RDWR| O_NONBLOCK, 0);
int cameraFd = open(”/dev/video0″, O_RDWR, 0);
open
函数最后会对应到驱动的fops
的open
函数,video_device[vdev->minor] = vdev;
vdev->cdev->ops = &v4l2_fops;
vdev->cdev->owner = owner;
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,
};
int __video_register_device(struct video_device *vdev, int type, int nr,
int warn_if_nr_in_use, struct module *owner)
{
int i = 0;
int ret;
int minor_offset = 0;
int minor_cnt = VIDEO_NUM_DEVICES;
const char *name_base;
/* A minor value of -1 marks this video device as never
having been registered */
vdev->minor = -1;
/* the release callback MUST be present */
if (WARN_ON(!vdev->release))
return -EINVAL;
/* the v4l2_dev pointer MUST be present */
if (WARN_ON(!vdev->v4l2_dev))
return -EINVAL;
/* v4l2_fh support */
spin_lock_init(&vdev->fh_lock);
INIT_LIST_HEAD(&vdev->fh_list);
/* Part 1: check device type */
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;
case VFL_TYPE_SDR:
/* Use device name 'swradio' because 'sdr' was already taken. */
name_base = "swradio";
break;
case VFL_TYPE_TOUCH:
name_base = "v4l-touch";
break;
default:
printk(KERN_ERR "%s called with unknown type: %d\n",
__func__, type);
return -EINVAL;
}
vdev->vfl_type = type;
vdev->cdev = NULL;
if (vdev->dev_parent == NULL)
vdev->dev_parent = vdev->v4l2_dev->dev;
if (vdev->ctrl_handler == NULL)
vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
/* If the prio state pointer is NULL, then use the v4l2_device
prio state. */
if (vdev->prio == NULL)
vdev->prio = &vdev->v4l2_dev->prio;
/* Part 2: find a free minor, device node number and device index. */
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
/* Keep the ranges for the first four types for historical
* reasons.
* Newer devices (not yet in place) should use the range
* of 128-191 and just pick the first free minor there
* (new style). */
switch (type) {
case VFL_TYPE_GRABBER:
minor_offset = 0;
minor_cnt = 64;
break;
case VFL_TYPE_RADIO:
minor_offset = 64;
minor_cnt = 64;
break;
case VFL_TYPE_VBI:
minor_offset = 224;
minor_cnt = 32;
break;
default:
minor_offset = 128;
minor_cnt = 64;
break;
}
#endif
/* Pick a device node number */
mutex_lock(&videodev_lock);
nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
if (nr == minor_cnt)
nr = devnode_find(vdev, 0, minor_cnt);
if (nr == minor_cnt) {
printk(KERN_ERR "could not get a free device node number\n");
mutex_unlock(&videodev_lock);
return -ENFILE;
}
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
/* 1-on-1 mapping of device node number to minor number */
i = nr;
#else
/* The device node number and minor numbers are independent, so
we just find the first free minor number. */
for (i = 0; i < VIDEO_NUM_DEVICES; i++)
if (video_device[i] == NULL)
break;
if (i == VIDEO_NUM_DEVICES) {
mutex_unlock(&videodev_lock);
printk(KERN_ERR "could not get a free minor\n");
return -ENFILE;
}
#endif
/*
* 注意:这里会设置设备子设备号,然后和设备进行关联
*/
vdev->minor = i + minor_offset;
vdev->num = nr;
devnode_set(vdev);
/*
* 注意:这里会以子设备号进行video_deice[子设备号
进行关联 video_devdata file->udev的映射*/
/* Should not happen since we thought this minor was free */
WARN_ON(video_device[vdev->minor] != NULL);
vdev->index = get_index(vdev);
video_device[vdev->minor] = vdev;
mutex_unlock(&videodev_lock);
if (vdev->ioctl_ops)
determine_valid_ioctls(vdev);
/*
* 初始化字符设备
*/
/* Part 3: Initialize the character device */
// cdev结构体的初始化
vdev->cdev = cdev_alloc();
if (vdev->cdev == NULL) {
ret = -ENOMEM;
goto cleanup;
}
// 设置cdev结构体ops
vdev->cdev->ops = &v4l2_fops;
vdev->cdev->owner = owner;
// 向系统中添加cdev
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);
vdev->dev.parent = vdev->dev_parent;
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);\
// 对应驱动查找识别会匹配已经加载好的设备驱动程序,从而执行probe函数
ret = device_register(&vdev->dev);
if (ret < 0) {
printk(KERN_ERR "%s: device_register failed\n", __func__);
goto cleanup;
}
/* Register the release callback that will be called when the last
reference to the device goes away. */
vdev->dev.release = v4l2_device_release;
if (nr != -1 && nr != vdev->num && warn_if_nr_in_use)
printk(KERN_WARNING "%s: requested %s%d, got %s\n", __func__,
name_base, nr, video_device_node_name(vdev));
/* Increase v4l2_device refcount */
v4l2_device_get(vdev->v4l2_dev);
/* Part 5: Register the entity. */
ret = video_register_media_controller(vdev, type);
// 设置的掩码位后面会用到
/* Part 6: Activate this minor. The char device can now be used. */
set_bit(V4L2_FL_REGISTERED, &vdev->flags);
return 0;
cleanup:
mutex_lock(&videodev_lock);
if (vdev->cdev)
cdev_del(vdev->cdev);
video_device[vdev->minor] = NULL;
devnode_clear(vdev);
mutex_unlock(&videodev_lock);
/* Mark this video device as never having been registered. */
vdev->minor = -1;
return ret;
}
EXPORT_SYMBOL(__video_register_device);
video_register_device
过程就不详细分析了,大概就是向核心层注册 video_device 结构体
,核心层注册字符设备
并提供一个统一的 fops
,当用户空间 read write ioctl
等,最终还是会跳转到 video_device->fops
,还有一点就是核心层会把我们注册进来的 video_device 结构
放入一个全局的 video_device数组
static int v4l2_open(struct inode *inode, struct file *filp)
{
struct video_device *vdev;
int ret = 0;
// 设备是否可用
mutex_lock(&videodev_lock);
/*
* 这里从filp中得到vdev
* 原理是获取filp的inode,然后从inode->i_rdev中获取次设备号
* 以次设备号为下标从数组video_device中获取vdev
* 注册video节点的时候保存方式是 video_device[vdev->minor] = vdev;
*/
vdev = video_devdata(filp);
// 检查设备是否已经注册,没有注册直接返回 NO DEV
/*
* video_is_registered通过判断 vdev->flags中是否置位 V4L2_FL_REGISTERED
* 驱动中注册video的时候置位代码
* set_bit(V4L2_FL_REGISTERED, &vdev->flags);
*/
if (vdev == NULL || !video_is_registered(vdev)) {
mutex_unlock(&videodev_lock);
return -ENODEV;
}
// 增加引用计数refcount
video_get(vdev);
mutex_unlock(&videodev_lock);
// 如果设备fops->open被设置且设备被注册,则执行后续的open函数指针
if (vdev->fops->open) {
if (video_is_registered(vdev))
ret = vdev->fops->open(filp);
else
ret = -ENODEV;
}
//...
// 减少引用计数
if (ret)
video_put(vdev);
return ret;
}