V4l2框架分析

Table of Contents

1.V4L2框架概述

1.1 v4l2设备应用层流程

1.2 内核V4L2模块

1.2.1 video_device

1.2.2  v4l2_subdev

1.2.3  videobuf2

2. video_device结构体

2.1 图像处理模块

2.2 video_device处理流程

2.2.1 video_device 结构体成员介绍:

3. video_buf2

3.1 与video device的关系 

3.2 Buffer 类型

3.2.1  vmalloc

3.2.2 Physically contiguous dma

3.2.3 Physically scattered

3.2.4  如何区分

3.3 vb2_ops

3.3.1 vb2_ops 结构体

3.3.2  videobuf2-core回调vb2_ops

3.4 mmap

4. subdev

4.1 概念

4.2 subdev注册流程

4.2.1  v4l2_subdev结构体

4.2.2  v4l2_subdev注册函数调用

4.3 应用层操作subdev

5. media framework

5.1 概念

6. v4l2设备节点组织流程

6.1 设备树解析

6.2 驱动组织流程

6.2.1  sensor驱动异步注册流程

6.3 驱动注册流程分析

6.4 video注册设备节点流



1.V4L2框架概述

   V4L2是Video for linux2的简称,为linux中关于视频设备的内核驱动。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写。V4L2在设计之初时,是要支持很多广泛的设备的,如声卡, display, FB, I2C, camera等.它们之中只有一部分在本质上是真正的视频设备,也是造成V4l2源码冗余的原因之一。

    kernel source code更新速度快,在display新增drm框架, 声卡上有ALSA框架.  目前V4L2主要用于camera驱动,本文也是通过camera驱动讲解V4l2内部原理。

1.1 v4l2设备应用层流程

注册的设备节点有/dev/video和/dev/v4l2-subdev。

应用层操作video设备主要流程如下:

    1. 通过打开video设备设置video参数。

    2. 设置采集方式。

    3. 将数据取出,处理,放回, 可循环处理。

    4. 完成相应的任务后关闭。

1.2 内核V4L2模块

应用层流程之所以简单, 是因为内核相关模块做了很多工作夯实了基础。 与V4L2相关的摸如下:

V4l2框架分析_第1张图片

1.2.1 video_device

用于实例化一个/dev/video设备的结构体。里面包含该video的类型, 回调函数,以及操作缓冲的队列。 接触内核v4l2驱动, 理解video_device结构体内部很重要。

1.2.2  v4l2_subdev

用于实例化一个/dev/subdev 设备的结构体。 一般只需通过ioctl设置采样属性即可。 内部实现部分v4l2_subdev_ops回调函数, 也可以用与其他驱动模块通讯.

1.2.3  videobuf2

用于video缓存的分配,释放,出队,入队等。提供多种缓存类型管理。

2. video_device结构体

  video_device 即注册 /dev/video# 设备的结构体, 应用层需要使用camera的时候open 与ioctl它。 下面分析video_device的数据结构, 以及内核注册流程。video_device 结构体定义位于v4l2_dev.h 

   注册video_device代码一般位于   driver/media/platform/[soc 对应的图像处理模块]。

2.1 图像处理模块

  这里说图像处理模块为SOC内部集成的一个针对Camera数据信号进行图像处理的硬件模块, 在不同芯片厂商中而不同, 如:i.mx6q 为IPU,  i.mx8q为ISI, renesas rcar-m3为VIN。主要作用是接收将CSI 或者 parallel digital信号,并进行图像数据进行格式转换,缩放,色域转换,DMA通道管理等, 然后映射到相应的内存上, 给用户使用。

2.2 video_device处理流程

   当总线检测到图像处理设备后,便会进入该设备driver中的probe函数, 在probe函数调用video_register_device注册video_device, 注册前需要先初始化video_device结构体内部成员。

2.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内部的函数指针成员为:

  1. vidioc_g_fmt_vid_cap 为VIDIOC_G_FMT 的ioctl逻辑中所指向的函数指针。
  2. vidioc_reqbufs 为VIDIOC_REQBUFS 的ioctl逻辑中所指向的函数指针。
  3. vidioc_querybuf为VIDIOC_QUERYBUF 的ioctl逻辑中所指向的函数指针。
  4. vidioc_querybuf为VIDIOC_DQBUF 的ioctl逻辑中所指向的函数指针。
  5. vidioc_streamoff为VIDIOC_STREAMOFF 的ioctl逻辑中所指向的函数指针。

soc图像处理驱动必须实现v4l2_ioctl_ops对应的成员函数。 应用层在ioctl时,才不会出错。

⑤ video_device->queue: 指向该video设备节点的vb2_queue指针。 很关键的一个成员,与缓冲数据操作有关。 详细分析在下一节。

3. video_buf2

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

    使用虚拟连续内存. 完全不需要考虑内存分配问题. 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使用的内存类型..

          /* Physically scattered */

         /* vmalloc() buffers    */

      /* Physically contiguous */

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

  1. call_qop(q, queue_setup....);
  2. call_vb_qop(vb, buf_prepare, vb);
  3. call_void_vb_qop(vb, buf_queue, vb);
  4. call_qop(q, start_streaming, ...);
  5. call_void_qop(q, stop_streaming, q);
  6. call_void_qop(q, wait_prepare, q);
  7. call_void_qop(q, wait_finish, q);

3.4 mmap

    下面通过分析 应用层mmap 函数的在kernel中流程,buffer类型为Physically contiguous dma. 进一步理解videobuf2在V4L2中的逻辑。

V4l2框架分析_第2张图片

  • 应用层调用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定义好的好的结构体和成员函数。 随着内部成员的增多,功能多样化。这要来来回回的调用必定增多,所以分析起来也很复杂。

4. 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框架分析_第3张图片

小结: V4l2_subdev是video设备的一个子设备, 在操作video时,一般需要先配置它,或者获取它的参数。 subdev驱动如果在probe初始化或者定义回调函数时有问题,导致主设备video 也无法正常工作。

5. 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形式可以多种多样的,最基础的如下:

V4l2框架分析_第4张图片

这样说还是有点抽象,下面以adv7482 sensor 外设作为例子来说明。

adv7482功能: 可以接受八路CVBS模拟和一路HDMI输入,然后转换成一路或者两路的CSI信号输出到SOC上。 硬件框图如下:

V4l2框架分析_第5张图片

  简单介绍以下内部的模块, 左侧有输入接口如: 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, 如下图所示:

V4l2框架分析_第6张图片

media pipeline 是在media graph 选出来的一个通道, 可以这么理解media graph是一张地图, pipeline就是其中的一条规划好的路线.

在驱动实现中,一个由三个media device: adv7482 sensor , SOC中的CSI, SOC 图像处理模块VIN[0-7]. 组成的pipeline如下:

pipeline 1

V4l2框架分析_第7张图片

pipeline 2

V4l2框架分析_第8张图片

当然pad 与entity 的属性还可以设置其他不同的值,这样link其他可以组成非常多的不同形式pipeline, 如:

①ADV7186可以选择8个camera模拟信号输入通道

②SOC中的CSI模块到VIN模块有4个DAM channel 

③SOC VIN模块可以设置不同的视频数据格式 

不同平台的SOC, 图像处理模块设计不同,media graph也不一样.具体详见下一节v4l2设备节点组织流程中有分析.

6. 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设备节点组织图.

V4l2框架分析_第9张图片

6.1 设备树解析

设备树通过endpoint组织每一个点到点的连接.

① 先从ti964设备树节点出发, 定义了CSI2_964 endpoint 并连接到CSI40_IN endpoint. endpoint通过v4l2-fwnode.c内部函数解析.

V4l2框架分析_第10张图片

②  csi40_in的endpoint为 SOC的 CSI40 设备节点的endpoint,要注意该endpoint上的port的reg等于0. csi40驱动通过V4L2-fwnode在解析该port时,设置port的reg为0 的为数据输入port.

V4l2框架分析_第11张图片

③  csi40 中port的reg为1,即包括所有数据输出的endpoint. 输出到vin0csi40 endpoint

V4l2框架分析_第12张图片

④  vin0 中vin0csi20 endpoint 连接csi20vin0 endpoint

V4l2框架分析_第13张图片

 driver通过设备树的endpoint将ti964 sensor, csi, vin 模块组成一个整体的media control graph.如下:

V4l2框架分析_第14张图片

6.2 驱动组织流程

6.2.1  sensor驱动异步注册流程

  在sensor ti 964驱动的ds90ub964_probe函数中,解析设备树获取 ti964 endpoint, 异步注册subdev.下面分析流程:

①  通过解析device tree 中endpoint获取fwnode赋值给sd->fwnode. fwnode成员是用来做match的,在SOC中的CSI驱动中会分析到.

V4l2框架分析_第15张图片

② 初始化sd->media_entity,设置flag为 MEDIA_PAD_FL_SOURCE. 有source就需要sink,这个是gstream的概念, source与sink分别类似于camera与screen.

V4l2框架分析_第16张图片

③  然后调用 v4l2_async_register_subdev(sd) 异步注册

V4l2框架分析_第17张图片

  1. 遍历notifier_list为头静态链表成员.
  2. 查找match到了相应的asd.
  3. 如果查找到调用v4l2_async_notifier中ops->complete
  4. 没有匹配到就加入subdev_list链表头, 等待hot-plugging真正注册,这大概就是异步的含义吧.

分析上述逻辑:

  1.  如果notifier之前没注册, v4l2_async_register_subdev功能就是将subdev加入subdev_list链表中,而且没有真正注册.
  2.  如果有notifier注册,一般在soc video驱动中注册,下文有分析.

两个的静态变量notifier_list和subdev_list很关键, 后续的异步调用都与它链接到的成员有关.

6.3 驱动注册流程分析

    在SOC CSI的驱动的rcsi2_probe函数中. CSI 解析设备树获取 ti964 endpoint, 并通过匹配sd->fwnode

6.3.1 通过 fwnode_graph_get_remote_endpoint获取ti964  的endpoint

V4l2框架分析_第18张图片

6.3.2 调用__v4l2_async_subdev_notifier_register 注册notifier.

V4l2框架分析_第19张图片

6.3.3 v4l2_async_notifier_try_all_subdevs 流程分析

V4l2框架分析_第20张图片

① 遍历静态链表头subdev_list.

② v4l2_async_find_match查找match到的asd

v4l2_async_find_match 函数功能为,根据match_type找到合适的async_subdev,并返回, match_type在CSI驱动中已经设置.如下:

V4l2框架分析_第21张图片

在CSI驱动中, 已经将match_type 设置为 V4L2_ASYNC_MATCH_FWNODE,所以sensor driver中必须设置sd->fwnode, 否则match失败,注册流程会异常.

V4l2框架分析_第22张图片

③ v4l2_async_match_notify

V4l2框架分析_第23张图片

i. 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框架分析_第24张图片

小结: __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函数部分源码

V4l2框架分析_第25张图片

  • 解析在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

V4l2框架分析_第26张图片

② 回调complete,即rvin_group_notify_complete

V4l2框架分析_第27张图片

③ 调用v4l2_device_register_subdev_nodes,真正注册subdev设备节点.

V4l2框架分析_第28张图片

      小结: SOC的内部图像处理模块,创建的video节点时,需要通过遍历设备树的endpoint,借助media framework, subdev, V4L2-async等子模块,分析media entity节点的拓扑是否符合SOC的要求.也就说media pipeline 能不能组建成功,以及如何组建依赖于soc video驱动构建的media graph. video驱动的设计的目的是将soc图像处理硬件的功能充分发挥. 所以想要理解video驱动的细节还必须必须仔细分析soc硬件模块功能. 最好结合代码, 硬件文档一起分析.

你可能感兴趣的:(内核,linux,内核)