标题: 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/
比如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 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);//在&pci_bus_type上查找ivtv的驱动,并返回驱动的指针drv
/* iterate over all ivtv device instances */
err = driver_for_each_device(drv, NULL, p, callback);//这里用到了callback,和drv
put_driver(drv);//在争用的那个硬件上使用驱动drv;
return err;
}
有时候你需要保持一个正在运行的设备实例的计数器(counter)。这个计数器经常用于映射(map)一个设备实例到一个模块可选数组的索引(an index of a module option array)。
建议的方法如下:
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;//atomic_inc_return(&drv_instance)自加的同时返回值
}
子设备结构(struct v4l2_subdev)
-----------------------------------------------------------------------------------
许多驱动需要与子设备(sub-devices)通信。这些设备可以对任务进行排序,但是更常见的是,它们处理audio and/or video复用(muxing),以及编码和解码。
对于webcams网络摄像头通常子设备是传感器(sensors)和摄像头控制器(camera controllers)。
通常这些是I2C设备,但不一定就是I2C设备。为了提供一个具有一致接口的驱动给这些子设备, v4l2_subdev 结构(v4l2-subdev.h)就是为此而诞生的.
每个子设备驱动必须有一个 v4l2_subdev 结构。这个结构可以让简单的子设备保持独立,或者可以被嵌入到一个更大的结构体(如果更多的状态信息需要被存储的话)。
通常,有一个低层次的(low-level)设备结构(例如:i2c_client),包含了这个设备数据(device data),它由内核建立。
建议使用v4l2_set_subdevdata()函数存储一个i2c_client的指针到“v4l2_subdev”结构中的私有数据区域( private data)。
这样使得很容易从v4l2_subdev交互到一个实际的低层次的特定总线(low-level bus-specific)设备数据。
你也需要一种方法去从低层次的结构 走到 v4l2_subdev。对于共有的i2c_client 结构,i2c_set_clientdata()这个函数调用可以被用来存储一个v4l2_subdev类型的指针,对于其他总线,你可能必须使用其他方法。
从桥接驱动的观点上来看(From the bridge driver perspective),你要加载子设备模块,并且以某种方式(somehow)获得v4l2_subdev的指针。
对于i2c设备这是很容易的:你调用i2c_get_clientdata()函数就可以了。对于其他总线,有类似的的做法。
在一个I2C总线的子设备中有帮助函数存在,这些函数会帮你做大部分繁琐复杂的工作。
每个v4l2_subdev包含了若干函数指针,子设备驱动可以实现这些函数(或者让它为 NULL,如果不用到它的话)。因为子设备可以做任何这样的事情,并且你不想以一个巨大的ops结构(一大串通常已经实现的ops,ops即包含上述函数指针的结构体)。这些函数指针已经按照分类排序了,并且每个分类具有它自己的ops结构体。
顶层的(top-level)的ops结构体包含了到分类ops结构体的指针,它们可以是NULL,如果子设备驱动(subdev driver)不支持这个分类的任何功能的话。
这些ops看起来是这样:
struct v4l2_subdev_core_ops {
int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip); 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 {
...
};
//最后这个子设备的 ops 包含了 core_ops 、tuner_ops、audio_ops、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;
};
核心ops(core_ops)是所有子设备(subdevs)公用的,其他分类的ops是否试下取决于子设备。例如,一个视频设备不太可能去支持一个 audio ops 和vice versa。那么就不用事先相应分类的ops。
这种建立ops关系的方式,在依然保持它们易于添加新的ops和分类的同时,也限制了函数指针的数量。
一个子设备驱动使用如下函数初始化 v4l2_subdev结构:
v4l2_subdev_init(sd, &ops);
然后,你需要初始化 使用一个唯一的名字来初始化 subdev->name,并且设置这个 module owner。如果你使用I2C帮助函数,那么这些帮助函数都已经为你做了这些工作了。
一个设备(桥接)驱动,需要使用如下函数去把 v4l2_subdev 与 v4l2_device 注册到一起:
int err = v4l2_device_register_subdev(v4l2_dev, sd);//sd代表v4l2_subdev;v4l2_dev代表:v4l2_device
如果子设备模块在它成功注册之前消失了,那这个注册函数肯定会失败的。
这个函数成功执行之后, subdev->dev 就会指向 v4l2_device。于是就关联起来了。
/*******2012年8月14日10:00 更新*********/
子设备移除(反注册);你也可以通过这个函数反注册掉之前注册的子设备:
v4l2_device_unregister_subdev(sd);
在这个函数执行之后,子设备模块就被卸载了(be unloaded),并且sd->dev==NULL;
你可以也可以直接调用ops函数:
err = sd->ops->core->g_chip_ident(sd, &chip);
但是,用下面这个宏的话,更方便,更快捷:
err = v4l2_subdev_call(sd, core, g_chip_ident, &chip);
这个宏将做NULL指针的检查,并且返回错误码 -ENODEV(如果子设备(subdev)是NULL的话);
subdev->core 或 subdev->core->g_chip_ident 是NULL的话,就会返回 -ENOIOCTLCMD;
如果输入参数都没有问题,那就会返回 subdev->ops->core->g_chip_ident 这个操作的返回值。
一般也会调用 sub-devices中的所有函数,或是一个子集(某类函数):
v4l2_device_call_all(v4l2_dev, 0, core, g_chip_ident, &chip);
任何不支持这个ops的子设备(subdev),会被跳过,并且错误的结果也会被忽略。
如果你想检查错误,请使用:
err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_chip_ident, &chip);
除了-ENOIOCTLCMD的任何错误,将带着错误退出循环(loop)。除了-ENOIOCTLCMD以外,如果没有错误发生,那么就返回0。
以上两个函数(v4l2_device_call_all,v4l2_device_call_until_err)的第二个参数是一个group ID。如果是0,那么所有的 subdevs被调用。如果是“非零”,那么这个非零的数字就是group ID,并且执行这个groupID内的所有的函数。
在一个桥接驱动注册一个subdev之前,它可以设置sd->grp_id为任意它希望的值(默认是0).这个值是桥接驱动(bridge driver)所拥有的,并且子设备驱动(sub-device driver)将不会修改或者使用它。
group ID 给了桥接驱动更多的控制,这些控制就是回调函数(callbacks)。
例如,现在有多个audio芯片在板上,每个都由改变音量的能力。但是,通常实际被使用的仅有一个(当用户需要去改变音量的时候)。那么你可以为那个子设备(subdev)设置 group ID 。例如,AUDIO_CONTROLLER,并且在调用v4l2_device_call_all()时,可以指定AUDIO_CONTROLLER 为group ID 值(第二个入参)。这样可以确保,它仅操作它需要的子设备(subdev)。
如果子设备需要通知(notify …… of ……) 它的父设备 v4l2_device 一个事件,那么就可以调用: v4l2_subdev_notify(sd, notification, arg)。这个宏会检查是否有一个 notify() 的回调函数已经定义,如果没有定义的话,就返回错误码:-ENODEV。否则,当然就是有定义notify() 回调,那自然就调用这个回调。
使用v4l2_subdev这个结构的优势是,它是一个通用的结构,并且没有包含任何底层硬件的特性(does
not contain any knowledge about the underlying hardware)。也就是说它是硬件无关,完全不用改动就可移植的。所以一个驱动包含了若干个subdevs,这些子设备(subdevs)都使用同一个I2C总线。但是也有一个subdev,它是可以被GPIO引脚来控制的。这些差别仅当设置设备的时候才有关系。但是一旦这个子设备被注册,它就是完全透明的。
I2C子设备驱动(I2C sub-device drivers)
---------------------------------------------------------------------------
因为这些驱动是公用的,这里提供了特别的帮助函数,它能有效的提升使用这些驱动的易用性(v4l2-common.h)。
在一个I2C驱动中添加v4l2_subdev支持,一个建议的方法是把v4l2_subdev结构体嵌入到为每个I2C设备实例创建的状态结构(state struct )当中去。很简单的设备可能没有状态结构(state struct),并且在这样的情况下,你可以直接创建一个v4l2_subdev。
一个典型的状态结构(state struct )将看起来像这样的(里面的“chipname”,应该是由 实际chip的名字来替代的):
struct chipname_state {
struct v4l2_subdev sd;
... /* additional state fields */
};
初始化 v4l2_subdev 结构:
v4l2_i2c_subdev_init(&state->sd, client, subdev_ops);
这个函数将填充所有v4l2_subdev的字段(fields),并且确保v4l2_subdev 和i2c_client 都指向对方。
你应该也添加一个内联帮助函数去从 v4l2_subdev 指针跳转到 chipname_state 的结构(即获得chipname_state结构的指针):
static inline struct chipname_state *to_state(struct v4l2_subdev *sd)
{
return container_of(sd, struct chipname_state, sd);//实际上是使用container_of跳转的;ddl3这本书里有说明这个宏是怎么用的。
}
使用如下函数从 v4l2_subdev 跳转到 i2c_client 结构(即获得指向i2c_client结构的指针):
struct i2c_client *client = v4l2_get_subdevdata(sd);
使用如下函数反过来从 i2c_client 跳转到 v4l2_subdev结构:
struct v4l2_subdev *sd = i2c_get_clientdata(client);
确保当remove()回调函数被调用的时候,调用 v4l2_device_unregister_subdev(sd) 来反注册子设备。
这将从桥接驱动(bridge driver)反注册掉子设备(sub-device)。如果子设备从未被注册,那这个调用也是安全的。
你需要这么做,因为当桥接驱动从销毁I2C adapter的时候, 这个remove()回调会被在哪个adapter上的i2c设备调用。
在这之后,相应的v4l2_subdev结构,是无效的,所以它们需要被先反注册掉。在 remove()回调函数中,调用v4l2_device_unregister_subdev(sd) 可以确保总是正确的(不会忘记,或者在不恰当的地方调用反注册)。
桥接驱动也具有一些帮助性的函数:
struct v4l2_subdev *sd = v4l2_i2c_new_subdev(v4l2_dev, adapter,
"module_foo", "chipid", 0x36, NULL);
这个加载了一些给出的模块(可以是NULL,如果没有模块需要被加载的话),并且调用
i2c_new_device() ,
使用给定的i2c_adapter 和 chip/address 作为入参。
如果一切没有问题的话,那么把subdev与v4l2_device 注册到一起的话就肯定会成功了。
你也可以使用
v4l2_i2c_new_subdev()
的最后一个参数来传递一个 可能的I2C addresses的数组,而且应该probe。
如果前一个参数是0的话,那么仅使用这些probe addresses。一个非零的参数,意味着你知道确切的I2C地址,所以,在那种情况下probing将不会发生。
如果有错误的话,i2c_new_device() 和 v4l2_i2c_new_subdev() 两个函数,都返回NULL。
注意 传递给 v4l2_i2c_new_subdev() 的 chipid 通常是与模块名是一样的。 这允许你制定一个chip的变量。例如"saa7114" 和 "saa7115"。通常第,尽管i2c驱动自动探测名字。
chipid的使用是为了满足需要去更近的查看一个最新的日期。它区别了i2c驱动和可能混淆的东西。
要看被支持的chip variants(变量),你可以查看 i2c driver 的代码,查找 i2c_device_id 这个表。i2c_device_id中列出了所有可能的。
这里有更多帮助的函数:
v4l2_i2c_new_subdev_cfg() // 位于 driver/media/video/v4l2-common.c
这个 函数添加了新的irq和platform_data 参数,和 'addr' 和 'probed_addrs' 两个参数。
如果addr不是0,“non-probing variant”将被使用,否则,“probed_addrs” 被probed。
例如:这里将probe addresse 0x10:
struct v4l2_subdev *sd = v4l2_i2c_new_subdev_cfg(v4l2_dev, adapter,
"module_foo", "chipid", 0, NULL, 0, I2C_ADDRS(0x10));
v4l2_i2c_new_subdev_board 使用一个 i2c_board_info 结构(是被传递到i2c driver,并且替代 irq, platform_data 和 addr 这三个参数的)。
如果subdev 支持 s_config core ops ,那么在子设备建立之后,那个op是被调用,并传入 irq 和 platform_data 两个参数。更早的一个函数v4l2_i2c_new_(probed_)subdev ,也会调用 s_config ,但是入参 irq是0,platform_data是NULL。
video_device结构体(struct video_device)
------------------------------------------------------------------------------------
实际的设备节点 被创建(使用video_device 结构体(v4l2-dev.h))在 /dev 目录。这个结构体也可以被动态分配,或是嵌入到更大的结构体。
struct video_device
{
/* device ops */
const struct v4l2_file_operations *fops;
/* sysfs */
struct device dev; /* v4l device */
struct cdev *cdev; /* character device */
/* Set either parent or v4l2_dev if your driver uses v4l2_device */
struct device *parent; /* device parent */
struct v4l2_device *v4l2_dev; /* v4l2_device parent */
/* device info */
char name[32];
int vfl_type;
/* 'minor' is set to -1 if the registration failed */
int minor;
u16 num;
/* use bitops to set/clear/test flags */
unsigned long flags;
/* attribute to differentiate multiple indices on one physical device */
int index;
/* V4L2 file handles */
spinlock_t fh_lock; /* Lock for all v4l2_fhs */
struct list_head fh_list; /* List of struct v4l2_fh */
int debug; /* Activates debug level*/
/* Video standard vars */
v4l2_std_id tvnorms; /* Supported tv norms */
v4l2_std_id current_norm; /* Current tvnorm */
/* callbacks */
void (*release)(struct video_device *vdev);
/* ioctl callbacks */
const struct v4l2_ioctl_ops *ioctl_ops;
};
动态分配使用:
struct video_device *vdev = video_device_alloc();
if (vdev == NULL)
return -ENOMEM;
vdev->release = video_device_release;
如果嵌入它到一个更大的结构体,那么你必须设置 release() 回调函数为你自己的函数。
struct video_device *vdev = &my_vdev->vdev;
vdev->release = my_vdev_release;
这个 release() 回调函数 必须被设置,并且他是在最后一次使用video设备退出的时候被调用的。
默认的 video_device_release() 回调函数 仅在 调用kfree 去 释放被分配的内存的时候,才调用。
你也需要设置如下字段:
- v4l2_dev: 设置为 v4l2_device 父设备;
- name: 设置为某个描述名字,保证唯一性;
- fops : 设置为 v4l2_file_operations 结构体;
- ioctl_ops: 如果你使用这个 v4l2_ioctl_ops 去简化 ioctl 的维护,那么设置它为 你的 v4l2_ioctl_ops 结构体。强烈建议使用 v4l2_ioctl_ops 结构体,这在将来是必须的。
- parent: 如果 v4l2_device 使用 NULL的作为父设备的结构体参数,如果没有父设备的话。这样也是可以被注册的。但是这仅发生在一个硬件具有多个PCI设备,并且所有的都共享同一个 v4l2_device core 的情况下。
cx88 驱动就是这样一个例子:一个 核心的v4l2_device结构体,但是,它只被一个 raw video PCI device (cx8800) 和 a MPEG PCI device (cx8802) 使用。因为 v4l2_device 不能与特殊的 没有父设备的PCI设备 关联起来。 但是,当 v4l2_device 被建立,你可以知道使用哪个父PCI设备。
如果你使用 v4l2_ioctl_ops 那么你需要设置 v4l2_file_operations 结构体内的 .unlocked_ioctl 或 .ioctl 为 video_ioctl2 。
v4l2_file_operations结构体 是一个 file_operations 的子集。主要的不同在于,inode 参数被忽略了,因为它从未被使用到。
video_device的注册(video_device registration)
----------------------------------------------------------------------------
你注册一个视频设备的时候,会创建一个字符设备。
err = video_register_device(vdev, VFL_TYPE_GRABBER, -1); //在这个函数里使用了cdev_add()
if (err) {
video_device_release(vdev); /* or kfree(my_vdev); */
return err;
}
被注册的设备取决于 type这个参数(第二个参数)。有如下的类型存在:
VFL_TYPE_GRABBER:
videoX for video input/output devices //视频输入输出设备
VFL_TYPE_VBI:
vbiX for vertical blank data (i.e. closed captions, teletext)//用于垂直块数据的VBI
VFL_TYPE_RADIO:
radioX for radio tuners //radio调谐设备
VFL_TYPE_VTX:
vtxX for teletext devices (deprecated, don't use //vtx 图文电视广播设备
最后的参数(第三个参数)给出一个被使用的确定的在设备的设备节点号(如videoX 中的 “X”)。通常,你要传-1 使 v4l2框架选择第一个未使用的数字。但是,有时候,用户希望选择一个特定的节点号。通常,驱动允许用户通过一个驱动选项,去选择一个特定的设备节点号(device node number)。这个节点号,会被传递到这个函数,video_register_device 将试图去选择哪个设备节点号。如果那么节点号已经在使用的那个中,那么下一个临近的没有使用的设备节点号将被选择,并且它发送一个警告到kernel 的log。
另外一个使用情况是,如果一个驱动创建了许多设备(many devices)。在这种情况下,它(video_register_device)可以把不同的视频设备放置到不同独立的范围(in separate ranges)。例如,视频捕获设备(video capture devices)是从0 开始,视频输出设备是从16开始。所以你可以使用最近的参数去指定一个最小的设备节点号,并且v4l2框架将尝试去选择第一个空闲的没有被使用的数字,那可能等于或者大于你传递进来的数字(设备节点号)。
如果申请等于或大于你传递进来的数字失败(因为之前可能已经被申请过了),那么它将会选择第一个没被使用的数字。
如果在这种情况下,你不关心这样的警告(不能选择特定设备节点号的警告),你可以调用这个函数:
video_register_device_no_warn() //忽略警告
无论什么时候创建设备节点,一些属性同时也会被创建。如果你查看 /sys/class/video4linux 这个目录,你会看到这些设备。 如 video0,和 你将看到 “name” 和 “index” 属性。“name”属性是 video_device 结构体的 “name” 字段。
“index”属性是这个设备节点的索引(index):
每一次调用 video_register_device() 成功,index就加1 。 第一个视频设备节点,你总可以从索引值为0的数字开始注册。
用户可以建立 udev 规则(rules) ,利用index属性去制作一个 喜爱的设备名(比如mpegX。 用于 MPEG 视频捕获节点)。
在设备成功注册之后,你可以使用如下字段(都是video_device结构体中的字段):
- vfl_type: 传递给 video_register_device 的设备类型.
- minor: 最后被分配的次设备号
- num: 设备节点号 the device node number (i.e. the X in videoX).
- index: 设备索引号 the device index number.
如果注册失败,那么你需要去调用 video_device_release() 来 释放分配的video_device结构体,或者 释放你自己的结构 ,如果 video_device 被嵌入在其中的话。 vdev->release() 回调将永远不会被调用, 如果注册失败的话。也你也不应该尝试反注册(unregister)这个设备,如果注册失败了。
video_device清理(video_device cleanup)
--------------------------------------------------------------------
当视频设备节点必须被移除的时候,可以在驱动的卸载器件,或者USB设备断开连接的时候,反注册它们(unregister them):
video_unregister_device(vdev);
这将从sysfs移除设备节点(导致udev从 /dev 移除它们)。
在 video_unregister_device() 返回之后,不能进行新的 open,即不能再打开这个设备。然而,USB设备的某些应用在这种情况下,依然有一个打开的设备节点。所以,在unregister所有的文件操作(all file operations )之后,将返回一个错误,除了ioctl 和 unlocked_ioctl 文件操作:
这些将依然被传递,因为一些 buffer ioctls 可能依然被需要。
当最后一个用户使用的视频设备节点存在时, vdev->release() 回调函数被调用,并且你可以在这个函数里做最后的清理(final cleanup)。
video_device帮助函数(video_device helper functions)
------------------------------------------------------------------------------------------
有一些使用的帮助函数:
*- file/video_device private data
你可以 set/get 驱动的私有数据(private data,在 video_device 结构中):
void *video_get_drvdata(struct video_device *vdev);
void video_set_drvdata(struct video_device *vdev, void *data);
注意,你可以在 video_register_device() 调用之前,安全的调用 video_set_drvdata()
下面这个video_devdata函数:
struct video_device *video_devdata(struct file *file);
返回了 属于这个 file结构的 video_device 结构。
video_drvdata 函数包含了 video_get_drvdata 和 video_devdata:
void *video_drvdata(struct file *file);
你可以从 video_device 结构 跳转至 v4l2_device 使用:
struct v4l2_device *v4l2_dev = vdev->v4l2_dev;
*- 设备节点名 (Device node name)
video_device的节点 内核名,可以被使用如下函数检索:
const char *video_device_node_name(struct video_device *vdev);
名字是被用作用户空间工具的示意,例如 udev (udev貌似是用户控件操作驱动的一类工具?)。这个函数应该在“可能代替访问 video_device::num h 和 video_device::minor 这两个字段的地方” 使用。
video buffer 帮助函数( helper functions)
--------------------------------------------------------------------------
v4l2内核API为处理video buffers 提供了一系列的标准方法(被叫做"videobuf")。这些方法允许一个驱动以常见的方式实现 read(), mmap() 和 overlay() 这些函数。当前已经有一些方法用于在设备上使用video buffers ,这些设备支持DMA;而DMA支持 scatter/gather 方法(videobuf-dma-sg)、线性访问(videobuf-dma-contig)、 vmalloced buffers ,通常被用在USB驱动上(videobuf-vmalloc)。
更多关于如何使用 videobuf 层的信息,请参考 Documentation/video4linux/videobuf 。
v4l2_fh结构体(struct v4l2_fh)
-----------------------------------------------------------
v4l2_fh结构体(fh 是 file handle的缩写)提供了一种方式使得“保持文件处理(file handle)特定的数据”变得容易(被V4L2框架使用的数据)。对于驱动来说是否使用 v4l2_fh结构体 是可选的。
v4l2_fh结构体(在V4L2框架中,而不是驱动中) 的使用者通过测试 video_device->flags 的 V4L2_FL_USES_V4L2_FH bit位 来知道是否一个驱动应该使用 v4l2_fh结构体 (file->private_data 指针指向v4l2_fh)。
很有用的函数:
- v4l2_fh_init()
初始化文件处理(file handle)。这些 *MUST* 由驱动中的 v4l2_file_operations->open() 来处理。
- v4l2_fh_add()
添加一个 v4l2_fh 结构体到 video_device 的文件处理列表(file handle list)。它可能在初始化文件处理(file handle)之后被调用。
- v4l2_fh_del()
从 video_device() 取消对 v4l2_fh 的关联。文件处理退出函数可能在此时被调用。
- v4l2_fh_exit()
反初始化(Uninitialise)文件处理。在反初始化操作之后,v4l2_fh 结构体所占的内存被释放。
v4l2_fh结构体被当做驱动自己的文件处理结构的一部分 被分配内存。并且它在驱动的 open函数中,被驱动设置v4l2_fh指针为 file->private_data 。也就是说 一旦open函数被调用之后, file->private_data 这个指针就指向 v4l2_fh 。驱动可以通过使用 container_of 这个宏来提取(extract)他们自己的文件处理结构。
比如 :
struct my_fh {
int blah;
struct v4l2_fh fh;
};
...
int my_open(struct file *file)
{
struct my_fh *my_fh;
struct video_device *vfd;
int ret;
...
ret = v4l2_fh_init(&my_fh->fh, vfd);
if (ret)
return ret;
v4l2_fh_add(&my_fh->fh);
file->private_data = &my_fh->fh;
...
}
int my_release(struct file *file)
{
struct v4l2_fh *fh = file->private_data;
struct my_fh *my_fh = container_of(fh, struct my_fh, fh); //container_of宏取出my_fh的地址
...
}
V4L2事件(V4L2 events)
----------------------------------------------------
V4L2的事件提供了一种通用的方式去 传递事件到用户空间。驱动必须使用v4l2_fh 结构体才能支持 V4L2事件。
很有用的函数:
- v4l2_event_alloc()
要使用事件,驱动必须为文件处理(file handle)分配事件。通过不止一次调用v4l2_event_alloc()函数,驱动能确保总共至少n个事件被分配。这个函数不能再原子上下文(atomic context)中调用。
- v4l2_event_queue()
到 视频设备(video device)的 事件队列(Queue events)。驱动仅负责去填充 type 和data 字段。别的字段将由 V4L2填充。
- v4l2_event_subscribe()
如果 video_device->ioctl_ops->vidioc_subscribe_event 必须检查驱动是否具备处理 具有指定事件ID(event id)的事件过程(produce events )的能力。那么它可以调用v4l2_event_subscribe() 去 签名(subscribe) 这些事件。
- v4l2_event_unsubscribe()
vidioc_unsubscribe_event 位于 v4l2_ioctl_ops 结构中。 一个驱动可以直接地使用 v4l2_event_unsubscribe() ,除非它想在 unsubscription 进程中被调用。
这些特殊的类型 V4L2_EVENT_ALL 可以被使用 来对所有事件进行 解除签名(unsubscribe)的操作。驱动希望可以以一个特殊的方式来处理这些。
- v4l2_event_pending()
返回 pending事件(pending events)的 number 。当实现 poll 函数时很有用。
驱动不直接初始化事件。video_device->ioctl_ops->vidioc_subscribe_event 不是NULL的话,这些事件是通过 v4l2_fh_init() 函数来初始化的。这些 *MUST* 是在 驱动的 v4l2_file_operations->open() 处理中进行的。
事件是通过poll系统调用被传递至用户空间的。驱动可以使用 v4l2_fh->events->wait(wait_queue_head_t) 的 作为poll_wait()的一个参数。
事件分为:标准事件 (standard events)和 私密事件(private events)。新的标准事件必须使用最小有效的事件类型(the smallest available event type)。驱动必须从它们自己的类(从基类开始their own class starting from class base)中分配它们的事件。 这里所说的”基类(class base)“ V4L2_EVENT_PRIVATE_START + n * 1000 (n 是 最低有效的数字 )。在class中的第一个事件类型是保留给将来使用的,所以第一个有效的事件类型是 'class base + 1' 。
一个关于如何使用 V4L2 events 的例子,可以在 OMAP 3 ISP 驱动中看到 ( at