原文连接node
本文开启 linux 内核 V4L2 框架部分的学习之旅,本文仅先对 V4L2 的框架作一个综述性的归纳介绍,而后接下来的文章中会对 V4L2 框架的各个子模块进行一个全面的介绍,包括每一部分的实现原理,如何使用,用在什么地方等等。预计接下来的文章大概有5篇(不带本篇)。坑已经挖好了,开始吧。
<!-- more -->linux
导读:V4L2 是专门为 linux 设备设计的一套视频框架,其主体框架在 linux 内核,能够理解为是整个 linux 系统上面的视频源捕获驱动框架。其普遍应用在嵌入式设备以及移动端、我的电脑设备上面,市面上的编码产品类如:SDV、手机、IPC、行车记录仪都会用到这个框架来进行视频采集,固然,有比较厉害的厂家直接就使用本身实现的一套视频采集框架,这种属因而厂家中战斗机了。下文主要参考linux-4.4内核文档对V4L2框架进行一次全局的介绍。编程
几乎全部的设备都有多个 IC 模块,它们多是实体的(例如 USB 摄像头里面包含 ISP、sensor 等)、也多是抽象的(如 USB 设备里面的抽象拓扑结构),它们在 /dev
目录下面生成了多个设备节点,而且这些 IC 模块还建立了一些非 v4l2 设备:DVB、ALSA、FB、I2C 和输入设备。正是因为硬件的复杂性,v4l2 的驱动也变得很是复杂。数组
特别是 v4l2 驱动要支持 IC 模块来进行音/视频的混合/编解码操做,这就更加使得 v4l2 驱动变得异常复杂。一般状况下,有些IC模块经过一个或者多个 I2C 总线链接到主桥驱动上面,同时其它的总线仍然可用,这些 IC 就称为 ‘sub-devices’,好比摄像头设备里面的 sensor 传感器就是使用 I2C 来进行命令沟通,同时使用 MIPI 或者 LVDS 等接口进行图像数据传输。安全
在很长一段时间内,该框架(指老旧的 V4L2 框架)仅限于经过 video_device 结构体建立 v4l 设备节点和 video_buf 来处理视频数据。这意味着全部的驱动都必须对设备实例进行设置并将其映射到子设备上。有些时候这些操做步骤十分复杂,很难正确完成,而且有些驱动程序历来没有正确的按照这些操做步骤编写。因为缺乏一个框架,有不少通用代码就没有办法被重构,从而致使这部分代码被重复编写,效率比较低下。网络
所以,本框架抽象构建了全部驱动都须要的代码并封装为一个个的模块,简化了设备驱动通用代码的重构。v4l2-pci-skeleton.c 是个很是好的参考例程,它是一个PCI采集卡的驱动框架。该例程演示了如何使用 v4l2 驱动框架,而且该例程能够做为一个 PCI 视频采集卡的驱动模板使用。在最开始的时候也能够参照这个代码编写方式进行联系,固然最适合的代码仍是 <font color='red'>drivers/media/video/omap3isp</font> 文件夹里面的代码,这个代码基本上能够做为一个完整的输入设备实例代码(由于它包含了 ISP、CSI、video 等设备,而且有着一个完整的数据流 pipeline,几乎用到了 V4L2 框架的方方面面,参考价值极大)来进行参考编写本身的设备驱动代码。app
这是一张很是大的图,可是我只选取了其中的一个,这张图对 V4L2 里面的子模块进行简化(简化到只有子模块的名字,没有内部实现的介绍),大图以下:
<center>V4L2 设备拓扑</center>框架
这张图怎么看呢?它有如下几个关键因素:异步
v4l2_device
:这个是整个输入设备的总结构体,能够认为它是整个 V4L2 框架的入口,充当驱动的管理者以及入口监护人。由该结构体引伸出来 v4l2_subdev
。用于视频输入设备总体的管理,有多少输入设备就有多少个v4l2_device
抽象(好比一个USB摄像头总体就能够看做是一个 V4L2 device)。再往下分是输入子设备,对应的是例如 ISP、CSI、MIPI 等设备,它们是从属于一个 V4L2 device 之下的。media_device
:用于运行时数据流的管理,嵌入在 V4L2 device 内部,运行时的意思就是:一个 V4L2 device 下属可能有很是多同类型的子设备(两个或者多个 sensor、ISP 等),那么在设备运行的时候我怎么知道个人数据流须要用到哪个类型的哪个子设备呢。这个时候就轮到 media_device
出手了,它为这一坨的子设备创建一条虚拟的连线,创建起来一个运行时的 pipeline(管道),而且能够在运行时动态改变、管理接入的设备。v4l2_ctrl_handler
:控制模块,提供子设备(主要是 video 和 ISP 设备)在用户空间的特效操做接口,好比你想改变下输出图像的亮度、对比度、饱和度等等,均可以经过这个来完成。vb2_queue
:提供内核与用户空间的 buffer 流转接口,输入设备产生了一坨图像数据,在内核里面应该放在哪里呢?能放几个呢?是整段连续的仍是仍是分段连续的又或者是物理不连续的?用户怎么去取用呢?都是它在管理。custom_v4l2_dev
,它是由用户定义的一个结构体,重要的不是它怎么定义的,重要的是它里面有一个 v4l2_device
结构体,上文说到,这个结构体总览全局,指挥若定,至关于中央管理处的位置,那么中央决定了,它就是整个输入设备总体的抽象(好比整个 USB 摄像头输入设备,好比整个 IPC 摄像头输入设备)。它还有一个 media_device
结构体,上文也说道,,它是管数据流线路的,属于搞结构路线规划管理的。v4l2_device
里面有一个链表,它维护了一个巨大的子设备链,全部的子设备都经过内核的双向循环链表结构以 v4l2_device
为中心牢牢团结在一块儿。另外 media_device
在往里面去就是一个个的 media_entity
(如今不须要了解它的具体含义,只须要知道它就是相似电路板上面的元器件同样的抽象体),media_entity
之间创建了本身的小圈子,在它们这个小圈子里面数据流按照必定的顺序畅通无阻,恣意遨游。/dev/videoX
设备节点,这个就是外交部的角色,它负责提供了一个内核与用户空间的交流枢纽。须要注意的是,该设备节点的本质仍是一个字符设备,其内部的一套操做与字符设备是同样的,只不过是进行了一层封装而已。
<center>V4L2 层次结构</center>async
全部的 V4L2 驱动都有如下结构体类型:
custom_v4l2_dev
),里面包含了设备的状态;v4l2_subdev
)的方法;media_device
)保持跟踪;v4l2_fh
文件句柄与句柄结构体一一对应);vb2_queue
);media_device
)与驱动结构体很是相似,参考上面的解释,这里再也不赘述。v4l2 框架也能够整合到 media framework 里面。若是驱动程序设置了 v4l2_device
的 mdev
成员,那么子设备与 video 节点都会被自动看成 media framework 里的 entitiy 抽象。
v4l2_device
结构体每个设备实例都被抽象为一个 v4l2_device
结构体。一些简单的设备能够仅分配一个 v4l2_device
结构体便可,可是
大多数状况下须要将该结构体嵌入到一个更大的结构体(custom_v4l2_dev
)里面。必须用 v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
来注册设备实例。该函数会初始化传入的 v4l2_device
结构体,若是 dev->driver_data
成员为空的话,该函数就会设置其指向传入的 v4l2_dev
参数。
若是驱动想要集成 media framework 的话,就须要人为地设置 dev->driver_data
指向驱动适配的结构体(该结构体由
驱动自定义- custom_v4l2_dev
,里面嵌入 v4l2_device
结构体)。在注册 v4l2_device
以前就须要调用 dev_set_drvdata
来完成设置。而且必须设置 v4l2_decice
的 mdev
成员指向注册的 media_device
结构体实例。
若是 v4l2_device
的 name
成员为空的话,就按照 dev
成员的名称来命名,若是 dev
成员也为空的话,就必须在注册 v4l2_device
以前设置它的 name
成员。可使用 v4l2_device_set_name
函数来设置 name
成员,该函数会基于驱动名以及驱动实例的索引号来生成 name
成员的名称,相似于 ivtv0、ivtv1 等等,若是驱动名的最后一个字母是整数的话,生成的名称就相似于cx18-0、cx18-1等等,该函数的返回值是驱动实例的索引号。
还能够提供一个 notify()
回调函数给 v4l2_device
接收来自子设备的事件通知。固然,是否须要设置该回调函数取决于子设备是否有向主设备发送通知事件的需求。v4l2_device
的卸载需调用到 v4l2_device_unregister
函数。在该函数被调用以后,若是 dev->driver_data
指向 v4l2_device
的话,该指针将会被设置为NULL。该函数会将全部的子设备所有卸载掉。若是设备是热拔插属性的话,当 disconnect 发生的时候,父设备就会失效,同时 v4l2_device
指向父设备的指针也必须被清除,能够调用 v4l2_device_disconnect
函数来清除指针,该函数并不卸载子设备,子设备的卸载仍是须要调用到 v4l2_device_unregister
来完成。若是不是热拔插设备的话,就没必要关注这些。
有些时候须要对驱动的全部设备进行迭代,这种状况一般发生在多个设备驱动使用同一个硬件设备的状况下,好比 ivtvfb 驱动就是个 framebuffer 驱动,它用到了 ivtv 这个硬件设备。可使用如下方法来迭代全部的已注册设备:
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 drv_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) { ... state->instance = atomic_inc_return(&drv_instance) - 1; }
若是一个热拔插设备有不少个设备节点(好比一个USB摄像头能够产生多路视频输出,虽然它的视频源是一个),那么很难知道在何时才可以安全地卸载 v4l2_device
设备。基于以上问题, v4l2_device
引入了引用计数机制,当 video_register_device
函数被调用的时候,引用计数会加一,当 video_device
被释放的时候,引用计数会减一,直到 v4l2_device
的引用计数到0的时候,v4l2_device
的 release
回调函数就会被调用,能够在该回调函数里面作一些清理工做。当其它的设备(alsa,由于这个不属于 video 设备,因此也就不能使用上面的 video 函数进行计数的加减操做)节点被建立的时候,能够人为调用如下函数对引用计数进行增减操做:
void v4l2_device_get(struct v4l2_device *v4l2_dev); int v4l2_device_put(struct v4l2_device *v4l2_dev);
须要注意的是,v4l2_device_register
函数将引用计数初始化为1,因此须要在remove
或者disconnect
回调方法里面调用v4l2_device_put
来减小引用计数,不然引用计数将永远不会达到0。
v4l2_subdev
结构体不少设备都须要与子设备进行交互,一般状况下子设备用于音视频的编解码以及混合处理,对于网络摄像机来讲子设备就是 sensors 和 camera 控制器。一般状况下它们都是 I2C 设备,但也有例外。v4l2_subdev
结构体被用于子设备管理。
每个子设备驱动都必须有一个 v4l2_subdev
结构体,这个结构体能够做为独立的简单子设备存在,也能够嵌入到更大的结构体(自定义的子设备结构体)里面。一般会有一个由内核设置的低层次结构体(i2c_client
,也就是上面说的 i2c 设备),它包含了一些设备数据,要调用 v4l2_set_subdevdata
来设置子设备私有数据指针指向它,这样的话就能够很方便的从 subdev
找到相关的 I2C 设备数据(这个要编程实现的时候才可以了解它的用意)。另外也须要设置低级别结构的私有数据指针指向 v4l2_subdev
结构体,方便从低级别的结构体访问 v4l2_subdev
结构体,达到双向访问的目的,对于 i2c_client
来讲,能够用 i2c_set_clientdata
函数来设置,其它的需使用与之相应的函数来完成设置。
桥驱动器须要存储每个子设备的私有数据,v4l2_subdev
结构体提供了主机私有数据指针成员来实现此目的,使用如下函数能够对主机私有数据进行访问控制:
v4l2_get_subdev_hostdata(); v4l2_set_subdev_hostdata();
从桥驱动器的角度来看,咱们加载子设备模块以后能够用某种方式获取子设备指针。对于 i2c 设备来讲,调用 i2c_get_clientdata
函数便可完成,其它类型的设备也有与之类似的操做,在内核里面提供了很多的帮助函数来协助完成这部分工做,编程时能够多多使用。
每一个 v4l2_subdev
结构体都包含有一些函数指针,指向驱动实现的回调函数,内核对这些回调函数进行了分类以免出现定义了一个巨大的回调函数集,可是里面只有那么几个用得上的尴尬状况。最顶层的操做函数结构体内部包含指向各个不一样类别操做函数结构体的指针成员,以下所示:
struct v4l2_subdev_core_ops { 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_pad_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; const struct v4l2_subdev_vbi_ops *vbi; const struct v4l2_subdev_ir_ops *ir; const struct v4l2_subdev_sensor_ops *sensor; const struct v4l2_subdev_pad_ops *pad; };
<font color='red'>这部分的设计我我的以为是很是实用的,linux 要想支持大量的设备的同时又要保持代码的精简就必须得这样去实现。</font>core ops
成员对于全部的子设备来讲都是通用的,其他的成员不一样的驱动会有选择的去使用,例如:video 设备就不须要支持 audio 这个 ops 成员。子设备驱动的初始化使用 v4l2_subdev_init
函数来完成(该函数只是初始化一些 v4l2_subdev
的成员变量,内容比较简单),在初始化以后须要设置子设备结构体的 name
和 owner
成员(若是是 i2c 设备的话,这个在 i2c helper 函数里面就会被设置)。该部分 ioctl 能够直接经过用户空间的 ioctl 命令访问到(前提是该子设备在用户空间生成了子设备节点,这样的话就能够操做子设备节点来进行 ioctl)。内核里面可使用 v4l2_subdev_call
函数来对这些回调函数进行调用,这个在 pipeline 管理的时候十分受用。
若是须要与 media framework 进行集成,必须初始化 media_entity
结构体并将其嵌入到 v4l2_subdev
结构体里面,操做以下所示:
struct media_pad *pads = &my_sd->pads; int err; err = media_entity_init(&sd->entity, npads, pads, 0);
其中 pads 结构体变量必须提早初始化,media_entity
的 flags
、name
、type
、ops
成员须要设置。entity 的引用计数在子设备节点被打开/关闭的时候会自动地增减。在销毁子设备的时候需使用 media_entity_cleanup
函数对 entity 进行清理。若是子设备须要处理 video 数据,就须要实现 v4l2_subdev_video_ops
成员,若是要集成到 media_framework 里面,就必需要实现 v4l2_subdev_pad_ops
成员,此时使用 pad_ops 中与 format
有关的成员代替 v4l2_subdev_video_ops
中的相关成员。
子设备驱动须要设置 link_validation
成员来提供本身的 link validation 函数,该回调函数用来检查 pipeline 上面的全部的 link 是否有效(是否有效由本身来作决定),该回调函数在 media_entity_pipeline_start
函数里面被循环调用。若是该成员没有被设置,那么 v4l2_subdev_link_validate_default
将会做为默认的回调函数被使用,该函数确保 link 的 source pad 和 sink pad 的宽、高、media 总线像素码是一致的,不然就会返回错误。
有两种方法能够注册子设备(注意是设备,不是设备驱动,经常使用的方式是经过设备树来注册),第一种(旧的方法,好比使用 platform_device_register
来进行注册)是使用桥驱动去注册设备。这种状况下,桥驱动拥有链接到它的子设备的完整信息,而且知道什么时候去注册子设备,内部子设备一般属于这种状况。好比 SOC 内部的 video 数据处理单元,链接到 USB 或 SOC 的相机传感器。另外一种状况是子设备必须异步地被注册到桥驱动上,好比基于设备树的系统,此时全部的子设备信息都独立于桥驱动器。使用这两种方法注册子设备的区别是 probing 的处理方式不一样。也就是一种是设备信息结构体由驱动自己持有并注册,一种是设备信息结构体由设备树持有并注册。
设备驱动须要用 v4l2_device
信息来注册 v4l2_subdev
,以下所示:
int err = v4l2_device_register_subdev(v4l2_dev, sd);
若是子设备模块在注册以前消失的话,该操做就会失败,若是成功的话就会使得 subdev->dev
指向 v4l2_device
。若是 v4l2_device
父设备的 mdev
成员不为空的话,子设备的 entity 就会自动地被注册到 mdev 指向的 media_device
里面。在子设备须要被卸载而且 sd->dev
变为NULL以后,使用以下函数来卸载子设备:
v4l2_device_unregister_subdev(sd);
若是子设备被注册到上层的 v4l2_device
父设备中,那么 v4l2_device_unregister
函数就会自动地把全部子设备卸载掉。但为了以防万一以及保持代码的风格统一,须要注册与卸载结对使用。能够用如下方式直接调用ops成员:err = sd->ops->core->g_std(sd, &norm);
使用下面的宏定义能够简化书写:err = v4l2_subdev_call(sd, core, g_std, &norm);
该操做会检查 sd->dev
指针是否为空,若是是,返回 -ENODEV
,同时若是 ops->core
或者 ops->core->g_std
为空,则返回 -ENOIOCTLCMD
。也能够经过如下函数调用来对 V4l2 下面挂载的全部子设备进行回调:
v4l2_device_call_all(v4l2_dev, 0, core, g_std, &norm);
该函数会跳过全部不支持该 ops 的子设备,而且全部的错误信息也被忽略,若是想捕获错误信息,可使用下面的函数:
err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_std, &norm);
该函数的第二个参数若是为 0,则全部的子设备都会被访问,若是非 0,则指定组的子设备会被访问。
组ID使得桥驱动可以更加精确的去调用子设备操做函数,例如:在一个单板上面有不少个声卡,每一个都可以改变音量,可是一般状况下只访问一个,这时就能够设置子设备的组 ID 为 AUDIO_CONTROLLER 并指定它的值,这时 v4l2_device_call_all
函数就会只去访问指定组的子设备,提升效率。
若是子设备须要向 v4l2_device
父设备发送事件通知的话,就能够调用 v4l2_subdev_notify
宏定义来回调 v4l2->notify
成员(前文有提到过)。
使用 v4l2_subdev
的优势是不包含任何底层硬件的信息,它是对底层硬件的一个抽象,所以一个驱动可能包含多个使用同一条 I2C 总线的子设备,也可能只包含一个使用 GPIO 管脚控制的子设备,只有在驱动设置的时候才有这些差异,而一旦子设备被注册以后,底层硬件对驱动来讲就是彻底透明的。
不清楚异步模式的用途
在异步模式下,子设备 probing 能够被独立地被调用以检查桥驱动是否可用,子设备驱动必须确认全部的 probing 请求是否成功,若是有任意一个请求条件没有知足,驱动就会返回-EPROBE_DEFER
来继续下一次尝试,一旦全部的请求条件都被知足,子设备就须要调用v4l2_async_register_subdev
函数来进行注册(用v4l2_async_unregister_subdev
卸载)。桥驱动反过来得注册一个 notifier 对象(v4l2_async_notifier_register
),该函数的第二个参数类型是v4l2_async_notifier
类型的结构体,里面包含有一个指向指针数组的指针成员,指针数组每个成员都指向v4l2_async_subdev
类型结构体。v4l2 核心层会利用上述的异步子设备结构体描述符来进行子设备的匹配,若是成功匹配,.bound()notifier
回调函数将会被调用,当全部的子设备所有被加载完毕以后,.complete()
回调函数就会被调用,子设备被移除的时候.unbind()
函数就会被调用。
另外子设备还提供了一组内部操做函数,该内部函数的调用时机在下面有描述,原型以下所示:
struct v4l2_subdev_internal_ops { int (*registered)(struct v4l2_subdev *sd); void (*unregistered)(struct v4l2_subdev *sd); int (*open)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh); int (*close)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh); };
这些函数仅供 v4l2 framework 使用,驱动程序不该该显式的去调用这些回调
v4l2_device_register_subdev
)/反注册的时候被调用。能够在 /dev
文件夹下建立 v4l-subdevX
设备节点以供用户直接操做子设备硬件。若是须要在用户空间建立设备节点的话,就须要在子设备节点注册以前设置 V4L2_SUBDEV_FL_HAS_DEVNODE
标志,而后调用 v4l2_device_register_subdev_nodes()
函数,就能够在用户空间建立设备节点,设备节点会在子设备卸载的时候自动地被销毁。
VIDIOC_QUERYCTRL VIDIOC_QUERYMENU VIDIOC_G_CTRL VIDIOC_S_CTRL VIDIOC_G_EXT_CTRLS VIDIOC_S_EXT_CTRLS VIDIOC_TRY_EXT_CTRLS
上述 ioctls 能够经过设备节点访问,也能够直接在子设备驱动里面调用。
VIDIOC_DQEVENT VIDIOC_SUBSCRIBE_EVENT VIDIOC_UNSUBSCRIBE_EVENT
要使用上述事件,就必须设置 v4l2_subdev
的 V4L2_SUBDEV_USES_EVENTS
标志位,实现 core_ops
的 subscribe
相关的回调函数,回调函数里面须要初始化 events,而后注册 v4l2_subdev
。一些私有的 ioctls 能够在 v4l2_subdev
的 ops->core->ioctl
里面实现。
要想在 I2C 驱动里面添加 v4l2_subdev
支持,就须要把 v4l2_subdev
结构体嵌入到每一个 I2C 实例结构体里面,有一些比较简单的 I2C 设备不须要自定义的状态结构体,此时只须要建立一个单独的 v4l2_subdev
结构体便可。一个典型的驱动自定义状态结构体以下所示:
struct chipname_state { struct v4l2_subdev sd; ... /* additional state fields */ };
使用 v4l2_i2c_subdev_init
去初始化一个 I2C 子设备,该函数会填充 v4l2_subdev
的全部成员并确保 v4l2_subdev
与 i2c_client
互相指向对方。也能够添加内联函数来从 v4l2_subdev
的指针获取到 i2c_client
结构体:
struct i2c_client *client = v4l2_get_subdevdata(sd); 也能够从i2c_client结构体指针获取到v4l2_subdev结构体: struct v4l2_subdev *sd = i2c_get_clientdata(client); 桥驱动可使用如下帮助函数来建立一个I2C子设备: struct v4l2_subdev *sd = v4l2_i2c_new_subdev (v4l2_dev, adapter,"module_foo", "chipid", 0x36, NULL);
该函数会加载给定的模块(能够为空)而且调用 i2c_new_device
根据传入的参数建立子设备结构体,最后注册 v4l2_subdev
。
video_device
结构体video_device
能够动态的分配:
struct video_device *vdev = video_device_alloc(); if (vdev == NULL) return -ENOMEM; vdev->release = video_device_release;
若是须要将 video_device
结构体嵌入到更大的结构体里面的话,就须要设置 vdev
的 release
成员。内核提供了两个默认的 release
回调函数,以下:
video_device_release() // 仅仅调用kfree释放分配的内存,用于动态分配状况下 video_device_release_empty() // 不作任何事情,静态变量
如下的函数成员必须被设置:
若是想忽略 ioctl_ops
中某个 ioctls 的话能够调用下面的函数:
void v4l2_disable_ioctl(struct video_device *vdev, unsigned int cmd);
若是要集成到 media_framework 里面,就须要设置 video_device
里面的 media_entity
成员,同时须要提供 media_pad
:
struct media_pad *pad = &my_vdev->pad; int err; err = media_entity_init(&vdev->entity, 1, pad, 0);
video_device
的注册video_device
的注册函数以下:
err = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
该段代码会注册一个字符设备驱动程序并在用户空间生成一个设备节点。若是 v4l2_device
父设备的 mdev
成员不为空的话,video_device
的 entity
会被自动的注册到 media framework 里面。函数最后一个参数是设备节点索引号,若是是 -1 的话就取用第一个内核中可用的索引号值。注册的设备类型以及用户空间中的节点名称取决于如下标识:
VFL_TYPE_GRABBER: videoX 输入输出设备 VFL_TYPE_VBI: vbiX VFL_TYPE_RADIO: radioX 硬件定义的音频调谐设备 VFL_TYPE_SDR: swradioX 软件定义的音频调谐设备
当一个设备节点被建立时,相关属性也会被建立,能够在 /sys/class/video4linux
里面看到这些设备文件夹,在文件夹里面能够看到 'name','dev_debug','index','uevent'
等属性,可使用 cat
命令查看。'dev_debug' 能够用于 video 设备调试,每一个 video 设备都会建立一个 'dev_debug' 属性,该属性以文件夹的形式存在与 /sys/class/video4linux/<devX>/
下面以供使能 log file operation。'dev_debug'是一个位掩码,如下位能够被设置:
0x01:记录ioctl名字与错误码。设置0x08位能够只记录VIDIOC_(D)QBUF 0x02:记录ioctl的参数与错误码。设置0x08位能够只记录VIDIOC_(D)QBUF 0x04:记录file ops操做。设置0x08位能够只记录read&write成员的操做 0x08:如上所示 0x10:记录poll操做
当以上的位被设置的时候,发生相关的调用或者操做的时候内核就会打印出来相关的调用信息到终端上面。相似于
[173881.402120] video4: VIDIOC_DQEVENT: error -2 [173884.906633] video4: VIDIOC_UNSUBSCRIBE_EVENT
当 video 设备节点须要被移除或者USB设备断开时,须要执行如下函数:
video_unregister_device(vdev);
来进行设备的卸载,该函数会移除 /dev
下的设备节点文件,同时不要忘记调用 media_entity_cleanup
来清理 entity。
V4L 核心层提供了可选的锁服务,最主要的就是 video_device
里面的锁,用来进行 ioctls 的同步。若是使用了 videobuf2
框架,那么 video_device->queue->lock
锁也会被用来作 queue 相关的 ioctls 同步。使用不一样的锁有不少优势,好比一些设置相关的 ioctls 花费的时间比较长,若是使用独立的锁,VIDIOC_DQBUF
就不用等待设置操做的完成就能够执行,这个在网络摄像机驱动中很常见。固然,也能够彻底由驱动自己去完成锁操做,这时能够设置全部的锁成员为NULL并实现一个驱动本身的锁。
若是使用旧的 videobuf,须要将 video_device
的锁传递给 videobuf queue 初始化函数,若是 videobuf 正在等待一帧数据的到达,此时会将锁暂时释放,等数据到达以后再次加锁,不然别的处理程序就没法访问。因此不推荐使用旧的 videobuf。若是是在 videobuf2 框架下,须要实现 wait_prepare
与 wait_finish
回调函数去释放或者获取锁,若是使用了 queue->lock
,可使用 V4L2 提供的回调 vb2_ops_wait_prepare/finish
帮助函数来完成加锁与解锁的操做,它们会使用 queue->lock
这个锁(此时必定要将该锁初始化)。
v4l2_fh
结构体该结构体提供了一种简单的保存文件句柄特定数据的方法。v4l2_fh
的使用者-v4l2 framework 能够经过检查 video_device->flags
的 V4L2_FL_USES_V4L2_FH
位来知道驱动是否使用 v4l2_fh
做为 file->private_data
指针,该标志位经过调用函数 v4l2_fh_init
来设置。
v4l2_fh
结构体做为驱动本身的文件句柄存在,而且在驱动的 open
函数里面设置 file->private_data
指向它,v4l2_fh
有多个的时候会做为一个链表存在于 file->private_data
中,能够遍历访问。在大多数状况下 v4l2_fh
结构体都被嵌入到更大的结构体里面,此时须要在 open
函数里面调用 v4l2_fh_init+v4l2_fh_add
进行添加,在 release
函数里面调用 v4l2_fh_del+v4l2_fh_exit
进行退出。驱动可使用 container_of
来访问本身的文件句柄结构体,以下所示:
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; my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL); v4l2_fh_init(&my_fh->fh, vfd); file->private_data = &my_fh->fh; v4l2_fh_add(&my_fh->fh); return 0; } 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); v4l2_fh_del(&my_fh->fh); v4l2_fh_exit(&my_fh->fh); kfree(my_fh); return 0; }
如以上代码所示,因为 open
函数可能会被多个应用 app 所调用,因此 fh 也会有多个,可是 file->private
永远指向最新的一个 v4l2_fh
,经过这个 v4l2_fh
能够找到整个 v4l2_fh
链表中的全部元素。一些驱动须要在第一个文件句柄打开后以及最后一个文件句柄关闭前的时候作一些其它的工做,下面两个帮助函数能够检查 v4l2_fh
结构体是否只剩下一个 entry:
int v4l2_fh_is_singular(struct v4l2_fh *fh) 若是是只有一个entry,返回1,不然返回0,若是fh为空也返回0。 int v4l2_fh_is_singular_file(struct file *filp) 和上面差很少,可是使用 filp->private_data 这一数据源,实际上它是指向最新的一个v4l2_fh的。
V4L2 events 提供一种通用的方法来传递 events 到用户空间,驱动程序必须使用 v4l2_fh
(设置 video_device
的 flags
位)才可以实现对 V4L2 events 的支持。events 用类型和 ID 做为区分标识,没有使用到的 events 的ID就是0。
当用户订阅 event 时,用户空间会相应地为每一个 event 分配一个 kevent 结构体(若是 elems 参数为0的话只有一个,不为0就按照指定的数量分配),因此每一个 event 都有一个或多个属于本身的 kevent 结构体,这就保证了若是驱动短期内生成了很是多的 events 也不会覆盖到其它的同类型 events,能够看做是分了好几个篮子来放不一样类型的水果。event 结构体是 v4l2_subscribed_event
结构体的最后一个成员,以数组的形式存在,而且是一个柔性数组(struct v4l2_kevent events[]
),也就是说在分配 v4l2_subscribed_event
结构体空间的时候,events 并不占用空间,须要额外为指定数量的 events 分配空间,kzalloc(siezof(struct v4l2_subscribed_event) + sizeof(struct v4l2_kevent) * num, GFP_KERNEL);
在使用的时候,彻底能够按照数组的方式去对 kevent 进行寻址,很方便。
若是得到的 event 数量比 kevent 的还要多,那么旧的 events 就会被丢弃。能够设置结构体 v4l2_subscribed_event
的 merge、replace
回调函数(其实默认的函数就足够用了),它们会在 event 被捕获而且没有更多的空间来存放 event 时被调用。在 v4l2_event.c
里面有一个很好的关于 replace/merge
的例子,ctrls_replace()与ctrls_merge()
被做为回调函数使用。因为这两个函数能够在中断上下文被调用,所以必须得快速执行完毕并返回。
关于events的循环是一个比较有意思的操做,入队时:三个变量(first-下一个准备被dequeue的eventm,elems-总kevent数量,in_use-已经使用的kevent数量)
- 若elems == in_use,说明队列成员已经用完。
- 取出第一个kevent,从available队列中删掉,first指向数组的下一个成员,in_use --。
- 找到上一步中(first指向数组的下一个成员),将上一步(取出第一个kevent)的changes位进行合并赋值给前者。
由于后者比前者更新,因此数值彻底能够覆盖前者,同时又保留了前者的变化。
- 取出第in_use + first >= elems ? in_use + first - elems : in_use + first;个数组kevent项做为新的填充项。
- in_use ++
一些有用的函数:
int v4l2_event_subscribe(struct v4l2_fh *fh, struct v4l2_event_subscription *sub, unsigned elems, const struct v4l2_subscribed_event_ops *ops)
当用户空间经过 ioctl 发起订阅请求以后,video_device->ioctl_ops->vidioc_subscribe_event
须要检查是否支持请求的 event,若是支持的话就调用上面的函数进行订阅。通常能够将 video 的相关 ioctl 指向内核默认的 v4l2_ctrl_subscribe_event()
函数。
int v4l2_event_unsubscribe(struct v4l2_fh *fh, struct v4l2_event_subscription *sub) 取消一个事件的订阅,V4L2_EVENT_ALL类型能够用于取消全部事件的订阅。通常能够将video的相关ioctl指向该函数。 void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev) 该函数用做events入队操做(由驱动完成),驱动只须要设置type以及data成员,其他的交由V4L2来完成。 int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event, int nonblocking) events出队操做,发生于用户空间的VIDIOC_DQEVENT调用,做用是从available队列中取出一个events。
v4l2_subscribed_event_ops
参数容许驱动程序设置如下四个回调函数成员:
events 经过 poll 系统调用传递到用户空间,驱动能够将 v4l2_fh->wait
做为 poll_wait()
的参数。子设备能够直接经过 notify 函数向 v4l2_device
发送 events(使用V4L2_DEVICE_NOTIFY_EVENT
)。drivers/media/platform/omap3isp
给出了如何使用event的实例。
注意事项:
- 注意v4l2_event_subscribe的elems参数,若是为0,则内核就默认分配为1,不然按照指定的参数值分配。
- 最好不要使用内核默认的v4l2_subscribed_event_ops,由于它的add函数会尝试在v4l2_ctrl里面查找相应id的ctrl,若是
是自定义的event id的话,有可能找不到相关的ctrl项,这样的话用户空间的VIDIOC_SUBSCRIBE_EVENT就会返回失败。
- 用户空间dqevent以后没必要关心还回的操做,由于内核会自动获取用过的kevent,用柔性数组去管理而不是分散的链表。
- 子设备能够经过v4l2_subdev_notify_event函数调用来入队一个event并通知v4l2设备的notify回调。
- v4l2_event_queue函数会遍历video_device上面全部的v4l2_fh,将event入队到每个fh的列表当中。fh由用户打开video
设备节点的时候产生,每个用户打开video节点时都会为其分配一个单独的v4l2_fh。
- file->private永远指向最新的一个v4l2_fh,经过这个v4l2_fh能够找到整个v4l2_fh链表中的全部元素。
- v4l2_fh_release函数会将全部挂载该fh上面的事件所有取消订阅。
写到这里,本文就算结束了,这部分会发现不少东西都是点到即撤,没有深刻去解释,深刻的这部分放在后面来完成,还有一个就是可能会感受里面有不少东西看着可能知道是什么,可是反应到实际代码里面,实际应用里面就不知道是什么了,这个时候就必须结合代码来进行实际操做实验才可以确切了解。还有一种状况就是可能需求比较简单,一些特性永远用不到,这个时候也不要紧,那就用到的时候再去翻看就好。
<center>想作的事就去作吧</center>
<center><img src="http://www.yellowmax2001.com/...;>