linux V4L2子系统——v4l2架构(3)之video_device

linux V4L2子系统——v4l2架构(3)之video_device

备注:
  1. Kernel版本:5.4
  2. 使用工具:Source Insight 4.0
  3. 参考博客:
(1)Linux V4L2子系统-Video设备框架分析(二)
(2)linux v4l2 学习之-v4l2设备注册过程及各个设备之间的联系
(3)V4L2框架-v4l2 device

概述

在V4L2子系统中,Video设备是一个字符设备,设备节点为/dev/videoX,主设备号为81,次设备号范围为0-63。在用户空间,应用可以通过 open/close/ioctl/mmap/read/write 系统调用操作Video设备。在内核空间中,Video设备的具体操作方法由驱动中的 struct video_device 提供。驱动使用video_register_device 函数将 struct video_device 注册到V4L2的核心层,然后V4L2的核心层在向上注册一个字符设备,该字符设备实现了虚拟文件系统要求的方法。这样应用就可以使用系统调用访问虚拟文件系统中Video设备提供的方法,然后进一步访问V4L2核心层提供的 v4l2_fops 方法集合,最后通过 struct video_device 结构体中的fops和ioctl_ops方法集合访问Video主设备。Video主设备通过 v4l2_subdev_call 方法访问Video从设备,同时Video从设备可以通过notify回掉方法通知主设备发生了事件。Camera Host控制器为Video主设备,Camear Sensor(摄像头)为Video从设备,一般为I2C设备。

在这里插入图片描述

主要数据结构体介绍

详见:linux V4L2子系统——v4l2的结构体(2)之video_device

如何注册 video 类型节点

使用 video_register_device 配合 VFL_TYPE_GRABBER 参数进行注册,此时该函数执行完毕并返回的时候就可以在用户空间看到形如 /dev/videoX 的设备节点了。

// 源码: drivers/media/platform/sunxi/sun6i/sun6i-csi/sun6i-video.c
int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
		     const char *name)
{
    ......
    
    /* Register video device */
	strscpy(vdev->name, name, sizeof(vdev->name));
	vdev->release		= video_device_release_empty; //必须设置此成员,此video_device为静态创建,无动作可执行
	vdev->fops		= &sun6i_video_fops;
	vdev->ioctl_ops		= &sun6i_video_ioctl_ops;
	vdev->vfl_type		= VFL_TYPE_GRABBER; // video设备类型
	vdev->vfl_dir		= VFL_DIR_RX;            // 接收数据
	vdev->v4l2_dev		= &csi->v4l2_dev;   // video_device 与 v4l2_device 进行绑定关联
	vdev->queue		= vidq;                     // video_device 与 vb2_queue 进行绑定关联
	vdev->lock		= &video->lock;
	vdev->device_caps	= V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
	video_set_drvdata(vdev, video);

	ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
	if (ret < 0) {
		v4l2_err(&csi->v4l2_dev,
			 "video_register_device failed: %d\n", ret);
		goto release_vb2;
	}
    
    ......
}

video_device注册过程分析

video_register_device函数

// 源码:include/media/v4l2-dev.h

/**
 *  video_register_device - register video4linux devices
 *
 * @vdev: struct video_device to register
 * @type: type of device to register, as defined by &enum vfl_devnode_type
 * @nr:   which device node number is desired:
 *	(0 == /dev/video0, 1 == /dev/video1, ..., -1 == first free)
 *
 * Internally, it calls __video_register_device(). Please see its
 * documentation for more details.
 *
 * .. note::
 *	if video_register_device fails, the release() callback of
 *	&struct video_device structure is *not* called, so the caller
 *	is responsible for freeing any data. Usually that means that
 *	you video_device_release() should be called on failure.
 */
// 注册video_device结构体
// vdev-video_device结构体指针
// type-注册的设备类型,有效的设备类型如下:
//     VFL_TYPE_GRABBER - A frame grabber
//     VFL_TYPE_VBI - Vertical blank data (undecoded)
//     VFL_TYPE_RADIO - A radio card
//     VFL_TYPE_SUBDEV - A subdevice
//     VFL_TYPE_SDR - Software Defined Radio
// nr-生成的设备节点数量(0 == /dev/video0, 1 == /dev/video1, ...-1 == first free)
// warn_if_nr_in_use-若设备节点编号已被占用则发出警告,同时会选择其他设备节点编号
// owner-video设备节点所属的模块
// 返回值-0成功,小于0失败
static inline int __must_check video_register_device(struct video_device *vdev,
						     enum vfl_devnode_type type,
						     int nr)
{
	return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}

__video_register_device函数

  • (1)在注册之前必须设置release和v4l2_dev成员,前者用于设备注销时回调释放资源,后者指向了管理video_device的v4l2_device结构体。
  • (2)检查设备类型并确定设备节点基本名称。
  • (3)设置设备类型、次设备号(由设备类型和全局video_device数组决定)及设备节点数量。
  • (4)将要注册的video_device结构体指针保存到全局的video_device数组中。
  • (5)根据设备类型验证那些ioctl函数可以使用。
  • (6)分配字符设备结构体。
  • (7)设置字符设备的操作函数集合为v4l2_fops。
  • (8)将video设备注册为字符设备。
  • (9)注册设备。
  • (10)设置设备引用计数为0时的回调函数,回调函数为 v4l2_device_release。
    v4l2_device_release 主要的工作是删除注册的字符设备,回调 v4l2_device 中的 release 函数(通常是video_device_release 函数)释放 video_device 结构体内存,最后减少 v4l2_device 的引用计数(一个v4l2_device 可管理多个 video_device,当 v4l2_device 的引用计数为0时,将进行注销工作)。
  • (11)增加video_device所属v4l2_device的引用计数。
  • (12)设置已注册标志 V4L2_FL_REGISTERED。
    在这里插入图片描述
// 源码:drivers/media/v4l2-core/v4l2-dev.c

int __video_register_device(struct video_device *vdev,
			    enum vfl_devnode_type 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;
	/* the device_caps field MUST be set for all but subdevs */
	if (WARN_ON(type != VFL_TYPE_SUBDEV && !vdev->device_caps))
		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:
		pr_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) {
		pr_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. */
	/* 查找到第一个空闲的minor号,可以理解成是次设备号*/
	for (i = 0; i < VIDEO_NUM_DEVICES; i++)
		if (video_devices[i] == NULL) //所有注册的video_device都会保存到这个数组中
			break;
	if (i == VIDEO_NUM_DEVICES) {
		mutex_unlock(&videodev_lock);
		pr_err("could not get a free minor\n");
		return -ENFILE;
	}
#endif

	// minor_offset一般是0,i就是查找到的空闲次设备号,
	// 这里总的次设备支持到256个
	vdev->minor = i + minor_offset;
	vdev->num = nr;

	/* Should not happen since we thought this minor was free */
	if (WARN_ON(video_devices[vdev->minor])) {
		mutex_unlock(&videodev_lock);
		pr_err("video_device not empty!\n");
		return -ENFILE;
	}
	devnode_set(vdev); //将标准位置为已用

	/* 下面这个方法字面意思看起来是获取一个index,但是查看源代码会发现
	   “这里会去查找不是直系的设备空闲号”,就是说只有video_device不为空,
	   而且v4l2 parent对象相同都会认为是同类,直接跳过相应的index号 */
	vdev->index = get_index(vdev);
	video_devices[vdev->minor] = vdev;
	mutex_unlock(&videodev_lock);

	if (vdev->ioctl_ops)
		determine_valid_ioctls(vdev);

	/* Part 3: Initialize the character device */
	// 申请一个字符设备
	vdev->cdev = cdev_alloc();
	if (vdev->cdev == NULL) {
		ret = -ENOMEM;
		goto cleanup;
	}

	// 务必留意这个ioctl,后面子设备中的ioctl都是通过这里查找的。
	vdev->cdev->ops = &v4l2_fops;
	vdev->cdev->owner = owner;

	// 添加一个字符设备
	ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
	if (ret < 0) {
		pr_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);
	ret = device_register(&vdev->dev);
	if (ret < 0) {
		pr_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)
		pr_warn("%s: requested %s%d, got %s\n", __func__,
			name_base, nr, video_device_node_name(vdev));

	/* Increase v4l2_device refcount */
	/* 下面这一步很关键,增加v4l2设备对象引用计数 */
	v4l2_device_get(vdev->v4l2_dev);

	/* Part 5: Register the entity. */
	ret = video_register_media_controller(vdev);

	/* 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_devices[vdev->minor] = NULL;
	devnode_clear(vdev);
	mutex_unlock(&videodev_lock);
	/* Mark this video device as never having been registered. */
	vdev->minor = -1;
	return ret;
}

由这里可以发现,创建video_device时,也创建了一个字符设备。并将该设备的parent节点指定为v4l2_device所依附的那个节点。主要需要注意下面几点。

1 .根据设备类型确定设备名字和次设备数量
由于系统可能包含很多媒体设备,所以v4l2核心将0~255次设备编号划分了区域如下所示:其中VFL_TYPE_GRABBER一般表示提供数据的设备如camera.

类型此设备号区间设备基名称
VFL_TYPE_GRABBER0~63video
VFL_TYPE_RADIO64~127radio
VFL_TYPE_VBI224~255vbi
其它(含VFL_TYPE_SUBDEV)128~191含“v4l2-subdev”

2.确定设备编号,注册字符设备驱动。

ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
	if (ret < 0) {
		pr_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);
	ret = device_register(&vdev->dev);
	if (ret < 0) {
		pr_err("%s: device_register failed\n", __func__);
		goto cleanup;
	}

3. 确定设备的入口

	/* Part 5: Register the entity. */
	ret = video_register_media_controller(vdev);
// 源码:drivers/media/v4l2-core/v4l2-dev.c
static int video_register_media_controller(struct video_device *vdev)
{
#if defined(CONFIG_MEDIA_CONTROLLER)
	u32 intf_type;
	int ret;

	/* Memory-to-memory devices are more complex and use
	 * their own function to register its mc entities.
	 */
	if (!vdev->v4l2_dev->mdev || vdev->vfl_dir == VFL_DIR_M2M)
		return 0;

	vdev->entity.obj_type = MEDIA_ENTITY_TYPE_VIDEO_DEVICE;
	vdev->entity.function = MEDIA_ENT_F_UNKNOWN;

	switch (vdev->vfl_type) {
	case VFL_TYPE_GRABBER:
		intf_type = MEDIA_INTF_T_V4L_VIDEO;
		vdev->entity.function = MEDIA_ENT_F_IO_V4L;
		break;
	case VFL_TYPE_VBI:
		intf_type = MEDIA_INTF_T_V4L_VBI;
		vdev->entity.function = MEDIA_ENT_F_IO_VBI;
		break;
	case VFL_TYPE_SDR:
		intf_type = MEDIA_INTF_T_V4L_SWRADIO;
		vdev->entity.function = MEDIA_ENT_F_IO_SWRADIO;
		break;
	case VFL_TYPE_TOUCH:
		intf_type = MEDIA_INTF_T_V4L_TOUCH;
		vdev->entity.function = MEDIA_ENT_F_IO_V4L;
		break;
	case VFL_TYPE_RADIO:
		intf_type = MEDIA_INTF_T_V4L_RADIO;
		/*
		 * Radio doesn't have an entity at the V4L2 side to represent
		 * radio input or output. Instead, the audio input/output goes
		 * via either physical wires or ALSA.
		 */
		break;
	case VFL_TYPE_SUBDEV:
		intf_type = MEDIA_INTF_T_V4L_SUBDEV;
		/* Entity will be created via v4l2_device_register_subdev() */
		break;
	default:
		return 0;
	}

    // VFL_TYPE_SUBDEV、VFL_TYPE_RADIO将不会进入此处
	if (vdev->entity.function != MEDIA_ENT_F_UNKNOWN) {
		vdev->entity.name = vdev->name;

		/* Needed just for backward compatibility with legacy MC API */
		vdev->entity.info.dev.major = VIDEO_MAJOR;
		vdev->entity.info.dev.minor = vdev->minor;

		ret = media_device_register_entity(vdev->v4l2_dev->mdev,
						   &vdev->entity);
		if (ret < 0) {
			pr_warn("%s: media_device_register_entity failed\n",
				__func__);
			return ret;
		}
	}

	vdev->intf_devnode = media_devnode_create(vdev->v4l2_dev->mdev,
						  intf_type,
						  0, VIDEO_MAJOR,
						  vdev->minor);
	if (!vdev->intf_devnode) {
		media_device_unregister_entity(&vdev->entity);
		return -ENOMEM;
	}

	if (vdev->entity.function != MEDIA_ENT_F_UNKNOWN) {
		struct media_link *link;

		link = media_create_intf_link(&vdev->entity,
					      &vdev->intf_devnode->intf,
					      MEDIA_LNK_FL_ENABLED |
					      MEDIA_LNK_FL_IMMUTABLE);
		if (!link) {
			media_devnode_remove(vdev->intf_devnode);
			media_device_unregister_entity(&vdev->entity);
			return -ENOMEM;
		}
	}

	/* FIXME: how to create the other interface links? */

#endif
	return 0;
}

video_device注销过程分析

调用video_unregister_device函数注销注册的video_device,主要执行流程如下:

// 源码:drivers/media/v4l2-core/v4l2-dev.c
/**
 *	video_unregister_device - unregister a video4linux device
 *	@vdev: the device to unregister
 *
 *	This unregisters the passed device. Future open calls will
 *	be met with errors.
 */
void video_unregister_device(struct video_device *vdev)
{
	/* Check if vdev was ever registered at all */
	/* 如果没有注册,则直接返回 */
	if (!vdev || !video_is_registered(vdev))
		return;

	mutex_lock(&videodev_lock);
	/* This must be in a critical section to prevent a race with v4l2_open.
	 * Once this bit has been cleared video_get may never be called again.
	 */
	// 清楚已注册标志
	clear_bit(V4L2_FL_REGISTERED, &vdev->flags);
	mutex_unlock(&videodev_lock);

	// 注销设备
	device_unregister(&vdev->dev);
}

videoX设备访问流程梳理

Video设备访问流程如下图所示。总结如下:

  • (1)首先通过系统调用访问/dev/videoX用户空间设备节点。
  • (2)进入到内核空间,访问字符设备struct file_operations中的方法。对于Vedio设备,该操作集合被V4L2子系统初始化为v4l2_fops集合。
  • (3)通过V4L2子系统提供的v4l2_fops集合,可直接调用底层驱动实现的Video主设备struct v4l2_file_operations方法,对于ioctl方法,则需要借助中间函数__video_do_ioctl调用底层驱动实现的struct v4l2_ioctl_ops中的ioctl功能。struct v4l2_file_operations方法和struct v4l2_ioctl_ops方法属于主设备方法,需要主设备的驱动实现。
  • (4)struct v4l2_file_operations和struct v4l2_ioctl_ops中的函数都可以通过v4l2_subdev_call调用Video从设备struct v4l2_subdev_core_ops、struct v4l2_subdev_video_ops、struct v4l2_subdev_pad_ops等方法,这些方法都要在从设备驱动中实现。

file_operations的实现

//源码: drivers/media/v4l2-core/v4l2-dev.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,
};

open调用流程

v4l2_open函数的实现:

//源码: drivers/media/v4l2-core/v4l2-dev.c
/* Override for the open function */
static int v4l2_open(struct inode *inode, struct file *filp)
{
	struct video_device *vdev;
	int ret = 0;

	/* Check if the video device is available */
	mutex_lock(&videodev_lock);
	vdev = video_devdata(filp);	//获取video_device
	
	/* return ENODEV if the video device has already been removed. */
	// 判断video_device是否为空及是否已注册
	if (vdev == NULL || !video_is_registered(vdev)) {
		mutex_unlock(&videodev_lock);
		return -ENODEV;
	}
	
	/* and increase the device refcount */
	// 引用计数加1
	video_get(vdev);
	mutex_unlock(&videodev_lock);

	//检查 v4l2_file_operations 的 open 函数是否已实现
	if (vdev->fops->open) {
		if (video_is_registered(vdev))
			ret = vdev->fops->open(filp);//回调 v4l2_file_operations 的 open 函数
		else
			ret = -ENODEV;
	}

	if (vdev->dev_debug & V4L2_DEV_DEBUG_FOP)
		dprintk("%s: open (%d)\n",
			video_device_node_name(vdev), ret);
	
	/* decrease the refcount in case of an error */
	// open 函数执行失败,引用计数减1
	if (ret)
		video_put(vdev);
	return ret;
}

ioctl 调用流程

v4l2_ioctl
    |---> video_ioctl2
         |---> video_usercopy
            |---> v4l2_is_known_ioctl // 检查命令是否有效
            |---> __video_do_ioctl    // 调用 v4l2_ioctl_info 数组中的函数处理命令
              |---> v4l_s_fmt / v4l_reqbufs / v4l_streamon ... // 回调 v4l2_ioctl_ops 中的函数处理命令
                |---> vb2_ioctl_streamon
                   |---> vb2_streamon
                      |---> vb2_core_streamon
                         |---> vb2_start_streaming
                            |---> vb2_queue->ops->start_streaming                                                              

v4l2_ioctl 函数实现:

//源码: drivers/media/v4l2-core/v4l2-dev.c
static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct video_device *vdev = video_devdata(filp);
	int ret = -ENODEV;

	//检查 v4l2_file_operations 的 unlocked_ioctl 函数是否已实现
	if (vdev->fops->unlocked_ioctl) {
		if (video_is_registered(vdev))
           // 回调 unlocked_ioctl 函数
			ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
	} else
		ret = -ENOTTY;

	return ret;
}

video_ioctl2 函数实现:

// 源码: drivers/media/v4l2-core/v4l2-ioctl.c
long video_ioctl2(struct file *file,
	       unsigned int cmd, unsigned long arg)
{
	return video_usercopy(file, cmd, arg, __video_do_ioctl);
}
EXPORT_SYMBOL(video_ioctl2);


__video_do_ioctl 函数实现:

// 源码: drivers/media/v4l2-core/v4l2-ioctl.c

static long __video_do_ioctl(struct file *file,
		unsigned int cmd, void *arg)
{
......

	if (v4l2_is_known_ioctl(cmd)) {
		info = &v4l2_ioctls[_IOC_NR(cmd)];

		if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) &&
		    !((info->flags & INFO_FL_CTRL) && vfh && vfh->ctrl_handler))
			goto done;

		if (vfh && (info->flags & INFO_FL_PRIO)) {
			ret = v4l2_prio_check(vfd->prio, vfh->prio);
			if (ret)
				goto done;
		}
	} else {
		default_info.ioctl = cmd;
		default_info.flags = 0;
		default_info.debug = v4l_print_default;
		info = &default_info;
	}
    
	write_only = _IOC_DIR(cmd) == _IOC_WRITE;
	if (info != &default_info) {
		ret = info->func(ops, file, fh, arg);
	} else if (!ops->vidioc_default) {
		ret = -ENOTTY;
	} else {
		ret = ops->vidioc_default(file, fh,
			vfh ? v4l2_prio_check(vfd->prio, vfh->prio) >= 0 : 0,
			cmd, arg);
	}

......

	return ret;
}

v4l_streamon 函数实现:

// 源码: drivers/media/v4l2-core/v4l2-ioctl.c

static int v4l_streamon(const struct v4l2_ioctl_ops *ops,
				struct file *file, void *fh, void *arg)
{
	return ops->vidioc_streamon(file, fh, *(unsigned int *)arg);
}

vb2_ioctl_streamon 函数实现:

// 源码: drivers/media/common/videobuf2/videobuf2-v4l2.c

int vb2_ioctl_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
	struct video_device *vdev = video_devdata(file);

	if (vb2_queue_is_busy(vdev, file))
		return -EBUSY;
	return vb2_streamon(vdev->queue, i);
}
EXPORT_SYMBOL_GPL(vb2_ioctl_streamon);
  • 6
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值