linux v4l2学习之-v4l2设备注册过程及各个设备之间的联系

感兴趣可以加QQ群85486140,大家一起交流相互学习下!

文章目录

    • 1.v4l2_device_register
    • 2.video_register_device注册过程
      • 2.1注册过程
      • 2.2 video在系统中的位置
    • 3.subdev注册过程
      • 3.1 v4l2_device_register_subdev()注册过程
      • 3.2 v4l2_device_register_subdev_nodes()注册设备节点
      • 3.3 subdev在系统中的位置
    • 4.media_device_register()多媒体设备注册
    • 5.总结

1.v4l2_device_register

  v4l2的设备注册其实没有注册设备或者设备驱动,只是将v4l2的大结构体与其他设备进行捆绑。如下面是v4l2注册设备的过程。

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中,这样的话当前的设备就成了v4l2设备,由于当前设备已经注册过了设备文件,所以后续不需要在重新注册设备文件。
	v4l2_dev->dev = dev;
	if (dev == NULL) {
		/* If dev == NULL, then name must be filled in by the caller */
		if (WARN_ON(!v4l2_dev->name[0]))
			return -EINVAL;
		return 0;
	}

	/* Set name to driver name + device name if it is empty. */
	/* 如果v4l2设备的名字为空,则会将当前设备的名字拷贝为v4l2设备中。*/
	if (!v4l2_dev->name[0])
		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
			dev->driver->name, dev_name(dev));
	//下面是非常重要的一步,即将v4l2设备对象,保存到dev设备中,这个dev可以是platform、char、block等设,我们可以使用dev_get_drvdata(dev)获取到v4l2对象。
	if (!dev_get_drvdata(dev))
		dev_set_drvdata(dev, v4l2_dev);
	return 0;
}

  这里上面代码比较简单,这需要知道此函数功能是将当前设备和v4l2设备捆绑,并保存v4l2设备对象到当前设备的driverdata中。此函数的作用就是将当前设备驱动和v4l2对象进行捆绑,便于挂接子设备

2.video_register_device注册过程

2.1注册过程

static inline int __must_check video_register_device(struct video_device *vdev,
		int type, int nr)
{
	return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
} 

上面方法又封装了一层,转而调用下面的方法

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;
	
    //这里省略若干查找空闲次设备号的过程--------------
    	/* 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;
	default:
		printk(KERN_ERR "%s called with unknown type: %d\n",
		       __func__, type);
		return -EINVAL;
	}
		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;
	}
    #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_device[i] == NULL)//所有注册的video_device都会保存到这个数组中
			break;
	if (i == VIDEO_NUM_DEVICES) {
		mutex_unlock(&videodev_lock);
		printk(KERN_ERR "could not get a free minor\n");
		return -ENFILE;
	}
#endif
    //minor_offset一般是0,i就是查找到的空闲次设备号,这里总的次设备支持到256个
    vdev->minor = i + minor_offset;//这里记录下次设备号
    //这里num会保存到session_id中,唯一表示一个任务。一般情况下nr == minor
	vdev->num = nr;
	devnode_set(vdev);//将标准位置为已用。

	/* Should not happen since we thought this minor was free */
	WARN_ON(video_device[vdev->minor] != NULL);
	/* 下面这个方法字面意思看起来是获取一个index,但是查看源代码会发现
	   “这里会去查找不是直系的设备空闲号”,就是说只有video_device不为空,而且v4l2 parent
	   对象相同都会认为是同类,直接跳过相应的index号*/
	vdev->index = get_index(vdev);
	/* 下面要重点了,这里根据次设备号将当前video_device保存到video_device[]数组中*/
	video_device[vdev->minor] = vdev;
	mutex_unlock(&videodev_lock);

	/* Part 3: Initialize the character device */
	/* 分配字符设备文件*/
	vdev->cdev = cdev_alloc();
	//务必留意这个ioctl,后面子设备中的ioctl都是通过这里查找的。
	vdev->cdev->ops = &v4l2_fops;
	vdev->cdev->owner = owner;
	ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);

	/* Part 4: register the device with sysfs */
	vdev->dev.class = &video_class;//根目录sys中有对应的属性可操作。
	vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);//主设备号为81
	vdev->dev.parent = vdev->dev_parent;//这里一般是v4l2_device对象
	dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
	ret = device_register(&vdev->dev);//注册该video_device到kernel中。
	vdev->dev.release = v4l2_device_release;

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

#if defined(CONFIG_MEDIA_CONTROLLER)
	/* Part 5: Register the entity. */
	if (vdev->v4l2_dev && vdev->v4l2_dev->mdev &&
	    vdev->vfl_type != VFL_TYPE_SUBDEV) {
		vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
		vdev->entity.name = vdev->name;
		vdev->entity.info.v4l.major = VIDEO_MAJOR;
		vdev->entity.info.v4l.minor = vdev->minor;
		ret = media_device_register_entity(vdev->v4l2_dev->mdev,
			&vdev->entity);
		if (ret < 0)
			printk(KERN_WARNING
			       "%s: media_device_register_entity failed\n",
			       __func__);
	}
#endif
	/* Part 6: Activate this minor. The char device can now be used. */
	set_bit(V4L2_FL_REGISTERED, &vdev->flags);	

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

  • 1.根据设备类型确定设备名字和次设备数量
    由于系统可能包含很多媒体设备,所以v4l2核心将0~255次设备编号划分了区域如下所示:其中VFL_TYPE_GRABBER一般表示提供数据的设备如camera.
类型 次设备号区间 设备基名称
VFL_TYPE_GRABBER 0~63 “video”
VFL_TYPE_VBI 224~255 “vbi”
VFL_TYPE_RADIO 64~127 “radio”
其它(含VFL_TYPE_SUBDEV) 128~223 含“v4l-subdev”
  • 2.确定设备编号,注册字符设备驱动。这里我曾今迷糊了,我以为下面这会注册2个设备到kernel中,后来才发现我错了。这是注册字符设备的一种方式。
	ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
	dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
	ret = device_register(&vdev->dev);
  • 3.确定设备的入口
	if (vdev->v4l2_dev && vdev->v4l2_dev->mdev &&
	    vdev->vfl_type != VFL_TYPE_SUBDEV) {
		vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
		vdev->entity.name = vdev->name;
		vdev->entity.info.v4l.major = VIDEO_MAJOR;
		vdev->entity.info.v4l.minor = vdev->minor;
		ret = media_device_register_entity(vdev->v4l2_dev->mdev,&vdev->entity);

上面能发现,如果不是SUBDEV才能进来,注意上面name一般代表真实的设备名称,最后一行可以看到将入口注册到v4l2_device包含的media_device设备中了。而SUBDEV设备的注册一般都是设备驱动中赋值,如下是高通eeprom子设备。

	e_ctrl->msm_sd.sd.entity.type = MEDIA_ENT_T_V4L2_SUBDEV;
	e_ctrl->msm_sd.sd.entity.group_id = MSM_CAMERA_SUBDEV_EEPROM;
	e_ctrl->msm_sd.sd.entity.name = video_device_node_name(vdev);

上面group_id在判断该设备类型时,起到确定作用。要注意。

2.2 video在系统中的位置

	struct v4l2_device ------|                      struct video_device   
		|                    |------------引用----------> |----*v4l2_dev
        |                                                |
		|-->ctrlhandler<------------挂载------------------|----ctrl_handler
		|--->media->entry-list<------------挂载-----------|----->entry

3.subdev注册过程

3.1 v4l2_device_register_subdev()注册过程

这里只是将子设备挂接到v4l2_device的subdevs链表上,还没有真正的注册设备节点。真正注册设备节点的是v4l2_device_register_subdev_nodes方法。下面会介绍。

int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
				struct v4l2_subdev *sd)
{
#if defined(CONFIG_MEDIA_CONTROLLER)
//获取到子设备的入口对象,方面后面注册到media_device上面。
	struct media_entity *entity = &sd->entity;
#endif
	int err;
   //此处省略一些错误检查代码
	/*
	 * The reason to acquire the module here is to avoid unloading
	 * a module of sub-device which is registered to a media
	 * device. To make it possible to unload modules for media
	 * devices that also register sub-devices, do not
	 * try_module_get() such sub-device owners.
	 */
	sd->owner_v4l2_dev = v4l2_dev->dev && v4l2_dev->dev->driver &&
		sd->owner == v4l2_dev->dev->driver->owner;

	if (!sd->owner_v4l2_dev && !try_module_get(sd->owner))
		return -ENODEV;
  //下面v4l2_dev一般为系统根v4l2_device设备。
	sd->v4l2_dev = v4l2_dev;
	//这里对应具体的子设备,可以发现调用了registered()回调,如果有需要可以在对应的设备驱动中实现。
	if (sd->internal_ops && sd->internal_ops->registered) {
		err = sd->internal_ops->registered(sd);
		if (err)
			goto error_module;
	}
  //印证了之前说的,子设备的ctrl_handler都会挂载到根设备v4l2_device的ctrl_handler上面。
	/* This just returns 0 if either of the two args is NULL */
	err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler, NULL);
	if (err)
		goto error_unregister;

#if defined(CONFIG_MEDIA_CONTROLLER)
	/* Register the entity. */
	if (v4l2_dev->mdev) {
	//上面将
		err = media_device_register_entity(v4l2_dev->mdev, entity);
		if (err < 0)
			goto error_unregister;
	}
#endif

	spin_lock(&v4l2_dev->lock);
	//将子设备链接到跟设备的,subdevs链表上。
	list_add_tail(&sd->list, &v4l2_dev->subdevs);
	spin_unlock(&v4l2_dev->lock);

	return 0;

error_unregister:
	if (sd->internal_ops && sd->internal_ops->unregistered)
		sd->internal_ops->unregistered(sd);
error_module:
	if (!sd->owner_v4l2_dev)
		module_put(sd->owner);
	sd->v4l2_dev = NULL;
	return err;
}
EXPORT_SYMBOL_GPL(v4l2_device_register_subdev);
  • 获取到子设备的entity对象,并将当前根设备对象赋值给subdev->v4l2_dev域。
  • 将子设备的ctrl_handler对象,添加到根设备的v4l2_dev->ctrl_handler域。
  • 将子设备的entity注册到根设备上的media_device设备中。

3.2 v4l2_device_register_subdev_nodes()注册设备节点

int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev)
{
	struct video_device *vdev;
	struct v4l2_subdev *sd;
	int err;

	/* Register a device node for every subdev marked with the
	 * V4L2_SUBDEV_FL_HAS_DEVNODE flag.
	 */
	list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
		if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE))
			continue;

		vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
		if (!vdev) {
			err = -ENOMEM;
			goto clean_up;
		}

		video_set_drvdata(vdev, sd);
		strlcpy(vdev->name, sd->name, sizeof(vdev->name));
		vdev->v4l2_dev = v4l2_dev;
		vdev->fops = &v4l2_subdev_fops;
		vdev->release = v4l2_device_release_subdev_node;
		vdev->ctrl_handler = sd->ctrl_handler;
		err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1,
					      sd->owner);
		if (err < 0) {
			kfree(vdev);
			goto clean_up;
		}
#if defined(CONFIG_MEDIA_CONTROLLER)
		sd->entity.info.v4l.major = VIDEO_MAJOR;
		sd->entity.info.v4l.minor = vdev->minor;
#endif
		sd->devnode = vdev;
	}
	return 0;

clean_up:
	list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
		if (!sd->devnode)
			break;
		video_unregister_device(sd->devnode);
	}

	return err;
}
EXPORT_SYMBOL_GPL(v4l2_device_register_subdev_nodes);

上面这个方法才是真正注册设备节点的地方,不过一般请看下不这样用,一般稍微修改一下v4l2_device_register_subdev方法,或者直接重新写一个注册方法,在注册设备到v4l2_device根设备上的同时注册设备文件到系统中。

3.3 subdev在系统中的位置

 struct v4l2_device
 		|
 		|-------->subdevs <-----> list <-----> list <-----> list
 		|       					|           |
 						|---------------|   |--------------|
 						|struct sub_dev |   |struct sub_dev|
 						|---------------|   |--------------|

子设备就这样一个一个挂在v4l2_device设备上了。

4.media_device_register()多媒体设备注册

其实这里又用宏转了一下,THIS_MODULE就是代表当前模块的struct module对象,也就代表当前驱动的实例对象。

#define media_device_register(mdev) __media_device_register(mdev, THIS_MODULE)
__media_device_register(mdev, THIS_MODULE)
/**
 * media_device_register - register a media device
 * @mdev:	The media device
 *
 * The caller is responsible for initializing the media device before
 * registration. The following fields must be set:
 *
 * - dev must point to the parent device
 * - model must be filled with the device model name
 */
int __must_check __media_device_register(struct media_device *mdev,
					 struct module *owner)
{
	int ret;  if (WARN_ON(mdev->dev == NULL || mdev->model[0] == 0))
		return -EINVAL;

	mdev->entity_id = 1;
	INIT_LIST_HEAD(&mdev->entities);
	spin_lock_init(&mdev->lock);
	mutex_init(&mdev->graph_mutex);
    /*上面同样是一些变量的初始化*/
	/* Register the device node. */
	mdev->devnode.fops = &media_device_fops;
	/*下面是绑定父设备,同上面的v4l2_devcie绑定的是同一个struct devie对象*/
	mdev->devnode.parent = mdev->dev;
	mdev->devnode.release = media_device_release;
	ret = media_devnode_register(&mdev->devnode, owner);
    //下面可以发现创建了属性文件,我们可以直接通过属性文件来操作这个文件节点。
    //但是可惜的是,在属性接口中只发现了下面这样的代码,只打印了media_device的描述信息。
    //“sprintf(buf, "%.*s\n", (int)sizeof(mdev->model), mdev->model);”
	ret = device_create_file(&mdev->devnode.dev, &dev_attr_model);
	//省略一些错误检测
	return 0;
}

上面初始化了一些media_devnode的设备域。便于后续标准接口的调用。紧接着重量级的操作来了,注释部分写的也很精彩。

/**
 * media_devnode_register - register a media device node
 * @mdev: media device node structure we want to register
 *
 * The registration code assigns minor numbers and registers the new device node
 * with the kernel. An error is returned if no free minor number can be found,
 * or if the registration of the device node fails.
 *
 * Zero is returned on success.
 * * Note that if the media_devnode_register call fails, the release() callback of
 * the media_devnode structure is *not* called, so the caller is responsible for
 * freeing any data.
 */
int __must_check media_devnode_register(struct media_devnode *mdev,
					struct module *owner)
{
	int minor;
	int ret;

	/* Part 1: Find a free minor number */
	mutex_lock(&media_devnode_lock);
	//获取可用的次设备编号。
	minor = find_next_zero_bit(media_devnode_nums, MEDIA_NUM_DEVICES, 0);
	if (minor == MEDIA_NUM_DEVICES) {
		mutex_unlock(&media_devnode_lock);
		pr_err("could not get a free minor\n");
		return -ENFILE;
	}

	set_bit(minor, media_devnode_nums);
	mutex_unlock(&media_devnode_lock);
    //注意下面保存了次设备号,便于后续打开设备节点。 
	mdev->minor = minor;

	/* Part 2: Initialize and register the character device */
	cdev_init(&mdev->cdev, &media_devnode_fops);
	mdev->cdev.owner = owner;

	ret = cdev_add(&mdev->cdev, MKDEV(MAJOR(media_dev_t), mdev->minor), 1);
	if (ret < 0) {
		pr_err("%s: cdev_add failed\n", __func__);
		goto error;
	}

	/* Part 3: Register the media device */
	mdev->dev.bus = &media_bus_type;
	mdev->dev.devt = MKDEV(MAJOR(media_dev_t), mdev->minor);
	mdev->dev.release = media_devnode_release;
	if (mdev->parent)
		mdev->dev.parent = mdev->parent;
	dev_set_name(&mdev->dev, "media%d", mdev->minor);
	ret = device_register(&mdev->dev);
	if (ret < 0) {
		pr_err("%s: device_register failed\n", __func__);
		goto error;
	}

	/* Part 4: Activate this minor. The char device can now be used. */
	set_bit(MEDIA_FLAG_REGISTERED, &mdev->flags);

	return 0;

error:
	cdev_del(&mdev->cdev);
	clear_bit(mdev->minor, media_devnode_nums);
	return ret;
}

上面代码注册了一个字符设备驱动,上面代码注释可以发现函数分成4部分功能.

  • part1:这一部分功能就是找一个空闲的次设备号。
    1.MEDIA_NUM_DEVICES:该宏表示系统最大支持256个子设备.
    2.media_devnode_nums:该全局变量用来表示所有位号是否已经使用了,这里可以理解成这个变量有256位,每一位都是一个标志。例如:我们得到一个次设备号是5,则该变量的第5位就会被置位。
  • part2:这里会根据主设备号media_dev_t和次设备号,创建字符设备。并将该字符设备的owner赋值为当前模块,然后添加到字符设备集合中。
  • part3:注册设备
  • part4:置标志变量,表示该字符设备可以使用了。

5.总结

上面media_device在v4l2中只用于遍历子设备,就没有描述出media_device在系统中的位置,等后面需要时在分析吧。总结到目前了解到v4l2需要一个根节点v4l2_device来管理子设备,然后上层用户空间,根据根节点,来查找所有子设备。针对高通平台,双摄camera来说。系统中存在3个media设备,分别对应3个物理camera.每一个camera对应的子设备都是根据media设备查找获取到的。

你可能感兴趣的:(v4l2,android系统)