码迷,mamicode.com
首页 > 其他好文 > 详细

V4L2源代码之旅一:struct v4l2_device

时间:2016-08-15 19:03:04      阅读:246      评论:0      收藏:0      [点我收藏+]

标签:

一. 文档位置:linux-3.08/Documentation/video4linux/v4l2-framework.txt

二.翻译总结:

  V4L2驱动程序往往是很复杂的,其原因是硬件的复杂性:大多数设备有多个IC,从而导出多个设备节点/dev,并且创建了non-V4L2设备如DVB、ALSA,FB,I2C和输入(IR)设备。

  特别的是,V4L2驱动必须组织IC支持完成音/视频的混流/编码/解码,这就导致V4L2驱动更加复杂。通常,这些IC是由一个或多个I2C总线连接到主桥驱动,其他总线也可以被使用。所有这些被连接到主桥驱动的设备我们称为“sub-devices”【例如:Camera就是通过I2C总线连接到主CPU上,那么Camera可以被称为“sub-device”】。

  很长一段时间,V4L2架构在V4L设备节点创建和video_buf对视频buffer的处理上受到video_device结构体的限制。

  这意味着,所有的Driver必须完成设备实例的安装并且连接到sub-devices。

  目前仍然由于框架的缺乏而存在大量的代码无法重构。

  因此,该框架建立的基本构建模块所有Driver都会使用。并且和此相同的框架使得重构common code为可以被所有驱动是用的统一的函数更加容易。

 

  1. 驱动框架:

    所有的驱动需要以下结构体:

1) A struct for each device instance containing the device state.
2) A way of initializing and commanding sub-devices (if any).
3) Creating V4L2 device nodes (/dev/videoX, /dev/vbiX and /dev/radioX)  and keeping track of device-node specific data.
4) Filehandle-specific structs containing per-filehandle data;
5) video buffer handling.

  

  粗略示意图:

 device instances
      |
      +-sub-device instances
      |
      \-V4L2 device nodes
      |
      \-filehandle instances

  2. 框架框架

  框架架构极其类似驱动架构:存在一个v4l2_device结构来表示device实例的数据,一个v4l2_subdev结构表示sud-device实例,video_device 结构体存储V4L2设备节点数据并且将来一个v4l2_fh结构体将处理跟踪文件操作句柄实例。

  V4L2架构也可选的集成了media framework。如果一个驱动设置了结构体v4l2_device的mdev成员,sub-devices那么video nodes将会自动显示在media framework的入口处。

 

  3. struct v4l2_device

  每个设备实例是由一个v4l2_device代表(v4l2-device.h)。对于非常简单的设备可以仅仅分配这个结构体,但是通常你需要将这个结构体嵌入到一个更大的结构体中。

struct v4l2_device {
    /* dev->driver_data points to this struct.
       Note: dev might be NULL if there is no parent device
       as is the case with e.g. ISA devices. */
    struct device *dev;
#if defined(CONFIG_MEDIA_CONTROLLER)
    struct media_device *mdev;
#endif
    /* used to keep track of the registered subdevs */
    struct list_head subdevs;    // 跟踪注册的所有subdev
    /* lock this struct; can be used by the driver as well if this
       struct is embedded into a larger struct. */
    spinlock_t lock;
    /* unique device name, by default the driver name + bus ID */
    char name[V4L2_DEVICE_NAME_SIZE];
    /* notify callback called by some sub-devices. */
    void (*notify)(struct v4l2_subdev *sd,
            unsigned int notification, void *arg);
    /* The control handler. May be NULL. */
    struct v4l2_ctrl_handler *ctrl_handler;
    /* Device‘s priority state */
    struct v4l2_prio_state prio;
    /* BKL replacement mutex. Temporary solution only. */
    struct mutex ioctl_lock;
    /* Keep track of the references to this struct. */
    struct kref ref;
    /* Release function that is called when the ref count goes to 0. */
    void (*release)(struct v4l2_device *v4l2_dev);
};

注册设备实例:

      v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
  注册操作将会初始化v4l2_device结构体,如果dev->driver_data != NULL,那么将会被挂到v4l2_dev上。

/* kernel/drivers/media/video/v4l2-device.c */
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
    if (v4l2_dev == NULL)
        return -EINVAL;

    INIT_LIST_HEAD(&v4l2_dev->subdevs);  // 初始化一个将要挂在v4l2_device上的子设备列表
    spin_lock_init(&v4l2_dev->lock);     // 初始化一个spin lock
    mutex_init(&v4l2_dev->ioctl_lock);   // 初始化一个mutex,看来会在ioctrl上使用的mutex
    v4l2_prio_init(&v4l2_dev->prio);     // 优先级
    kref_init(&v4l2_dev->ref);           // 将v4l2_dev的父设备指定为struct device *dev
    v4l2_dev->dev = dev;                 // 父设备
    if (dev == NULL) {                   // 如果dev为空,v4l2->dev->name必须设置
        /* If dev == NULL, then name must be filled in by the caller */
        WARN_ON(!v4l2_dev->name[0]);
        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",  // 如果v4l2_dev->name为空,那么由dev_name(dev)来设置 driver name + device name
            dev->driver->name, dev_name(dev));
    if (!dev_get_drvdata(dev))
        dev_set_drvdata(dev, v4l2_dev);   // 将dev和v4l2_dev关联:dev->p->driver_data = v4l2_dev
    return 0;
}
EXPORT_SYMBOL_GPL(v4l2_device_register);
int dev_set_drvdata(struct device *dev, void *data)
{
    int error;

    if (!dev->p) {
        error = device_private_init(dev);
        if (error)
            return error;
    }
    dev->p->driver_data = data;
    return 0;
}
EXPORT_SYMBOL(dev_set_drvdata);

  想要和media device framework集成的驱动程序需要手动设置dev->driver_data。如上述代码所看到的。同时也必须设置v4l2_device的medv成员指向正确初始化的且注册过的media_device实例。【关于struct media_device不是很懂】。

  你也可以使用v4l2_device_set_name()来基于驱动名称设置一个名称和一个驱动全局的原子实例。这将会产生名称类似:ivtv0, ivtv1等。如果名称以数字结尾,那么会插入一个“-”:cx18-0,cx18-1等。这个函数返回最后的那个数字。【没用过】

  int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)的第一个参数“dev”通常是一个pci_dev,usb_interface or platform_device指针。dev为空是不常见的,但是在ISA设备或者当一个设备创建多个PCI设备,因此关联v4l2_dev作为一个特定的父设备是不可能的。

  你也可以提供notify()回调函数,可以被sub-devices调用来通知你事件。是否设置取决于你的sub-device。sub-device支持的所有事件类型定义在头文件:include/media/<subdevice>.h中。

v4l2_device_unregister(struct v4l2_device *v4l2_dev);

/* kernel/drivers/media/video/v4l2-device.c */
void
v4l2_device_disconnect(struct v4l2_device *v4l2_dev) { if (v4l2_dev->dev == NULL) return; if (dev_get_drvdata(v4l2_dev->dev) == v4l2_dev) // 如果v4l2->dev->p->driver_data 指向v4l2_dev dev_set_drvdata(v4l2_dev->dev, NULL); // 设置v4l2->dev->p->driver_data为NULL v4l2_dev->dev = NULL; // 设置v4l2->dev为NULL } EXPORT_SYMBOL_GPL(v4l2_device_disconnect);
/*
* 如果是一个热插拔设备(如USB设备),当断开连接的时候父设备变为无效。v4l2_device->dev将设置为空,并且标记父设备消失。这就通过v4l2_device_disconnect实现。
* 从代码可以看出,这个方法并不注销subdev,所以需要注销subdev需要调用v4l2_device_unregister函数。如果你的设备不支持热插拔无需调用v4l2_device_disconnect.
*/
void v4l2_device_unregister(struct v4l2_device *v4l2_dev) { struct v4l2_subdev *sd, *next; if (v4l2_dev == NULL) return; v4l2_device_disconnect(v4l2_dev); /* Unregister subdevs */ list_for_each_entry_safe(sd, next, &v4l2_dev->subdevs, list) { // 遍历v4l2_dev->subdevs这个链表,这个链表是组织所有的sub-dev v4l2_device_unregister_subdev(sd); #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) if (sd->flags & V4L2_SUBDEV_FL_IS_I2C) { struct i2c_client *client = v4l2_get_subdevdata(sd); /* We need to unregister the i2c client explicitly. We cannot rely on i2c_del_adapter to always unregister clients for us, since if the i2c bus is a platform bus, then it is never deleted. */ if (client) 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_unregister_device(spi); continue; } #endif } } EXPORT_SYMBOL_GPL(v4l2_device_unregister); 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); // 将sd从v4l2_dev->subdev链表中删除 spin_unlock(&v4l2_dev->lock); if (sd->internal_ops && sd->internal_ops->unregistered) sd->internal_ops->unregistered(sd); // 回掉 sd->internal_ops->unregistered sd->v4l2_dev = NULL; #if defined(CONFIG_MEDIA_CONTROLLER) if (v4l2_dev->mdev) media_device_unregister_entity(&sd->entity); #endif video_unregister_device(&sd->devnode); // 这个方法很关键,解除v4l2_subdev和video_device之间的关系 module_put(sd->owner); } EXPORT_SYMBOL_GPL(v4l2_device_unregister_subdev);
/* kernel/drivers/media/video/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);
}
EXPORT_SYMBOL(video_unregister_device);

   上边函数,目前看不懂。

有时,你需要迭代由驱动注册的所有设备。这通常是多个设备驱动程序使用相同的硬件。例如:ivtvfb驱动是一个使用ivtv硬件的framebuffer驱动。相同的比如ALSA驱动是真实的。

/* 用如下代码迭代所有的注册设备 */
static int callback(struct device *dev, void *p)
{
    struct v4l2_device *v4l2_dev = dev_get_drvdata(dev);

    /* test if this device was inited */
    if (v4l2_dev == NULL)
        return 0;
    ...
    return 0;
}

int iterate(void *p)
{
    struct device_driver *drv;
    int err;

    /* Find driver ‘ivtv‘ on the PCI bus.
       pci_bus_type is a global. For USB busses use usb_bus_type. */
    drv = driver_find("ivtv", &pci_bus_type);
    /* iterate over all ivtv device instances */
    err = driver_for_each_device(drv, NULL, p, callback);
    put_driver(drv);
    return err;
}

 

  有时,需要维护一个运行设备实例计数器。推荐方法如下:

    static atomic_t drv_instance = ATOMIC_INIT(0);

    static int __devinit drv_probe(struct pci_dev *pdev,
                    const struct pci_device_id *pci_id)
    {
        ...
        state->instance = atomic_inc_return(&drv_instance) - 1;
    }

    #define atomic_inc_return(v) (atomic_add_return(1, v))       /*将v原子自加1*/

  如果有多个设备节点,那么安全的注销v4l2_device是困难的。因此,v4l2_device支持refcounting计数,就是v4l2_device的ref成员。refcount当调用video_register_device时会增加,当设备节点释放后会减小。当refcount为0时,v4l2_device release()被调用,可以在这个函数中做最后的清理工作。

  如果其他设备节点(如:ALSA)被创建,你可以手动的增加和减少refcount,使用如下方法:

void v4l2_device_get(struct v4l2_device *v4l2_dev); // 增加引用计数

int v4l2_device_put(struct v4l2_device *v4l2_dev);  // 较小引用计数
/* kernel/include/media/v4l2-device.h */
static inline void v4l2_device_get(struct v4l2_device *v4l2_dev)
{
    kref_get(&v4l2_dev->ref);
}
/* kernel/drivers/media/video/v4l2-device.c */
int v4l2_device_put(struct v4l2_device *v4l2_dev)
{
    return kref_put(&v4l2_dev->ref, v4l2_device_release);  // 当引用计数为0时,调用v4l2_device_release函数
}
EXPORT_SYMBOL_GPL(v4l2_device_put);
static 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源代码之旅一:struct v4l2_device

标签:

原文地址:http://www.cnblogs.com/ronnydm/p/5771221.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!