Linux V4L2 子系统源码分析

来源:互联网 发布:淘宝全球买手香港 编辑:程序博客网 时间:2024/06/15 09:48

V4L2 设备分析


目录

  • V4L2 设备分析
    • 目录
    • v4l2_device 结构体
    • V4L2设备注册
      • v4l2_device_release
      • v4l2_device_disconnect
    • V4L2设备的注销
      • v4l2_device_unregister_subdev v4l2_device_register_subdev_nodes

v4l2_device 结构体

struct v4l2_device 结构体是v4l2 设备驱动的主要结构体。v4l2_device结构体如下:

//linux/include/media/v4l2-device.hstruct v4l2_device {    struct device *dev;                               //pointer to struct device.#if defined(CONFIG_MEDIA_CONTROLLER)    struct media_device *mdev;#endif    struct list_head subdevs;                        //used to keep track of the registered subdevs    spinlock_t lock;                              char name[V4L2_DEVICE_NAME_SIZE];                //unique device name, by default the driver name + bus ID    void (*notify)(struct v4l2_subdev *sd,            unsigned int notification, void *arg); //notify callback called by some sub-devices    struct v4l2_ctrl_handler *ctrl_handler;       //The control handler. May be %NULL.    struct v4l2_prio_state prio;    struct kref ref;                             //Keep track of the references to this struct.    void (*release)(struct v4l2_device *v4l2_dev);//Release function that is called when the ref count goes to 0.};

每一个v4l2 设备都需要对用有一个struct v4l2_device。 这里需要注意的是成员subdevs,它用于管理已经注册的各个subdevice。在驱动程序中还经常需要用的是,dev->driver_data 指向了这个结构体。方便通过device 结构体找到此结构体。

enum v4l2_priority {    V4L2_PRIORITY_UNSET       = 0,  /* not initialized */    V4L2_PRIORITY_BACKGROUND  = 1,    V4L2_PRIORITY_INTERACTIVE = 2,    V4L2_PRIORITY_RECORD      = 3,    V4L2_PRIORITY_DEFAULT     = V4L2_PRIORITY_INTERACTIVE,};struct v4l2_prio_state {    atomic_t prios[4];};
struct v4l2_ctrl_handler {    struct mutex _lock;    struct mutex *lock;    struct list_head ctrls;    struct list_head ctrl_refs;    struct v4l2_ctrl_ref *cached;    struct v4l2_ctrl_ref **buckets;    v4l2_ctrl_notify_fnc notify;    void *notify_priv;    u16 nr_of_buckets;    int error;};

V4L2设备注册

v4l2_device_register 函数完成对v4l2设备的注册:

//linux/include/media/v4l2-core/v4l2-device.cint 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);    v4l2_prio_init(&v4l2_dev->prio);    kref_init(&v4l2_dev->ref);    get_device(dev);    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. */    if (!v4l2_dev->name[0])        snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",            dev->driver->name, dev_name(dev));    if (!dev_get_drvdata(dev))        dev_set_drvdata(dev, v4l2_dev);    return 0;}EXPORT_SYMBOL_GPL(v4l2_device_register);

v4l2_device_register 函数为v4l2设备注册一个v4l2_device结构体。分析代码可以的出:首先初始化subdevs链表、自旋锁lock、v4l2_prio_state 结构体、以及ref。之后get_device(dev)对dev进行引用计数。最后通过dev_set_drvdata 函数将dev->driver_data 指向了v4l2_device结构体。

v4l2_device_release

//linux/include/media/v4l2-core/v4l2-device.cstatic void v4l2_device_release(struct kref *ref){    struct v4l2_device *v4l2_dev =        container_of(ref, struct v4l2_device, ref);    if (v4l2_dev->release)        v4l2_dev->release(v4l2_dev);}

函数v4l2_device_release 函数在v4l2_device -> ref 为0时调用(也就意味着已经没有人在使用v4l2设备了),也即调用v4l2_dev->release函数释放v4l2设备。

v4l2_device_disconnect

void v4l2_device_disconnect(struct v4l2_device *v4l2_dev){    if (v4l2_dev->dev == NULL)        return;    if (dev_get_drvdata(v4l2_dev->dev) == v4l2_dev)        dev_set_drvdata(v4l2_dev->dev, NULL);    put_device(v4l2_dev->dev);    v4l2_dev->dev = NULL;}

v4l2_device_disconnect 函数用于将v4l2设备的state改变为disconnect。

V4L2设备的注销

void v4l2_device_unregister(struct v4l2_device *v4l2_dev){    struct v4l2_subdev *sd, *next;    if (v4l2_dev == NULL || !v4l2_dev->name[0])        return;    v4l2_device_disconnect(v4l2_dev);    /* Unregister subdevs */    list_for_each_entry_safe(sd, next, &v4l2_dev->subdevs, list) {        v4l2_device_unregister_subdev(sd);#if IS_ENABLED(CONFIG_I2C)        if (sd->flags & V4L2_SUBDEV_FL_IS_I2C) {            struct i2c_client *client = v4l2_get_subdevdata(sd);            if (client &&                !client->dev.of_node && !client->dev.fwnode)                i2c_unregister_device(client);            continue;        }#endif#if defined(CONFIG_SPI)        if (sd->flags & V4L2_SUBDEV_FL_IS_SPI) {            struct spi_device *spi = v4l2_get_subdevdata(sd);            if (spi && !spi->dev.of_node && !spi->dev.fwnode)                spi_unregister_device(spi);            continue;        }#endif    }    /* Mark as unregistered, thus preventing duplicate unregistrations */    v4l2_dev->name[0] = '\0';}

v4l2_device_unregister 函数将会注销掉所有的sub_device和其他的一些资源,如IIC或则SPI。在函数中首先便利链表找到每一个已经注册sub_device,并调用
v4l2_device_unregister_subdev函数注销掉相应的sub_device。在这里可以适当关注一下函数v4l2_get_subdevdata

static inline void *v4l2_get_subdevdata(const struct v4l2_subdev *sd){    return sd->dev_priv;}

此函数使用了v4l2_subdev结构体中的dev_priv。

v4l2_device_unregister_subdev &v4l2_device_register_subdev_nodes

下面分析一下函数v4l2_device_unregister_subdev:

void v4l2_device_unregister_subdev(struct v4l2_subdev *sd){    struct v4l2_device *v4l2_dev;    /* return if it isn't registered */    if (sd == NULL || sd->v4l2_dev == NULL)        return;    v4l2_dev = sd->v4l2_dev;    spin_lock(&v4l2_dev->lock);    list_del(&sd->list);    spin_unlock(&v4l2_dev->lock);    if (sd->internal_ops && sd->internal_ops->unregistered)        sd->internal_ops->unregistered(sd);    sd->v4l2_dev = NULL;#if defined(CONFIG_MEDIA_CONTROLLER)    if (v4l2_dev->mdev) {        /*         * No need to explicitly remove links, as both pads and         * links are removed by the function below, in the right order         */        media_device_unregister_entity(&sd->entity);    }#endif    video_unregister_device(sd->devnode);    if (!sd->owner_v4l2_dev)        module_put(sd->owner);}

函数首先删除sd->list,然后通过调用sd->internal_ops->unregistered(sd)注销掉相应的sub_device。sd->internal_ops相关的内容会在后续对sub_device结构体的分析中介绍说明。然后设置sd->v4l2_dev = NULL。并注销掉相应media_device和video_device节点。(media_device和video_device)会在后面的分析中介绍说明。与之想对应的函数为:v4l2_device_register_subdev_nodes 完成对sub_device设备的注册:

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.dev.major = VIDEO_MAJOR;        sd->entity.info.dev.minor = vdev->minor;        if (vdev->v4l2_dev->mdev) {            struct media_link *link;            link = media_create_intf_link(&sd->entity,                              &vdev->intf_devnode->intf,                              MEDIA_LNK_FL_ENABLED);            if (!link) {                err = -ENOMEM;                goto clean_up;            }        }#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;}

函数首先遍历v4l2_dev->subdevs 链表找到每个已注册的sub_device,并且判断是否已经生成设备节点(通过V4L2_SUBDEV_FL_HAS_DEVNODEflag判断),如果没有生成则会在注册video_device设备并生成相应的设备节点:首先申请video_device结构体,通过video_set_drvdata 函数将video_device -> dev ->driver_data 指向了sub_device结构体,并通过v4l2_device设置video_device结构体。最后通过__video_register_device 函数注册video_device设备并创建设备节点。最后设置sd->devnode = vdev,完成为v4l2设备注册一个sub_device。


注:linux内核博大精深,菜鸟学习难免会有一些错误,各位大神发现还请批评指正。