在看了很多关于v4l2驱动的例程之后,想深入研究下linux内核的v4l2框架,顺便把这些记录下来,以备查用。
Video for Linux 2
随着一些视频或者图像硬件的复杂化,V4L2驱动也越来越趋于复杂。许多硬件有多个IC,在/dev下生成多个video设备或者其他的诸如,DVB,ALSA,FB,I2C ,IR等等非V4L2的设备。所以,V4L2驱动程序就要为这些硬件设备提供音视频的合成以及编解码的功能接口,另外,通常这些设备都通过多个I2C总线实现和CPU的通讯,不仅是I2C总线,其他的也有可能被使用,比如SPI,1-wire,等等。挂在这些总线上的设备叫做sub-devices,即V4L2设备的子设备。
之前相当长的一段时间内,V4L2被限制在使用video_device来创建V4L2设备节点,使用videobuf来处理视频缓存。这就意味着,所有的驱动驱动程序除了创建一个设备实例外,还要单独实现连接”子设备”的步骤。这个过程比较复杂,也容易产生错误。正是缺少这样一种框架,使得在代码重用方面做得不够好,驱动程序看起来很臃肿。
所以V4L2框架才被整理出来,提供一些基础的组件,通过一些共享的功能函数简化驱动的编写,使代码的重用性增强,硬件设备驱动只需要实现相关的操作而不必关心交互式的应用,同时应用可以更加透明地使用硬件来驱动音视频的处理。而且这个框架也在不断地更新扩展,基础部分就是提供的v4l2API。但这里不讨论V4L2提供了哪些API和他们如何被使用,我们只讨论和v4l2核心及驱动相关的知识。
首先来看看所有的v4l2驱动都必须要有的几个组成部分:
– 用来描述每一个v4l2设备实例状态的结构(structv4l2_device)。
– 用来初始化和控制子设备的方法(structv4l2_subdev)。
– 要能创建设备节点并且能够对该节点所持有的数据进行跟踪(structvideo_device)。
– 为每一个被打开的节点维护一个文件句柄(structv4l2_fh)。
– 视频缓冲区的处理(videobuf或者videobuf2 framework)。
在linux3.0以上的内核对这些结构的定义,从定义当中就可以窥探整个v4l2的框架。这些结构体有:
struct v4l2_device; 用来描述一个v4l2设备实例
struct v4l2_subdev, 用来描述一个v4l2的子设备实例
struct video_device; 用来创建设备节点/dev/videoX
struct v4l2_fh; 用来跟踪文件句柄实例
我们把videobuf及videobuf2框架放到后面的系列来讨论。
用一个比较粗糙的图来表现他们之间的关系,大致为:
设备实例(v4l2_device)
|______子设备实例(v4l2_subdev)
|______视频设备节点(video_device)
|______文件访问控制(v4l2_fh)
|______视频缓冲的处理(videobuf/videobuf2)
好了,接下来我们一一分析一下这些结构的定义。
1、v4l2_device
这个定义在linux/media/v4l2-device.h当中定义
struct v4l2_device {
//指向设备模型的指针
struct device *dev;
#if defined(CONFIG_MEDIA_CONTROLLER)
//指向一个媒体控制器的指针
struct media_device *mdev;
#endif
//管理子设备的双向链表,所有注册到的子设备都需要加入到这个链表当中
struct list_head subdevs;
//全局锁
spinlock_t lock;
//设备名称
char name[V4L2_DEVICE_NAME_SIZE];
//通知回调函数,通常用于子设备传递事件,这些事件可以是自定义事件
void (*notify)(struct v4l2_subdev*sd, uint notification, void *arg);
//控制句柄
struct v4l2_ctrl_handler*ctrl_handler;
//设备的优先级状态,一般有后台,交互,记录三种优先级,依次变高
struct v4l2_prio_state prio;
//ioctl操作的互斥量
struct mutex ioctl_lock;
//本结构体的引用追踪
struct kref ref;
//设备释放函数
void (*release)(struct v4l2_device*v4l2_dev);
};
要注册一个实例,需要使用函数
v4l2_device_register(struct device*dev, struct v4l2_device *v4l2_dev);
该函数将会初始化v4l2_device结构,如果dev->driver_data为空,那么将把v4l2_dev赋值给这个driver_data。
如果驱动要集成媒体设备框架,就需要手动设置dev->driver_data来指向一个嵌入了v4l2_device结构的媒体设备结构,这个结构可以是指定驱动的状态描述。在注册函数之前调用dev_set_drvdata来完成,这些都要在调用register函数之前就要设置好,同样,如果有媒体设备的话,必须也要在此之前就要初始化好,并且设置v4l2_device的mdev域来指向一个已经初始化过的媒体设备实例。
如果v4l2_dev->name是空,注册函数也将根据dev->name来设置v4l2_dev的name,如果已经设置,那么注册函数将不再过问。如果dev是空,那么就必须在调用注册函数之前设置v4l2_dev->name。当然,也可以使用v4l2_device_set_name()来设置设备实例名称。
要移除注册的话,调用函数:
v4l2_device_unregister(structv4l2_device *vd)
如果是可热插拔的设备,那么还需要调用
来断开设备的连接,否则会产生空指针的问题。
v4l2_device_disconnect(structv4l2_device *vd)
有的时候需要迭代驱动注册的所有设备,这个通常出现在多个设备驱动使用相同的硬件的情况,比如说ivtvfb是一个framebuffer驱动,它使用ivtv硬件,同时也是一个tv驱动。相同的情况对于alsa驱动也是适用的。那么,迭代该怎么完成呢?看下面这个例程:
static int callback(struce device *dev,void *p)
{
struct v4l2_device *vdev =dev_get_drvdata(dev);
if (vdev == NULL) return 0;
/*do something*/
return 0;
}
int iterate(void *p)
{
struct device_driver *drv;
int err = 0;
/*Find driver 'vivi' on the PCI bus*/
drv = driver_find(“vivi”,&pci_bus_type);
err = driver_for_each_device(drv,NULL, p, callback);
put_driver(drv);
return err;
}
有时候还需要维护一个运行时的设备实例计数,定义一个原子变量就可以了。如果一个v4l2设备上注册了很多设备节点,我们在移除注册v4l2_device的时候,就要等到所有的设备节点移除之后,ref成员帮助我们记录v4l2_device的节点注册数,每次调用video_register_device都会加1,反之则减一。一旦这个值为0的时候,我们才可以调用v4l2_device_unregister。如果不是视频节点,那么手动调用这两个函数来计数:
void v4l2_device_get(struct v4l2_device*vd) //ref +1
int v4l2_device_put(struct v4l2_device*vd) // ref -1
2、v4l2_subdev
struct v4l2_subdev {
#if defined(CONFIG_MEDIA_CONTROLLER)
//媒体控制器的实体,和v4l2_device
struct media_entity entity;
#endif
struct list_head list;
struct module *owner;
u32 flags;
//指向一个v4l2设备
struct v4l2_device *v4l2_dev;
//子设备的操作函数集
const struct v4l2_subdev_ops *ops;
//子设备的内部操作函数集
const struct v4l2_subdev_internal_ops*internal_ops;
//控制函数处理器
struct v4l2_ctrl_handler*ctrl_handler;
//子设备的名称
char name[V4L2_SUBDEV_NAME_SIZE];
//子设备所在的组标识
u32 grp_id;
//子设备私有数据指针,一般指向总线接口的客户端
void *dev_priv;
//子设备私有的数据指针,一般指向总线接口的host端
void *host_priv;
//设备节点
struct video_device devnode;
//子设备的事件
unsigned int nevents;
很多 v4l2 驱动程序都需要和子设备 (sub_device) 来进行通讯,这些设备实际上完成了所有的任务,比如说音视频的合成,编码,解码。对于 webcam 来说,子设备就是 sensor 和 camera 控制器。通常这些都是 I2C 设备,但也不是必须的。为了给这些子设备提供一个一致的接口, v4l2_subdev 结构才应运而生。
};
每一个子设备都必须有一个v4l2_subdev结构。这个结构可以单独地使用或者被嵌入一个更大的结构。通常有一个更低级的设备结构(比如i2c_client),它包含设备的一些初始化数据,所以建议v4l2_subdev->dev_priv指向该数据,可以通过函数:
来设置,然后调用 v4l2_get_subdevdata() 这样就会很方便的从 v4l2_subdev 找到实际的总线相关的设备数据。总之是一些私有的数据,可以是平台相关的数据,可以是自己定义的包含了 v4l2_subdev 结构的设备实例等等。
v4l2_set_subdevdata()
v4l2_get_subdevdata()
同样也需要一个从总线相关的设备方便的找到v4l2_subdev,可以这样来实现例如:i2c_set_clientdata().调用这个函数来把v4l2_subdev结构指针赋给i2c_client的private数据。然后调用i2c_get_clientdata()获得v4l2_subdev的指针。当然也可以通过container_of来操作,但是内核既然提供了这样的api,用之何乐不为呢?
每一个v4l2_subdev包含了子设备可以实现的函数指针。这些函数可以做很多很多不同的事情,它们根据不同的操作类别被放在不同的结构当中。最高一级的操作函数集涵盖了各种操作大类。比如:
struct v4l2_subdev_core_ops {
int (*g_chip_ident)(struct v4l2_subdev*, struct v4l2_dbg_chip_ident *);
int (*log_status)(struct v4l2_subdev*sd);
int (*init)(struct v4l2_subdev *sd,u32 val);
….
};
struct v4l2_subdev_tuner_ops {};
struct v4l2_subdev_audio_ops {};
struct v4l2_subdev_video_ops {};
struct v4l2_subdev_ops {
const struct v4l2_subdev_core_ops*core;
const struct v4l2_subdev_tuner_ops*tuner;
const struct v4l2_subdev_audio_ops*audio;
const struct v4l2_subdev_video_ops*video;
};
core操作对于所有子设备来说是共通的,其他的类型可以根据子设备的需要来实现,比如一个视频子设备不太可能实现音频的操作等等。
至此我们介绍了v4l2_subdev的一些成员及操作函数,那么下面就可以进行初始化了,初始化函数调用:
v4l2_subdev_init(sd, &ops);
之后就需要初始化子设备的名字和owner等等。如果需要集成媒体框架,那么我们就必须初始化这个media_entity结构,并将其嵌入到v4l2_subdev的结构当中,这个通过调用media_entity_init()来实现。
struct media_pad *pads = &my_sd->pads;
media_entity_init(&sd->entity,npads, pads, 0);
pads数组必须在之前就已经初始化好。这里不需要进行media_entity的类型和名字的手动初始化,但是revision域如果需要的话就必须要初始化。Entity的引用参数会自动在子设备节点被打开和关闭的时候进行加减。在子设备被销毁之前不要忘了cleanup这个mediaentity.调用media_entity_cleanup(&sd->entity)。关于media_entity的相关知识这里不做讨论。下面继续讨论v4l2_subdev的注册。
注册v4l2_subdev子设备实例到v4l2_device设备系统当中,用这个函数:
v4l2_device_unregister_subdev(sd)
这个函数执行成功之后,subdev->dev将指向v4l2_device,如果v4l2_device的mdev是一个非空的值,那么subdev->entity也将会被自动注册为mdev。要移除注册的子设备,调用:
v4l2_device_unregister_subdev(sd)
接下来介绍子设备提供的功能调用,如果要使用子设备提供的接口函数,有两种方法,第一种就是直接使用ops中的回调函数,但是不推荐这样做,一般是用第二种方法,调用函数:
v4l2_subdev_call(sd, o, f, arg...)
来获取子设备芯片的标识。其中,sd就是子设备实例,o是子设备下操作函数的大类,例如可以是core/video/audio/tuner,f是大类下面的功能回调函数,arg是传入的参数。另外,还可以通过v4l2设备实例调用全部子设备的功能回调函数,使用这个函数:
v4l2_device_call_all(v4l2, grp_id, o,f, arg...)
其中grp_id就是子设备的组标识。举个例子:
v4l2_subdev_call(sd, video,g_chip_cap, &cap);
v4l2_device_call_all(v4l2, 0, core,g_chip_id, &cap);
前者是调用子设备sd的video类下的g_chip_cap功能回调函数;后者是v4l2设备调用所有子设备的core类下的g_chip_id功能回调函数。grp_id非0则指定调用相同组标识的该方法。子设备还需要通知它的v4l2父设备发生了什么事件,这个通过调用下面这个函数实现。
v4l2_subdev_notify(sd, notification,arg)
但是父设备必须要有能够处理这些事件的能力,就是实现v4l2_device的notify功能。
除了通过v4l2_subdev_ops结构暴露给内核的API之外,v4l2子设备也同样可以被用户程序直接控制。设备节点名为v4l-subdevX创建在/dev目录下,这样就可以通过打开设备文件来直接访问子设备。如果一个子设备支持直接的用户空间访问,那么它就必须在被注册之前就设置V4L2_SUBDEV_FL_HAS_DEVNODE标志。注册子设备之后,v4l2_device驱动就会为所有持此标志的子设备创建设备节点。这个通过v4l2_device_register_subdev_nodes()来实现。这个设备节点可以处理一组标准的V4l2API子集,如下:
VIDIOC_QUERYCTRL
VIDIOC_QUERYMENU
VIDIOC_G_CTRL
VIDIOC_S_CTRL
VIDIOC_G_EXT_CTRLS
VIDIOC_S_EXT_CTRLS
VIDIOC_TRY_EXT_CTRLS
所有上面这些控制调用都可以通过core:ioctl操作来完成。至此,关于v4l2子设备相关的知识就介绍完毕。
备注:
内核为我们提供了很多帮助函数,比如v4l2_i2c子设备驱动框架,使编写此类驱动变得容易很多。因为此类驱动有很多共通之处,所以可以将其抽象出来以便易于使用。这个抽象在v4l2_common.h当中。
给一个I2C驱动添加v4l2_subdev支持的推荐方法是将v4l2_subdev结构嵌入I2C设备实例的state结构当中。非常简单的设备没有这个结构,那么就直接创建一个v4l2_subdev实例就好了。一个典型的state结构就像这样:
struct chipname_state {
struct v4l2_subdev sd;
….. /*这里存放额外的状态域*/
};
初始化一个v4l2_subdev并且将其和i2c总线设备连接起来
v4l2_i2c_subdev_init(&state->sd,client, subdev_ops);
这个函数将填充所有v4l2_subdev结构的域,并且保证v4l2_subdev和i2c_client能够互相找到。最好是能够实现一个state和subdev互访的inline函数:
static inline struct chipname_state*to_state(struct v4l2_subdev *sd)
{
return container_of(sd, structchipname_state, sd);
}
然后通过如下函数实现v4l2_subdev和i2c_client的互访:
struct i2c_client *client =v4l2_get_subdevdata(sd);
struct v4l2_subdev *sd =i2c_get_clientdata(client);
要确保在subdev的驱动被remove的时候调用如下函数:
v4l2_device_unregister_subdev(sd);
另外还有一些帮助函数可以使用:
struct v4l2_subdev *sd =v4l2_i2c_new_subdev(v4l2_dev, adapter, “module_foo”,“chipid”, 0x36, NULL);
这个函数会load一个i2c的adapter然后调用i2d_new_device并且根据chipid和i2c的地址(0x36)来创建一个新的i2c设备,之后会将模块名为module_foo的subdev注册到v4l2_dev里面去。关于其他的帮助函数,请查阅v4l2-common.h文件
3、video_device
struct video_device{
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_entity entity;
#endif
const struct v4l2_file_operations*fops;
struct device dev; /* v4l device */
struct cdev *cdev; /* characterdevice */
struct device *parent; /* deviceparent */
struct v4l2_device *v4l2_dev; /*v4l2_device parent */
struct v4l2_ctrl_handler*ctrl_handler;
struct v4l2_prio_state *prio;
char name[32];
int vfl_type;
int minor;
u16 num;
unsigned long flags;
int index;
spinlock_t fh_lock; /* Lock forall v4l2_fhs */
struct list_head fh_list; /* List ofstruct v4l2_fh */
int debug; /* Activates debuglevel*/
v4l2_std_id tvnorms; /* Supported tvnorms */
v4l2_std_id current_norm; /* Currenttvnorm */
void (*release)(struct video_device*vdev);
const struct v4l2_ioctl_ops*ioctl_ops;
struct mutex *lock;
};
在/dev中实际的设备节点使用video_deice结构创建。该结构既可以被动态创建,也可以被嵌入到更大的结构当中:
动态创建:
struct video_device *vdev =video_device_alloc();
vdec->release =video_device_release;
嵌入到更大的结构当中:
Struct video_device *vdev =&my_vdev->vdev;
vdev->relase = my_vdev_release;
要完成这个结构的初始化,还需要设置以下的域:
.v4l2_dev 设置v4l2_device父设备
.name 设置唯一的描述名
.fops 设置v4l2_file_operations结构
.ioctl_ops 使用v4l2_ioctl_ops来简化ioctl的维护
.lock 如果想在驱动中进行全局锁定的话设置为NULL,否则初始化为一个 mutex_lock,这样就可以在unlocked_ioctl的操作之前和之后对操作内容进行保护
.prio 跟踪属性。用来实现VIDIOC_G/S_PRIORITY,如果设置为NULL,将使用v4l2_device中的v4l2_prio_state。
.parent NULL . 如果硬件中有多个PCI设备共享v4l2_device核心,那么就要设置父设备。
.flags 可选,设置V4L2_FL_USE_FH_PRIO,如果想让framework来处理VIDIOC_G/S_PRIORITYioctls的话。
如果要使用v4l2_ioctl_ops,那么就需要将.unlocked_ioctl设置为video_ioctl2,这样的话,就可以打通上层应用在使用ioctl操作/dev/videoX的时候和v4l2设备之间的信 息交换通道。
在某些应用场景下,还需要mask掉在v4l2_ioctl_ops中指定的功能,那么需要调用
void v4l2_disable_ioctl(structvideo_device *vdev, unsigned int cmd)来屏蔽对该cmd的调用。
v4l2_file_operations结构是一个file_operations的子集,主要的区别是inode参数不再使用了,因为从来没有用到过。
Ioctls和locking域:
V4L2核心提供了可选的锁定服务,主要体现在video_device结构当中的lock域,指向一个mutex,如果你初始化了这个域,那么它将被用在unlocked_ioctl中用来序列化所有的ioctl操作。
如果使用videobuf2框架来管理视频缓冲,那么还得在初始化一个video_device->queue->lock,并且这个锁会替代video_device->lock来序列化所有队列ioctl的操作。
队列ioctl的操作使用不同的锁有个优点,就是某些设备的操作需要很长的时间,而在此期间的其他非队列ioctl操作也可以进行。举个例子,有个场景就是既要设置camera的闪光灯亮,也要从videobuf当中读取数据的时候,我们就需要分开来锁定两个ioctl,以便两者能够基本上保持同步。
在videobuf2当中,需要实现wait_prepare和wait_finish回调函数来lock/unlock,如果想要使用queue->lock,最好是使用vb2_ops_wait_prepare/finish
video_device 的注册:
接下来,通过video_device_alloc()分配好一个video_device之后,就要把它注册到系统当中,这将会为你创建一个字符设备。
err = video_register_device(vdev,VFL_TYPE_GRABBER, -1);
if (err) {
video_device_release(vdev);
return err;
}
如果v4l2_device有一个非空的mdev,那么video_device实体也会自动注册媒体设备。
具体注册成什么类型的V4l2设备,要看type指定为什么:
VFL_TYPE_GRABBER video的输入输出设备,体现为/dev/videoX
VFL_TYPE_VBI vertical blank data体现为/dev/vbiX
VFL_TYPE_RADIO radio tuners设备/dev/radioX
第三个参数是指定设备号,如果传入-1的话,就是让v4l2框架自动选择一个可用的node号,如果指定了非-1的参数,并且这个参数代表的设备已经被注册了,那么系统也会自动选择下一个可用的设备号,但是会给出警告。
一旦一个video_device被创建,那么框架也会同时为你创建一些设备属性节点,在/sys/class/video4linux/videoX/下你会看到诸如name,index等的属性节点。
如果注册失败了,那么就要调用video_device_release()来释放所有申请的资源。
video_device的清理工作:
当要移除视频设备节点的时候,就要调用video_unregister_device(vdev)将之前注册到系统中的信息销毁掉。
一些videodevice的帮助函数
file/video_device的private_data,我们可以通过以下这些函数来设置和获取驱动的private_data:
void * video_get_drvdata(structvideo_device *vdev)
void video_set_drvdata(structvideo_device *vdev, void * data)
struct video_device*video_devdata(struct file *file)会返回一个属于file的video_device结构体指针。
Void * video_drvdata(struct file*file)首先使用video_devdata获取video_device,然后通过video_get_drvdata获取private_data.
设备节点名:
video_device_node_name(structvideo_device *vdev)返回一个字符串。
到这儿,就基本上了解了v4l2的控制框架。接下来的博文中,会介绍videobuf及videobuf2的相关知识。
Videobuf
下面来介绍以下videobuffer相关的一些东西。
V4L2核心api提供了一套标准的方法来处理视频缓冲,这些方法允许驱动实现read(),mmap(), overlay()等操作。同样也有方法支持DMA的scatter/gather操作,并且支持vmallocbuffer(这个大多用在USB驱动上)。
videobuf层功能是一种在v4l2驱动和用户空间当中的依附层,这话看起来有点绕,说白了就是提供一种功能框架,用来分配和管理视频缓冲区,它相对独立,却又被v4l2驱动使用。它有一组功能函数集用来实现许多标准的POSIX系统调用,包括read(),poll()和mmap()等等,还有一组功能函数集用来实现流式(streaming)IO的v4l2_ioctl调用,包括缓冲区的分配,入队和出队以及数据流控制等操作。使用videobuf需要驱动程序作者遵从一些强制的设计规则,但带来的好处是代码量的减少和v4l2框架API的一致。
缓冲类型
并不是所有的视频设备都使用相同的缓冲类型。实际上,有三种通用的类型:
–被分散在物理和内核虚拟地址空间的缓冲,几乎所有的用户空间缓冲都是这种类型,
如果可能的话分配内核空间的缓冲也很有意义,但是不幸的是,这个通常需要那些支持离散聚合DMA操作的硬件设备。
–物理上离散的但是虚拟地址是连续的,换句话说,就是用vmalloc分配的内核缓冲。这些缓冲很难用于DMA操作。
– 物理上连续的缓冲。
videobuf可以很好地处理这三种类型的缓冲,但是在此之前,驱动程序作者必须选择一种类型,并且以此类型为基础设计驱动。
数据结构,回调函数和初始化
根据选择的类型,包含不同的头文件,这些头文件在include/media/下面
<media/videobuf-dma-sg.h>
<media/videobuf-vmalloc.h>
<media/videobuf-dma-contig.h>
v4l2驱动需要包含一个videobuf_queue的实例用来管理缓冲队列,同时还要一个链表来维护这个队列,另外还要一个中断安全的spin_lock来保护队列的操作。
下一步就是要填充一个回调函数集来处理实际的缓冲区队列,这个函数集用videobuf_queue_ops来描述:
struct videobuf_queue_ops {
int *(buf_setup)(struct videobuf_queue*q, uint *count, uint *size);
int *(buf_prepare)(structvideobuf_queue *q, struct videobuf_buffer *vb,
enum v4l2_field field);
void *(buf_queue)(structvideobuf_queue*q,struct videobuf_buffer *vb);
void *(buf_release)(...);
}
buf_setup在IO处理请求之前被调用。目的是告诉videobuf关于IO的信息。 count参数提供一个缓冲区个数的参考,驱动必须检查它的合理性,一个经验是大于等于2,小于等于32个。Size参数指定了每一帧数据的大小。
buf_prepare每一个缓冲(videobuf_buffer结构描述的)将被传递给该回调函数,用来配置缓冲的height,width和fileds。如果field参数被设置为VIDEOBUF_NEEDS_INIT,那么驱动将把vb传递给videobuf_iolock()这个函数。除此之外,该回调函数通常也将为vb分配内存,最后把vb的状态置为VIDEOBUF_PREPARED。
buf_queue当一个vb需要被放入IO请求队列时,调用该回调。它将把这个buffer放到可用的buffer链表当中去,然后把状态置为VIDEOBUF_QUEUED。
buf_release当一个buffer不再使用的时候,调用该回调函数。驱动必须保证 buffer上没有活跃的IO请求,之后就可以将这个buffer传递给合适的 free函数,根据申请的buffer类型调用对应的释放函数:
scatter/gather类型的调用
videobuf_dma_unmap(structvideobuf_queue, videobuf_dmabuf)
videobuf_dma_free(videobuf_dmabuf)
vmalloc类型的调用
videobuf_vmalloc_free(videobuf_buffer)
contiguous类型的调用
videobuf_dma_contig_free(videobuf_queue,videobuf_buffer)
有一种方法可以保证buffer上没有IO请求,调用函数
videobuf_waiton(videobuf_buffer,non_blocking, intr)
文件操作(v4l2_file_operations)
到了这儿,很多工作也就做完了,剩下的事情就是将对videobuf的调用传递给具体的驱动实现了。首先就是打开操作,这个操作要先对videobuf_queue进行初始化,初始化取决于申请的buffer是什么类型,有如下三种初始化函数可供调用:
void videobuf_queue_sg_init(structvideobuf_queue *q,
struct videobuf_queue_ops *ops,
struct device *dev,
spinlock_t *irqlock,
enum v4l2_buf_type type,
enum v4l2_field_ field,
unsigned int msize,
void *priv,
struct mutex *ext_lock)
void videobuf_queue_vmalloc_init(structvideobuf_queue *q,
struct videobuf_queue_ops *ops,
struct device *dev,
spinlock_t *irqlock,
enum v4l2_buf_type type,
enum v4l2_field field,
unsigned int mszie,
void *priv,
struct mutex *ext_lock);
voidvideobuf_queue_dma_contig_init(struct videobuf_queue *q,
struct videobuf_queue_ops *ops,
struct device *dev,
spinlock_t *irqlock,
enum v4l2_buf_type type,
enum v4l2_field field,
unsigned int mszie,
void *priv,
struct mutex *ext_lock);
以上三种初始化函数,有相同的参数,这些参数的从他们的名称就可以看出来锁代表的意义是什么。
这里着重说下v4l2_buf_type类型,
V4L2_BUF_TYPE_VIDEO_CAPTURE 指定buf的类型为capture,用于视频捕获设备
V4L2_BUF_TYPE_VIDEO_OUTPUT 指定buf的类型output,用于视频输出设备
V4L2_BUF_TYPE_VIDEO_OVERLAY 指定buf的类型为overlay,用于overlay设备
V4L2_BUF_TYPE_VBI_CAPTURE 用于vbi捕获设备
V4L2_BUF_TYPE_VBI_OUTPUT 用于vbi输出设备
V4L2_BUF_TYPE_SLICED_VBI_CAPTURE 用于切片vbi捕获设备
V4L2_BUF_TYPE_SLICED_VBI_OUTPUT 用于切片vbi输出设备
V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY 用于视频输出overlay设备
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 用于多平面存储格式的视频捕获设备
V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE用于多平面存储格式的视频输出设备
v4l2_field指定video的field,也就是说interleaved或者progressive的,一般都是指定为V4L2_FIELD_NONE,用于逐行扫描的设备。
V4L2捕获设备驱动可以支持两种API:read()系统调用和更为复杂的流机制。一般的做法是两种都支持以确保所有的应用都可以使用该设备。videobuf框架使得这种驱动的编写变得更为简单。比如说要实现read()系统调用,那么驱动程序只需要调用
ssize_t videobuf_read_one(structvideobuf_queue *q,
char __user *data, size_t count,loff_t *ppos, int nonblocking)
ssize_t videobuf_read_streaming(structvideobuf_queue *q, char __user *data, size_t count, loff_t *ppos,int vbihack, int nonblocking)
这两个函数都是把帧数据读入到data当中,然后返回实际上读取的字节数。不同的是前者只读取一帧数据,而后者可以选择读取多帧。一个典型的应用read()系统调用必须开启捕获设备,然后返回之前停止该设备。
poll()系统调用通常由以下函数来实现:
unsigned intvideobuf_poll_stream(struct file *file, struct videobuf_queue *q,
poll_table *wait)
注意,实际最终使用的q是可用的第一个buffer。
当内核空间缓冲的流IO请求完成后,驱动还必须支持mmap系统调用以使能用户空间可以访问data数据。在v4l2驱动中,通常很复杂的mmap的实现被简化了,只需要调用下面这个函数就可以了:
int videobuf_mmap_mapper(structvideobuf_queue *q,
struct vma_area_struct * vma)
剩下的事情就交给videobuf核心层来完成好了。
release函数需要调用两个单独的函数来完成:
void videobuf_stop(structvideobuf_queue *q);
int videobuf_mmap_free(structvideobuf_queue *q)
前者终止所有buffer的IO操作。后者保证所有的buffer被unmap掉,如果已经被unmap掉的话,这个buffer就会被传递给buf_release回调函数。如果buffer还没有被unmap,那么后者将返回一个错误代码。
Ioctl操作:
v4l2api涵盖了很长一组驱动回调函数来响应用户的ioctl操作,有很大一部分和流IO操作相关的都是直接调用到videobuf里面来。相关的函数如下:
int videobuf_reqbufs(structvideobuf_queue *q,
structv4l2_requestbuffers *req);
int videobuf_querybuf(structvideobuf_queue *q, struct v4l2_buffer *vb)
int videobuf_qbuf(strurctvideobuf_queue *q, struct v4l2_buffer *vb)
int videobuf_dqbuf(structvideobuf_queue *q, struct v4l2_buffer *vb)
int videobuf_streamon(structvideobuf_queue *q);
int videobuf_streamoff(structvideobuf_queue *q);
Buffer的分配
讲到这儿,我们讨论了很多关于buffer的话题,但是却没有提到他们是怎么分配的。Scatter/gather例子比较复杂,驱动程序可以完全让videobuf层去完成buffer的分配,在这种情况下,buffer将被分配为匿名用户空间页并且实际上将非常分散。如果应用程序使用用户空间的buffer的话,驱动也就不需要分配了,videobuf层将小心的调用get_user_pages()并且填充离散列表数组(scatterlistarray)。
如果驱动程序要自己做内存分配,那么将在vidioc_reqbufs函数中进行,在调用了videobuf_reqbufs()之后,首先第一步就是要调用到
struct videobuf_dmabuf*videobuf_to_dma(struct videobuf_buffer *buf)
返回的videobuf_dmabuf包含了一对相关的域
struct scatterlist *sglist;
int sglen;
驱动必须分配合适大小的scatterlist数组,并且将分配的内存片和指针对应起来,sglen指定了scatterlist数组的大小。
驱动当中如果使用了vmalloc()来分配内存的话,就不用关心buffer的分配了,videobuf层将处理具体的细节,一些驱动程序使用了小技巧,就是在系统启动的时候就分配好了dma内存,以避免动态申请有的时候会申请不到的问题,但是对于此类的设计,videobuf层目前还不能很好的胜任。在3.0以上的内核当中,出现了一种新的框架videobuf2,已经解决了这个问题,我们会在后面详细介绍这个框架。
Filling缓冲区
videobuf层的最后一部分就是关于将帧数据传递到buffer中的实现。这一部分没有直接的回调函数,通常都是在设备的中断响应中来完成。对于所有类型的驱动,流程大概是这个样子的:
– 获取下一个buffer,并且确保有人正在等待这个buffer
– 得到一个内存指针,然后将视频数据放到那个地方
-- 标记buffer完成,并且唤醒等待的进程
第一步,buffer可以通过驱动管理的一个链表获得,这个链表由buf_queue回调函数填充,所以驱动最先要是链表初始化为空,并且如果链表当中的buffer没有一个进程在其上等待的话,是不能被移除或者填充的。
另外buffer在被map到dma之前,要把它的状态设置为VIDEOBUF_ACTIVE,这将保证在设备传输数据的时候videobuf层不去尝试任何操作。
第二步,得到一个内存指针,对于scatter/gather类型的内存来说,可以从scatterlist当中找到内存指针;对于vmalloc类型的来说调用
void * videobuf_to_vmalloc(structvideobuf_buffer *vb)
对于连续物理内存类型来说调用
dma_addr_tvideobuf_to_dma_contig(struct videobuf_buffer *buf)
第三步,就是设置videobuf_buffer中的大小,并且把buffer的状态设置为VIDEOBUF_DONE,然后在完成队列上调用wake_up().到此,buffer就真正的属于videobuf层了,驱动程序不用再去关心它如何被调度。
最后,一个很好的关于v4l2的例子就是drviers/media/video/vivi.c,它使用了vmalloc类型的videobuf,可以通过阅读这份例子来学习v4l2驱动的写法。
Videobuf2框架
1. 什么是videobuf2框架?
它是一个针对多媒体设备的v4l2兼容驱动框架,是用户空间应用程序和设备驱动的中间层。它为驱动提供更为底层的模块化的内存管理功能。
它能够使得驱动开发变得简单,减少代码量,帮助合理的连续的实现驱动当中的v4l2接口。
videobuf2的内存管理是完全模块化的,这就允许在不改变更高级别缓冲管理框架的情况下可以为设备和平台定制内存管理方法。
框架提供了一下三种:
实现了v4l2_ioctl的流控和文件操作
高级的视频缓冲,视频队列和状态管理
视频缓冲内存分配和管理
2.为什么要新开发一种框架呢?
在当前的videobuf实现当中,有很多问题,在2010年举行的赫尔欣基峰会上重点提到了这么几个:
V4L2 API出现异常和错误的内存管理设计
不能停止stream请求,缓冲在streamoff的时候被释放
VIDIOC_REQBUFS 不释放内存,也不能重新分配内存
视频内存在mmap,qbuf或者页错误的时候才分配
每个缓冲都有一个等待队列
扩展性不够强,尤其对于嵌入式多媒体设备来说支持不够
很难加入定制的内存分配和管理机制
不支持对缓存一致性和IOMMU设备
不够灵活,只有一个包办一切的函数来处理内存锁定,缓存,sg-list的创建
很多未使用的域,还有代码的重复,模糊晦涩的命名
很多驱动程序作者发布基于videobuf的基础组件.开发者也承认videobuf的功绩,也乐意使用它,但是由于灵活性不够现在不能这么做了。
3.重新设计的目的
修正V4L2API的实现,修复videobuf的问题和缺陷
分离缓冲队列管理和内存管理
在内存的分配和管理上更加灵活,可以嵌入定制的机制
更加有针对性的驱动回调函数,在不同的地方调用
支持新的V4L2API扩展,例如多平面视频帧存储的支持
4. 驱动回调函数
对称的驱动回调函数设计:
buf_init 在内存被分配后或者一个新的USERPTR缓冲入队之后调用一次,比如用来锁定页,验证连续性,设置IOMMU映射等等。
buf_prepare每个QBUF都要调用,用来同步缓存,拷贝数据到buffer等
buf_finish每个DQBUF调用,用来同步缓存,从buffer中取回数据等
buf_cleanup 在free/release内存的时候调用
其余的回调函数也有重新设计:
queue_negotiate现在合并了多平面的扩展;驱动返回所需要的缓冲数和每个缓冲的平面数。
plane_setup 驱动返回平面的尺寸大小
这两个调用取代了老的buf_setup
buf_queue 保留了原来的功能,将buffer放入请求队列。
5. 内存分配和处理
内存处理这块设计得更加个性化,使得内存分配可以定制,定制的函数放在一个叫做v4l2_alloc_ctx的结构体当中。它的目的是给videobuf提供操作函数,并且存放一些私有数据。私有数据可以被嵌入到更大的一些结构体当中。
Struct vb2_alloc_ctx {
const struct vb2_mem_ops *mem_ops;
}
struct vb2_foo_alloc_conf {
strucdt vb2_alloc_ctx alloc_ctx;
/* private data*/
}
更重要的是引入了一个buffer上下文结构的概念,在每次分配之后,分配器返回他们自己,定制的和每个buffer的结构。这个结构可以当作cookie传递给其他的内存处理方法。
存放在分配器上下文的内存操作可以被其他的分配器取代,详细的文档可以参考videobuf2-core.h。
一个非常好的例子从三星galaxy S系列的android手机内核源码中的videbuf2-cma.c,可以看看这个例子。
from:http://blog.csdn.net/ramon1892/article/details/8443668