感兴趣可以加QQ群85486140,大家一起交流相互学习下!
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对象进行捆绑,便于挂接子设备
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所依附的那个节点。主要需要注意下面几点。
类型 | 次设备号区间 | 设备基名称 |
---|---|---|
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” |
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);
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在判断该设备类型时,起到确定作用。要注意。
struct v4l2_device ------| struct video_device
| |------------引用----------> |----*v4l2_dev
| |
|-->ctrlhandler<------------挂载------------------|----ctrl_handler
|--->media->entry-list<------------挂载-----------|----->entry
这里只是将子设备挂接到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);
v4l2_dev->ctrl_handler
域。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根设备上的同时注册设备文件到系统中。
struct v4l2_device
|
|-------->subdevs <-----> list <-----> list <-----> list
| | |
|---------------| |--------------|
|struct sub_dev | |struct sub_dev|
|---------------| |--------------|
子设备就这样一个一个挂在v4l2_device设备上了。
其实这里又用宏转了一下,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部分功能.
上面media_device在v4l2中只用于遍历子设备,就没有描述出media_device在系统中的位置,等后面需要时在分析吧。总结到目前了解到v4l2需要一个根节点v4l2_device来管理子设备,然后上层用户空间,根据根节点,来查找所有子设备。针对高通平台,双摄camera来说。系统中存在3个media设备,分别对应3个物理camera.每一个camera对应的子设备都是根据media设备查找获取到的。