=========================================================================
该文档为V4L2框架给出了了各种数据结构,以及它们之间的关系
2 V4L2 介绍V4L2驱动由于硬件的复杂性使得V4L2驱动趋向变得很复杂,大多数的V4L2设备都具有多个的IC,这样的设
备在/dev目录下面也拥有多个设备节点,同时还会创建non-V4L2设备例如DVB, ALSA, FB, I2C以及input(IR)设备
特别是当V4L2驱动对应的IC在被设置成支持audio/video muxing/encoding/decoding的时候它将变得更加的复杂,通常这些IC和main bridge driver相连使用的是一或更多的I2C总线实现的,当然也可以使用其他的总线相连,像这种诸如通过总线和主控相连的设备被称为'sub-devices'.
3 structure of a driverV4L2驱动程序都应该包含下面这些结构信息:
1) 每一个设备都包含描述设备状态的实例结构.
2) 如果该设备包含子设备,则包含子设备的初始化和命令方式.
3) 创建V4L2的设备节点 (/dev/videoX,/dev/vbiX and /dev/radioX)和记录设备节点的特定数据.
4) 文件句柄特定的结构,包含每个文件句柄数据;
5) 视频缓冲的处理.
用一个大致的图来描述他们的关系:
device instances
|
+-sub-device instances
|
\-V4L2 device nodes
|
\-filehandle instances
4 v4l2 structure of the framework
该框架类似于驱动结构:驱动中用struct v4l2_devices结构来描述一个v4l2设备,struct v4l2_subdev结构来描述子设备实例,struct video_device结构用于存储视频设备节点的数据,在后续Linux的发展中会用一个struct v4l2_fh结构来记录一个视频文件的句柄(目前未实现)。该框架还关联了V4L2媒体框架。如果一个驱动程序设置struct v4l2_device mdev字段,子设备和视频节点将自动出现在media框架中
4.1 struct v4l2_device
每个v4l2设备通过struct v4l2_device (v4l2-device.h)来描述,该结构可以单独申请,同时你也可以将它嵌入到你的设备驱动的数据结构中去,struct v4l2-device结构定义如下:
include/media/v4l2-device.h
点击(此处)折叠或打开
- struct v4l2_device {
- struct device *dev; /*dev->driver_data指向该结构*/
- #if defined(CONFIG_MEDIA_CONTROLLER)
- struct media_device *mdev; /*关联的media设备*/
- #endif
- struct list_head subdevs; /*该链表用于记录该v4l2设备下注册过的子设备*/
- /* 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]; /*可以赋值成camera host平台设备名*/
- /* 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; /*该结构的引用计数,控制该设备的生命周期*/
- /*当引用计数为0的时候需要回调该方法,来释放该结构占用的资源*/
- void (*release)(struct v4l2_device *v4l2_dev);
- };
v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
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_subdev设备的链表*/
- spin_lock_init(&v4l2_dev->lock); /*初始化用于保护v4l2_device结构的锁*/
- mutex_init(&v4l2_dev->ioctl_lock);
- v4l2_prio_init(&v4l2_dev->prio);
- kref_init(&v4l2_dev->ref); /*初始化引用计数为1*/
- v4l2_dev->dev = dev; /*关联camera host所属的设备模型和v4l2所属的设备模型*/
- if (dev == NULL) {
- /* 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]) /*如果注册前该字段为NULL,则格式化赋值name字段*/
- snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
- dev->driver->name, dev_name(dev));
- if (!dev_get_drvdata(dev))/*将v4l2_dev结构记录到dev->p->driver_data中*/
- dev_set_drvdata(dev, v4l2_dev);
- return 0;
- }
- EXPORT_SYMBOL_GPL(v4l2_device_register);
1)该API用来注册struct v4l2_device设备结构,注册的时候会初始化struct v4l2_device结构(正如上述代码所示),如果dev->driver_data=NULL,则将dev->driver_data指向v4l2_dev(second argument),注意在这种情况下(dev->driver_data==NULL),在回调该API之前必须初始化struct v4l2_device结构中的name字段.
2)如果驱动想关联media_device框架则还需要设置dev->driver_data字段指向驱动特定的设备结构(driver-specific device),这个driver-specific device结构指的就是struct v4l2_device,一般的它会被嵌入到某个比较大驱动数据结构中去,这一步通常在回调注册该设备方法之前,通过回调dev_set_drvdata(struct device *dev,void *data)方法来实现(事实上v4l2_device_register接口回调了该方法),同时还必须设置struct v4l2_device mdev字段指向一个正确初始化和已注册了的struct media_device实例。
3)如果v4l2_dev->name=NULL,则v4l2_dev->name将从dev(struct device *dev)结构中取值填充,详细操作请看v4l2_device_register的实现,如果v4l2_dev->name字段你在回调该注册方法前就已经赋值的话,该值是不会被改变的,另外一种情况,如果dev结构构此时为空,那在回调v4l2_device_register方法之前你必须要初始化v4l2_dev->name字段。
4)你可以根据驱动的名字和一个驱动的全局原子实例然后通过回调v4l2_device_set_name()来设置v4l2_dev->name字段,这样产生的名字形如ivtv0, ivtv1. 如果name字段的最后一个字符是一个数字,将会在该数字之后插入一个"-":cx18-0,cx18-1.
5)v4l2_device_register()接口的第一个参数"dev"通常指向(struct device) pci_dev,usb_interface 或 platform_device的设备实例。比如说camera host设备一般做为平台设备,这个时候,该dev就指向camera host设备所属的设备结构了
6)你还可以提供一个notify()回调子设备,可以通过它的调用通知你想知道的事件。这依赖于你是否需要设置子设备。一个子设备支持的任何通知必须包含头文件中
include<media/subdevice.h>.
下面用一个例子来说明v4l2_device_register函数的使用,一般的一个camera host主控驱动将实现v4l2_device结构。在驱动程序中假设我们将struct v4l2_device结构有嵌入到xxx_dev结构中去,其中 struct xxx_dev结构是用来记录该camera host平台驱动的驱动结构体.
eg:
点击(此处)折叠或打开
- struct xxx_dev { /*用该结构来描述一个camera host设备驱动结构*/
- …
- struct v4l2_device v4l2_dev;/*指向v4l2_device*/
- }
当xxx_dev成功注册平台驱动后,会回调它的probe函数,那么注册v4l2_device就发生在probe函数当中了
点击(此处)折叠或打开
- static int xxx_probe(struct platform_device *pdev)
- {
- struct xxx_dev *xdev;
- int ret=-1;
- ….........
- /* v4l2 device register */
- ret = v4l2_device_register(&pdev->dev, &xdev->v4l2_dev);
- if (ret) {
- debug_err("Error registering v4l2 device\n");
- goto err_xx;
-
- }
- /*将struct xxx_dev *dev 这个实例保存到设备模型当中去,方便以后获取*/
- dev_set_drvdata(&(pdev)->dev, (xdev));
- }
点击(此处)折叠或打开
- v4l2_device_unregister(struct v4l2_device *v4l2_dev);
驱动程序调用该API来注销一个v4l2_device,如果dev->driver_data字段指向v4l2_dev,调用该API后它将被重置为NULL,如果你的v4l2_device上挂了v4l2_subdev设备(这是必然的),那么这些sub device也将被注销.
如果你的设备支持可热插拔事件(如USB设备)当发生断开连接的时候父设备将变为无效。由于v4l2_device有指向父设备的指针,它将被清除,以及标记父设备消失。这一步通过调用下面这个接口来实现
点击(此处)折叠或打开
- v4l2_device_disconnect(struct v4l2_device *v4l2_dev);
drivers/media/video/v4l2-device.c
点击(此处)折叠或打开
- void v4l2_device_disconnect(struct v4l2_device *v4l2_dev)
- {
- if (v4l2_dev->dev == NULL)/*如果v4l2_dev隶属的父设备已经为NULL了那么将直接返回*/
- return;
-
- if (dev_get_drvdata(v4l2_dev->dev) == v4l2_dev)
- dev_set_drvdata(v4l2_dev->dev, NULL);
- v4l2_dev->dev = NULL;/*只是将v4l2_dev->dev设置成NULL*/
- }
注意了上面这个API它只将v4l2_dev->dev设置成NULL,所以调用这个函数,并不会帮我们去注销掉属于v4l2 device下面的那些sub device的,所以要达到全局的注销还是要调用v4l2_device_unregister来实现的,当然了如果设备不支持热插拔就不需要调用该API了
通常我们都在驱动程序中用一个引用计数来记录一个设备实例的生命周期,推荐的方法如下所示:
点击(此处)折叠或打开
- 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的生命周期,如果你有多个设备节点,此时你在调用设备注销接口对v4l2_device设备进行注销的时候是可能会引起不安全的现象产生,为此v4l2_device结构提供了refcounting引用计数的支持(即struct v4l2_device结构中的ref字段),当一个视频设备被注册即video_register_device被回调的时候,refcount引用计数将增加,当有设备节点被释放时refcount引用计数减少,当refcount引用计数为0时,v4l2_device会回调release()函数指针来做最后的清理工作,v4l2 device sub system为我们提供了相应的API,当注册一个video设备的时候v4l2_dev->ref引用计数将自增1,实现如下:
点击(此处)折叠或打开
- static inline void v4l2_device_get(struct v4l2_device *v4l2_dev)
- {
- kref_get(&v4l2_dev->ref);/*引用计数自加*/
- }
当一个video设备被注销的时候会回调下面这两个API
点击(此处)折叠或打开
- 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);
- }
-
- int v4l2_device_put(struct v4l2_device *v4l2_dev)
- {
- return kref_put(&v4l2_dev->ref, v4l2_device_release);/*引用计数自减*/
- }
由此可以看出当v4l2_device的引用计数为0的时候会回调v4l2_device中的release()接口来释放一系列的内存等。
最后我们再来看看v4l2_device_unregister的实现过程,代码在drivers/media/v4l2-dev.c中
点击(此处)折叠或打开
- void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
- {
- struct v4l2_subdev *sd, *next;
-
- if (v4l2_dev == NULL)
- return;
- v4l2_device_disconnect(v4l2_dev);/*1)如果支持热插拔先断开热插拔*/
-
- /* 2)注销v4l2 subdevs 这一步在下文中会有详细的说明
- * 包括I2C,SPI设备的注销,一个摄像头模组,可以通过I2C,SPI等和camera host相连
- */
- list_for_each_entry_safe(sd, next, &v4l2_dev->subdevs, list) {
- v4l2_device_unregister_subdev(sd);
- /*3)I2C设备的注销*/
- #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)/*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
- }
- }
上面这些就主要介绍了v4l2_device结构以及他的注册和注销的信息,下面用一副图来理解v4l2_device和camera host 平台设备之间的关系