Edit online

DVP 架构

V4L2 软件框架

Linux 中的 L2 框架是一个专门为视频输入输出设备而设计的成熟方案,DVP 驱动需要基于 L2。

../../images/v4l2_system.jpg
1. Linux V4L2 子系统架构图
  1. V4L2,Video For Linux 第 2 版,最早出现在 1998 年,一个针对无线广播(收音机)、视频捕获、视频输出设备的通用框架,源码目录 drivers/media/v4l2-core。V4L2 中支持的 5 大类接口设备:
    • Video capture interface:影像捕获接口;

    • Video output interface:视频输出接口,主要用于电视信号类;

    • Video overlay interface:视频覆盖接口,方便视频显示设备直接从捕获设备上获取数据;

    • VBI interface:垂直消隐接口,可提供垂直消隐区的数据接入,包括 raw 和 sliced 两种;

    • Radio interface:广播接口,主要是从 AM 或 FM 调谐器中获取音频数据。

  2. V4L2 为用户空间提供了字符设备的通用接口,设备节点/dev/videoX,主设备号 81,次设备号的分配跟设备类型有关,规则定义如下:

    设备类型 次设备号
    视频设备 0~63
    Radio 设备 64~127
    Teletext 设备 192~233
    VBI 设备 224~255

    用户态 APP 通过 ioctl 控制 video 设备,通过 mmap 进行内存映射。在/dev 目录中会产生 videoX、radioX 和 vbiX 设备节点。

  3. 在 L2 框架中,将每一个 Sensor、DVP 硬件设备都看作一个 subdev,相应的有一个字符设备节点/dev/v4l-subdevX,用户态通过这些节点的 ioctl 接口可以完成 subdev 的格式协商、时序配置等功能。
  4. Notifier 子模块是为了解决多个设备之间的初始化顺序、以及媒体流对接的匹配检查,如 DVP 需要等 Sensor 初始化完成后,才能去真正完成 device 的注册。可见,DVP 和 Sensor 要有一个绑定的关系,这个关系是由 DTS 中的 remote-endpoint 来指定的。Notifier 会调用 Fwnode 系列接口来解析和获取 remote-endpoint 的属性字段。
  5. 为了进行数据流的管理,V4l2 维护了一个 device 链表,每一个 device、v4l2 device、v4l2-subdev 都是一个 device 实例,这些 device 在数据结构的设计上第一个成员变量都是一个 entity,其中有 head 成员,借此互相链接起来。见下一节详述。

V4L2 的实例管理

一个完整的 L2 驱动涉及 4 种设备实例:V4l2 device、Video device、V4l2 subdev、Media device,这 4 个实例都需要在 DVP 驱动中去定义。它们的引用关系如下图:

../../images/v4l2_instance.jpg
2. Linux V4L2 的实例关系示意图
  • V4l2 device

    可以理解为最高统帅,它纵览全局,用成员指针指向以下其他实例,只服务于内核态,不包含面向用户态的接口。 整个内核中只有一个 device 实例。

  • V4l2 subdev
    V4L2 将每一个硬件模块都看作一个 subdev,如 DVP、Sensor 都各自注册一个 subdev,并且每个 subdev 会向用户态透出一个/dev/v4l-subdevX 设备节点(该设备节点的编号 X 取决于注册顺序)。这些 subdev 会形成一个链表,都挂在 device 下面的 subdevs 链表中。在 subdev 的 ops 数据接口 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;
    };

对于 DVP 控制器来说,它对应的 ops 只提供 pad_ops 即可;对于 Sensor 设备来说,要提供 ops 和 ops。

  • Video device

    是给用户态提供/dev/videoX 接口的设备实例,这里不区分设备类型,统一使用 L2 标准的 80+个 ioctl 命令接口。video device 更像是逻辑功能,如果未来我们的 DVP 增加了 ISP 功能,就需要注册两个 device,但是 DVP 对应的 subdev 只需要一个。

  • Media device

    主要作用是为了将有上下游关系的 entity 串联起来(通过连接各 entity 的 pad 或 interface 属性),形成一个媒体流(V4L2 启动/停止命令称作 start/stop stream)。并且会透出一个/dev/mediaX 设备节点,目前用户态还没有用到,所以在上图中未体现。

  • Media entity

    以上实例除了最高统帅 device 其他都可以看作一个 entity,都挂在 device 维护的一个 media entity 链表。

实际上,在上图中的每个 subdev,注册的时候也是生成一个 device,由 device 向用户态透一个/dev/v4l-subdevX 设备过去,这样做的好处是由 device 统一处理对接用户态的接口。所以完整的实例关系图应该再加一层 device,如下图:

../../images/v4l2_instance_full.jpg
3. Linux V4L2 的完整实例关系示意图

V4L2 的 Media 管理

V4L2 提供了一个 Framework 来管理 Media 数据组成 pipeline,在运行过程中可以调整 pipeline 中各个节点的配置,达到“运行时设备控制”的效果。

需要用到 5 个关键数据结构:media_device、media_entity、media_link、media_pad、media interface。

  • Media device 是框架管理者,下面维护 4 个链表:entity、pad、link、interface。
  • 将每个硬件设备、或者一个软件模块抽象成一个 entity,如 DVP 控制器、Sensor、DMA 通道、连接器
  • Entity 之间通过 link 来连接,link 的两端是 pad。数据流是从一个 pad(源)到一个 pad(目的/接收端)。Pad:硬件设备上的端口抽象,类似于芯片上面的管脚 pad 概念。
    ../../images/v4l2_pad.png
    4. Linux V4L2 的 pad 和 link 关系示意图
  • 另外还有一个 interface 结构,表示提供给用户态什么接口,目前只有一种类型:device node
  • link 有两种:pad to pad、Interface to entity(暂未用到)。从 link 的定义看它们的连接关系:
    struct media_link {
        struct media_gobj graph_obj; // 是对 entity、pad、link、Interface 的抽象,它们的公共头数据
        struct list_head list;
        union {
            struct media_gobj *gobj0; // 是 pad 和 Interface 头部的公共数据
            struct media_pad *source; // 源 pad
            struct media_interface *intf; // 需要连接到 entity 的 interface
        };
        union {
            struct media_gobj *gobj1; // 同上,是 pad 和 entity 头部的公共数据
            struct media_pad *sink; // 目的 pad
            struct media_entity *entity; // 需要连接到 Interface 的 entity
        };
        struct media_link *reverse; // 指向对称(两端 pad 相同方向相反)的那个 media_link
        unsigned long flags;
        bool is_backlink; // 是否逆方向(相对于数据流方向)
    };
  • 从数据结构定义来看一个 entity,可以有多个 pad,当然随之而来会有多个 link,定义如下:
    注: 其中的 pads 成员是个指针,指向的实例需要 DVP 驱动先定义好
    ../../images/v4l2_media_entity.jpg
    5. Linux V4L2 的 entity 和 pad 的引用关系

    结合我们的 DVP 硬件结构,media entity、pad 和 link 的关系如下:

    ../../images/dvp_media_entity.png
    6. DVP 驱动中的 entity 设计

    从上图中可以看到,如果要设置 DVP 的 输入 格式,就通过 subdev;如果要设置 DVP 的 输出 格式,则通过 device。

V4L2 的 ioctl 调用关系

在“实例管理”中已经知道,用户态看到的/dev/videoX 和 /dev/v4l-subdevX 两个设备节点,进入内核态后都是直接先跟 device 设备实例对接,那么当用户调用 ioctl 命令时,是如何传给下面 subdev 呢?依靠注册时传入的参数 struct v4l2_file_operations *fops。

  • V4L2 subdev 在为自己注册 device 时(见 c 中的 v4l2_device_register_subdev_nodes())传入的 fops 是预定义好的 fops(定义见 c),其中的 ioctl 接口指向框架中提供的接口 subdev_ioctl()。而 ioctl 会逐层调用到“实例管理”中提到的 v4l2_subdev_ops。
  • 而 DVP 驱动在为自己注册 Video device 时,传入的 fops 是自己定义的 fops,其中 ioctl 接口指向框架中的公共接口 video_ioctl2()。
  • 为解决不同硬件设备有不同的 ioctl 处理需求,DVP 驱动还需要另外提供一个专门为 ioctl 定义的扩展 ops:aic_dvp_ioctl_ops(数据结构定义见 ops)。

针对一个从用户态传来的 ioctl()命令,其内部调用关系如下图:

../../images/v4l2_ioctl.jpg
7. Linux V4L2 的 ioctl 处理关系图

V4L2 的 Buf 队列管理

V4L2 的 Buffer 管理由 videobuf 子模块实现,从源头看分为两种方式的 Buffer:

  1. 驱动申请 Buffer

    用户态通过 ioctl 命令触发 Buffer 申请,然后使用 mmap 接口来获取用户态的 Buffer 地址。这种方式,Buffer 个数一般有个最大值 32 VIDEO_MAX_FRAME。

  2. 用户态申请 Buffer

    用户态根据实际需要知道要申请多少 Buffer,然后借助其他机制(可以是 buf)在用户态完成 Buffer(当然必须是物理连续的)申请,并将物理地址告诉 Video 驱动。

按照 Buffer 的物理连续,又可以分为三种情况:(详见 rst)

  1. 物理连续、不连续的 Buffer 混用

    几乎所有用户空间的 Buffer 都属于这种情况,这样最大可能的发挥虚拟内存管理的灵活性。缺点也很明显,这些 Buffer 给硬件的话需要有支持 scatter 的 DMA。

  2. 物理不连续、虚拟地址连续的 Buffer

    它们由 vmalloc()分配,也用于支持 scatter 接口的 DMA 硬件。

  3. 物理连续的 Buffer

    非常适合 DMA 类硬件的访问。

注:

驱动开发者必须三选一,对于我们的 DVP 模块来说,要选择 3。并且底层用到 buf。

V4L 第二版不再支持 Overlay 类型,而 L 第一版不支持 DMABUF 类型。

V4L2 在数据流传输的时候需要多个 Buf 切换,通过 queue 结构中的 Qbuf 两个 Buf 队列来管理,DVP 驱动中还需要维护一个 list 来配合 DVP 控制器的地址更新。整个 Buf 流转的过程如下图:

../../images/dvp_buf_list.jpg
8. DVP 驱动中的 Buf 队列管理
  • Qbuf 队列(在代码中见 struct vb2_queue->queued_list):是一些空闲 Buf,等待 Sensor 的数据到来后,DVP 驱动会从这个队列中找可用 Buf 来保存下一帧数据。
  • DQbuf 队列(在代码中见 struct vb2_queue->done_list):是一些填了视频数据的 Buf,等待用户来处理这些数据,一般用户处理完后需要将 Buf 还给驱动,也就是还给 Qbuf。
  • 从 Qbuf 和 DQbuf 来的 buf 格式是 buffer,其中没有字段可以保存物理地址(DVP 控制器需要),同时还需要为每个 buf 记录一个是否正在被 DVP 使用的状态,所以 DVP 驱动中定义了基于 vb2_buffer 结构的封装 buffer,并且维护一个和 Qbuf 几乎同步的队列。
  • 从图中的流转过程看,运行期间,在某一时刻,DVP 需要使用一个 Buf,APP 需要使用一个 Buf,QBuf 需要有一个 Buf 在等待(否则 DVP 的 done 中断来了后发现没有等待的 Qbuf 会发生丢帧),一共至少要有 3 个 Buf。
  • 以 YUV422 格式计算,有两个 plane,在 L2 框架中这一组 plane 算一个 Buf,3 个 Buf 就需要申请 6 个 plane,总大小 = 长 * 宽 * 2 * 3
  • DVP 驱动中需要定义一个 queue 实例,Video device 中会有一个指针*queue 指向该实例。

DVP 驱动的子模块结构

基于以上对 L2 框架的分析,DVP 驱动内部可以分为以下 5 个子模块:

../../images/dvp_drv_structure.jpg
9. DVP 驱动的子模块结构
  • Video Dvice 管理,主要实现和 device 相关的注册、ioctl 处理;
  • Notifier 管理,主要处理和 Sensor 设备的初始化次序的依赖关系;
  • Buf 管理,主要实现 Buf 的入队、出队,以及在中断响应时切换 DVP 控制器的输出地址等;
  • Subdev 管理,主要实现输入格式相关的接口;
  • 寄存器访问,封装了对 DVP 控制器寄存器的读写访问。