1ff768f59SMauro Carvalho ChehabChinese translated version of Documentation/driver-api/media/index.rst 28db0f934SNinja Tekkaman 38db0f934SNinja TekkamanIf you have any comment or update to the content, please contact the 48db0f934SNinja Tekkamanoriginal document maintainer directly. However, if you have a problem 58db0f934SNinja Tekkamancommunicating in English you can also ask the Chinese maintainer for 68db0f934SNinja Tekkamanhelp. Contact the Chinese maintainer if this translation is outdated 78db0f934SNinja Tekkamanor if there is a problem with the translation. 88db0f934SNinja Tekkaman 932590819SMauro Carvalho ChehabMaintainer: Mauro Carvalho Chehab <mchehab@kernel.org> 108db0f934SNinja TekkamanChinese maintainer: Fu Wei <tekkamanninja@gmail.com> 118db0f934SNinja Tekkaman--------------------------------------------------------------------- 12ff768f59SMauro Carvalho ChehabDocumentation/driver-api/media/index.rst 的中文翻译 138db0f934SNinja Tekkaman 148db0f934SNinja Tekkaman如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文 158db0f934SNinja Tekkaman交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻 168db0f934SNinja Tekkaman译存在问题,请联系中文版维护者。 1732590819SMauro Carvalho Chehab英文版维护者: Mauro Carvalho Chehab <mchehab@kernel.org> 188db0f934SNinja Tekkaman中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com> 198db0f934SNinja Tekkaman中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com> 208db0f934SNinja Tekkaman中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com> 218db0f934SNinja Tekkaman 228db0f934SNinja Tekkaman 238db0f934SNinja Tekkaman以下为正文 248db0f934SNinja Tekkaman--------------------------------------------------------------------- 258db0f934SNinja TekkamanV4L2 驱动框架概览 268db0f934SNinja Tekkaman============== 278db0f934SNinja Tekkaman 288db0f934SNinja Tekkaman本文档描述 V4L2 框架所提供的各种结构和它们之间的关系。 298db0f934SNinja Tekkaman 308db0f934SNinja Tekkaman 318db0f934SNinja Tekkaman介绍 328db0f934SNinja Tekkaman---- 338db0f934SNinja Tekkaman 348db0f934SNinja Tekkaman大部分现代 V4L2 设备由多个 IC 组成,在 /dev 下导出多个设备节点, 358db0f934SNinja Tekkaman并同时创建非 V4L2 设备(如 DVB、ALSA、FB、I2C 和红外输入设备)。 368db0f934SNinja Tekkaman由于这种硬件的复杂性,V4L2 驱动也变得非常复杂。 378db0f934SNinja Tekkaman 388db0f934SNinja Tekkaman尤其是 V4L2 必须支持 IC 实现音视频的多路复用和编解码,这就更增加了其 398db0f934SNinja Tekkaman复杂性。通常这些 IC 通过一个或多个 I2C 总线连接到主桥驱动器,但也可 408db0f934SNinja Tekkaman使用其他总线。这些设备称为“子设备”。 418db0f934SNinja Tekkaman 428db0f934SNinja Tekkaman长期以来,这个框架仅限于通过 video_device 结构体创建 V4L 设备节点, 438db0f934SNinja Tekkaman并使用 video_buf 处理视频缓冲(注:本文不讨论 video_buf 框架)。 448db0f934SNinja Tekkaman 458db0f934SNinja Tekkaman这意味着所有驱动必须自己设置设备实例并连接到子设备。其中一部分要正确地 468db0f934SNinja Tekkaman完成是比较复杂的,使得许多驱动都没有正确地实现。 478db0f934SNinja Tekkaman 488db0f934SNinja Tekkaman由于框架的缺失,有很多通用代码都不可重复利用。 498db0f934SNinja Tekkaman 508db0f934SNinja Tekkaman因此,这个框架构建所有驱动都需要的基本结构块,而统一的框架将使通用代码 518db0f934SNinja Tekkaman创建成实用函数并在所有驱动中共享变得更加容易。 528db0f934SNinja Tekkaman 538db0f934SNinja Tekkaman 548db0f934SNinja Tekkaman驱动结构 558db0f934SNinja Tekkaman------- 568db0f934SNinja Tekkaman 578db0f934SNinja Tekkaman所有 V4L2 驱动都有如下结构: 588db0f934SNinja Tekkaman 598db0f934SNinja Tekkaman1) 每个设备实例的结构体--包含其设备状态。 608db0f934SNinja Tekkaman 618db0f934SNinja Tekkaman2) 初始化和控制子设备的方法(如果有)。 628db0f934SNinja Tekkaman 638db0f934SNinja Tekkaman3) 创建 V4L2 设备节点 (/dev/videoX、/dev/vbiX 和 /dev/radioX) 648db0f934SNinja Tekkaman 并跟踪设备节点的特定数据。 658db0f934SNinja Tekkaman 668db0f934SNinja Tekkaman4) 特定文件句柄结构体--包含每个文件句柄的数据。 678db0f934SNinja Tekkaman 688db0f934SNinja Tekkaman5) 视频缓冲处理。 698db0f934SNinja Tekkaman 708db0f934SNinja Tekkaman以下是它们的初略关系图: 718db0f934SNinja Tekkaman 728db0f934SNinja Tekkaman device instances(设备实例) 738db0f934SNinja Tekkaman | 748db0f934SNinja Tekkaman +-sub-device instances(子设备实例) 758db0f934SNinja Tekkaman | 768db0f934SNinja Tekkaman \-V4L2 device nodes(V4L2 设备节点) 778db0f934SNinja Tekkaman | 788db0f934SNinja Tekkaman \-filehandle instances(文件句柄实例) 798db0f934SNinja Tekkaman 808db0f934SNinja Tekkaman 818db0f934SNinja Tekkaman框架结构 828db0f934SNinja Tekkaman------- 838db0f934SNinja Tekkaman 848db0f934SNinja Tekkaman该框架非常类似驱动结构:它有一个 v4l2_device 结构用于保存设备 858db0f934SNinja Tekkaman实例的数据;一个 v4l2_subdev 结构体代表子设备实例;video_device 868db0f934SNinja Tekkaman结构体保存 V4L2 设备节点的数据;将来 v4l2_fh 结构体将跟踪文件句柄 878db0f934SNinja Tekkaman实例(暂未尚未实现)。 888db0f934SNinja Tekkaman 898db0f934SNinja TekkamanV4L2 框架也可与媒体框架整合(可选的)。如果驱动设置了 v4l2_device 908db0f934SNinja Tekkaman结构体的 mdev 域,子设备和视频节点的入口将自动出现在媒体框架中。 918db0f934SNinja Tekkaman 928db0f934SNinja Tekkaman 938db0f934SNinja Tekkamanv4l2_device 结构体 948db0f934SNinja Tekkaman---------------- 958db0f934SNinja Tekkaman 968db0f934SNinja Tekkaman每个设备实例都通过 v4l2_device (v4l2-device.h)结构体来表示。 978db0f934SNinja Tekkaman简单设备可以仅分配这个结构体,但在大多数情况下,都会将这个结构体 988db0f934SNinja Tekkaman嵌入到一个更大的结构体中。 998db0f934SNinja Tekkaman 1008db0f934SNinja Tekkaman你必须注册这个设备实例: 1018db0f934SNinja Tekkaman 1028db0f934SNinja Tekkaman v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev); 1038db0f934SNinja Tekkaman 1048db0f934SNinja Tekkaman注册操作将会初始化 v4l2_device 结构体。如果 dev->driver_data 域 1058db0f934SNinja Tekkaman为 NULL,就将其指向 v4l2_dev。 1068db0f934SNinja Tekkaman 1078db0f934SNinja Tekkaman需要与媒体框架整合的驱动必须手动设置 dev->driver_data,指向包含 1088db0f934SNinja Tekkamanv4l2_device 结构体实例的驱动特定设备结构体。这可以在注册 V4L2 设备 1098db0f934SNinja Tekkaman实例前通过 dev_set_drvdata() 函数完成。同时必须设置 v4l2_device 1108db0f934SNinja Tekkaman结构体的 mdev 域,指向适当的初始化并注册过的 media_device 实例。 1118db0f934SNinja Tekkaman 1128db0f934SNinja Tekkaman如果 v4l2_dev->name 为空,则它将被设置为从 dev 中衍生出的值(为了 1138db0f934SNinja Tekkaman更加精确,形式为驱动名后跟 bus_id)。如果你在调用 v4l2_device_register 1148db0f934SNinja Tekkaman前已经设置好了,则不会被修改。如果 dev 为 NULL,则你*必须*在调用 1158db0f934SNinja Tekkamanv4l2_device_register 前设置 v4l2_dev->name。 1168db0f934SNinja Tekkaman 1178db0f934SNinja Tekkaman你可以基于驱动名和驱动的全局 atomic_t 类型的实例编号,通过 1188db0f934SNinja Tekkamanv4l2_device_set_name() 设置 name。这样会生成类似 ivtv0、ivtv1 等 1198db0f934SNinja Tekkaman名字。若驱动名以数字结尾,则会在编号和驱动名间插入一个破折号,如: 1208db0f934SNinja Tekkamancx18-0、cx18-1 等。此函数返回实例编号。 1218db0f934SNinja Tekkaman 1228db0f934SNinja Tekkaman第一个 “dev” 参数通常是一个指向 pci_dev、usb_interface 或 1238db0f934SNinja Tekkamanplatform_device 的指针。很少使其为 NULL,除非是一个ISA设备或者 1248db0f934SNinja Tekkaman当一个设备创建了多个 PCI 设备,使得 v4l2_dev 无法与一个特定的父设备 1258db0f934SNinja Tekkaman关联。 1268db0f934SNinja Tekkaman 1278db0f934SNinja Tekkaman你也可以提供一个 notify() 回调,使子设备可以调用它实现事件通知。 1288db0f934SNinja Tekkaman但这个设置与子设备相关。子设备支持的任何通知必须在 1298db0f934SNinja Tekkamaninclude/media/<subdevice>.h 中定义一个消息头。 1308db0f934SNinja Tekkaman 1318db0f934SNinja Tekkaman注销 v4l2_device 使用如下函数: 1328db0f934SNinja Tekkaman 1338db0f934SNinja Tekkaman v4l2_device_unregister(struct v4l2_device *v4l2_dev); 1348db0f934SNinja Tekkaman 1358db0f934SNinja Tekkaman如果 dev->driver_data 域指向 v4l2_dev,将会被重置为 NULL。注销同时 1368db0f934SNinja Tekkaman会自动从设备中注销所有子设备。 1378db0f934SNinja Tekkaman 1388db0f934SNinja Tekkaman如果你有一个热插拔设备(如USB设备),则当断开发生时,父设备将无效。 1398db0f934SNinja Tekkaman由于 v4l2_device 有一个指向父设备的指针必须被清除,同时标志父设备 1408db0f934SNinja Tekkaman已消失,所以必须调用以下函数: 1418db0f934SNinja Tekkaman 1428db0f934SNinja Tekkaman v4l2_device_disconnect(struct v4l2_device *v4l2_dev); 1438db0f934SNinja Tekkaman 1448db0f934SNinja Tekkaman这个函数并*不*注销子设备,因此你依然要调用 v4l2_device_unregister() 1458db0f934SNinja Tekkaman函数。如果你的驱动器并非热插拔的,就没有必要调用 v4l2_device_disconnect()。 1468db0f934SNinja Tekkaman 1478db0f934SNinja Tekkaman有时你需要遍历所有被特定驱动注册的设备。这通常发生在多个设备驱动使用 1488db0f934SNinja Tekkaman同一个硬件的情况下。如:ivtvfb 驱动是一个使用 ivtv 硬件的帧缓冲驱动, 1498db0f934SNinja Tekkaman同时 alsa 驱动也使用此硬件。 1508db0f934SNinja Tekkaman 1518db0f934SNinja Tekkaman你可以使用如下例程遍历所有注册的设备: 1528db0f934SNinja Tekkaman 1538db0f934SNinja Tekkamanstatic int callback(struct device *dev, void *p) 1548db0f934SNinja Tekkaman{ 1558db0f934SNinja Tekkaman struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); 1568db0f934SNinja Tekkaman 1578db0f934SNinja Tekkaman /* 测试这个设备是否已经初始化 */ 1588db0f934SNinja Tekkaman if (v4l2_dev == NULL) 1598db0f934SNinja Tekkaman return 0; 1608db0f934SNinja Tekkaman ... 1618db0f934SNinja Tekkaman return 0; 1628db0f934SNinja Tekkaman} 1638db0f934SNinja Tekkaman 1648db0f934SNinja Tekkamanint iterate(void *p) 1658db0f934SNinja Tekkaman{ 1668db0f934SNinja Tekkaman struct device_driver *drv; 1678db0f934SNinja Tekkaman int err; 1688db0f934SNinja Tekkaman 1698db0f934SNinja Tekkaman /* 在PCI 总线上查找ivtv驱动。 1708db0f934SNinja Tekkaman pci_bus_type是全局的. 对于USB总线使用usb_bus_type。 */ 1718db0f934SNinja Tekkaman drv = driver_find("ivtv", &pci_bus_type); 1728db0f934SNinja Tekkaman /* 遍历所有的ivtv设备实例 */ 1738db0f934SNinja Tekkaman err = driver_for_each_device(drv, NULL, p, callback); 1748db0f934SNinja Tekkaman put_driver(drv); 1758db0f934SNinja Tekkaman return err; 1768db0f934SNinja Tekkaman} 1778db0f934SNinja Tekkaman 1788db0f934SNinja Tekkaman有时你需要一个设备实例的运行计数。这个通常用于映射一个设备实例到一个 1798db0f934SNinja Tekkaman模块选择数组的索引。 1808db0f934SNinja Tekkaman 1818db0f934SNinja Tekkaman推荐方法如下: 1828db0f934SNinja Tekkaman 1838db0f934SNinja Tekkamanstatic atomic_t drv_instance = ATOMIC_INIT(0); 1848db0f934SNinja Tekkaman 18563a29f74SGreg Kroah-Hartmanstatic int drv_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) 1868db0f934SNinja Tekkaman{ 1878db0f934SNinja Tekkaman ... 1888db0f934SNinja Tekkaman state->instance = atomic_inc_return(&drv_instance) - 1; 1898db0f934SNinja Tekkaman} 1908db0f934SNinja Tekkaman 1918db0f934SNinja Tekkaman如果你有多个设备节点,对于热插拔设备,知道何时注销 v4l2_device 结构体 1928db0f934SNinja Tekkaman就比较困难。为此 v4l2_device 有引用计数支持。当调用 video_register_device 1938db0f934SNinja Tekkaman时增加引用计数,而设备节点释放时减小引用计数。当引用计数为零,则 1948db0f934SNinja Tekkamanv4l2_device 的release() 回调将被执行。你就可以在此时做最后的清理工作。 1958db0f934SNinja Tekkaman 1968db0f934SNinja Tekkaman如果创建了其他设备节点(比如 ALSA),则你可以通过以下函数手动增减 1978db0f934SNinja Tekkaman引用计数: 1988db0f934SNinja Tekkaman 1998db0f934SNinja Tekkamanvoid v4l2_device_get(struct v4l2_device *v4l2_dev); 2008db0f934SNinja Tekkaman 2018db0f934SNinja Tekkaman或: 2028db0f934SNinja Tekkaman 2038db0f934SNinja Tekkamanint v4l2_device_put(struct v4l2_device *v4l2_dev); 2048db0f934SNinja Tekkaman 2058db0f934SNinja Tekkaman由于引用技术初始化为 1 ,你也需要在 disconnect() 回调(对于 USB 设备)中 2068db0f934SNinja Tekkaman调用 v4l2_device_put,或者 remove() 回调(例如对于 PCI 设备),否则 2078db0f934SNinja Tekkaman引用计数将永远不会为 0 。 2088db0f934SNinja Tekkaman 2098db0f934SNinja Tekkamanv4l2_subdev结构体 2108db0f934SNinja Tekkaman------------------ 2118db0f934SNinja Tekkaman 2128db0f934SNinja Tekkaman许多驱动需要与子设备通信。这些设备可以完成各种任务,但通常他们负责 2138db0f934SNinja Tekkaman音视频复用和编解码。如网络摄像头的子设备通常是传感器和摄像头控制器。 2148db0f934SNinja Tekkaman 2158db0f934SNinja Tekkaman这些一般为 I2C 接口设备,但并不一定都是。为了给驱动提供调用子设备的 2168db0f934SNinja Tekkaman统一接口,v4l2_subdev 结构体(v4l2-subdev.h)产生了。 2178db0f934SNinja Tekkaman 2188db0f934SNinja Tekkaman每个子设备驱动都必须有一个 v4l2_subdev 结构体。这个结构体可以单独 2198db0f934SNinja Tekkaman代表一个简单的子设备,也可以嵌入到一个更大的结构体中,与更多设备状态 2208db0f934SNinja Tekkaman信息保存在一起。通常有一个下级设备结构体(比如:i2c_client)包含了 2218db0f934SNinja Tekkaman内核创建的设备数据。建议使用 v4l2_set_subdevdata() 将这个结构体的 2228db0f934SNinja Tekkaman指针保存在 v4l2_subdev 的私有数据域(dev_priv)中。这使得通过 v4l2_subdev 2238db0f934SNinja Tekkaman找到实际的低层总线特定设备数据变得容易。 2248db0f934SNinja Tekkaman 2258db0f934SNinja Tekkaman你同时需要一个从低层结构体获取 v4l2_subdev 指针的方法。对于常用的 2268db0f934SNinja Tekkamani2c_client 结构体,i2c_set_clientdata() 函数可用于保存一个 v4l2_subdev 2278db0f934SNinja Tekkaman指针;对于其他总线你可能需要使用其他相关函数。 2288db0f934SNinja Tekkaman 2298db0f934SNinja Tekkaman桥驱动中也应保存每个子设备的私有数据,比如一个指向特定桥的各设备私有 2308db0f934SNinja Tekkaman数据的指针。为此 v4l2_subdev 结构体提供主机私有数据域(host_priv), 2318db0f934SNinja Tekkaman并可通过 v4l2_get_subdev_hostdata() 和 v4l2_set_subdev_hostdata() 2328db0f934SNinja Tekkaman访问。 2338db0f934SNinja Tekkaman 2348db0f934SNinja Tekkaman从总线桥驱动的视角,驱动加载子设备模块并以某种方式获得 v4l2_subdev 2358db0f934SNinja Tekkaman结构体指针。对于 i2c 总线设备相对简单:调用 i2c_get_clientdata()。 2368db0f934SNinja Tekkaman对于其他总线也需要做类似的操作。针对 I2C 总线上的子设备辅助函数帮你 2378db0f934SNinja Tekkaman完成了大部分复杂的工作。 2388db0f934SNinja Tekkaman 2398db0f934SNinja Tekkaman每个 v4l2_subdev 都包含子设备驱动需要实现的函数指针(如果对此设备 2408db0f934SNinja Tekkaman不适用,可为NULL)。由于子设备可完成许多不同的工作,而在一个庞大的 2418db0f934SNinja Tekkaman函数指针结构体中通常仅有少数有用的函数实现其功能肯定不合适。所以, 2428db0f934SNinja Tekkaman函数指针根据其实现的功能被分类,每一类都有自己的函数指针结构体。 2438db0f934SNinja Tekkaman 2448db0f934SNinja Tekkaman顶层函数指针结构体包含了指向各类函数指针结构体的指针,如果子设备驱动 2458db0f934SNinja Tekkaman不支持该类函数中的任何一个功能,则指向该类结构体的指针为NULL。 2468db0f934SNinja Tekkaman 2478db0f934SNinja Tekkaman这些结构体定义如下: 2488db0f934SNinja Tekkaman 2498db0f934SNinja Tekkamanstruct v4l2_subdev_core_ops { 2508db0f934SNinja Tekkaman int (*log_status)(struct v4l2_subdev *sd); 2518db0f934SNinja Tekkaman int (*init)(struct v4l2_subdev *sd, u32 val); 2528db0f934SNinja Tekkaman ... 2538db0f934SNinja Tekkaman}; 2548db0f934SNinja Tekkaman 2558db0f934SNinja Tekkamanstruct v4l2_subdev_tuner_ops { 2568db0f934SNinja Tekkaman ... 2578db0f934SNinja Tekkaman}; 2588db0f934SNinja Tekkaman 2598db0f934SNinja Tekkamanstruct v4l2_subdev_audio_ops { 2608db0f934SNinja Tekkaman ... 2618db0f934SNinja Tekkaman}; 2628db0f934SNinja Tekkaman 2638db0f934SNinja Tekkamanstruct v4l2_subdev_video_ops { 2648db0f934SNinja Tekkaman ... 2658db0f934SNinja Tekkaman}; 2668db0f934SNinja Tekkaman 2678db0f934SNinja Tekkamanstruct v4l2_subdev_pad_ops { 2688db0f934SNinja Tekkaman ... 2698db0f934SNinja Tekkaman}; 2708db0f934SNinja Tekkaman 2718db0f934SNinja Tekkamanstruct v4l2_subdev_ops { 2728db0f934SNinja Tekkaman const struct v4l2_subdev_core_ops *core; 2738db0f934SNinja Tekkaman const struct v4l2_subdev_tuner_ops *tuner; 2748db0f934SNinja Tekkaman const struct v4l2_subdev_audio_ops *audio; 2758db0f934SNinja Tekkaman const struct v4l2_subdev_video_ops *video; 2768db0f934SNinja Tekkaman const struct v4l2_subdev_pad_ops *video; 2778db0f934SNinja Tekkaman}; 2788db0f934SNinja Tekkaman 2798db0f934SNinja Tekkaman其中 core(核心)函数集通常可用于所有子设备,其他类别的实现依赖于 2808db0f934SNinja Tekkaman子设备。如视频设备可能不支持音频操作函数,反之亦然。 2818db0f934SNinja Tekkaman 2828db0f934SNinja Tekkaman这样的设置在限制了函数指针数量的同时,还使增加新的操作函数和分类 2838db0f934SNinja Tekkaman变得较为容易。 2848db0f934SNinja Tekkaman 2858db0f934SNinja Tekkaman子设备驱动可使用如下函数初始化 v4l2_subdev 结构体: 2868db0f934SNinja Tekkaman 2878db0f934SNinja Tekkaman v4l2_subdev_init(sd, &ops); 2888db0f934SNinja Tekkaman 2898db0f934SNinja Tekkaman然后,你必须用一个唯一的名字初始化 subdev->name,并初始化模块的 2908db0f934SNinja Tekkamanowner 域。若使用 i2c 辅助函数,这些都会帮你处理好。 2918db0f934SNinja Tekkaman 292ab22e77cSMauro Carvalho Chehab若需同媒体框架整合,你必须调用 media_entity_pads_init() 初始化 v4l2_subdev 2938db0f934SNinja Tekkaman结构体中的 media_entity 结构体(entity 域): 2948db0f934SNinja Tekkaman 2958db0f934SNinja Tekkaman struct media_pad *pads = &my_sd->pads; 2968db0f934SNinja Tekkaman int err; 2978db0f934SNinja Tekkaman 298ab22e77cSMauro Carvalho Chehab err = media_entity_pads_init(&sd->entity, npads, pads); 2998db0f934SNinja Tekkaman 3008db0f934SNinja Tekkamanpads 数组必须预先初始化。无须手动设置 media_entity 的 type 和 3018db0f934SNinja Tekkamanname 域,但如有必要,revision 域必须初始化。 3028db0f934SNinja Tekkaman 3038db0f934SNinja Tekkaman当(任何)子设备节点被打开/关闭,对 entity 的引用将被自动获取/释放。 3048db0f934SNinja Tekkaman 3058db0f934SNinja Tekkaman在子设备被注销之后,不要忘记清理 media_entity 结构体: 3068db0f934SNinja Tekkaman 3078db0f934SNinja Tekkaman media_entity_cleanup(&sd->entity); 3088db0f934SNinja Tekkaman 3098db0f934SNinja Tekkaman如果子设备驱动趋向于处理视频并整合进了媒体框架,必须使用 v4l2_subdev_pad_ops 3108db0f934SNinja Tekkaman替代 v4l2_subdev_video_ops 实现格式相关的功能。 3118db0f934SNinja Tekkaman 3128db0f934SNinja Tekkaman这种情况下,子设备驱动应该设置 link_validate 域,以提供它自身的链接 3138db0f934SNinja Tekkaman验证函数。链接验证函数应对管道(两端链接的都是 V4L2 子设备)中的每个 3148db0f934SNinja Tekkaman链接调用。驱动还要负责验证子设备和视频节点间格式配置的正确性。 3158db0f934SNinja Tekkaman 3168db0f934SNinja Tekkaman如果 link_validate 操作没有设置,默认的 v4l2_subdev_link_validate_default() 3178db0f934SNinja Tekkaman函数将会被调用。这个函数保证宽、高和媒体总线像素格式在链接的收发两端 3188db0f934SNinja Tekkaman都一致。子设备驱动除了它们自己的检测外,也可以自由使用这个函数以执行 3198db0f934SNinja Tekkaman上面提到的检查。 3208db0f934SNinja Tekkaman 3218db0f934SNinja Tekkaman设备(桥)驱动程序必须向 v4l2_device 注册 v4l2_subdev: 3228db0f934SNinja Tekkaman 3238db0f934SNinja Tekkaman int err = v4l2_device_register_subdev(v4l2_dev, sd); 3248db0f934SNinja Tekkaman 3258db0f934SNinja Tekkaman如果子设备模块在它注册前消失,这个操作可能失败。在这个函数成功返回后, 3268db0f934SNinja Tekkamansubdev->dev 域就指向了 v4l2_device。 3278db0f934SNinja Tekkaman 3288db0f934SNinja Tekkaman如果 v4l2_device 父设备的 mdev 域为非 NULL 值,则子设备实体将被自动 3298db0f934SNinja Tekkaman注册为媒体设备。 3308db0f934SNinja Tekkaman 3318db0f934SNinja Tekkaman注销子设备则可用如下函数: 3328db0f934SNinja Tekkaman 3338db0f934SNinja Tekkaman v4l2_device_unregister_subdev(sd); 3348db0f934SNinja Tekkaman 3358db0f934SNinja Tekkaman此后,子设备模块就可卸载,且 sd->dev == NULL。 3368db0f934SNinja Tekkaman 3378db0f934SNinja Tekkaman注册之设备后,可通过以下方式直接调用其操作函数: 3388db0f934SNinja Tekkaman 3392249aa5cSHans Verkuil err = sd->ops->core->g_std(sd, &norm); 3408db0f934SNinja Tekkaman 3418db0f934SNinja Tekkaman但使用如下宏会比较容易且合适: 3428db0f934SNinja Tekkaman 3432249aa5cSHans Verkuil err = v4l2_subdev_call(sd, core, g_std, &norm); 3448db0f934SNinja Tekkaman 3458db0f934SNinja Tekkaman这个宏将会做 NULL 指针检查,如果 subdev 为 NULL,则返回-ENODEV;如果 3462249aa5cSHans Verkuilsubdev->core 或 subdev->core->g_std 为 NULL,则返回 -ENOIOCTLCMD; 3472249aa5cSHans Verkuil否则将返回 subdev->ops->core->g_std ops 调用的实际结果。 3488db0f934SNinja Tekkaman 3498db0f934SNinja Tekkaman有时也可能同时调用所有或一系列子设备的某个操作函数: 3508db0f934SNinja Tekkaman 3512249aa5cSHans Verkuil v4l2_device_call_all(v4l2_dev, 0, core, g_std, &norm); 3528db0f934SNinja Tekkaman 3538db0f934SNinja Tekkaman任何不支持此操作的子设备都会被跳过,并忽略错误返回值。但如果你需要 3548db0f934SNinja Tekkaman检查出错码,则可使用如下函数: 3558db0f934SNinja Tekkaman 3562249aa5cSHans Verkuil err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_std, &norm); 3578db0f934SNinja Tekkaman 3588db0f934SNinja Tekkaman除 -ENOIOCTLCMD 外的任何错误都会跳出循环并返回错误值。如果(除 -ENOIOCTLCMD 3598db0f934SNinja Tekkaman外)没有错误发生,则返回 0。 3608db0f934SNinja Tekkaman 3618db0f934SNinja Tekkaman对于以上两个函数的第二个参数为组 ID。如果为 0,则所有子设备都会执行 3628db0f934SNinja Tekkaman这个操作。如果为非 0 值,则只有那些组 ID 匹配的子设备才会执行此操作。 3638db0f934SNinja Tekkaman在桥驱动注册一个子设备前,可以设置 sd->grp_id 为任何期望值(默认值为 3648db0f934SNinja Tekkaman0)。这个值属于桥驱动,且子设备驱动将不会修改和使用它。 3658db0f934SNinja Tekkaman 3668db0f934SNinja Tekkaman组 ID 赋予了桥驱动更多对于如何调用回调的控制。例如,电路板上有多个 3678db0f934SNinja Tekkaman音频芯片,每个都有改变音量的能力。但当用户想要改变音量的时候,通常 3688db0f934SNinja Tekkaman只有一个会被实际使用。你可以对这样的子设备设置组 ID 为(例如 AUDIO_CONTROLLER) 3698db0f934SNinja Tekkaman并在调用 v4l2_device_call_all() 时指定它为组 ID 值。这就保证了只有 3708db0f934SNinja Tekkaman需要的子设备才会执行这个回调。 3718db0f934SNinja Tekkaman 3728db0f934SNinja Tekkaman如果子设备需要通知它的 v4l2_device 父设备一个事件,可以调用 3738db0f934SNinja Tekkamanv4l2_subdev_notify(sd, notification, arg)。这个宏检查是否有一个 3748db0f934SNinja Tekkamannotify() 回调被注册,如果没有,返回 -ENODEV。否则返回 notify() 调用 3758db0f934SNinja Tekkaman结果。 3768db0f934SNinja Tekkaman 3778db0f934SNinja Tekkaman使用 v4l2_subdev 的好处在于它是一个通用结构体,且不包含任何底层硬件 3788db0f934SNinja Tekkaman信息。所有驱动可以包含多个 I2C 总线的子设备,但也有子设备是通过 GPIO 3798db0f934SNinja Tekkaman控制。这个区别仅在配置设备时有关系,一旦子设备注册完成,对于 v4l2 3808db0f934SNinja Tekkaman子系统来说就完全透明了。 3818db0f934SNinja Tekkaman 3828db0f934SNinja Tekkaman 3838db0f934SNinja TekkamanV4L2 子设备用户空间API 3848db0f934SNinja Tekkaman-------------------- 3858db0f934SNinja Tekkaman 3868db0f934SNinja Tekkaman除了通过 v4l2_subdev_ops 结构导出的内核 API,V4L2 子设备也可以直接 3878db0f934SNinja Tekkaman通过用户空间应用程序来控制。 3888db0f934SNinja Tekkaman 3898db0f934SNinja Tekkaman可以在 /dev 中创建名为 v4l-subdevX 设备节点,以通过其直接访问子设备。 3908db0f934SNinja Tekkaman如果子设备支持用户空间直接配置,必须在注册前设置 V4L2_SUBDEV_FL_HAS_DEVNODE 3918db0f934SNinja Tekkaman标志。 3928db0f934SNinja Tekkaman 3938db0f934SNinja Tekkaman注册子设备之后, v4l2_device 驱动会通过调用 v4l2_device_register_subdev_nodes() 3948db0f934SNinja Tekkaman函数为所有已注册并设置了 V4L2_SUBDEV_FL_HAS_DEVNODE 的子设备创建 3958db0f934SNinja Tekkaman设备节点。这些设备节点会在子设备注销时自动删除。 3968db0f934SNinja Tekkaman 3978db0f934SNinja Tekkaman这些设备节点处理 V4L2 API 的一个子集。 3988db0f934SNinja Tekkaman 3998db0f934SNinja TekkamanVIDIOC_QUERYCTRL 4008db0f934SNinja TekkamanVIDIOC_QUERYMENU 4018db0f934SNinja TekkamanVIDIOC_G_CTRL 4028db0f934SNinja TekkamanVIDIOC_S_CTRL 4038db0f934SNinja TekkamanVIDIOC_G_EXT_CTRLS 4048db0f934SNinja TekkamanVIDIOC_S_EXT_CTRLS 4058db0f934SNinja TekkamanVIDIOC_TRY_EXT_CTRLS 4068db0f934SNinja Tekkaman 4078db0f934SNinja Tekkaman 这些 ioctls 控制与 V4L2 中定义的一致。他们行为相同,唯一的 4088db0f934SNinja Tekkaman 不同是他们只处理子设备的控制实现。根据驱动程序,这些控制也 4098db0f934SNinja Tekkaman 可以通过一个(或多个) V4L2 设备节点访问。 4108db0f934SNinja Tekkaman 4118db0f934SNinja TekkamanVIDIOC_DQEVENT 4128db0f934SNinja TekkamanVIDIOC_SUBSCRIBE_EVENT 4138db0f934SNinja TekkamanVIDIOC_UNSUBSCRIBE_EVENT 4148db0f934SNinja Tekkaman 4158db0f934SNinja Tekkaman 这些 ioctls 事件与 V4L2 中定义的一致。他们行为相同,唯一的 4168db0f934SNinja Tekkaman 不同是他们只处理子设备产生的事件。根据驱动程序,这些事件也 4178db0f934SNinja Tekkaman 可以通过一个(或多个) V4L2 设备节点上报。 4188db0f934SNinja Tekkaman 4198db0f934SNinja Tekkaman 要使用事件通知的子设备驱动,在注册子设备前必须在 v4l2_subdev::flags 4208db0f934SNinja Tekkaman 中设置 V4L2_SUBDEV_USES_EVENTS 并在 v4l2_subdev::nevents 4218db0f934SNinja Tekkaman 中初始化事件队列深度。注册完成后,事件会在 v4l2_subdev::devnode 4228db0f934SNinja Tekkaman 设备节点中像通常一样被排队。 4238db0f934SNinja Tekkaman 4248db0f934SNinja Tekkaman 为正确支持事件机制,poll() 文件操作也应被实现。 4258db0f934SNinja Tekkaman 4268db0f934SNinja Tekkaman私有 ioctls 4278db0f934SNinja Tekkaman 4288db0f934SNinja Tekkaman 不在以上列表中的所有 ioctls 会通过 core::ioctl 操作直接传递 4298db0f934SNinja Tekkaman 给子设备驱动。 4308db0f934SNinja Tekkaman 4318db0f934SNinja Tekkaman 4328db0f934SNinja TekkamanI2C 子设备驱动 4338db0f934SNinja Tekkaman------------- 4348db0f934SNinja Tekkaman 4358db0f934SNinja Tekkaman由于这些驱动很常见,所以内特提供了特定的辅助函数(v4l2-common.h)让这些 4368db0f934SNinja Tekkaman设备的使用更加容易。 4378db0f934SNinja Tekkaman 4388db0f934SNinja Tekkaman添加 v4l2_subdev 支持的推荐方法是让 I2C 驱动将 v4l2_subdev 结构体 4398db0f934SNinja Tekkaman嵌入到为每个 I2C 设备实例创建的状态结构体中。而最简单的设备没有状态 4408db0f934SNinja Tekkaman结构体,此时可以直接创建一个 v4l2_subdev 结构体。 4418db0f934SNinja Tekkaman 4428db0f934SNinja Tekkaman一个典型的状态结构体如下所示(‘chipname’用芯片名代替): 4438db0f934SNinja Tekkaman 4448db0f934SNinja Tekkamanstruct chipname_state { 4458db0f934SNinja Tekkaman struct v4l2_subdev sd; 4468db0f934SNinja Tekkaman ... /* 附加的状态域*/ 4478db0f934SNinja Tekkaman}; 4488db0f934SNinja Tekkaman 4498db0f934SNinja Tekkaman初始化 v4l2_subdev 结构体的方法如下: 4508db0f934SNinja Tekkaman 4518db0f934SNinja Tekkaman v4l2_i2c_subdev_init(&state->sd, client, subdev_ops); 4528db0f934SNinja Tekkaman 4538db0f934SNinja Tekkaman这个函数将填充 v4l2_subdev 结构体中的所有域,并保证 v4l2_subdev 和 4548db0f934SNinja Tekkamani2c_client 都指向彼此。 4558db0f934SNinja Tekkaman 4568db0f934SNinja Tekkaman同时,你也应该为从 v4l2_subdev 指针找到 chipname_state 结构体指针 4578db0f934SNinja Tekkaman添加一个辅助内联函数。 4588db0f934SNinja Tekkaman 4598db0f934SNinja Tekkamanstatic inline struct chipname_state *to_state(struct v4l2_subdev *sd) 4608db0f934SNinja Tekkaman{ 4618db0f934SNinja Tekkaman return container_of(sd, struct chipname_state, sd); 4628db0f934SNinja Tekkaman} 4638db0f934SNinja Tekkaman 4648db0f934SNinja Tekkaman使用以下函数可以通过 v4l2_subdev 结构体指针获得 i2c_client 结构体 4658db0f934SNinja Tekkaman指针: 4668db0f934SNinja Tekkaman 4678db0f934SNinja Tekkaman struct i2c_client *client = v4l2_get_subdevdata(sd); 4688db0f934SNinja Tekkaman 4698db0f934SNinja Tekkaman而以下函数则相反,通过 i2c_client 结构体指针获得 v4l2_subdev 结构体 4708db0f934SNinja Tekkaman指针: 4718db0f934SNinja Tekkaman 4728db0f934SNinja Tekkaman struct v4l2_subdev *sd = i2c_get_clientdata(client); 4738db0f934SNinja Tekkaman 4748db0f934SNinja Tekkaman当 remove()函数被调用前,必须保证先调用 v4l2_device_unregister_subdev(sd)。 4758db0f934SNinja Tekkaman此操作将会从桥驱动中注销子设备。即使子设备没有注册,调用此函数也是 4768db0f934SNinja Tekkaman安全的。 4778db0f934SNinja Tekkaman 4788db0f934SNinja Tekkaman必须这样做的原因是:当桥驱动注销 i2c 适配器时,remove()回调函数 4798db0f934SNinja Tekkaman会被那个适配器上的 i2c 设备调用。此后,相应的 v4l2_subdev 结构体 4808db0f934SNinja Tekkaman就不存在了,所有它们必须先被注销。在 remove()回调函数中调用 4818db0f934SNinja Tekkamanv4l2_device_unregister_subdev(sd),可以保证执行总是正确的。 4828db0f934SNinja Tekkaman 4838db0f934SNinja Tekkaman 4848db0f934SNinja Tekkaman桥驱动也有一些辅组函数可用: 4858db0f934SNinja Tekkaman 4868db0f934SNinja Tekkamanstruct v4l2_subdev *sd = v4l2_i2c_new_subdev(v4l2_dev, adapter, 4878db0f934SNinja Tekkaman "module_foo", "chipid", 0x36, NULL); 4888db0f934SNinja Tekkaman 4898db0f934SNinja Tekkaman这个函数会加载给定的模块(如果没有模块需要加载,可以为 NULL), 4908db0f934SNinja Tekkaman并用给定的 i2c 适配器结构体指针(i2c_adapter)和 器件地址(chip/address) 491*6feb76dbSWolfram Sang作为参数调用 i2c_new_client_device()。如果一切顺利,则就在 v4l2_device 4928db0f934SNinja Tekkaman中注册了子设备。 4938db0f934SNinja Tekkaman 4948db0f934SNinja Tekkaman你也可以利用 v4l2_i2c_new_subdev()的最后一个参数,传递一个可能的 4958db0f934SNinja TekkamanI2C 地址数组,让函数自动探测。这些探测地址只有在前一个参数为 0 的 4968db0f934SNinja Tekkaman情况下使用。非零参数意味着你知道准确的 i2c 地址,所以此时无须进行 4978db0f934SNinja Tekkaman探测。 4988db0f934SNinja Tekkaman 4998db0f934SNinja Tekkaman如果出错,两个函数都返回 NULL。 5008db0f934SNinja Tekkaman 5018db0f934SNinja Tekkaman注意:传递给 v4l2_i2c_new_subdev()的 chipid 通常与模块名一致。 5028db0f934SNinja Tekkaman它允许你指定一个芯片的变体,比如“saa7114”或“saa7115”。一般通过 5038db0f934SNinja Tekkamani2c 驱动自动探测。chipid 的使用是在今后需要深入了解的事情。这个与 5048db0f934SNinja Tekkamani2c 驱动不同,较容易混淆。要知道支持哪些芯片变体,你可以查阅 i2c 5058db0f934SNinja Tekkaman驱动代码的 i2c_device_id 表,上面列出了所有可能支持的芯片。 5068db0f934SNinja Tekkaman 5078db0f934SNinja Tekkaman还有两个辅助函数: 5088db0f934SNinja Tekkaman 5098db0f934SNinja Tekkamanv4l2_i2c_new_subdev_cfg:这个函数添加新的 irq 和 platform_data 5108db0f934SNinja Tekkaman参数,并有‘addr’和‘probed_addrs’参数:如果 addr 非零,则被使用 5118db0f934SNinja Tekkaman(不探测变体),否则 probed_addrs 中的地址将用于自动探测。 5128db0f934SNinja Tekkaman 5138db0f934SNinja Tekkaman例如:以下代码将会探测地址(0x10): 5148db0f934SNinja Tekkaman 5158db0f934SNinja Tekkamanstruct v4l2_subdev *sd = v4l2_i2c_new_subdev_cfg(v4l2_dev, adapter, 5168db0f934SNinja Tekkaman "module_foo", "chipid", 0, NULL, 0, I2C_ADDRS(0x10)); 5178db0f934SNinja Tekkaman 5188db0f934SNinja Tekkamanv4l2_i2c_new_subdev_board 使用一个 i2c_board_info 结构体,将其 5198db0f934SNinja Tekkaman替代 irq、platform_data 和 add r参数传递给 i2c 驱动。 5208db0f934SNinja Tekkaman 5218db0f934SNinja Tekkaman如果子设备支持 s_config 核心操作,这个操作会在子设备配置好之后以 irq 和 5228db0f934SNinja Tekkamanplatform_data 为参数调用。早期的 v4l2_i2c_new_(probed_)subdev 函数 5238db0f934SNinja Tekkaman同样也会调用 s_config,但仅在 irq 为 0 且 platform_data 为 NULL 时。 5248db0f934SNinja Tekkaman 5258db0f934SNinja Tekkamanvideo_device结构体 5268db0f934SNinja Tekkaman----------------- 5278db0f934SNinja Tekkaman 5288db0f934SNinja Tekkaman在 /dev 目录下的实际设备节点根据 video_device 结构体(v4l2-dev.h) 5298db0f934SNinja Tekkaman创建。此结构体既可以动态分配也可以嵌入到一个更大的结构体中。 5308db0f934SNinja Tekkaman 5318db0f934SNinja Tekkaman动态分配方法如下: 5328db0f934SNinja Tekkaman 5338db0f934SNinja Tekkaman struct video_device *vdev = video_device_alloc(); 5348db0f934SNinja Tekkaman 5358db0f934SNinja Tekkaman if (vdev == NULL) 5368db0f934SNinja Tekkaman return -ENOMEM; 5378db0f934SNinja Tekkaman 5388db0f934SNinja Tekkaman vdev->release = video_device_release; 5398db0f934SNinja Tekkaman 5408db0f934SNinja Tekkaman如果将其嵌入到一个大结构体中,则必须自己实现 release()回调。 5418db0f934SNinja Tekkaman 5428db0f934SNinja Tekkaman struct video_device *vdev = &my_vdev->vdev; 5438db0f934SNinja Tekkaman 5448db0f934SNinja Tekkaman vdev->release = my_vdev_release; 5458db0f934SNinja Tekkaman 5468db0f934SNinja Tekkamanrelease()回调必须被设置,且在最后一个 video_device 用户退出之后 5478db0f934SNinja Tekkaman被调用。 5488db0f934SNinja Tekkaman 5498db0f934SNinja Tekkaman默认的 video_device_release()回调只是调用 kfree 来释放之前分配的 5508db0f934SNinja Tekkaman内存。 5518db0f934SNinja Tekkaman 5528db0f934SNinja Tekkaman你应该设置这些域: 5538db0f934SNinja Tekkaman 5548db0f934SNinja Tekkaman- v4l2_dev: 设置为 v4l2_device 父设备。 5558db0f934SNinja Tekkaman 5568db0f934SNinja Tekkaman- name: 设置为唯一的描述性设备名。 5578db0f934SNinja Tekkaman 5588db0f934SNinja Tekkaman- fops: 设置为已有的 v4l2_file_operations 结构体。 5598db0f934SNinja Tekkaman 5608db0f934SNinja Tekkaman- ioctl_ops: 如果你使用v4l2_ioctl_ops 来简化 ioctl 的维护 5618db0f934SNinja Tekkaman (强烈建议使用,且将来可能变为强制性的!),然后设置你自己的 5628db0f934SNinja Tekkaman v4l2_ioctl_ops 结构体. 5638db0f934SNinja Tekkaman 5648db0f934SNinja Tekkaman- lock: 如果你要在驱动中实现所有的锁操作,则设为 NULL 。否则 5658db0f934SNinja Tekkaman 就要设置一个指向 struct mutex_lock 结构体的指针,这个锁将 5668db0f934SNinja Tekkaman 在 unlocked_ioctl 文件操作被调用前由内核获得,并在调用返回后 5678db0f934SNinja Tekkaman 释放。详见下一节。 5688db0f934SNinja Tekkaman 5698db0f934SNinja Tekkaman- prio: 保持对优先级的跟踪。用于实现 VIDIOC_G/S_PRIORITY。如果 5708db0f934SNinja Tekkaman 设置为 NULL,则会使用 v4l2_device 中的 v4l2_prio_state 结构体。 5718db0f934SNinja Tekkaman 如果要对每个设备节点(组)实现独立的优先级,可以将其指向自己 5728db0f934SNinja Tekkaman 实现的 v4l2_prio_state 结构体。 5738db0f934SNinja Tekkaman 5748db0f934SNinja Tekkaman- parent: 仅在使用 NULL 作为父设备结构体参数注册 v4l2_device 时 5758db0f934SNinja Tekkaman 设置此参数。只有在一个硬件设备包含多一个 PCI 设备,共享同一个 5768db0f934SNinja Tekkaman v4l2_device 核心时才会发生。 5778db0f934SNinja Tekkaman 5788db0f934SNinja Tekkaman cx88 驱动就是一个例子:一个 v4l2_device 结构体核心,被一个裸的 5798db0f934SNinja Tekkaman 视频 PCI 设备(cx8800)和一个 MPEG PCI 设备(cx8802)共用。由于 5808db0f934SNinja Tekkaman v4l2_device 无法与特定的 PCI 设备关联,所有没有设置父设备。但当 5818db0f934SNinja Tekkaman video_device 配置后,就知道使用哪个父 PCI 设备了。 5828db0f934SNinja Tekkaman 5838db0f934SNinja Tekkaman如果你使用 v4l2_ioctl_ops,则应该在 v4l2_file_operations 结构体中 5848db0f934SNinja Tekkaman设置 .unlocked_ioctl 指向 video_ioctl2。 5858db0f934SNinja Tekkaman 5868db0f934SNinja Tekkaman请勿使用 .ioctl!它已被废弃,今后将消失。 5878db0f934SNinja Tekkaman 5888db0f934SNinja Tekkaman某些情况下你要告诉核心:你在 v4l2_ioctl_ops 指定的某个函数应被忽略。 5898db0f934SNinja Tekkaman你可以在 video_device_register 被调用前通过以下函数标记这个 ioctls。 5908db0f934SNinja Tekkaman 5918db0f934SNinja Tekkamanvoid v4l2_disable_ioctl(struct video_device *vdev, unsigned int cmd); 5928db0f934SNinja Tekkaman 5938db0f934SNinja Tekkaman基于外部因素(例如某个板卡已被使用),在不创建新结构体的情况下,你想 5948db0f934SNinja Tekkaman要关闭 v4l2_ioctl_ops 中某个特性往往需要这个机制。 5958db0f934SNinja Tekkaman 5968db0f934SNinja Tekkamanv4l2_file_operations 结构体是 file_operations 的一个子集。其主要 5978db0f934SNinja Tekkaman区别在于:因 inode 参数从未被使用,它将被忽略。 5988db0f934SNinja Tekkaman 599ab22e77cSMauro Carvalho Chehab如果需要与媒体框架整合,你必须通过调用 media_entity_pads_init() 初始化 6008db0f934SNinja Tekkaman嵌入在 video_device 结构体中的 media_entity(entity 域)结构体: 6018db0f934SNinja Tekkaman 6028db0f934SNinja Tekkaman struct media_pad *pad = &my_vdev->pad; 6038db0f934SNinja Tekkaman int err; 6048db0f934SNinja Tekkaman 605ab22e77cSMauro Carvalho Chehab err = media_entity_pads_init(&vdev->entity, 1, pad); 6068db0f934SNinja Tekkaman 6078db0f934SNinja Tekkamanpads 数组必须预先初始化。没有必要手动设置 media_entity 的 type 和 6088db0f934SNinja Tekkamanname 域。 6098db0f934SNinja Tekkaman 6108db0f934SNinja Tekkaman当(任何)子设备节点被打开/关闭,对 entity 的引用将被自动获取/释放。 6118db0f934SNinja Tekkaman 6128db0f934SNinja Tekkamanv4l2_file_operations 与锁 6138db0f934SNinja Tekkaman-------------------------- 6148db0f934SNinja Tekkaman 6158db0f934SNinja Tekkaman你可以在 video_device 结构体中设置一个指向 mutex_lock 的指针。通常 6168db0f934SNinja Tekkaman这既可是一个顶层互斥锁也可为设备节点自身的互斥锁。默认情况下,此锁 6178db0f934SNinja Tekkaman用于 unlocked_ioctl,但为了使用 ioctls 你通过以下函数可禁用锁定: 6188db0f934SNinja Tekkaman 6198db0f934SNinja Tekkaman void v4l2_disable_ioctl_locking(struct video_device *vdev, unsigned int cmd); 6208db0f934SNinja Tekkaman 6218db0f934SNinja Tekkaman例如: v4l2_disable_ioctl_locking(vdev, VIDIOC_DQBUF); 6228db0f934SNinja Tekkaman 6238db0f934SNinja Tekkaman你必须在注册 video_device 前调用这个函数。 6248db0f934SNinja Tekkaman 6258db0f934SNinja Tekkaman特别是对于 USB 驱动程序,某些命令(如设置控制)需要很长的时间,可能 6268db0f934SNinja Tekkaman需要自行为缓冲区队列的 ioctls 实现锁定。 6278db0f934SNinja Tekkaman 6288db0f934SNinja Tekkaman如果你需要更细粒度的锁,你必须设置 mutex_lock 为 NULL,并完全自己实现 6298db0f934SNinja Tekkaman锁机制。 6308db0f934SNinja Tekkaman 6318db0f934SNinja Tekkaman这完全由驱动开发者决定使用何种方法。然而,如果你的驱动存在长延时操作 6328db0f934SNinja Tekkaman(例如,改变 USB 摄像头的曝光时间可能需要较长时间),而你又想让用户 6338db0f934SNinja Tekkaman在等待长延时操作完成期间做其他的事,则你最好自己实现锁机制。 6348db0f934SNinja Tekkaman 6358db0f934SNinja Tekkaman如果指定一个锁,则所有 ioctl 操作将在这个锁的作用下串行执行。如果你 6368db0f934SNinja Tekkaman使用 videobuf,则必须将同一个锁传递给 videobuf 队列初始化函数;如 6378db0f934SNinja Tekkamanvideobuf 必须等待一帧的到达,则可临时解锁并在这之后重新上锁。如果驱动 6388db0f934SNinja Tekkaman也在代码执行期间等待,则可做同样的工作(临时解锁,再上锁)让其他进程 6398db0f934SNinja Tekkaman可以在第一个进程阻塞时访问设备节点。 6408db0f934SNinja Tekkaman 6418db0f934SNinja Tekkaman在使用 videobuf2 的情况下,必须实现 wait_prepare 和 wait_finish 回调 6428db0f934SNinja Tekkaman在适当的时候解锁/加锁。进一步来说,如果你在 video_device 结构体中使用 6438db0f934SNinja Tekkaman锁,则必须在 wait_prepare 和 wait_finish 中对这个互斥锁进行解锁/加锁。 6448db0f934SNinja Tekkaman 6458db0f934SNinja Tekkaman热插拔的断开实现也必须在调用 v4l2_device_disconnect 前获得锁。 6468db0f934SNinja Tekkaman 6478db0f934SNinja Tekkamanvideo_device注册 6488db0f934SNinja Tekkaman--------------- 6498db0f934SNinja Tekkaman 6508db0f934SNinja Tekkaman接下来你需要注册视频设备:这会为你创建一个字符设备。 6518db0f934SNinja Tekkaman 652238e4a5bSHans Verkuil err = video_register_device(vdev, VFL_TYPE_VIDEO, -1); 6538db0f934SNinja Tekkaman if (err) { 6548db0f934SNinja Tekkaman video_device_release(vdev); /* or kfree(my_vdev); */ 6558db0f934SNinja Tekkaman return err; 6568db0f934SNinja Tekkaman } 6578db0f934SNinja Tekkaman 6588db0f934SNinja Tekkaman如果 v4l2_device 父设备的 mdev 域为非 NULL 值,视频设备实体将自动 6598db0f934SNinja Tekkaman注册为媒体设备。 6608db0f934SNinja Tekkaman 6618db0f934SNinja Tekkaman注册哪种设备是根据类型(type)参数。存在以下类型: 6628db0f934SNinja Tekkaman 663238e4a5bSHans VerkuilVFL_TYPE_VIDEO: 用于视频输入/输出设备的 videoX 6648db0f934SNinja TekkamanVFL_TYPE_VBI: 用于垂直消隐数据的 vbiX (例如,隐藏式字幕,图文电视) 6658db0f934SNinja TekkamanVFL_TYPE_RADIO: 用于广播调谐器的 radioX 6668db0f934SNinja Tekkaman 6678db0f934SNinja Tekkaman最后一个参数让你确定一个所控制设备的设备节点号数量(例如 videoX 中的 X)。 6688db0f934SNinja Tekkaman通常你可以传入-1,让 v4l2 框架自己选择第一个空闲的编号。但是有时用户 6698db0f934SNinja Tekkaman需要选择一个特定的节点号。驱动允许用户通过驱动模块参数选择一个特定的 6708db0f934SNinja Tekkaman设备节点号是很普遍的。这个编号将会传递给这个函数,且 video_register_device 6718db0f934SNinja Tekkaman将会试图选择这个设备节点号。如果这个编号被占用,下一个空闲的设备节点 6728db0f934SNinja Tekkaman编号将被选中,并向内核日志中发送一个警告信息。 6738db0f934SNinja Tekkaman 6748db0f934SNinja Tekkaman另一个使用场景是当驱动创建多个设备时。这种情况下,对不同的视频设备在 6758db0f934SNinja Tekkaman编号上使用不同的范围是很有用的。例如,视频捕获设备从 0 开始,视频 6768db0f934SNinja Tekkaman输出设备从 16 开始。所以你可以使用最后一个参数来指定设备节点号最小值, 6778db0f934SNinja Tekkaman而 v4l2 框架会试图选择第一个的空闲编号(等于或大于你提供的编号)。 6788db0f934SNinja Tekkaman如果失败,则它会就选择第一个空闲的编号。 6798db0f934SNinja Tekkaman 6808db0f934SNinja Tekkaman由于这种情况下,你会忽略无法选择特定设备节点号的警告,则可调用 6818db0f934SNinja Tekkamanvideo_register_device_no_warn() 函数避免警告信息的产生。 6828db0f934SNinja Tekkaman 6838db0f934SNinja Tekkaman只要设备节点被创建,一些属性也会同时创建。在 /sys/class/video4linux 6848db0f934SNinja Tekkaman目录中你会找到这些设备。例如进入其中的 video0 目录,你会看到‘name’和 6858db0f934SNinja Tekkaman‘index’属性。‘name’属性值就是 video_device 结构体中的‘name’域。 6868db0f934SNinja Tekkaman 6878db0f934SNinja Tekkaman‘index’属性值就是设备节点的索引值:每次调用 video_register_device(), 6888db0f934SNinja Tekkaman索引值都递增 1 。第一个视频设备节点总是从索引值 0 开始。 6898db0f934SNinja Tekkaman 6908db0f934SNinja Tekkaman用户可以设置 udev 规则,利用索引属性生成花哨的设备名(例如:用‘mpegX’ 6918db0f934SNinja Tekkaman代表 MPEG 视频捕获设备节点)。 6928db0f934SNinja Tekkaman 6938db0f934SNinja Tekkaman在设备成功注册后,就可以使用这些域: 6948db0f934SNinja Tekkaman 6958db0f934SNinja Tekkaman- vfl_type: 传递给 video_register_device 的设备类型。 6968db0f934SNinja Tekkaman- minor: 已指派的次设备号。 6978db0f934SNinja Tekkaman- num: 设备节点编号 (例如 videoX 中的 X)。 6988db0f934SNinja Tekkaman- index: 设备索引号。 6998db0f934SNinja Tekkaman 7008db0f934SNinja Tekkaman如果注册失败,你必须调用 video_device_release() 来释放已分配的 7018db0f934SNinja Tekkamanvideo_device 结构体;如果 video_device 是嵌入在自己创建的结构体中, 7028db0f934SNinja Tekkaman你也必须释放它。vdev->release() 回调不会在注册失败之后被调用, 7038db0f934SNinja Tekkaman你也不应试图在注册失败后注销设备。 7048db0f934SNinja Tekkaman 7058db0f934SNinja Tekkaman 7068db0f934SNinja Tekkamanvideo_device 注销 7078db0f934SNinja Tekkaman---------------- 7088db0f934SNinja Tekkaman 7098db0f934SNinja Tekkaman当视频设备节点已被移除,不论是卸载驱动还是USB设备断开,你都应注销 7108db0f934SNinja Tekkaman它们: 7118db0f934SNinja Tekkaman 7128db0f934SNinja Tekkaman video_unregister_device(vdev); 7138db0f934SNinja Tekkaman 7148db0f934SNinja Tekkaman这个操作将从 sysfs 中移除设备节点(导致 udev 将其从 /dev 中移除)。 7158db0f934SNinja Tekkaman 7168db0f934SNinja Tekkamanvideo_unregister_device() 返回之后,就无法完成打开操作。尽管如此, 7178db0f934SNinja TekkamanUSB 设备的情况则不同,某些应用程序可能依然打开着其中一个已注销设备 7188db0f934SNinja Tekkaman节点。所以在注销之后,所有文件操作(当然除了 release )也应返回错误值。 7198db0f934SNinja Tekkaman 7208db0f934SNinja Tekkaman当最后一个视频设备节点的用户退出,则 vdev->release() 回调会被调用, 7218db0f934SNinja Tekkaman并且你可以做最后的清理操作。 7228db0f934SNinja Tekkaman 7238db0f934SNinja Tekkaman不要忘记清理与视频设备相关的媒体入口(如果被初始化过): 7248db0f934SNinja Tekkaman 7258db0f934SNinja Tekkaman media_entity_cleanup(&vdev->entity); 7268db0f934SNinja Tekkaman 7278db0f934SNinja Tekkaman这可以在 release 回调中完成。 7288db0f934SNinja Tekkaman 7298db0f934SNinja Tekkaman 7308db0f934SNinja Tekkamanvideo_device 辅助函数 7318db0f934SNinja Tekkaman--------------------- 7328db0f934SNinja Tekkaman 7338db0f934SNinja Tekkaman一些有用的辅助函数如下: 7348db0f934SNinja Tekkaman 7358db0f934SNinja Tekkaman- file/video_device 私有数据 7368db0f934SNinja Tekkaman 7378db0f934SNinja Tekkaman你可以用以下函数在 video_device 结构体中设置/获取驱动私有数据: 7388db0f934SNinja Tekkaman 7398db0f934SNinja Tekkamanvoid *video_get_drvdata(struct video_device *vdev); 7408db0f934SNinja Tekkamanvoid video_set_drvdata(struct video_device *vdev, void *data); 7418db0f934SNinja Tekkaman 7428db0f934SNinja Tekkaman注意:在调用 video_register_device() 前执行 video_set_drvdata() 7438db0f934SNinja Tekkaman是安全的。 7448db0f934SNinja Tekkaman 7458db0f934SNinja Tekkaman而以下函数: 7468db0f934SNinja Tekkaman 7478db0f934SNinja Tekkamanstruct video_device *video_devdata(struct file *file); 7488db0f934SNinja Tekkaman 7498db0f934SNinja Tekkaman返回 file 结构体中拥有的的 video_device 指针。 7508db0f934SNinja Tekkaman 7518db0f934SNinja Tekkamanvideo_drvdata 辅助函数结合了 video_get_drvdata 和 video_devdata 7528db0f934SNinja Tekkaman的功能: 7538db0f934SNinja Tekkaman 7548db0f934SNinja Tekkamanvoid *video_drvdata(struct file *file); 7558db0f934SNinja Tekkaman 7568db0f934SNinja Tekkaman你可以使用如下代码从 video_device 结构体中获取 v4l2_device 结构体 7578db0f934SNinja Tekkaman指针: 7588db0f934SNinja Tekkaman 7598db0f934SNinja Tekkamanstruct v4l2_device *v4l2_dev = vdev->v4l2_dev; 7608db0f934SNinja Tekkaman 7618db0f934SNinja Tekkaman- 设备节点名 7628db0f934SNinja Tekkaman 7638db0f934SNinja Tekkamanvideo_device 设备节点在内核中的名称可以通过以下函数获得 7648db0f934SNinja Tekkaman 7658db0f934SNinja Tekkamanconst char *video_device_node_name(struct video_device *vdev); 7668db0f934SNinja Tekkaman 7678db0f934SNinja Tekkaman这个名字被用户空间工具(例如 udev)作为提示信息使用。应尽可能使用 7688db0f934SNinja Tekkaman此功能,而非访问 video_device::num 和 video_device::minor 域。 7698db0f934SNinja Tekkaman 7708db0f934SNinja Tekkaman 7718db0f934SNinja Tekkamanv4l2_fh 结构体 7728db0f934SNinja Tekkaman------------- 7738db0f934SNinja Tekkaman 7748db0f934SNinja Tekkamanv4l2_fh 结构体提供一个保存用于 V4L2 框架的文件句柄特定数据的简单方法。 775ff792c85SRamakrishnan Muthukrishnan如果 video_device 标志,新驱动 7768db0f934SNinja Tekkaman必须使用 v4l2_fh 结构体,因为它也用于实现优先级处理(VIDIOC_G/S_PRIORITY)。 7778db0f934SNinja Tekkaman 7788db0f934SNinja Tekkamanv4l2_fh 的用户(位于 V4l2 框架中,并非驱动)可通过测试 7798db0f934SNinja Tekkamanvideo_device->flags 中的 V4L2_FL_USES_V4L2_FH 位得知驱动是否使用 7808db0f934SNinja Tekkamanv4l2_fh 作为他的 file->private_data 指针。这个位会在调用 v4l2_fh_init() 7818db0f934SNinja Tekkaman时被设置。 7828db0f934SNinja Tekkaman 7838db0f934SNinja Tekkamanv4l2_fh 结构体作为驱动自身文件句柄结构体的一部分被分配,且驱动在 7848db0f934SNinja Tekkaman其打开函数中将 file->private_data 指向它。 7858db0f934SNinja Tekkaman 7868db0f934SNinja Tekkaman在许多情况下,v4l2_fh 结构体会嵌入到一个更大的结构体中。这钟情况下, 7878db0f934SNinja Tekkaman应该在 open() 中调用 v4l2_fh_init+v4l2_fh_add,并在 release() 中 7888db0f934SNinja Tekkaman调用 v4l2_fh_del+v4l2_fh_exit。 7898db0f934SNinja Tekkaman 7908db0f934SNinja Tekkaman驱动可以通过使用 container_of 宏提取他们自己的文件句柄结构体。例如: 7918db0f934SNinja Tekkaman 7928db0f934SNinja Tekkamanstruct my_fh { 7938db0f934SNinja Tekkaman int blah; 7948db0f934SNinja Tekkaman struct v4l2_fh fh; 7958db0f934SNinja Tekkaman}; 7968db0f934SNinja Tekkaman 7978db0f934SNinja Tekkaman... 7988db0f934SNinja Tekkaman 7998db0f934SNinja Tekkamanint my_open(struct file *file) 8008db0f934SNinja Tekkaman{ 8018db0f934SNinja Tekkaman struct my_fh *my_fh; 8028db0f934SNinja Tekkaman struct video_device *vfd; 8038db0f934SNinja Tekkaman int ret; 8048db0f934SNinja Tekkaman 8058db0f934SNinja Tekkaman ... 8068db0f934SNinja Tekkaman 8078db0f934SNinja Tekkaman my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL); 8088db0f934SNinja Tekkaman 8098db0f934SNinja Tekkaman ... 8108db0f934SNinja Tekkaman 8118db0f934SNinja Tekkaman v4l2_fh_init(&my_fh->fh, vfd); 8128db0f934SNinja Tekkaman 8138db0f934SNinja Tekkaman ... 8148db0f934SNinja Tekkaman 8158db0f934SNinja Tekkaman file->private_data = &my_fh->fh; 8168db0f934SNinja Tekkaman v4l2_fh_add(&my_fh->fh); 8178db0f934SNinja Tekkaman return 0; 8188db0f934SNinja Tekkaman} 8198db0f934SNinja Tekkaman 8208db0f934SNinja Tekkamanint my_release(struct file *file) 8218db0f934SNinja Tekkaman{ 8228db0f934SNinja Tekkaman struct v4l2_fh *fh = file->private_data; 8238db0f934SNinja Tekkaman struct my_fh *my_fh = container_of(fh, struct my_fh, fh); 8248db0f934SNinja Tekkaman 8258db0f934SNinja Tekkaman ... 8268db0f934SNinja Tekkaman v4l2_fh_del(&my_fh->fh); 8278db0f934SNinja Tekkaman v4l2_fh_exit(&my_fh->fh); 8288db0f934SNinja Tekkaman kfree(my_fh); 8298db0f934SNinja Tekkaman return 0; 8308db0f934SNinja Tekkaman} 8318db0f934SNinja Tekkaman 8328db0f934SNinja Tekkaman以下是 v4l2_fh 函数使用的简介: 8338db0f934SNinja Tekkaman 8348db0f934SNinja Tekkamanvoid v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev) 8358db0f934SNinja Tekkaman 8368db0f934SNinja Tekkaman 初始化文件句柄。这*必须*在驱动的 v4l2_file_operations->open() 8378db0f934SNinja Tekkaman 函数中执行。 8388db0f934SNinja Tekkaman 8398db0f934SNinja Tekkamanvoid v4l2_fh_add(struct v4l2_fh *fh) 8408db0f934SNinja Tekkaman 8418db0f934SNinja Tekkaman 添加一个 v4l2_fh 到 video_device 文件句柄列表。一旦文件句柄 8428db0f934SNinja Tekkaman 初始化完成就必须调用。 8438db0f934SNinja Tekkaman 8448db0f934SNinja Tekkamanvoid v4l2_fh_del(struct v4l2_fh *fh) 8458db0f934SNinja Tekkaman 8468db0f934SNinja Tekkaman 从 video_device() 中解除文件句柄的关联。文件句柄的退出函数也 8478db0f934SNinja Tekkaman 将被调用。 8488db0f934SNinja Tekkaman 8498db0f934SNinja Tekkamanvoid v4l2_fh_exit(struct v4l2_fh *fh) 8508db0f934SNinja Tekkaman 8518db0f934SNinja Tekkaman 清理文件句柄。在清理完 v4l2_fh 后,相关内存会被释放。 8528db0f934SNinja Tekkaman 8538db0f934SNinja Tekkaman 8548db0f934SNinja Tekkaman如果 v4l2_fh 不是嵌入在其他结构体中的,则可以用这些辅助函数: 8558db0f934SNinja Tekkaman 8568db0f934SNinja Tekkamanint v4l2_fh_open(struct file *filp) 8578db0f934SNinja Tekkaman 8588db0f934SNinja Tekkaman 分配一个 v4l2_fh 结构体空间,初始化并将其添加到 file 结构体相关的 8598db0f934SNinja Tekkaman video_device 结构体中。 8608db0f934SNinja Tekkaman 8618db0f934SNinja Tekkamanint v4l2_fh_release(struct file *filp) 8628db0f934SNinja Tekkaman 8638db0f934SNinja Tekkaman 从 file 结构体相关的 video_device 结构体中删除 v4l2_fh ,清理 8648db0f934SNinja Tekkaman v4l2_fh 并释放空间。 8658db0f934SNinja Tekkaman 8668db0f934SNinja Tekkaman这两个函数可以插入到 v4l2_file_operation 的 open() 和 release() 8678db0f934SNinja Tekkaman操作中。 8688db0f934SNinja Tekkaman 8698db0f934SNinja Tekkaman 8708db0f934SNinja Tekkaman某些驱动需要在第一个文件句柄打开和最后一个文件句柄关闭的时候做些 8718db0f934SNinja Tekkaman工作。所以加入了两个辅助函数以检查 v4l2_fh 结构体是否是相关设备 8728db0f934SNinja Tekkaman节点打开的唯一文件句柄。 8738db0f934SNinja Tekkaman 8748db0f934SNinja Tekkamanint v4l2_fh_is_singular(struct v4l2_fh *fh) 8758db0f934SNinja Tekkaman 8768db0f934SNinja Tekkaman 如果此文件句柄是唯一打开的文件句柄,则返回 1 ,否则返回 0 。 8778db0f934SNinja Tekkaman 8788db0f934SNinja Tekkamanint v4l2_fh_is_singular_file(struct file *filp) 8798db0f934SNinja Tekkaman 8808db0f934SNinja Tekkaman 功能相同,但通过 filp->private_data 调用 v4l2_fh_is_singular。 8818db0f934SNinja Tekkaman 8828db0f934SNinja Tekkaman 8838db0f934SNinja TekkamanV4L2 事件机制 8848db0f934SNinja Tekkaman----------- 8858db0f934SNinja Tekkaman 8868db0f934SNinja TekkamanV4L2 事件机制提供了一个通用的方法将事件传递到用户空间。驱动必须使用 8878db0f934SNinja Tekkamanv4l2_fh 才能支持 V4L2 事件机制。 8888db0f934SNinja Tekkaman 8898db0f934SNinja Tekkaman 8908db0f934SNinja Tekkaman事件通过一个类型和选择 ID 来定义。ID 对应一个 V4L2 对象,例如 8918db0f934SNinja Tekkaman一个控制 ID。如果未使用,则 ID 为 0。 8928db0f934SNinja Tekkaman 8938db0f934SNinja Tekkaman当用户订阅一个事件,驱动会为此分配一些 kevent 结构体。所以每个 8948db0f934SNinja Tekkaman事件组(类型、ID)都会有自己的一套 kevent 结构体。这保证了如果 8958db0f934SNinja Tekkaman一个驱动短时间内产生了许多同类事件,不会覆盖其他类型的事件。 8968db0f934SNinja Tekkaman 8978db0f934SNinja Tekkaman但如果你收到的事件数量大于同类事件 kevent 的保存数量,则最早的 8988db0f934SNinja Tekkaman事件将被丢弃,并加入新事件。 8998db0f934SNinja Tekkaman 9008db0f934SNinja Tekkaman此外,v4l2_subscribed_event 结构体内部有可供驱动设置的 merge() 和 9018db0f934SNinja Tekkamanreplace() 回调,这些回调会在新事件产生且没有多余空间的时候被调用。 9028db0f934SNinja Tekkamanreplace() 回调让你可以将早期事件的净荷替换为新事件的净荷,将早期 9038db0f934SNinja Tekkaman净荷的相关数据合并到替换进来的新净荷中。当该类型的事件仅分配了一个 9048db0f934SNinja Tekkamankevent 结构体时,它将被调用。merge() 回调让你可以合并最早的事件净荷 9058db0f934SNinja Tekkaman到在它之后的那个事件净荷中。当该类型的事件分配了两个或更多 kevent 9068db0f934SNinja Tekkaman结构体时,它将被调用。 9078db0f934SNinja Tekkaman 9088db0f934SNinja Tekkaman这种方法不会有状态信息丢失,只会导致中间步骤信息丢失。 9098db0f934SNinja Tekkaman 9108db0f934SNinja Tekkaman 9118db0f934SNinja Tekkaman关于 replace/merge 回调的一个不错的例子在 v4l2-event.c 中:用于 9128db0f934SNinja Tekkaman控制事件的 ctrls_replace() 和 ctrls_merge() 回调。 9138db0f934SNinja Tekkaman 9148db0f934SNinja Tekkaman注意:这些回调可以在中断上下文中调用,所以它们必须尽快完成并退出。 9158db0f934SNinja Tekkaman 9168db0f934SNinja Tekkaman有用的函数: 9178db0f934SNinja Tekkaman 9188db0f934SNinja Tekkamanvoid v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev) 9198db0f934SNinja Tekkaman 9208db0f934SNinja Tekkaman 将事件加入视频设备的队列。驱动仅负责填充 type 和 data 域。 9218db0f934SNinja Tekkaman 其他域由 V4L2 填充。 9228db0f934SNinja Tekkaman 9238db0f934SNinja Tekkamanint v4l2_event_subscribe(struct v4l2_fh *fh, 9248db0f934SNinja Tekkaman struct v4l2_event_subscription *sub, unsigned elems, 9258db0f934SNinja Tekkaman const struct v4l2_subscribed_event_ops *ops) 9268db0f934SNinja Tekkaman 9278db0f934SNinja Tekkaman video_device->ioctl_ops->vidioc_subscribe_event 必须检测驱动能 9288db0f934SNinja Tekkaman 产生特定 id 的事件。然后调用 v4l2_event_subscribe() 来订阅该事件。 9298db0f934SNinja Tekkaman 9308db0f934SNinja Tekkaman elems 参数是该事件的队列大小。若为 0,V4L2 框架将会(根据事件类型) 9318db0f934SNinja Tekkaman 填充默认值。 9328db0f934SNinja Tekkaman 9338db0f934SNinja Tekkaman ops 参数允许驱动指定一系列回调: 9348db0f934SNinja Tekkaman * add: 当添加一个新监听者时调用(重复订阅同一个事件,此回调 9358db0f934SNinja Tekkaman 仅被执行一次)。 9368db0f934SNinja Tekkaman * del: 当一个监听者停止监听时调用。 9378db0f934SNinja Tekkaman * replace: 用‘新’事件替换‘早期‘事件。 9388db0f934SNinja Tekkaman * merge: 将‘早期‘事件合并到‘新’事件中。 9398db0f934SNinja Tekkaman 这四个调用都是可选的,如果不想指定任何回调,则 ops 可为 NULL。 9408db0f934SNinja Tekkaman 9418db0f934SNinja Tekkamanint v4l2_event_unsubscribe(struct v4l2_fh *fh, 9428db0f934SNinja Tekkaman struct v4l2_event_subscription *sub) 9438db0f934SNinja Tekkaman 9448db0f934SNinja Tekkaman v4l2_ioctl_ops 结构体中的 vidioc_unsubscribe_event 回调函数。 9458db0f934SNinja Tekkaman 驱动程序可以直接使用 v4l2_event_unsubscribe() 实现退订事件过程。 9468db0f934SNinja Tekkaman 9478db0f934SNinja Tekkaman 特殊的 V4L2_EVENT_ALL 类型,可用于退订所有事件。驱动可能在特殊 9488db0f934SNinja Tekkaman 情况下需要做此操作。 9498db0f934SNinja Tekkaman 9508db0f934SNinja Tekkamanint v4l2_event_pending(struct v4l2_fh *fh) 9518db0f934SNinja Tekkaman 9528db0f934SNinja Tekkaman 返回未决事件的数量。有助于实现轮询(poll)操作。 9538db0f934SNinja Tekkaman 9548db0f934SNinja Tekkaman事件通过 poll 系统调用传递到用户空间。驱动可用 9558db0f934SNinja Tekkamanv4l2_fh->wait (wait_queue_head_t 类型)作为参数调用 poll_wait()。 9568db0f934SNinja Tekkaman 9578db0f934SNinja Tekkaman事件分为标准事件和私有事件。新的标准事件必须使用可用的最小事件类型 9588db0f934SNinja Tekkaman编号。驱动必须从他们本类型的编号起始处分配事件。类型的编号起始为 9598db0f934SNinja TekkamanV4L2_EVENT_PRIVATE_START + n * 1000 ,其中 n 为可用最小编号。每个 9608db0f934SNinja Tekkaman类型中的第一个事件类型编号是为以后的使用保留的,所以第一个可用事件 9618db0f934SNinja Tekkaman类型编号是‘class base + 1’。 9628db0f934SNinja Tekkaman 9638db0f934SNinja TekkamanV4L2 事件机制的使用实例可以在 OMAP3 ISP 的驱动 9648db0f934SNinja Tekkaman(drivers/media/video/omap3isp)中找到。 965