【genius_platform软件平台开发】第五十四讲:Linux系统之V4L2视频驱动-open函数代码详解

文章目录

  • 打开设备节点动作
    • 1. open函数
    • 2. __video_register_device函数
    • 3. v4l2_open函数

  • 有幸在项目中使用到了v4l2视频驱动,作为一个字符设备驱动程序,有必要进行一下深层次的内核代码阅读。对驱动如何实现VIDIOC_QUERYCAP、VIDIOC_REQBUFS、VIDIOC_G_FMT,VIDIOC_S_FMT、VIDIOC_QBUF、VIDIOC_STREAMON、VIDIOC_DQBUF、VIDIOC_STREAMOFF等操作展开细节查阅;
  • V4L2 的核心是 v4l2-dev.c 它向上提供统一的文件操作接口 v4l2_fops向下提供 video_device 注册接口 register_video_device ,作为一个具体的驱动,需要做的工作就是分配、设置、注册一个 video_device.框架很简单,复杂的是视频设备相关众多的 ioctl

打开设备节点动作

1. open函数

  • 阻塞模式 / 非阻塞模式
    用程序能够使用阻塞模式或非阻塞模式,打开视频设备。

阻塞操作: 是指在执行设备操作时,若不能获得资源,则挂起进程直到满足操作条件后再进行操作。被挂起的进程进入休眠(不占用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函数最后会对应到驱动的fopsopen函数,
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,
};

2. __video_register_device函数

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数组

3. v4l2_open函数

  • v4l2-dev.c中实现
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;
}


你可能感兴趣的:(3,linux系统V4L2视频驱动,v4l2,VIDIOC_QUERYCAP,VIDIOC_S_FMT,VIDIOC_REQBUFS,VIDIOC_QBUF)