6.4.5. 设计说明

6.4.5.1. 源码说明

本模块源代码在内核目录linux-5.4/drivers/media/platform/zx下,目录结构如下:

drivers/media/platform/zx/
├── aic_buf.c  // 和buf管理相关的处理代码
├── aic_dvp.c  // DVP驱动的初始化入口,主要实现了probe、Notifier接口
├── aic_dvp.h  // DVP驱动共用的头文件,其中定义了寄存器、共用数据结构、全局函数等
├── aic_dvp_hw.c  // 对寄存器的访问封装
├── aic_video.c // 和V4L2框架强相关的一些接口定义,如file_ops、ioctl_ops的接口实现等
├── Kconfig
└── Makefile

6.4.5.2. 模块架构

6.4.5.2.1. V4L2 软件框架

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

../../../_images/v4l2_system.jpg

图 6.38 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调谐器中获取音频数据。

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

    设备类型

    次设备号

    视频设备

    0~63

    Radio设备

    64~127

    Teletext设备

    192~233

    VBI设备

    224~255

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

  1. 在V4L2框架中,将每一个Sensor、DVP硬件设备都看作一个subdev,相应的有一个字符设备节点/dev/v4l-subdevX,用户态通过这些节点的ioctl接口可以完成subdev的格式协商、时序配置等功能。

  2. Notifier子模块是为了解决多个设备之间的初始化顺序、以及媒体流对接的匹配检查,如DVP需要等Sensor初始化完成后,才能去真正完成video device的注册。可见,DVP和Sensor要有一个绑定的关系,这个关系是由DTS中的remote-endpoint来指定的。Notifier会调用Fwnode系列接口来解析和获取remote-endpoint的属性字段。

  3. 为了进行数据流的管理,V4l2维护了一个Media device链表,每一个video device、v4l2 device、v4l2-subdev都是一个Media device实例,这些Media device在数据结构的设计上第一个成员变量都是一个struct media_entity,其中有list_head成员,借此互相链接起来。见下一节详述。

6.4.5.2.2. V4L2 的实例管理

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

../../../_images/v4l2_instance.jpg

图 6.39 Linux V4L2的实例关系示意图

  • V4l2 device

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

  • V4l2 subdev

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

  • Video device

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

  • Media device

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

  • Media entity

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

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

../../../_images/v4l2_instance_full.jpg

图 6.40 Linux V4L2的完整实例关系示意图

6.4.5.2.3. V4L2 的 Media 管理

V4L2提供了一个Media 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。数据流是从一个source pad(源)到一个sink pad(目的/接收端)。Pad:硬件设备上的端口抽象,类似于芯片上面的管脚pad概念。

../../../_images/v4l2_pad.png

图 6.41 Linux V4L2的pad和link关系示意图

  • 另外还有一个media_interface结构,表示提供给用户态什么接口,目前只有一种类型:device node

  • link有两种:pad to pad、Interface to entity(暂未用到)。从media_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(注意其中的pads成员是个指针,指向的实例需要DVP驱动先定义好),当然随之而来会有多个link,定义如下:

../../../_images/v4l2_media_entity.jpg

图 6.42 Linux V4L2的Media entity和pad的引用关系

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

../../../_images/dvp_media_entity.png

图 6.43 DVP驱动中的Media entity设计

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

6.4.5.2.4. V4L2 的 ioctl 调用关系

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

  • V4L2 subdev在为自己注册Video device时(见v4l2-device.c中的v4l2_device_register_subdev_nodes())传入的fops是预定义好的v4l2_subdev_fops(定义见v4l2-subdev.c),其中的ioctl接口指向框架中提供的接口subdev_ioctl()。而subdev_ioctl会逐层调用到“实例管理”中提到的 v4l2_subdev_ops。

  • 而DVP驱动在为自己注册Video device时,传入的fops是自己定义的aic_dvp_fops,其中ioctl接口指向框架中的公共接口video_ioctl2()。

  • 为解决不同硬件设备有不同的ioctl处理需求,DVP驱动还需要另外提供一个专门为ioctl定义的扩展ops:aic_dvp_ioctl_ops(数据结构定义见struct v4l2_ioctl_ops)。

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

../../../_images/v4l2_ioctl.jpg

图 6.44 Linux V4L2的ioctl处理关系图

6.4.5.2.5. V4L2 的 Buf 队列管理

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

  1. 驱动申请Buffer

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

  2. 用户态申请Buffer

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

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

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

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

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

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

  3. 物理连续的Buffer

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

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

备注

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

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

../../../_images/dvp_buf_list.jpg

图 6.45 DVP 驱动中的 Buf 队列管理

  • Qbuf队列(在代码中见struct vb2_queue->queued_list):是一些空闲Buf,等待Sensor的数据到来后,DVP驱动会从这个队列中找可用Buf来保存下一帧数据。

  • DQbuf队列(在代码中见struct vb2_queue->done_list):是一些填了视频数据的Buf,等待用户来处理这些数据,一般用户处理完后需要将Buf还给驱动,也就是还给Qbuf。

  • 从Qbuf和DQbuf来的buf格式是struct vb2_buffer,其中没有字段可以保存物理地址(DVP控制器需要),同时还需要为每个buf记录一个是否正在被DVP使用的状态,所以DVP驱动中定义了基于vb2_buffer结构的封装vb2_v4l2_buffer,并且维护一个和Qbuf几乎同步的队列。

  • 从图中的流转过程看,运行期间,在某一时刻,DVP需要使用一个Buf,APP需要使用一个Buf,QBuf需要有一个Buf在等待(否则DVP的done中断来了后发现没有等待的Qbuf会发生丢帧),一共至少要有3个Buf。

  • 以YUV422格式计算,有两个plane,在V4L2框架中这一组plane算一个Buf,3个Buf就需要申请6个plane,总大小 = * * 2 * 3

  • DVP驱动中需要定义一个struct vb2_queue实例,Video device中会有一个指针*queue指向该实例。

6.4.5.2.6. DVP 驱动的子模块结构

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

../../../_images/dvp_drv_structure.jpg

图 6.46 DVP 驱动的子模块结构

  • Video Dvice管理,主要实现和Video device相关的注册、ioctl处理;

  • Notifier管理,主要处理和Sensor设备的初始化次序的依赖关系;

  • Buf管理,主要实现Buf的入队、出队,以及在中断响应时切换DVP控制器的输出地址等;

  • Subdev管理,主要实现输入格式相关的接口;

  • 寄存器访问,封装了对DVP控制器寄存器的读写访问。

6.4.5.3. 关键流程设计

6.4.5.3.1. 初始化流程

总体上看,DVP驱动的初始化过程分为两大段:

  1. 阶段一:由probe()接口完成,完成资源申请、注册subdev、注册buf、注册notifier等;

  2. 阶段二:由notifier的complete()接口完成,是需要等Sensor执行完初始化(其probe()接口)后才能执行,完成的操作有:注册video device、注册media device、配置link等。

6.4.5.3.1.1. probe 过程

注册DVP控制器的注册过程:

../../../_images/dvp_probe_flow.png

图 6.47 DVP 驱动的注册过程

  • 初始化media device,

  • 注册subdev,提供subdev_ops(其中定义了pad_ops);

  • 注册pad,包括为subdev注册两个pad:source + sink;为video device注册一个sink pad。

  • 注册buf,初始化vb2_queue,需要提供vb2_ops(驱动相关)和vb2_mem_ops(内存分配的回调)

  • 注册v4l2 device,主要是将DVP的dev关联到v4l2_device->dev

  • 注册notifier,为了解决sensor和DVP控制器之间的初始化顺序依赖问题,需要dts中定义好endpoint,并提供notifier_ops。

初始化notifier的时候,会去调用v4l2_fwnode_endpoint_parse ()解析DTS中关于endpoint中的配置,包括bus-type(BT656等)、极性等,将这些信息保存在vep->bus中(在aic_dvp->bus需要有备份)。

6.4.5.3.1.2. notifier 初始化过程

在Sensor的probe()过程中也会调用Notifier注册,因为DTS中两个设备用remote-endpoint已经有关联,DVP驱动注册过的notifier_ops->bound()接口首先会被触发,对方(Sensor)会传过来一个pad编号,DVP将其记录下来方便后续使用(调用Sensor的subdev接口完成stream启动、停止操作)。

../../../_images/v4l2_notify_call.png

图 6.48 V4L2 中notify的调用过程

随后,DVP的notifier_ops->complete()接口也会被触发调用,DVP驱动中完成后续的初始化,包括:

../../../_images/v4l2_notify_complete.png

图 6.49 V4L2 中notify的complete处理流程

  • 关联v4l2_device 和subdev

  • 注册video device,

  • 注册media device

  • 创建pad之间的link,会用到media_link结构

备注

media device出现了两次,是为了在所有media graph完全初始化之前就可以提供media device给用户态空间。所以一开始先用一部分entity初始化media device。

其中:

  • Master设备执行probe函数的时候,先使用component_match_add()接口声明一个match队列。

  • 然后,使用component_master_add_with_match函数将自己作为master注册到component框架。

  • 各component slave设备执行probe函数的时候,仅使用component_add()完成slave注册。

  • 以上各模块的probe()函数调用先后顺序并不影响。

  • 各个component都要实现自己的bind()和unbind()接口(struct component_ops),component框架在判断所有match队列中的模块都完成了probe,就会按 先slave、后master 的去调用他们的bind()接口。而各模块真正的初始化动作都是在各自的bind()中去实现。

  • 在执行各bind()接口时,各slave间的先后顺序和match队列一致。Component保证master最后执行。

  • aicfb->bind()中,主要完成framebuffer申请、fb设备注册、使能UI图层、使能panel等动作。

6.4.5.3.2. Buf 管理

DVP的Buf管理需要用到V4L2框架提供的Video queue机制外,还需要用到dma-buf和CMA(详见DE设计文档中的描述)。

对于每一帧图像数据来说,DVP的输出有两个plane:Y和UV。针对DVP的两种输出格式:YUV422_COMBINED_NV16和YUV420_COMBINED_NV12,两个plane的空间大小如下表:

YUV422_COMBINED_NV16

YUV420_COMBINED_NV12

Plane Y

Width * height

Width * height

Plane UV

Width * height

Width * height / 2

根据前面对“Buf队列管理”的分析可知:我们要分配的内存空间 至少要有3个Buf,每个Buf有两个Plane

对应到Buf的ioctl接口,我们要用到*_MPLANE结尾的接口。

注册video queue时提需要提供vb2_ops,其中需要DVP驱动实现的有五个接口:

  • queue_setup

    在APP发起申请buf时调用,这里面主要设置plane个数、各plane的大小;

  • buf_prepare和buf_queue

    在APP每次调用QBuf时会调用,分别完成获取Buf物理地址、同步Qbuf list的处理;

  • Stream start和stream stop

    启动和停止媒体数据(处理流程详见下节描述)。

6.4.5.3.3. Stream 启动流程

Stream的启动是由APP发起的,APP通过ioctl接口传入命令VIDIOC_STREAMON(相应的,停止的命令是VIDIOC_STREAMOFF)。

../../../_images/dvp_stream_on.png

图 6.50 DVP 驱动中Stream启动过程

Stream的停止流程相对简单很多,会调用到Sensor的停止传输接口:

../../../_images/dvp_stream_off.png

图 6.51 DVP 驱动中Stream停止过程

6.4.5.3.4. 中断处理流程

DVP的中断处理函数中主要处理Buf的队列切换操作。 DVP硬件提供的中断可以反映出多个状态(包括出错状态),其中有两个比较重要:

  1. Update done

    表示硬件已经读走了当前的Register值(影子寄存器),软件可以为下一帧去修改了;

  2. Frame done

    表示当前帧的数据传输完成。

可见,Update done会先于Frame done发生,驱动中用Update done判断当前Register是否可以修改,用Frame done判断当前buf是否完成(done状态),该buf就可以从QBuf list切换到DQbuf list了。

按照DVP硬件设计的逻辑,Update done和Frame done会间隔着产生(不会连续两个Update done): Update done -> Frame done -> Update done -> Frame done -> Update done -> Frame done ...

../../../_images/dvp_irq_flow.png

图 6.52 DVP 驱动中IRQ处理流程

“处理Frame done事件”的子流程如下:

../../../_images/dvp_frame_done_flow.png

图 6.53 DVP 驱动中Frame done处理流程

“处理Update done事件”的子流程如下:

../../../_images/dvp_update_done_flow.png

图 6.54 DVP 驱动中Update done处理流程

  • “异常!DVP同时使用了两个Buf”

    理论上不应该发生,可认为是DVP硬件异常,但因为DVP还在向Buf写数据,所以先不执行stop,软件上报错。

  • “DVP在使用”

    表示“DVP控制器硬件正在使用”。

6.4.5.4. 数据结构设计

DVP自定义的数据结构都在aic_dvp.h中。

6.4.5.4.1. aic_dvp

定义了DVP控制器的设备管理信息:

struct aic_dvp {
    /* Device resources */
    struct device           *dev;

    void __iomem            *regs;
    struct clk          *clk;
    struct reset_control        *rst;
    u32             clk_rate;
    int             irq;
    int             ch;

    struct vb2_v4l2_buffer      *vbuf[DVP_MAX_BUF];

    struct aic_dvp_config       cfg; /* The configuration of DVP HW */
    struct v4l2_fwnode_bus_parallel bus; /* The format of input data */
    struct v4l2_pix_format_mplane   fmt; /* The format of output data */

    /* Main Device */
    struct v4l2_device      v4l2;
    struct media_device     mdev;
    struct video_device     vdev;
    struct media_pad        vdev_pad;

    /* Local subdev */
    struct v4l2_subdev      subdev;
    struct media_pad        subdev_pads[DVP_SUBDEV_PAD_NUM];
    struct v4l2_mbus_framefmt   subdev_fmt;

    /* V4L2 Async variables */
    struct v4l2_async_subdev    asd;
    struct v4l2_async_notifier  notifier;
    struct v4l2_subdev      *src_subdev;
    int             src_pad;

    /* V4L2 variables */
    struct mutex            lock;

    /* Videobuf2 */
    struct vb2_queue        queue;
    struct list_head        buf_list;
    spinlock_t          qlock;
    unsigned int            sequence;
};

6.4.5.4.2. aic_dvp_config

定义了V4L2媒体数据的配置信息:

/**
 * Save the configuration information for DVP controller.
 * @code:   media bus format code (MEDIA_BUS_FMT_*, in media-bus-format.h)
 * @field:  used interlacing type (enum v4l2_field)
 * @width:  frame width
 * @height: frame height
 */
struct aic_dvp_config {
    /* Input format */
    enum dvp_input      input;
    enum dvp_input_yuv_seq  input_seq;
    enum v4l2_field     field;

    /* Output format */
    enum dvp_output output;
    u32     width;
    u32     height;
    u32     stride[DVP_MAX_PLANE];
    u32     sizeimage[DVP_MAX_PLANE];
};

6.4.5.4.3. aic_dvp_buf

定义了DVP驱动的Buf管理信息:

struct aic_dvp_buf {
    struct vb2_v4l2_buffer  vb;
    struct list_head    list;
    dma_addr_t      paddr[DVP_MAX_PLANE];
    bool            dvp_using;
};

6.4.5.4.4. 输入输出的数据格式

6.4.5.4.4.1. enum dvp_input

定义了DVP输入数据的格式:

enum dvp_input {
    DVP_IN_RAW  = 0,
    DVP_IN_YUV422   = 1,
    DVP_IN_BT656    = 2,
};

6.4.5.4.4.2. enum dvp_input_yuv_seq

定义了DVP输入数据的YUV格式:

enum dvp_input_yuv_seq {
    DVP_YUV_DATA_SEQ_YUYV   = 0,
    DVP_YUV_DATA_SEQ_YVYU   = 1,
    DVP_YUV_DATA_SEQ_UYVY   = 2,
    DVP_YUV_DATA_SEQ_VYUY   = 3,
};

6.4.5.4.4.3. enum dvp_output

定义了DVP输出数据的格式:

enum dvp_output {
    DVP_OUT_RAW_PASSTHROUGH     = 0,
    DVP_OUT_YUV422_COMBINED_NV16    = 1,
    DVP_OUT_YUV420_COMBINED_NV12    = 2,
};

6.4.5.5. 接口设计

6.4.5.5.1. V4l2 device 的外部接口

6.4.5.5.1.1. ioctl 接口

用户态通过/dev/videoX节点的ioctl()接口与内核态V4L2框架、DVP驱动进行交互。主要功能有:

  • 获取、设置格式

  • 申请、释放Buf

  • QBuf、DQBuf

  • 导出dma-buf文件描述符

  • 启动、停止Stream

定义在include/uapi/linux/videodev2.h:

#define VIDIOC_QUERYCAP      _IOR('V',  0, struct v4l2_capability)
#define VIDIOC_ENUM_FMT         _IOWR('V',  2, struct v4l2_fmtdesc)
#define VIDIOC_G_FMT        _IOWR('V',  4, struct v4l2_format)
#define VIDIOC_S_FMT        _IOWR('V',  5, struct v4l2_format)
#define VIDIOC_REQBUFS      _IOWR('V',  8, struct v4l2_requestbuffers)
#define VIDIOC_QUERYBUF     _IOWR('V',  9, struct v4l2_buffer)
#define VIDIOC_G_FBUF        _IOR('V', 10, struct v4l2_framebuffer)
#define VIDIOC_S_FBUF        _IOW('V', 11, struct v4l2_framebuffer)
#define VIDIOC_OVERLAY       _IOW('V', 14, int)
#define VIDIOC_QBUF     _IOWR('V', 15, struct v4l2_buffer)
#define VIDIOC_EXPBUF       _IOWR('V', 16, struct v4l2_exportbuffer)
#define VIDIOC_DQBUF        _IOWR('V', 17, struct v4l2_buffer)
#define VIDIOC_STREAMON      _IOW('V', 18, int)
#define VIDIOC_STREAMOFF     _IOW('V', 19, int)
#define VIDIOC_G_PARM       _IOWR('V', 21, struct v4l2_streamparm)
#define VIDIOC_S_PARM       _IOWR('V', 22, struct v4l2_streamparm)
#define VIDIOC_G_STD         _IOR('V', 23, v4l2_std_id)
#define VIDIOC_S_STD         _IOW('V', 24, v4l2_std_id)
#define VIDIOC_ENUMSTD      _IOWR('V', 25, struct v4l2_standard)
#define VIDIOC_ENUMINPUT    _IOWR('V', 26, struct v4l2_input)
#define VIDIOC_G_CTRL       _IOWR('V', 27, struct v4l2_control)
#define VIDIOC_S_CTRL       _IOWR('V', 28, struct v4l2_control)
#define VIDIOC_QUERYCTRL    _IOWR('V', 36, struct v4l2_queryctrl)
#define VIDIOC_QUERYMENU    _IOWR('V', 37, struct v4l2_querymenu)
#define VIDIOC_G_INPUT       _IOR('V', 38, int)
#define VIDIOC_S_INPUT      _IOWR('V', 39, int)
#define VIDIOC_G_OUTPUT      _IOR('V', 46, int)
#define VIDIOC_S_OUTPUT     _IOWR('V', 47, int)
#define VIDIOC_ENUMOUTPUT   _IOWR('V', 48, struct v4l2_output)
#define VIDIOC_G_FREQUENCY  _IOWR('V', 56, struct v4l2_frequency)
#define VIDIOC_S_FREQUENCY   _IOW('V', 57, struct v4l2_frequency)
#define VIDIOC_CROPCAP      _IOWR('V', 58, struct v4l2_cropcap)
#define VIDIOC_G_CROP       _IOWR('V', 59, struct v4l2_crop)
#define VIDIOC_S_CROP        _IOW('V', 60, struct v4l2_crop)
#define VIDIOC_G_JPEGCOMP    _IOR('V', 61, struct v4l2_jpegcompression)
#define VIDIOC_S_JPEGCOMP    _IOW('V', 62, struct v4l2_jpegcompression)
#define VIDIOC_QUERYSTD      _IOR('V', 63, v4l2_std_id)
#define VIDIOC_TRY_FMT      _IOWR('V', 64, struct v4l2_format)
#define VIDIOC_G_PRIORITY    _IOR('V', 67, __u32) /* enum v4l2_priority */
#define VIDIOC_S_PRIORITY    _IOW('V', 68, __u32) /* enum v4l2_priority */
#define VIDIOC_G_SLICED_VBI_CAP _IOWR('V', 69, struct v4l2_sliced_vbi_cap)
#define VIDIOC_LOG_STATUS         _IO('V', 70)
#define VIDIOC_G_EXT_CTRLS  _IOWR('V', 71, struct v4l2_ext_controls)
#define VIDIOC_S_EXT_CTRLS  _IOWR('V', 72, struct v4l2_ext_controls)
#define VIDIOC_TRY_EXT_CTRLS    _IOWR('V', 73, struct v4l2_ext_controls)
#define VIDIOC_ENUM_FRAMESIZES  _IOWR('V', 74, struct v4l2_frmsizeenum)
#define VIDIOC_ENUM_FRAMEINTERVALS _IOWR('V', 75, struct v4l2_frmivalenum)
#define VIDIOC_G_ENC_INDEX       _IOR('V', 76, struct v4l2_enc_idx)
#define VIDIOC_ENCODER_CMD      _IOWR('V', 77, struct v4l2_encoder_cmd)
#define VIDIOC_TRY_ENCODER_CMD  _IOWR('V', 78, struct v4l2_encoder_cmd)

6.4.5.5.2. V4l2 subdev 的外部接口

用户态通过/dev/v4l-subdevX节点的ioctl()接口与内核态V4L2框架、DVP驱动、Sensor驱动进行交互。主要功能有:

  • 获取、设置格式

  • 获取、设置帧间隔

  • 枚举支持的mbus类型

  • 枚举支持的分辨率

  • 获取、设置区域裁剪

定义在 include/uapi/linux/v4l2-subdev.h:

#define VIDIOC_SUBDEV_G_FMT         _IOWR('V',  4, struct v4l2_subdev_format)
#define VIDIOC_SUBDEV_S_FMT         _IOWR('V',  5, struct v4l2_subdev_format)
#define VIDIOC_SUBDEV_G_FRAME_INTERVAL      _IOWR('V', 21, struct v4l2_subdev_frame_interval)
#define VIDIOC_SUBDEV_S_FRAME_INTERVAL      _IOWR('V', 22, struct v4l2_subdev_frame_interval)
#define VIDIOC_SUBDEV_ENUM_MBUS_CODE        _IOWR('V',  2, struct v4l2_subdev_mbus_code_enum)
#define VIDIOC_SUBDEV_ENUM_FRAME_SIZE       _IOWR('V', 74, struct v4l2_subdev_frame_size_enum)
#define VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL   _IOWR('V', 75, struct v4l2_subdev_frame_interval_enum)
#define VIDIOC_SUBDEV_G_CROP            _IOWR('V', 59, struct v4l2_subdev_crop)
#define VIDIOC_SUBDEV_S_CROP            _IOWR('V', 60, struct v4l2_subdev_crop)
#define VIDIOC_SUBDEV_G_SELECTION       _IOWR('V', 61, struct v4l2_subdev_selection)
#define VIDIOC_SUBDEV_S_SELECTION       _IOWR('V', 62, struct v4l2_subdev_selection)
/* The following ioctls are identical to the ioctls in videodev2.h */
#define VIDIOC_SUBDEV_G_STD         _IOR('V', 23, v4l2_std_id)
#define VIDIOC_SUBDEV_S_STD         _IOW('V', 24, v4l2_std_id)
#define VIDIOC_SUBDEV_ENUMSTD           _IOWR('V', 25, struct v4l2_standard)
#define VIDIOC_SUBDEV_G_EDID            _IOWR('V', 40, struct v4l2_edid)
#define VIDIOC_SUBDEV_S_EDID            _IOWR('V', 41, struct v4l2_edid)
#define VIDIOC_SUBDEV_QUERYSTD          _IOR('V', 63, v4l2_std_id)
#define VIDIOC_SUBDEV_S_DV_TIMINGS      _IOWR('V', 87, struct v4l2_dv_timings)
#define VIDIOC_SUBDEV_G_DV_TIMINGS      _IOWR('V', 88, struct v4l2_dv_timings)
#define VIDIOC_SUBDEV_ENUM_DV_TIMINGS       _IOWR('V', 98, struct v4l2_enum_dv_timings)
#define VIDIOC_SUBDEV_QUERY_DV_TIMINGS      _IOR('V', 99, struct v4l2_dv_timings)
#define VIDIOC_SUBDEV_DV_TIMINGS_CAP        _IOWR('V', 100, struct v4l2_dv_timings_cap)

6.4.5.6. APP Demo

6.4.5.6.1. APP层的处理流程

APP 中实现从Sensor -> DVP -> DE的数据通路,整体的处理流程如下图(图中按照访问对象分为三列,实际上整体是串行执行):

../../../_images/demo_flow.png

图 6.55 APP 中的处理流程

6.4.5.6.2. APP Demo参考实现

Demo代码见test_dvp/dvp_test.c,如下:

#include <linux/dma-buf.h>
#include <linux/dma-heap.h>
#include <linux/videodev2.h>
#include <linux/v4l2-subdev.h>
#include <video/zx_fb.h>
#include <zx/sample_base.h>

/* Global macro and variables */

#define VID_BUF_NUM     3
#define DVP_PLANE_NUM       2
#define CMA_BUF_MAX     (8 * 1024 * 1024)
#define DMA_HEAP_DEV        "/dev/dma_heap/reserved"
#define FB_DEV          "/dev/fb0"
#define VIDEO_DEV       "/dev/video0"
#define SENSOR_DEV      "/dev/v4l-subdev0"
#define DVP_SUBDEV_DEV      "/dev/v4l-subdev1"

static const char sopts[] = "f:c:u";
static const struct option lopts[] = {
    {"format",    required_argument, NULL, 'f'},
    {"capture",   required_argument, NULL, 'c'},
    {"usage",       no_argument, NULL, 'u'},
    {0, 0, 0, 0}
};

struct video_plane {
    int fd;
    int buf;
    int len;
};

struct video_buf_info {
    char *vaddr;
    u32 len;
    u32 offset;
    struct video_plane planes[DVP_PLANE_NUM];
};

struct aic_video_data {
    int w;
    int h;
    int frame_size;
    int frame_cnt;
    int fmt;  // output format
    struct v4l2_subdev_format src_fmt;
    struct video_buf_info binfo[VID_BUF_NUM];
};

static int g_fb_fd = -1;
static int g_video_fd = -1;
static int g_sensor_fd = -1;
static int g_dvp_subdev_fd = -1;
static struct aic_video_data g_vdata = {0};

/* Functions */

void usage(char *program)
{
    printf("Usage: %s [options]: \n", program);
    printf("\t -f, --format\t\tformat of input video, NV16/NV12 etc\n");
    printf("\t -c, --count\t\tthe number of capture frame \n");
    printf("\t -u, --usage \n");
    printf("\n");
    printf("Example: %s -f yuv422 -c 1\n", program);
}

/* Open a device file to be needed. */
int device_open(char *_fname, int _flag)
{
    s32 fd = -1;

    fd = open(_fname, _flag);
    if (fd < 0) {
        ERR("Failed to open %s errno: %d[%s]\n",
            _fname, errno, strerror(errno));
        exit(0);
    }
    return fd;
}

int set_ui_layer_alpha(int val)
{
    int ret = 0;
    struct aicfb_alpha_config alpha = {0};

    alpha.layer_id = 1;
    alpha.enable = 1;
    alpha.mode = 1;
    alpha.value = val;
    ret = ioctl(g_fb_fd, AICFB_UPDATE_ALPHA_CONFIG, &alpha);
    if (ret < 0)
        ERR("ioctl() failed! errno: %d[%s]\n", errno, strerror(errno));

    return ret;
}

void vidbuf_dmabuf_begin(struct aic_video_data *vdata)
{
    int i, j;
    struct aicfb_dmabuf_fd fds = {0};

    for (i = 0; i < VID_BUF_NUM; i++) {
        struct video_plane *plane = (struct video_plane *)&vdata->binfo[i];
        for (j = 0; j < DVP_PLANE_NUM; j++, plane++) {
            fds.fd = plane->fd;
            if (ioctl(g_fb_fd, AICFB_GET_DMABUF, &fds) < 0)
                ERR("ioctl() failed! err %d[%s]\n",
                    errno, strerror(errno));
        }
    }
}

void vidbuf_dmabuf_end(struct aic_video_data *vdata)
{
    int i, j;
    struct aicfb_dmabuf_fd fds = {0};

    for (i = 0; i < VID_BUF_NUM; i++) {
        struct video_plane *plane = (struct video_plane *)&vdata->binfo[i];
        for (j = 0; j < DVP_PLANE_NUM; j++, plane++) {
            fds.fd = plane->fd;
            if (ioctl(g_fb_fd, AICFB_PUT_DMABUF, &fds) < 0)
                ERR("ioctl() failed! err %d[%s]\n",
                    errno, strerror(errno));
        }
    }
}

int sensor_get_fmt(void)
{
    struct v4l2_subdev_format f = {0};

    g_sensor_fd = device_open(SENSOR_DEV, O_RDWR);
    if (g_sensor_fd < 0)
        return -1;

    f.pad = 0;
    f.which = V4L2_SUBDEV_FORMAT_ACTIVE;
    if (ioctl(g_sensor_fd, VIDIOC_SUBDEV_G_FMT, &f) < 0) {
        ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
        return -1;
    }
#if 0
    f.format.code = MEDIA_BUS_FMT_YUYV8_2X8;
    if (ioctl(g_sensor_fd, VIDIOC_SUBDEV_S_FMT, &f) < 0) {
        ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
        return -1;
    }
#endif
    g_vdata.src_fmt = f;
    g_vdata.w = g_vdata.src_fmt.format.width;
    g_vdata.h = g_vdata.src_fmt.format.height;
    return 0;
}

int dvp_subdev_set_fmt(void)
{
    struct v4l2_subdev_format f = g_vdata.src_fmt;

    g_dvp_subdev_fd = device_open(DVP_SUBDEV_DEV, O_RDWR);
    if (g_dvp_subdev_fd < 0)
        return -1;

    f.pad = 0;
    f.which = V4L2_SUBDEV_FORMAT_ACTIVE;
    if (ioctl(g_dvp_subdev_fd, VIDIOC_SUBDEV_S_FMT, &f) < 0) {
        ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
        return -1;
    }

    return 0;
}

int dvp_cfg(int width, int height, int format)
{
    struct v4l2_format f = {0};

    f.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    f.fmt.pix_mp.width = g_vdata.src_fmt.format.width;
    f.fmt.pix_mp.height = g_vdata.src_fmt.format.height;
    f.fmt.pix_mp.pixelformat = g_vdata.fmt;
    f.fmt.pix_mp.num_planes = DVP_PLANE_NUM;
    if (ioctl(g_video_fd, VIDIOC_S_FMT, &f) < 0) {
        ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
        return -1;
    }

    return 0;
}

int dvp_expbuf(int index)
{
    int i;
    struct video_buf_info *binfo = &g_vdata.binfo[index];
    struct v4l2_exportbuffer expbuf = {0};

    for (i = 0; i < DVP_PLANE_NUM; i++) {
        memset(&expbuf, 0, sizeof(struct v4l2_exportbuffer));
        expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        expbuf.index = index;
        expbuf.plane = i;
        if (ioctl(g_video_fd, VIDIOC_EXPBUF, &expbuf) < 0) {
            ERR("ioctl() failed! err %d[%s]\n",
                errno, strerror(errno));
            return -1;
        }
        binfo->planes[i].fd = expbuf.fd;
    }

    return 0;
}

int dvp_request_buf(int num)
{
    int i;
    struct v4l2_buffer buf = {0};
    struct v4l2_requestbuffers req = {0};
    struct v4l2_plane planes[DVP_PLANE_NUM];

    req.count  = num;
    req.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    req.memory = V4L2_MEMORY_MMAP; // Only MMAP will do alloc memory
    if (ioctl(g_video_fd, VIDIOC_REQBUFS, &req) < 0) {
        ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
        return -1;
    }

    for (i = 0; i < num; i++) {
            if (dvp_expbuf(i) < 0)
            return -1;

            memset(&buf, 0, sizeof(struct v4l2_buffer));
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
            buf.index = i;
            buf.length = DVP_PLANE_NUM;
            buf.memory = V4L2_MEMORY_DMABUF;
            buf.m.planes = planes;
            if (ioctl(g_video_fd, VIDIOC_QUERYBUF, &buf) < 0) {
            ERR("ioctl() failed! err %d[%s]\n",
                errno, strerror(errno));
            return -1;
            }
    }

    return 0;
}

void dvp_release_buf(int num)
{
    int i;
    struct video_buf_info *binfo = NULL;

    for (i = 0; i < num; i++) {
        binfo = &g_vdata.binfo[i];
        if (binfo->vaddr) {
            munmap(binfo->vaddr, binfo->len);
            binfo->vaddr = NULL;
        }
    }
}

int dvp_queue_buf(int index)
{
    struct v4l2_buffer buf = {0};
    struct v4l2_plane planes[DVP_PLANE_NUM] = {0};

    buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index  = index;
    buf.length = DVP_PLANE_NUM;
    buf.m.planes = planes;
    if (ioctl(g_video_fd, VIDIOC_QBUF, &buf) < 0) {
        ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
        return -1;
    }

    return 0;
}

int dvp_dequeue_buf(int *index)
{
    struct v4l2_buffer buf = {0};
    struct v4l2_plane planes[DVP_PLANE_NUM] = {0};

    buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.length = DVP_PLANE_NUM;
    buf.m.planes = planes;
    if (ioctl(g_video_fd, VIDIOC_DQBUF, &buf) < 0) {
        ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
        return -1;
    }

    *index = buf.index;
    return 0;
}

int dvp_start(void)
{
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;

    if (ioctl(g_video_fd, VIDIOC_STREAMON, &type) < 0) {
        ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
        return -1;
    }

    return 0;
}

int dvp_stop(void)
{
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;

    if (ioctl(g_video_fd, VIDIOC_STREAMOFF, &type) < 0) {
        ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
        return -1;
    }

    return 0;
}

int video_layer_set(struct aic_video_data *vdata, int index)
{
    struct aicfb_layer_data layer = {0};
    struct video_buf_info *binfo = &vdata->binfo[index];

    layer.layer_id = 0;
    layer.enable = 1;
#if 1
    layer.scale_size.width = vdata->w;
    layer.scale_size.height = vdata->h;
#else
    layer.scale_size.width = 780;
    layer.scale_size.height = 600;
#endif
    layer.pos.x = 10;
    layer.pos.y = 10;
    layer.buf.size.width = vdata->w;
    layer.buf.size.height = vdata->h;
    layer.buf.format = AIC_FMT_NV16;
    layer.buf.dmabuf_fd[0] = binfo->planes[0].fd;
    layer.buf.dmabuf_fd[1] = binfo->planes[1].fd;
    layer.buf.stride[0] = vdata->w;
    layer.buf.stride[1] = vdata->w;

    if (ioctl(g_fb_fd, AICFB_UPDATE_LAYER_CONFIG, &layer) < 0) {
        ERR("ioctl() failed! err %d[%s]\n", errno, strerror(errno));
        return -1;
    }

    return 0;
}

int main(int argc, char **argv)
{
    int c, frame_cnt = 1;
    int i, index = 0;

    DBG("Compile time: %s\n", __TIME__);
    g_vdata.fmt = V4L2_PIX_FMT_NV16;
    while ((c = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
        switch (c) {
        case 'f':
            if (strncasecmp("nv12", optarg, strlen(optarg)) == 0)
                g_vdata.fmt = V4L2_PIX_FMT_NV12;
            continue;
        case 'c':
            frame_cnt = str2int(optarg);
            continue;
        case 'u':
            usage(argv[0]);
            return 0;
        default:
            break;
        }
    }

    if (sensor_get_fmt() < 0)
        return -1;
    if (dvp_subdev_set_fmt() < 0)
        return -1;

    if (g_vdata.fmt == V4L2_PIX_FMT_NV16)
        g_vdata.frame_size = g_vdata.w * g_vdata.h * 2;
    if (g_vdata.fmt == V4L2_PIX_FMT_NV12)
        g_vdata.frame_size = (g_vdata.w * g_vdata.h * 3) >> 1;

    g_fb_fd = device_open(FB_DEV, O_RDWR);
    if (g_fb_fd < 0)
        return -1;
    if (set_ui_layer_alpha(128) < 0)
        goto end;

    g_video_fd = device_open(VIDEO_DEV, O_RDWR);
    if (g_video_fd < 0)
        goto end;
    if (dvp_cfg(g_vdata.w, g_vdata.h, g_vdata.fmt) < 0)
        goto end;
    if (dvp_request_buf(VID_BUF_NUM) < 0)
        goto end;

    vidbuf_dmabuf_begin(&g_vdata);
    for (i = 0; i < VID_BUF_NUM; i++)
        if (dvp_queue_buf(i) < 0)
            goto end;

    if (dvp_start() < 0)
        goto end;
    for (i = 0; i < frame_cnt; i++ ) {
        if (dvp_dequeue_buf(&index) < 0)
            break;
        DBG("Set the buf %d to video layer\n", index);
        if (video_layer_set(&g_vdata, index) < 0)
            break;
        dvp_queue_buf(index);
    }
    dvp_stop();
    vidbuf_dmabuf_end(&g_vdata);
    dvp_release_buf(VID_BUF_NUM);

end:
    if (g_fb_fd > 0)
        close(g_fb_fd);
    if (g_video_fd > 0)
        close(g_video_fd);
    if (g_sensor_fd > 0)
        close(g_sensor_fd);
    if (g_dvp_subdev_fd > 0)
        close(g_dvp_subdev_fd);

    return 0;
}