标题: V4L2核心框架分析
驱动的结构
------------------------------------------------------
1)一个为设备实例定义的,并且包含设备状态信息的结构;
2)一种初始化和命令子设备(sub-devices)的方式;
3)创建V4L2设备节点(/dev/videoX, /dev/vbiX, /dev/radioX and /dev/vtxX)
并且 keeping track of device-node specific data.
4)Filehandle-specific structs containing per-filehandle data;
5)视频buffer处理;
下面有个大略的关系描述图:
device instances
|
+-sub-device instances
|
\-V4L2 device nodes
|
\-filehandle instances
框架的结构
------------------------------------------------------------
框架结构与驱动结构类似:
它有:
一个用于device实例数据的结构体: v4l2_device ;
一个用于sub-device实例的结构体:v4l2_subdev ;
一个存储设备节点数据的结构体:video_device ;
将来会用于保持对文件操作实例的追踪的结构体:v4l2_fh ;
v4l2_device结构体(struct v4l2_device)
-----------------------------------------------------------------------
每个设备实例是由一个struct v4l2_device结构(v4l2-device.h)来描述的。
很简单的设备可以只分配这个结构,但是,大部分时候,你将嵌入这个结构到一个更大的与描述特定设备的结构。
你必须注册设备实例(在drivers/media/video/v4l2-device.c中):
v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
注册将初始化v4l2_device结构,和连接(link) dev->driver_data 到v4l2_dev。
如果v4l2_dev->name 是空的,那么它将被设置成一个值,这个值是由dev参数得到的(严格来说,驱动名跟随bus_id)。
如果你在调用v4l2_device_register()之前设置了v4l2_dev->name,那么v4l2_dev->name将不被改变(即使用你之前设置的值)。
如果dev是NULL,那么你必须在调用v4l2_device_register()之前填充v4l2_dev->name。
你可以使用 v4l2_device_set_name()来设置这个名称为驱动的名字 和 一个 driver-global atomic_t instance。
这将产生一些名字如: ivtv0, ivtv1 等。如果这些名字(ivtv0中的ivtv就是一个名字)以一个数字结束,那么它将插入一个“-”,然后跟上一个顺序的编号。
v4l2_device_set_name()这个函数返回这个实例号。
int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename,
atomic_t *instance)
{
int num = atomic_inc_return(instance) - 1;
int len = strlen(basename);
if (basename[len - 1] >= '0' && basename[len - 1] <= '9')//如果名字的最后是数字;
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
"%s-%d", basename, num);//那么名字后面加“-”
else
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
"%s%d", basename, num);
return num;
}
EXPORT_SYMBOL_GPL(v4l2_device_set_name);
现在回头说v4l2_device_register函数。它的第一个参数dev通常都是一个pci_dev
、usb_interface 或者 platform_device 的“struct device”类型的指针。
dev基本上不会是NULL,但是在ISA devices或者当一个设备创建了多个PCI设备的时候,这是可能发生的。
这时候它与v4l2_dev产生了关系,v4l2_dev下可能有个dev成员(即v4l2_dev是父,dev是子)。
你也可以提供一个 notify()的回调函数,子设备(sub-devices)可以调用这个回调函数通知你一些事件(events);
你是否需要设置这个回调函数,取决于这个子设备。一个子设备支持的任何通知(notifications)必须被定义在头文件“include/media/<subdevice>.h”当中。
比如include/media/ 下有如下文件:
v4l2-chip-ident.h v4l2-fh.h v4l2-mem2mem.h videobuf-dvb.h
v4l2-common.h v4l2-i2c-drv.h v4l2-subdev.h videobuf-vmalloc.h
v4l2-dev.h v4l2-int-device.h videobuf-core.h tvp5150.h(这个就是subdevice的头文件)
v4l2-device.h v4l2-ioctl.h videobuf-dma-contig.h
v4l2-event.h v4l2-mediabus.h videobuf-dma-sg.h …………
有注册,就有反注册。那对应的反注册的函数是:
v4l2_device_unregister(struct v4l2_device *v4l2_dev);
反注册,也将会自动从设备反注册掉所有的子设备(subdevs)。
如果你有一个可热插拔的(hotpluggable)的设备(例如USB设备),那么的那个一个disconnect(断开)发生时,父设备变成无效的(invalid)。
因为v4l2_device中有一个指针指向父设备,所以他可以同时被清除掉,并且标记父设备is gone(这里应该是在父设备的结构里标记子设备已经无效了)。
v4l2_device_disconnect(struct v4l2_device *v4l2_dev); //热插拔使用的断开函数
void v4l2_device_disconnect(struct v4l2_device *v4l2_dev)
{
if (v4l2_dev->dev) {//这个函数制式标记了父设备那边告诉子设备已经无效。
dev_set_drvdata(v4l2_dev->dev, NULL);
v4l2_dev->dev = NULL;
}
}
EXPORT_SYMBOL_GPL(v4l2_device_disconnect);
v4l2_device_disconnect不会反注册掉子设备,所以你依然需要为子设备调用v4l2_device_unregister这个函数。
事实上,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);
…………
}
如果你的设备不支持热插拔,那么就不需要调用v4l2_device_disconnect()了。
有时候,你需要迭代把所有的设备通过一个特定的驱动注册。这种情况通常发生在,多个设备驱动使用同一个硬件(hardware)的时候。
例如(E.g.),ivtvfb 驱动是一个 帧缓冲(framebuffer)设备驱动,这个驱动使用ivtv这个硬件。类似的 alsa驱动也是一样。
你可以像如下这样迭代所有已注册的设备:
static int callback(struct devic