1. V4L2框架概述
1.1 v4l2设备应用层流程
1.2 内核V4L2模块 2
2. video_device
2.1图像处理模块
2.2 video注册流程
3. videobuf2
3.1 与video device的关系:
3.2 buffer类型
3.3 vb2_ops回调函数
3.4 mmap 流程
4. Subdev
4.1 概念
4.2 subdev注册流程
5. media framework
6. v4l2设备节点组织流程
6.1设备树分析
6.2 驱动注册流程分析
6.3 SOC中CSI驱动异步注册流程
6.4 Video 驱动注册节点流程
- V4L2框架概述
V4L2是Video for linux2的简称,为linux中关于视频设备的内核驱动。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写。
V4L2在设计之初时,是要支持很多广泛的设备的,如声卡, display, FB, I2C, camera等.它们之中只有一部分在本质上是真正的视频设备,也是造成V4l2源码冗余的原因之一。
kernel更新速度快,在display上有drm框架和framebuffer框架, 声卡上有ALSA框架. 目前V4L2主要用于camera驱动,本文也是通过camera驱动讲解V4l2内部原理。
1.1 v4l2设备应用层流程
注册的设备节点有/dev/video和/dev/v4l2-subdev。
应用层操作video设备主要流程如下:
1. 通过打开video设备设置video参数。
2. 设置采集方式。
3. 将数据取出,处理,放回, 可循环处理。
4. 完成相应的任务后关闭。
1.2 内核V4L2模块
应用层流程之所以简单, 是因为内核相关模块做了很多工作夯实了基础。 与V4L2相关的摸如下:
① video_device
用于实例化一个/dev/video设备的结构体。里面包含该video的类型, 回调函数,以及操作缓冲的队列。 接触内核v4l2驱动, 理解video_device结构体内部很重要。
② v4l2_subdev
用于实例化一个/dev/subdev 设备的结构体。 一般只需通过ioctl设置采样属性即可。 内部实现部分v4l2_subdev_ops回调函数, 也可以用与其他驱动模块通讯.
③ videobuf2
用于video缓存的分配,释放,出队,入队等。提供多种缓存类型管理。
- video_device
video_device 即注册 /dev/video# 设备的结构体, 应用层需要使用camera的时候open 与ioctl它。 下面分析video_device的数据结构, 以及内核注册流程。
video_device 结构体定义位于v4l2_dev.h, 注册流程位于对应的soc 驱动下, 一般位于driver/media/platform/[soc 对应的图像处理模块]。
1.1图像处理模块
图像处理模块在不同芯片厂商中而不同, 如:i.mx6q 为IPU, i.mx8q为ISI, renesas rcar-m3为VIN。主要作用是接收将CSI 或者 parallel digital信号,并进行图像数据进行格式转换,缩放,色域转换,DMA通道管理等, 然后映射到相应的内存上, 给用户使用。
1.2 video注册流程
当总线检测到图像处理设备后,便会进入该设备driver中的probe函数, 在probe函数调用video_register_device注册video_device, 注册前需要先初始化video_device结构体内部成员。
1.2.1 video_device 结构体成员介绍:
① video_device->v4l2_dev: 必须的设置成V4l2_device父设备,该父设备通过v4l2_device_register函数初始化
② video_device->name: 设置一个唯一设备名描述符
③ video_device->fops: 设置成v4l2_file_operations 结构体。 图像处理video驱动一般只实现open release 成员函数,其他交给V4l2-core 实现。
static const struct v4l2_file_operations rvin_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
.open = rvin_open, // soc 驱动实现
.release = rvin_release, // soc 驱动实现
.poll = vb2_fop_poll, //videobuf2内部实现
.mmap = vb2_fop_mmap, //videobuf2内部实现
.read = vb2_fop_read, //videobuf2内部实现
};
④ video_device->ioctl_ops: 设置成v4l2_ioctl_ops结构, 该结构体成员为应用层操作video_device_fd句柄的ioctl(VIDEOC_XXX) ,所回调的函数指针。 我们配置/dev/video主要通过使用ioctl(VIDEOC_XXX), 命令宏如:VIDIOC_S_FMT, VIDIOC_G_FMT, VIDIOC_REQBUFS, VIDIOC_QUERYBUF, VIDIOC_DQBUF, VIDIOC_STREAMOFF。
对应v4l2_ioctl_ops内部的函数指针成员为:
- vidioc_s_fmt_vid_cap 为VIDIOC_S_FMT 的ioctl逻辑中所指向的函数指针。
- vidioc_g_fmt_vid_cap 为VIDIOC_G_FMT 的ioctl逻辑中所指向的函数指针。
- vidioc_reqbufs 为VIDIOC_REQBUFS 的ioctl逻辑中所指向的函数指针。
- vidioc_querybuf为VIDIOC_QUERYBUF 的ioctl逻辑中所指向的函数指针。
- vidioc_querybuf为VIDIOC_DQBUF 的ioctl逻辑中所指向的函数指针。
- vidioc_streamoff为VIDIOC_STREAMOFF 的ioctl逻辑中所指向的函数指针。
soc图像处理驱动必须实现v4l2_ioctl_ops对应的成员函数。 应用层在ioctl时,才不会出错。
⑤ video_device->queue: 指向该video设备节点的vb2_queue指针。 很关键的一个成员,与缓冲数据操作有关。 详细分析在下一节。
- videobuf2
videobuf2 是v4l2驱动一个重要组成部分, 用来管理和存储video帧缓存。
3.1 与video device的关系:
① video_device结构体内部有vb2_queue结构体成员指针。
② 在应用层操作/dev/video# 句柄的函数read, poll, mmap(),ioctl等,即与帧缓存相关的函数。涉及到videobuf2。
③ video_device结构体内部有 v4l2_ioctl_ops结构体, v4l2_ioctl_ops内部函数成员的实现在V4l2-core中的videobuf2-v4l2.c 。 在videobuf2-core.c中又会回调平台图像处理器驱动实现vb2_ops。
User Function name |
Description |
v4l2_ioctl_ops ’s callback in video_device struct |
Implement in videobuf2-v4l2.c |
ioctl(VIDIOC_REQBUFS) |
缓冲需求与发送模式选择 |
vidioc_reqbufs |
vb2_ioctl_reqbufs |
ioctl(VIDIOC_DQBUF) |
在队列中弹出一个空缓冲 |
vidioc_dqbuf |
vb2_ioctl_dqbuf |
ioctl(VIDIOC_QBUF) |
在队列中压入一个填入数据的缓冲 |
vidioc_qbuf |
vb2_ioctl_qbuf |
3.2 buffer类型
3.2.1 vmalloc() buffers
使用虚拟连续内存. 完全不需要考虑内存分配问题. videobuf2内部完成所有细节。 但是性能最低。
3.2.2 Physically contiguous dma
物理地址连续内存传输,传输完一个block物理地址连续内存后,发生中断。 大多数capture中使用,使用较为简单,性能中等。
3.2.3 Physically scattered
一种特殊类型的流DMA映射机制–发散/汇聚映射。该机制允许一次为多个缓冲区创建DMA映射,它是用一个链表描述物理不连续的存储器,然后把链表首地址告诉dma master。dma master传输完一块物理连续的数据后,就不用再发中断了,而是根据链表传输下一块物理连续的数据。性能最高,实现起来复杂。
3.2.4 通过调用的以下那个头文件, 即可查出soc V4l2使用的内存类型..
3.3 vb2_ops回调函数
提供 videobuf2-core 回调的函数的结构体体vb2_ops
3.3.1 vb2_ops 结构体
上文提到v4l2-core的videobuf2-core.c中,需要回调图像处理驱动中的实现的函数。 这些函数在vb2_ops结构体中, 如下图所示:
static const struct vb2_ops rvin_qops = {
.queue_setup = vin_queue_setup,
.buf_prepare = vin_buffer_prepare,
.buf_queue = vin_buffer_queue,
.start_streaming = vin_start_streaming,
.stop_streaming =vin_stop_streaming,
.wait_prepare = vin_ops_wait_prepare,
.wait_finish = vin_ops_wait_finish,
};
3.3.2 videobuf2-core回调vb2_ops函数指针宏:
- call_qop(q, queue_setup....);
- call_vb_qop(vb, buf_prepare, vb);
- call_void_vb_qop(vb, buf_queue, vb);
- call_qop(q, start_streaming, ...);
- call_void_qop(q, stop_streaming, q);
- call_void_qop(q, wait_prepare, q);
- call_void_qop(q, wait_finish, q);
3.4 mmap 流程
下面通过分析 应用层mmap 函数的在kernel中流程,buffer类型为Physically contiguous dma. 进一步理解videobuf2在V4L2中的逻辑。
- 应用层调用mmap 后,系统调用sys_mmap。 v4l2-core调用vdev->fops->mmap. vdev 是由soc 平台驱动注册。
- 在soc驱动中, 将V4l2-core 中的videobuf2-v4l2中的vb2_fop_mmap赋值给 V4l2_fops->mmap。 vb2_fop_mmap调用了videobuf2-core中的vb2_mmap
- vb2_mmap 又会调用call_memop(vb,mmap,...)。 call_memop中有的需要在soc驱动中的初始化vb2_queue->mem_ops为vb2_dma_contig_memops。 vb2_dma_contig_memops中的vb2_dc_mmap即为最终回调。 之后操作vm_area
小结: 上面的mmap流程分析, 可谓三进三出。 v4l2-core需要调用soc驱动注册的video device以及回调其内部实现的函数, 而soc 驱动初始化时有需要v4l2-core定义好的好的结构体和成员函数。 随着内部成员的增多,功能多样化。这要来来回回的调用必定增多,所以分析起来也很复杂。
- Subdev
4.1 概念
在camera驱动中,subdev一般指与video device 相关的外围senor子设备, 可以是csi接口,fpdlink,camera内部isp等。 大部分为I2c_subdev。 subdev可用于应用层的调用,以及驱动间的交互。 video device 可组织和控制V4l2_subdev。
4.2 subdev注册流程
4.2.1 v4l2_subdev结构体
每个subdev驱动必须实现一个v4l2_subdev结构体, 这个结构体可以单独存在,或者嵌入到其他更大的结构体中,如果该subdev需要储存更多信息。
如果集成了media framework, 必须通过media_entity_pads_init()初始化media_entity,作为media framework 构建单元。 而且subdev驱动还需实现v4l2_subdev_video_ops的v4l2_subdev_pad_ops。 用于 ioctl 的handler,或者其他设备驱动回调。 部分回调函数对应如下:
v4l2_subdev_video_ops中的成员 |
User ioctl 命令 |
其他驱动回调 |
g_std |
VIDIOC_G_STD |
v4l2_subdev_call(sd, video, g_std, a); |
s_std |
VIDIOC_S_STD |
v4l2_subdev_call(sd, video, s_std, a); |
v4l2_subdev_pad_ops中的成员 |
User ioctl 命令 |
其他驱动回调 |
get_fmt |
VIDIOC_SUBDEV_G_FMT |
v4l2_subdev_call(sd, pad, get_fmt...) |
set_fmt |
VIDIOC_SUBDEV_S_FMT |
v4l2_subdev_call(sd, pad, set_fmt...) |
v4l2_subdev_video_ops与 video device 中v4l2_ioctl_ops结构体内部成员有很多看上去相似的地方。 确实在v4l2头文件中, 许多 V4l2_xxx_ops结构体成员看上去类似, 再加上这些ops 要被其他driver或者user 的调用, 如果是新手, 很容易混淆。
那么如何理解V4l2_xxx_ops中的逻辑?
① 得区分它是那个subdev的 ops。 可以通过打印subdev指针区分。
② 分析这个ops的功能。 是set还是get? ops是否对sensor的寄存器更改了? 还是没有进行任何操作。
③ 然后在分析它的调用逻辑。 执行完之后, 成功和错误分别会执行些什么?
④ 最好做个笔记, 描述某些特殊情况, 例如某些图像处理器无法处理某些特定格式。
4.2.2 v4l2_subdev注册函数调用
① 给包含v4l2_subdev的数据结构kzalloc()一片内存出来
② 调用v4l2_subdev_init函数初始化V4l2_subdev结构体成员
③ v4l2_async_register_subdev 异步注册, 没有真正的注册. 缓存到某个变量中
⑤ 主设备调用组织subdev后,调用v4l2_device_register_subdev_nodes函数真正注册subdev节点
那么主设备是如何组织subdev的呢? 这事就需要 media framework, V4L2 asynchronous 模块, V4L2 fwnode绑定分析模块辅助,以及设备树。 逻辑复杂,第6节v4l2设备节点组织流程中有分析。
4.3 应用层操作subdev
应用层一般通过ioctl(VIDIOC_SUBDEV_XXXX), 操作subdev. 对应流程如下:
小结: V4l2_subdev是video设备的一个子设备, 在操作video时,一般需要先配置它,或者获取它的参数。 subdev驱动如果在probe初始化或者定义回调函数时有问题,导致主设备video 也无法正常工作。
- media framework
5.1 概念
Media framework的目标之一构建媒体设备内部的拓扑, 并且在运行时配置该设备。 V4L2为了实现它, 将与V4L2 相关的设备抽象化成一个个media entity, 然后通过pad 将 entity 连接(link) 起来, 组成一个media pipeline。 然后使pipeline可控化。
① Entity
是一个多媒体硬件设备的基础构块。它可以虚拟的设备为:cmos senor外围设备, soc 图像处理模块, dma通道,或者硬件接口(CSI, parallel)。
② Pad
是一个可以使一个或者多个entity交互的可连接端点,单独存在没有意义,可以理解为一个硬件设备对外的接口。
③ Link
是两个pads的有向连接,并且是点对点的
④ pipeline
由entity pad link 组成, 一个entity可以有多个pad, pad 可以link其他entity的pad,所以pipeline形式可以多种多样的,最基础的如下:
这样说还是有点抽象,下面以adv7482作为例子来说明。
adv7482功能: 可以接受八路CVBS模拟和一路HDMI输入,然后转换成一路或者两路的CSI信号输出到SOC上。 硬件框图如下:
简单介绍以下内部的模块, 左侧有输入接口如: AIN1-AIN8为模拟输入接口, RX0~RX2为hdmi差分输入接口. 右侧有CSI-2 transmiter A, CSI-2 transmiter B输出接口. 内部模块负责信号检测,模数转换, 始终管理, 色域转换等.
这种sensor controller 可以抽象为多个subdev 负责不同模块之间的处理, 再与SOC 内部模块交互,组成可用的一个可用video节点. 但是单纯控制subdev没有数据流的概念,很难分析逻辑层次和拓扑关系, 这时就需要media framework 抽象化这些设备的拓扑,组成一个media graph.
adv7482与renesas rcar-m3 SOC组成的media graph, 如下图所示:
media pipeline 是在media graph 选出来的一个通道, 可以这么理解media graph是一张地图, pipeline就是其中的一条规划好的路线.
在驱动实现中,一个由三个media device: adv7482 sensor , SOC中的CSI, SOC 图像处理模块VIN[0-7]. 组成的pipeline如下:
① pipeline 1
② pipeline 2
当然pad 与entity 的属性还可以设置其他不同的值,这样link其他可以组成非常多的不同形式pipeline, 如:
①ADV7186可以选择8个camera模拟信号输入通道
②SOC中的CSI模块到VIN模块有4个DAM channel
③SOC VIN模块可以设置不同的视频数据格式
不同平台的SOC, 图像处理模块设计不同,media graph也不一样.具体详见下一节v4l2设备节点组织流程中有分析.
- v4l2设备节点组织流程
在media framework一文中,应用层根据不同需要, 可以通过设置media entity属性,组织不同的模块,构建一个media pipeline.
media pipeline 是在media control graph 选出来的一个通道, 可以这么理解media graph是一张地图, pipeline就是其中的一条规划好的路线. 而media control graph需要 内核组织media device节点去构建.在V4L2中, 需要 media framework, V4L2 asyn模块, 以及设备树等模块构建media control graph. 下面是V4l2设备节点组织图.
6.1设备树分析:
设备树通过endpoint组织每一个点到点的连接.
① 先从ti964设备树节点出发, 定义了CSI2_964 endpoint 并连接到CSI40_IN endpoint. endpoint通过v4l2-fwnode.c内部函数解析.
② csi40_in的endpoint为 SOC的 CSI40 设备节点的endpoint,要注意该endpoint上的port的reg等于0. csi40驱动通过V4L2-fwnode在解析该port时,设置port的reg为0 的为数据输入port.
③ csi40 中port的reg为1,即包括所有数据输出的endpoint. 输出到vin0csi40 endpoint
③ vin0 中vin0csi20 endpoint 连接csi20vin0 endpoint
driver通过设备树的endpoint将ti964 sensor, csi, vin 模块组成一个整体的media control graph.如下:
6.2 驱动注册流程分析
6.2.1 sensor驱动异步注册流程
在sensor ti 964驱动的ds90ub964_probe函数中,解析设备树获取 ti964 endpoint, 异步注册subdev.下面分析流程:
① 通过解析device tree 中endpoint获取fwnode赋值给sd->fwnode. fwnode成员是用来做match的,在SOC中的CSI驱动中会分析到.
② 初始化sd->media_entity,设置flag为 MEDIA_PAD_FL_SOURCE. 有source就需要sink,这个是gstream的概念, source与sink分别类似于camera与screen.
③ 然后调用 v4l2_async_register_subdev(sd) 异步注册
- 遍历notifier_list为头静态链表成员.
- 查找match到了相应的asd.
- 如果查找到调用v4l2_async_notifier中ops->complete
- 没有匹配到就加入subdev_list链表头, 等待hot-plugging真正注册,这大概就是异步的含义吧.
分析上述逻辑:
- 如果notifier之前没注册, v4l2_async_register_subdev功能就是将subdev加入subdev_list链表中,而且没有真正注册.
- 如果有notifier注册,一般在soc video驱动中注册,下文有分析.
两个的静态变量notifier_list和subdev_list很关键, 后续的异步调用都与它链接到的成员有关.
6.3 SOC中CSI驱动异步注册流程
在SOC CSI的驱动的rcsi2_probe函数中. CSI 解析设备树获取 ti964 endpoint, 并通过匹配sd->fwnode
6.3.1 通过 fwnode_graph_get_remote_endpoint获取ti964 的endpoint
6.3.2 调用__v4l2_async_subdev_notifier_register 注册notifier.
6.3.3 v4l2_async_notifier_try_all_subdevs 流程分析
① 遍历静态链表头subdev_list.
② v4l2_async_find_match查找match到的asd
v4l2_async_find_match 函数功能为,根据match_type找到合适的async_subdev,并返回, match_type在CSI驱动中已经设置.如下:
在CSI驱动中, 已经将match_type 设置为 V4L2_ASYNC_MATCH_FWNODE,所以sensor driver中必须设置sd->fwnode, 否则match失败,注册流程会异常.
③ v4l2_async_match_notify
- v4l2_device_register_subdev调用subdev驱动中实现的registered函数: sd->internal_ops->registered
II.v4l2_async_notifier_call_bound调用驱动中实现的bound函数:notifier->ops->bound
CSI driver 实现为:rcar_csi2_notify_bound
III.查找该sub-device的notifier
IV.又调用v4l2_async_notifier_try_all_subdevs是不是晕了? 两个函数相互调用!!!
最后分析下 __v4l2_async_subdev_notifier_register 中 v4l2_async_notifier_try_complete
流程:
v4l2_async_notifier_try_complete
-> v4l2_async_notifier_call_complete
-> notifier->ops->complete但是 csi2中只有ops->bound.因为它也只是一个subdev,无法建立一个pipeline. Bound完成调用media_create_pad_link 建立连接即可.
小结: __v4l2_async_subdev_notifier_register跟之前的v4l2_async_register_subdev 逻辑很相似,再加上两个函数相互调用,这也是V4L2-async容易搞混淆的地方.
6.4 Video 驱动注册节点流程
与csi异步机制机制一样vin probe中注册一个async_notifier
6.4.1 Video 解析整个endpoint
video驱动解析设备树遍历CSI endpoint,从probe开始如下:
rcar_vin_probe
-> rvin_group_init
->rvin_group_graph_parse
这里很多逻辑涉及到SOC的图像处理模块与CSI接口模块的之间硬件通道,不同的soc逻辑差异很大. 但一般都是通过设备树节点,获取subdev对应的entity,组成一个 media graph.
rvin_group_graph_parse函数部分源码
- 解析在VIN 和CSI 上的所有endpoint.
- 解析与CSI连接的endpoint
- 已经解析过的会保存在VIN->group, 避免重复分析
- 再次调用rvin_group_graph_parse,递归深度遍历
解析过程,驱动会将已经解析的endpoint保存到vin->group. 其中fwnode值可用于subdev 的注册.
6.4.2 v4l2_async_notifier_operations回调
6.4.3 match到相应的subdev 后回调v4l2_async_notifier_operations 中的bound 和 complete
① 回调 bound, 根据fwnode获取CSI的subdev 赋值到 vin->group->csi[i].subdev
② 回调complete,即rvin_group_notify_complete
③ 调用v4l2_device_register_subdev_nodes,真正注册subdev设备节点.
小结: SOC的内部图像处理模块,创建的video节点时,需要通过遍历设备树的endpoint,借助media framework, subdev, V4L2-async等子模块,分析media entity节点的拓扑是否符合SOC的要求.也就说media pipeline 能不能组建成功,以及如何组建依赖于soc video驱动构建的media graph. video驱动的设计的目的是将soc图像处理硬件的功能充分发挥. 所以想要理解video驱动的细节还必须必须仔细分析soc硬件模块功能. 最好结合代码, 硬件文档一起分析.