V4L2驱动框架概述
=====================================
这个文本文件讲述V4L2的框架所提供的各种结构和它们之间的关系.
介绍
------------
由于硬件的复杂性v412驱动往往是非常复杂的: 大多数设备有多个IC,在
/dev目录下有多个设备节点, 并也创建non-V4L2的设备,如DVB,ALSA,FB,
I2C和input(IR)设备。
特别是v412驱动设置配套的IC做音频/视频多路复用/编码/解码,使得它更
比大多数复杂的事实。通常这些芯片连接到主桥驱动器通过一个或多个I2C
总线,但也可以使用其他总线。这种设备被称为“子设备”。
很长一段时间有限制的video_device结构框架创建v4l设备节点和video_buf
的的视频缓冲区处理(请注意,本文档不讨论video_buf框架)。
这意味着,所有驱动程序必须做的设备实例的设置和连接子设备本身。
这部分是相当复杂,做应该做的事,很多驱动程序不这样做是正确的。
由于缺乏一个框架也有很多共同的代码不可重构。
因此,这个框架的基本构建块,所有的驱动程序需要与此相同的框架应更容
易进入所有驱动程序共享的实用功能重构的通用代码。
驱动程序的结构
---------------------
所有的驱动程序有以下结构:
1) 每个设备包含设备状态的实例结构。
2) 子设备的初始化和命令方式(如果有).
3) 创建V4L2的设备节点 (/dev/videoX, /dev/vbiX and /dev/radioX)
和跟踪设备节点的具体数据。
4)文件句柄特定的结构,包含每个文件句柄数据;
5) 视频缓冲处理。
这是一个粗略的示意图,这一切是如何涉及的:
也可以选择集成框架的V4L2媒体框架。如果设置一个驱动程序的结构
v4l2_device mdev,sub-devices 和 video节点会自动出现在媒体框架作为实体。
struct v4l2_device
你必须注册设备的实例:
v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
注册将初始化 v4l2_device 结构体. 如果 dev->driver_data字段是空, 它将连接到 v4l2_dev.
要与媒体设备框架集成的驱动程序需要设置DEV-> driver_data手动指向驱动程序特定的设备结构
嵌入结构体v4l2_device实例。这是通过一个dev_set_drvdata()调用之前注册V4L2的设备实例。
他们必须还设置结构体v4l2_device MDEV领域指向一个正确初始化和注册media_device实例。
如果v4l2_dev->name是空,那么这将被设置为从dev取得一个值(驱动程序名称后的
bus_id要准确)。 如果你在调用v4l2_device_register之前设置它,那么它没有改变。
如果dev是NULL,那么你‘必须’设置v4l2_dev>name在调用v4l2_device_register前。
你可以使用v4l2_device_set_name()设置名称根据驱动程序的名字和
driver-global atomic_t实例。这将产生的名字一样,ivtv0,ivtv1,等。
如果名字最后一个数字,然后将它插入一个破折号:cx18-0,cx18-1,等
这个函数返回的实例数量。 第一个参数‘dev’通常是一个pci_dev的struct device的指针,
但它是ISA设备或一个设备创建多个PCI设备时这是罕见的DEV为NULL,
因此makingit不可能联想到一个特定的父母v4l2_dev。
您也可以提供一个notify()回调子设备,可以通过调用通知你的事件。
取决于你是否需要设置子设备。一个子设备支持的任何通知必须在头文件中定义
include/media/.h.
注销:
v4l2_device_unregister(struct v4l2_device *v4l2_dev);
如果dev-> driver_data字段指向v4l2_dev,它将被重置为NULL。
注销也将自动注销从设备所有子设备。
如果你有一个可热插拔设备(如USB设备),然后发生断开连接的时候父设备将变为无效。
由于v4l2_device有指向父设备的指针,它被清除,以及标记父设备消失。
要做到这一点调用:
v4l2_device_disconnect(struct v4l2_device *v4l2_dev);
这并*不*注销subdevs,所以你仍然需要调用该的v4l2_device_unregister()函数。
如果你的驱动是不能热插拔,则有无需调用v4l2_device_disconnect()。
有时你需要遍历一个特定的驱动程序注册的所有设备。
这是通常情况下如果多个设备驱动程序使用相同的硬件。
例如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;
}
如果你有多个设备节点然后它可以是很难知道它是安全注销v4l2_device时候。
v4l2_device引用计数的支持就是对于这样做的目的。当video_register_device
时被称之为增加引用计数,有设备节点被释放时减少引用计数。
当引用计数达到零,则v4l2_device回调release()。你可以做最后的清理。
如果其他设备节点(例如ALSA)的创建,然后你可以增加和减少以及手动调用引用计数:
void v4l2_device_get(struct v4l2_device *v4l2_dev);
or:
int v4l2_device_put(struct v4l2_device *v4l2_dev);
struct v4l2_subdev
------------------
许多驱动程序需要与子设备进行通信。这些设备可以完成所有这类任务
但他们最常用的处理音频 和/或 视频混流,编码或解码。对于常见子设备摄像头传感器和摄像头控制器。
每个子设备驱动程序必须有一个v4l2_subdev结构。这个结构可以独立简单的设备或者
如果需要存储更多的状态信息它可能被嵌入在一个更大的结构。一般有一个低级别
设备结构(如i2c_client),其中包含设备的数据为内核设置。
它建议在v4l2_subdev使用v4l2_set_subdevdata()的私人数据存储的指针。
这使得很容易地从一个v4l2_subdev实际低级别的总线特定的器件数据。
你还需要很长的路要走从低级结构v4l2_subdev。对于常见的i2c_client结构的i2c_set_clientdata()调用用来存储v4l2_subdev指针,
为其他总线您可能必须使用其他方法。桥接可能还需要存储每subdev私人数据如桥特定每个-subdev私人数据指针。
v4l2_subdev结构提供与v4l2_get_subdev_hostdata()和v4l2_set_subdev_hostdata()可以访问目的主机私人数据。
从桥式驱动器角度你加载的子设备模块和某种取得的v4l2_subdev指针。
对于I2C器件这是很容易:
你调用i2c_get_clientdata()。对于其他总线类似的东西需要做。Helper函数做你最棘手的工作,
这一个I2C总线上存在的子设备。每个v4l2_subdev包含函数指针的子设备驱动程序可以实现(或保留NULL,如果它不适用)。
由于子设备可以做很多不同的东西,你不想结束一个巨大的OPS结构其中只有少数的OPS通常执行,函数指针进行排序按类别,
每个类别都有其自己的OPS结构。顶层OPS结构包含的类别OPS结构,这可能是NULL如果在subdev驱动程序不支持任何从该类别指针。
它看起来像这样:
struct v4l2_subdev_core_ops {
是共同所有子设备的核心操作函数,其他类别的执行取决于子设备。例如视频设备是不大可能支持音频操作,反之亦然。
此设置限制的函数指针,同时还使其易于添加新操作函数和类别。
子设备驱动初始化的v4l2_subdev结构使用:
v4l2_subdev_init(sd, &ops);
之后,您需要初始化一个独特的名字subdev->name和设置模块所有者。这样做是为了如果你使用的I2C辅助功能。
如果框架媒介整合是必要的,你必须初始化的media_entity结构通过调用media_entity_init()嵌入在v4l2_subdev结构(实体领域):
struct media_pad *pads = &my_sd->pads;
int err;
err = media_entity_init(&sd->entity, npads, pads, 0);
pads数组必须此前已初始化。有没有需要手动设置结构media_entity类型和name字段,但如果需要修改字段必须初始化。
参考实体将自动获得/释放时,subdev设备节点(如果有的话)打开/关闭。
但是使用这个宏将更好和更容易:
err = v4l2_subdev_call(sd, core, g_chip_ident, &chip);
宏将到右边的空指针检查,并如果subdev是NULL返回-ENODEV,
-ENOIOCTLCMD if either subdev->core or subdev->core->g_chip_ident is
NULL, 或实际的结果 subdev->ops->core->g_chip_ident ops.
它也可以调用全部或部分子设备:
v4l2_device_call_all(v4l2_dev, 0, core, g_chip_ident, &chip);
不支持这个OPS的任何子设备跳过错误的效果将被忽略。如果你想检查是否有错误,使用这个:
err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_chip_ident, &chip);
Any error except -ENOIOCTLCMD will exit the loop with that error. If no
errors (except -ENOIOCTLCMD) occurred, then 0 is returned.
这两个调用的第二个参数是一组ID。如果为0,然后所有subdevs被调用。如果不为零,
但
那么只有那些其组ID匹配值将被调用。前桥驱动注册一个subdev它可以设置SD-> grp_id
任何值是想要(这是默认为0)。这个值是由桥式驱动器和拥有子设备驱动程序将不会修改或使用它。
这组ID给桥驱动器控制如何调用回调。例如,可能有多个音频芯片板,每一个有改变音量。但通常只
有一个实际上将被用来当用户想改变音量。
你可以设置组ID,subdev例如AUDIO_CONTROLLER和指定组ID值时调用v4l2_device_call_all()。
以确保它只会去的subdev需要。如果子设备需要通知其事件v4l2_device父母,那么它可以调用
v4l2_subdev_notify(SD,notification,ARG)。
这个宏检查是否有一个notify()的回调定义,如果没有返回-ENODEV。否则结果的通知()调用返回。
使用v4l2_subdev优势是它是一个通用结构,不包含任何对底层硬件知识。因此驱动程序可能包含几个subdevs使用I2C总线,
但也通过GPIO引脚控制subdev。这种区别是设置设备时只有关,但一旦subdev注册是是完全透明的。
V4L2的子设备用户空间的API
-----------------------------
旁边公开通过v4l2_subdev_ops结构内核API,V4L2的子设备也可以直接控制用户空间应用。可以直接访问
子设备在/ dev中创建名为V4L-subdevX设备节点。
如果一个子设备支持直接用户空间的配置它必须设定以前注册的V4L2_SUBDEV_FL_HAS_DEVNODE标志。
子设备登记后,v4l2_device司机可以由调用v4l2_device_register_subdev_nodes()与
V4L2_SUBDEV_FL_HAS_DEVNODE标记所有注册子设备创建设备节点。子设备未注册时将被自动删除这些
设备节点。设备节点处理的V4L2的API一个子集。
VIDIOC_QUERYCTRL
VIDIOC_QUERYMENU
VIDIOC_G_CTRL
VIDIOC_S_CTRL
VIDIOC_G_EXT_CTRLS
VIDIOC_S_EXT_CTRLS
VIDIOC_TRY_EXT_CTRLS
控制ioctls函数调用在V4L2的定义是相同的。他们的行为是相同的,唯一的例外,
他们只处理子设备执行控制。这些控制可以根据驱动程序,也可以通过一个(或几个)V4L2
的设备节点访问。
VIDIOC_DQEVENT
VIDIOC_SUBSCRIBE_EVENT
VIDIOC_UNSUBSCRIBE_EVENT
事件ioctls函数调用在V4L2的定义是相同。他们的行为是相同的,唯一的例外他们只分设备产生事件处理。
根据驱动程序,这些事件也可以报一个(或几个)V4L2的设备节点。
子设备驱动程序要使用的事件需要设置的V4L2_SUBDEV_USES_EVENTS v4l2_subdev::flags
和初始化之前注册的子设备v4l2_subdev:: nevents事件队列深度。注册事件后可以像往常一样
排队的v4l2_subdev:: devnode设备节点。
要正确地支持事件,poll()的文件操作实施。Private ioctls没有在上述列表所有ioctls函数调用会直接传递
到子设备驱动程序通过core::ioctl操作。
I2C sub-device drivers
----------------------
由于这些驱动程序是很常见的,特殊的辅助功能是可以缓解使用这些驱动程序(V4L2-common.h)。
对加入v4l2_subdev支持I2C驱动的推荐的方法是嵌入到每个I2C设备实例创建状态结构v4l2_subdev结构。
很简单的设备,没有状态结构,在这种情况下,你可以直接创建一个v4l2_subdev。
一个典型的状态结构看起来像这样(where 'chipname' is replaced bythe name of the chip):
struct chipname_state {
struct v4l2_subdev sd;
... /* additional state fields */
};
如下初始化v4l2_subdev结构:
v4l2_i2c_subdev_init(&state->sd, client, subdev_ops);
此功能将填补所有领域v4l2_subdev和确保在v4l2_subdev和i2c_client都指向一个。
您还应该添加一个辅助内联函数从v4l2_subdev指针的去到chipname_state结构:
struct chipname_state {
struct video_device
-------------------
实际设备节点在/ dev目录使用的video_device结构(V4L2 dev.h)创建。
这个结构可以动态分配或嵌入在一个更大的结构。要动态分配使用:
To allocate it dynamically use:如果是嵌入到一个更大的结构,那么你必须设置的release()函数回调到自己的函数:
struct video_device *vdev = &my_vdev->vdev;释放回调必须设置时被称为最后一个用户的视频设备出口。的默认video_device_release()
回调只是调用kfree释放分配的内存。
你也应该设定这些字段:
如果使用v4l2_ioctl_ops,那么你应该设置。unlocked_ioctl video_ioctl2在v4l2_file_operations结构。
不要使用。IOCTL!将在未来过时。
v4l2_file_operations结构的file_operations的一个子集。主要区别在于该inode参数被忽略,因为它从来没有使用过。
如果框架传媒的整合是必要的,你必须初始化media_entity结构嵌入式通过调用media_entity_init()的
video_device结构(entity字段):
struct media_pad *pad = &my_vdev->pad;
int err;
err = media_entity_init(&vdev->entity, 1, pad, 0);
垫阵列必须先前已初始化。有没有需要手动设置的结构media_entity type和name字段。
A reference to the entity will be automatically acquired/released when the
video device is opened/closed.
v4l2_file_operations 和 锁
--------------------------------
你可以设置一个指针,在结构的video_device中mutex_lock。
通常这将是一个顶层互斥或互斥每个设备节点。如果你想更细致的锁那么你必须将其设置为NULL和你自己的锁定。
如果锁被指定那么所有的文件操作将被序列化该锁。如果您使用videobuf那么你
必须通过相同的锁的videobuf队列初始化函数:如果videobuf等待一帧到达,
然后它会暂时解除锁定和重新锁定它之后。如果您的驱动程序代码中的等待,
那么你应该做同样允许其他进程访问设备节点的第一道工序,而在等待着什么。
一个热插拔断开实施也应采取之前调用v4l2_device_disconnect锁。
video_device 注册
-------------------------
VFL_TYPE_VTX: vtxX图文电视设备(不建议使用,不使用)
最后一个参数给你一定数量的控制了设备使用设备节点(即在videoXX)。通常情况下你将通过-1让V4L2框架选择第一个释放号码。或自己结构的video_device如果被嵌入。的VDEV>发布()回调将永远不会被调用,
如果注册失败,也不应该你曾经尝试注销的设备如果注册失败。
video_device 清除
--------------------
当被删除,在卸载的驱动程序,或因为被断开USB设备,视频设备节点,那么你应该注销他们:video_device 辅助函数
-----------------------------
有一些有用的辅助功能:
- file/video_device private data
userspace工具,如udev的名字被用来作为一个提示。应在可能的情况下使用,
而不是访问的video_device:: num和的video_device::minor字段的功能。
video buffer 辅助函数
-----------------------------
The v4l2 core API provides a set of standard methods (called "videobuf")struct v4l2_fh
--------------
结构v4l2_fh提供了一种轻松地保持文件句柄是由V4L2的框架使用具体数据。
新的驱动程序必须使用struct v4l2_fh,因为它也可以用来实现优先处理
(VIDIOC_G/ S_PRIORITY)如果的video_device标志V4L2_FL_USE_FH_PRIO也。
(在V4L2的框架,而不是驱动器)v4l2_fh用户知道驱动程序是否使用通过测试
V4L2_FL_USES_V4L2_FH位的video_device-> flags中v4l2_fh作为其文件> private_data
的指针。每当v4l2_fh_init()被调用时设置此位。
多个驱动程序需要做一些事情时,第一个文件句柄打开和关闭最后一个文件句柄时。
增加了两个辅助功能检查,是否v4l2_fh结构是唯一的打开文件句柄相关联的设备节点:
int v4l2_fh_is_singular(struct v4l2_fh *fh)V4L2 events
-----------
V4L2的事件提供了一个通用的方式来传递事件到用户空间。
必须使用v4l2_fh的驱动程序能够支持V4L2的事件。
Useful functions:
- v4l2_event_alloc()
使用事件驱动程序必须分配的文件句柄的事件。驱动程序通过调用函数不止一次,
可以保证至少n个总事件已分配。功能可能无法在原子上下文中被调用。
- v4l2_event_queue()
视频设备的队列中的事件。驱动程序的唯一责任是填写的类型和数据字段。V4L2的其他领域将被填充。
- v4l2_event_subscribe()
的video_device-> ioctl_ops> vidioc_subscribe_event必须检查驱动程序是否能
够产生与特定事件ID的事件。然后,它调用v4l2_event_subscribe()订阅该事件。
- v4l2_event_unsubscribe()
在结构v4l2_ioctl_ops vidioc_unsubscribe_event。一个驱动程序可以直接使用
v4l2_event_unsubscribe(),除非它想在取消订阅过程中涉及。
在特殊类型V4L2_EVENT_ALL的可能被用来取消所有活动。
驱动程序可能需要一种特殊的方式来处理。- v4l2_event_pending() 返回挂起的事件数量。实施调查时非常有用。
Events are delivered to user space through the poll system call. The driver
V4L2-framework.txt 源文档在/Documentation/video4linux目录下. 内核自带的 Documentation目录是一个非常有用的参考资料和学习资料。 建议多读!!!