| Linux V4L2子系统-videobuf2框架分析 
 一、概述:
 Video设备产生的数据较多,传统的缓冲机制已不能满足需求。为此,Linux内核抽象出了videobuf2机制,用于管理存放视频图像的帧缓冲。videobuf2抽象层像一座桥梁,将用户空间和V4L2 driver连接起来。videobuf2抽象层向用户空间提供了标准POSIX I/O系统调用,包括read、poll及mmap等,同时还提供了大量与流式I/O相关的V4L2 ioctl调用,包括缓冲区分配、缓冲区入队、缓冲区出队及流控制。虽然使用videobuf2会给驱动程序强加一些设计决策,但是使用它的收益是videobuf2可以减少驱动程序代码和保持V4L2子系统在用户空间API的一致性,显然使用videobuf2更为合理。二、分类: 不是所有的Video设备都使用同一种类型的videobuf2。实时上,Linux内核中有3中不同类型的videobuf2。 (1)缓冲区物理地址和虚拟地址不连续。大多数用户空间缓冲区就属于这种情况,在可能的情况下,内核空间以这种方式分配缓冲区也是有意义的。然而在有些情况下,则不适用。对于不连续的缓冲区,需要硬件上支持scatter/gather DMA operations。使用该缓冲区,需要包含头文件<include/media/videobuf-dma-sg.h>(适用于V4L2)或<include/media/videobuf2-dma-sg.h>(适用于V4L2)。(2)缓冲区物理地址不连续但虚拟地址连续。缓冲区分配使用vmalloc。这种缓冲区无法使用DMA进行传输。但在不使用DMA的情形下,虚拟地址连续的缓冲器很便利。使用该缓冲区,需要包含头文件<include/media/videobuf-vmalloc.h>(适用于V4L)或<include/media/videobuf2-vmalloc.h>(适用于V4L2)。(3)缓冲区物理地址和虚拟地址都连续。在页式内存管理系统中,分配物理地址和虚拟地址都连续的缓冲区是不可靠的,因为这种分配方式容易造成更多的内存碎片,某些情况下内存碎片过多会造成内存分配失败,从而导致系统无法正常功能工作。但是对于DMA传输来说却很方便。使用该缓冲区,需要包含头文件<include/media/videobuf-dma-contig.h>(适用于V4L)或<include/media/videobuf2-dma-contig.h>(适用于V4L2)。这中类型的videobuf2比较常用。 
 除此之外,还存在一种overlay缓冲区,其位于系统的显存中。目前overlay缓冲区已被弃用,但在一些片上系统的驱动中偶尔还能看到。 至于使用哪一种videobuf2,需要驱动工程师根据实际的使用环境进行评估。三、数据结构: videobuf2的核心数据结构是一个缓冲区队列,用来管理所有的缓冲区。缓冲区队列用struct vb2_queue描述,缓冲区用struct vb2_buffer描述。struct vb2_ops和struct vb2_mem_ops结构体中的函数指针需要驱动实现。数据结构的关系图如下图所示。视频帧数据保存和管理的数据结构由struct vb2_buffer描述。buf_struct_size定义了缓冲区的大小,需要驱动设置;num_buffers定义了缓冲区的数量,不能大于VIDEO_MAX_FRAME,不能小于min_buffers_needed。缓冲区的大小由buf_struct_size定义,驱动可以定义自己的缓冲区,同时设置buf_struct_size,若为0表示驱动不定义自己缓冲结构,则使用sizeof(struct vb2_buffer)初始化buf_struct_size。缓冲区类型由enum v4l2_buf_type枚举定义,图像采集(摄像头)使用V4L2_BUF_TYPE_VIDEO_CAPTURE类型。   struct vb2_queue和struct vb2_buffer数据结构的关系可用下图描述,所以动态分配的struct vb2_buffer结构体保存到bufs[VIDEO_MAX_FRAME]数组中,由struct vb2_queue统一管理。struct vb2_buffer结构体的最大数量由VIDEO_MAX_FRAME宏定义;queued_list保存所有从用户空间入队的缓冲区;done_list保存准备出队到用户空间的缓冲区。   视频图像缓冲区的I/O模型由enum vb2_io_modes描述,这里需要特别说明。 Linux系统分为用户空间和内核空间,应用程序处于用户空间,而内核运行在内核空间。V4L2子系统属于内核的组件,也运行在内核空间,其采集的数据也保存在内核空间的内存中。应用程序无法直接访问内核空间的内存,需要借助一些方法才能访问。缓冲区的I/O模式主要有以下三种方式: (1)VB2_READ和VB2_WRITE: 使用传统的I/O接口函数read和write进行读写。这种方式虽然最简单,但会在用户空间和内核空间之间来回复制数据,效率低,且在用户空间和内核空间都占用内存,开销大。 (2)VB2_MMAP: 内存映射方式。mmap把驱动程序中videobuf2管理的内存映射到用户空间,应用程序可直接访问videobuf2管理的内存。这种方式效率高,内存占用低。但是videobuf2管理的内存属于内核空间,不能被交换到SWAP分区中,若同时运行大量的进程,会占用较多的内存,而这些内存又不能被交换,会使系统的性能降低。 (3)VB2_USERPTR: 用户指针模式。内存由应用程序分配,并将内存地址传递到内核V4L2驱动程序中,然后由V4L2驱动程序将数据填充到用户空间的内存中。[include/uapi/linux/videodev2.h] // videobuf2类型枚举
 enum v4l2_buf_type {
 V4L2_BUF_TYPE_VIDEO_CAPTURE        = 1,  // 图像采集
 V4L2_BUF_TYPE_VIDEO_OUTPUT         = 2,  // 图像输出
 V4L2_BUF_TYPE_VIDEO_OVERLAY        = 3,
 V4L2_BUF_TYPE_VBI_CAPTURE          = 4,
 V4L2_BUF_TYPE_VBI_OUTPUT           = 5,
 V4L2_BUF_TYPE_SLICED_VBI_CAPTURE   = 6,
 V4L2_BUF_TYPE_SLICED_VBI_OUTPUT    = 7,
 #if 1
 V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8, /* Experimental */
 #endif
 V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9,
 V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE  = 10,
 V4L2_BUF_TYPE_SDR_CAPTURE          = 11,
 };
 // 缓冲区类型
 enum v4l2_memory {
 V4L2_MEMORY_MMAP             = 1,  // 内存映射
 V4L2_MEMORY_USERPTR          = 2,  // 用户空间指针
 V4L2_MEMORY_OVERLAY          = 3,  // 内存重叠
 V4L2_MEMORY_DMABUF           = 4,  // DMA内存
 };
 /* Timestamp type */
 #define V4L2_BUF_FLAG_TIMESTAMP_MASK  0x0000e000
 #define V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN  0x00000000
 // 递增类型时间戳,在内核的 monotonic 时钟时间轴上面生成的时间戳
 #define V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC 0x00002000
 #define V4L2_BUF_FLAG_TIMESTAMP_COPY  0x00004000
 /* Timestamp sources. */
 #define V4L2_BUF_FLAG_TSTAMP_SRC_MASK  0x00070000
 #define V4L2_BUF_FLAG_TSTAMP_SRC_EOF  0x00000000
 #define V4L2_BUF_FLAG_TSTAMP_SRC_SOE  0x00010000
 
 [include/media/videobuf2-core.h]
 enum vb2_io_modes {
 VB2_MMAP = (1 << 0),  // 驱动程序支持mmap的流式API
 VB2_USERPTR = (1 << 1),  // 驱动程序支持用户指针模式的流式API
 VB2_READ = (1 << 2),  // 驱动程序支持read()方式访问
 VB2_WRITE = (1 << 3),  // 驱动程序支持write()方式访问
 VB2_DMABUF = (1 << 4),  // 驱动程序支持DMABUF的流式API
 };
 struct vb2_queue {
 enum v4l2_buf_type  type;      // videobuf2类型,见enum v4l2_buf_type枚举
 unsigned int  io_modes;        // 支持的IO模式,见enum vb2_io_modes枚举
 unsigned  fileio_read_once:1;  // 读取第一个缓冲区后报告EOF
 unsigned  fileio_write_immediately:1;  // write写入的数据都添加到缓冲队列中
 unsigned  allow_zero_bytesused:1;
 // 保护struct vb2_queue的互斥锁,使缓冲队列的操作串行化,若驱动实有互斥锁,
 // 则可设置为NULL,videobuf2核心层API不使用此锁
 struct mutex  *lock;
 const struct vb2_ops  *ops;          // 驱动实现的回调函数
 const struct vb2_mem_ops  *mem_ops;  // 内存分配器需要的函数
 void    *drv_priv;       // 驱动私有数据
 // 驱动的缓冲结构体大小,若为0表示驱动不定义自己缓冲结构,则使用sizeof(struct vb2_buffer)
 unsigned int  buf_struct_size;
 // 时间戳标志,见V4L2_BUF_FLAG_TIMESTAMP_XX和V4L2_BUF_FLAG_TSTAMP_SRC_XX宏定义
 u32    timestamp_flags;
 // 分配缓冲区时的内存标志,通常为0,也有使用GFP_DMA或__GFP_DMA32强制将内存分配到明确的内存区域
 gfp_t    gfp_flags;
 u32    min_buffers_needed;  // 在处理数据流之前,需要最小的缓冲区数目
 // 私有锁,保护缓冲区的分配、释放、映射
 struct mutex   mmap_lock;
 // 目前使用的缓冲区类型,取值为enum v4l2_memory枚举
 enum v4l2_memory  memory;
 struct vb2_buffer  *bufs[VIDEO_MAX_FRAME];  // 保存分配缓冲区的地址
 unsigned int   num_buffers;  // 分配的缓冲区数量
 struct list_head  queued_list;  // 用户空间入队的缓冲区链表
 unsigned int   queued_count; // 入队的就绪缓冲区数量
 atomic_t  owned_by_drv_count;         // 属于驱动的缓冲区数量
 struct list_head  done_list;  // 此链表中的缓冲区已填充数据,可以出队被用户空间使用
 spinlock_t  done_lock;        // 保护done_list链表的自旋锁
 wait_queue_head_t  done_wq;   // 等待缓冲区出队的等待队列
 void  *alloc_ctx[VIDEO_MAX_PLANES];
 unsigned int  plane_sizes[VIDEO_MAX_PLANES];
 unsigned int  streaming:1;  // 当前流的状态
 unsigned int  start_streaming_called:1;  // start_streaming()被成功调用
 unsigned int  error:1;  // struct vb2_queue发生了致命错误
 unsigned int  waiting_for_buffers:1;  // 在poll函数中使用,以检查是否还在等待数据
 ......
 };
 
 
 驱动需要实现struct vb2_ops中的函数,当然也可以实现一部分,也可以直接使用内核提供的函数。 queue_setup由ioctl命令VIDIOC_REQBUFS和VIDIOC_CREATE_BUFS调用,经常使用的是VIDIOC_REQBUFS,在实际分配内存之前调用一次,若分配的buffer数量不足num_buffers,则会再次调用。该函数需要将分配的buffer数量保存到num_buffers,将buffer的planes的数量保存到num_planes中,每个plans的大小存放在sizes数组中。planes和视频的像素格式有关,如YUV420SP格式的planes为2。alloc_ctxs数组保存每一个plane的特定数据。 wait_prepare和wait_finish函数内核提供了默认的实现,可以直接使用,分别对应函数vb2_ops_wait_prepare和vb2_ops_wait_finish。这两个函数实现很简单,vb2_ops_wait_prepare释放互斥锁vb2_ops_wait_finish获取互斥锁。用户调用ioctl并使用VIDIOC_QBUF命令时,内核会判断是否是阻塞调用,如果是阻塞调用并且没有准备好数据,内核此时会调用wait_prepare释放锁并进行休眠等待,直到数据准备好被唤醒,然后再调用wait_finish重新持有锁。 buf_init在分配缓冲区之后调用或获取了新的USERPTR之后调用(in MMAP case),驱动需要完成一些缓冲区初始化的工作,若初始化失败,则返回不为0的数,此时queue_setup将失败,一般用不到。 buf_prepare缓冲区每次从用户空间入队都需要调用或被ioctl的VIDIOC_PREPARE_BUF命令调用,驱动需要执行一些初始化工作或获取、修改缓冲区,若驱动支持VIDIOC_CREATE_BUFS,则需要验证缓冲区的大小,若有错误发生,则缓冲区不会入队。 start_streaming调用后流进入开启状态,在调用之前驱动必须先调用buf_queue接收缓冲区。如果发生硬件错误,驱动可以通过该回调返回一个错误,此时所有的buffer都会被归还给videobuf2(调用vb2_buffer_done(*vb, VB2_BUF_STATE_QUEUED))。如果需要设置start_streaming时buffer的最小数量,那么应该在调用该函数之前设置最少的buffer数量值vb2_queue->min_buffers_needed,只有buffer数量大于vb2_queue->min_buffers_needed时start_streaming才能被成功调用。 stop_streaming调用后流进入关闭状态,驱动需要停止DMA传输或等待工作完成和归还全部buffers(还给videobuf2)。调用vb2_buffer_done来归还所有驱动持有的buffers,可使用VB2_BUF_STATE_DONE(操作完成)和VB2_BUF_STATE_ERR(操作出错)参数。若要等待完成,可使用vb2_wait_for_all_buffers。// 驱动需要明确定义的函数,用于操作vb2_queue [include/media/videobuf2-core.h]
 struct vb2_ops {
 // 设置缓冲区队列相关参数
 int (*queue_setup)(struct vb2_queue *q, const struct v4l2_format *fmt,
 unsigned int *num_buffers, unsigned int *num_planes,
 unsigned int sizes[], void *alloc_ctxs[]);
 // 在调用ioctl等待新的缓冲区时释放所有锁,避免阻塞时产生死锁
 void (*wait_prepare)(struct vb2_queue *q);
 // 重新获取在前一个回调函数中释放的锁
 void (*wait_finish)(struct vb2_queue *q);
 // buffer初始化
 int (*buf_init)(struct vb2_buffer *vb);
 // buffer准备好
 int (*buf_prepare)(struct vb2_buffer *vb);
 // 缓冲区每次出队到用户空间都需要调用,驱动可以访问或修改缓冲区。
 void (*buf_finish)(struct vb2_buffer *vb);
 // 调用后缓冲区被释放,驱动可以做一些清理工作
 void (*buf_cleanup)(struct vb2_buffer *vb);
 // 调用后流进入开启状态,在调用之前驱动必须先调用buf_queue接收缓冲区,
 int (*start_streaming)(struct vb2_queue *q, unsigned int count);
 // 调用后流进入关闭状态,驱动需要停止DMA传输或等待工作完成和缓冲区全部入队。
 void (*stop_streaming)(struct vb2_queue *q);
 // 缓冲区加入驱动的队列中
 void (*buf_queue)(struct vb2_buffer *vb);
 };
 
 
 struct vb2_buffer是保存视频数据和信息的核心结构体,每一帧的图像都对应一个struct vb2_buffer结构体,图像信息保存在struct v4l2_buffer结构中,如时间戳、编号、序列号等信息。应用使用ioctl的VIDIOC_REQBUFS命令请求缓冲区时,分配struct vb2_buffer。struct vb2_buffer的状态由enum vb2_buffer_state枚举定义描述。若是V4L2_MEMORY_MMAP类型,则会额外分配内存,图像数据则保存在额外分配的内存中,额外分配的内存指针保存在planes[VIDEO_MAX_PLANES]数组中。[include/media/videobuf2-core.h] enum vb2_buffer_state {  // 缓冲区状态枚举
 VB2_BUF_STATE_DEQUEUED,  // 缓冲区出队,处于用户空间的控制下,默认状态
 VB2_BUF_STATE_PREPARING, // videobuf2正在准备缓冲区
 VB2_BUF_STATE_PREPARED,  // 缓冲区已准备好
 VB2_BUF_STATE_QUEUED,    // 缓冲区入队,处于videobuf2中,不处于驱动中
 VB2_BUF_STATE_ACTIVE,    // 缓冲区位于驱动中
 VB2_BUF_STATE_DONE,      // 缓冲区从驱动返回到videobuf2,但还没出队到用户空间
 VB2_BUF_STATE_ERROR,     // 出错,dequeued到用户空间会报错
 };
 struct vb2_buffer {  // video缓冲区描述符
 struct v4l2_buffer v4l2_buf;  // 关联的缓冲区,可以被驱动read
 // 可以由driver读写,(byteused)即使是对于single-planar类型,v4l2_planes[0]应该被
 // 使用而不是仅仅使用v4l2_buf的byteused。
 // 驱动使用vb2_set_plane_payload()设置byteused.
 struct v4l2_plane v4l2_planes[VIDEO_MAX_PLANES];
 struct vb2_queue *vb2_queue;  // 该vb2_buffer所属的vb2_queue
 unsigned int  num_planes;  // 该buffer有多少个planes
 enum vb2_buffer_state state;   // buffer的当前状态
 // queued buffer链表,保存所有从userspace queued进的buffers
 struct list_head queued_entry;
 // 保存所有准备dequeued到userspace的buffers链表
 struct list_head done_entry;
 // 私有的per-plane信息,驱动禁止修改
 struct vb2_plane planes[VIDEO_MAX_PLANES];
 ......
 };
 // 分配struct vb2_buffer时,若是V4L2_MEMORY_MMAP类型,则会额外分配内存,
 // 图像数据则保存在额外分配的内存中,额外分配的内存指针保存在该结构体当中
 struct vb2_plane {
 void  *mem_priv;        //存放一帧图片数据(针对MMAP类型模式)
 struct  dma_buf *dbuf;  //(针对DMA类型模式)
 unsigned int  dbuf_mapped;
 };
 
 // struct v4l2_buffer用来指定与描述一帧帧缓冲,应用可以设置
 struct v4l2_buffer {
 __u32   index;  // buffer的编号
 __u32   type;   // buffer的类型,由enum v4l2_buf_type定义
 // 数据在缓冲区(有效负载)中所占的字节数,对于多平面缓冲区未使用(设置为0)
 __u32   bytesused;
 // 标志位,见V4L2_BUF_FLAG_XX宏定义,常见值有V4L2_BUF_FLAG_MAPPED、
 // V4L2_BUF_FLAG_QUEUED、V4L2_BUF_FLAG_DONE,分别代表当前缓存已经映射、
 // 缓存可以采集数据、缓存可以提取数据
 __u32   flags;
 __u32   field;
 struct timeval  timestamp;  // 视频帧时间戳
 struct v4l2_timecode timecode;  // 时间码
 __u32   sequence;  // 该帧的序列号
 __u32   memory;  // enum v4l2_memory枚举定义
 union {
 // V4L2_MEMORY_MMAP,从将要mapping的device memory头到数据头的offset
 __u32           offset;
 // V4L2_MEMORY_USERPTR,用户空间指针指向此buffer
 unsigned long   userptr;
 // for multiplanar buffers; userspace pointer to the array of plane
 // info structs for this buffer
 struct v4l2_plane *planes;
 // V4L2_MEMORY_DMABUF,用户空间的描述符关联此描述符
 __s32  fd;
 } m;
 // 对于single-plane,表示buffer的字节数
 // 对于multi-plane buffers,则表示planes array中的元素数量
 __u32   length;
 __u32   reserved2;
 __u32   reserved;
 };
 
 
 struct vb2_mem_ops是buffer内存分配和处理的操作函数集合,这些函数和buffer的类型有关系,即enum v4l2_memory枚举定义的类型。具体如下。 (1)get_userptr和put_userptr函数用于处理USERPTR类型的buffer。(2)alloc、put、num_users和mmap函数用于处理MMAP类型的buffer。(3)alloc、put、num_users和vaddr函数用于处理read/write访问类型的buffer。(4)attach_dmabuf、detach_dmabuf、map_dmabuf和unmap_dmabuf函数用于处理DMABUF类型的buffer。 
 struct vb2_mem_ops结构体通常被初始化为vb2_dma_contig_memops结构体,该结构体是内核提供的,可以直接使用。[include/linux/dma-direction.h] enum dma_data_direction {   // DMA数据传输方向
 DMA_BIDIRECTIONAL = 0,  // 双向
 DMA_TO_DEVICE = 1,      // 传输到设备
 DMA_FROM_DEVICE = 2,    // 从设备往外传输
 DMA_NONE = 3,
 };
 [include/media/videobuf2-core.h]
 // 缓冲区内存处理和分配操作函数集合
 struct vb2_mem_ops {
 // 分配video内存和私有数据(可选)分配器
 void *(*alloc)(void *alloc_ctx, unsigned long size,
 enum dma_data_direction dma_dir, gfp_t gfp_flags);
 // 告诉分配器此缓冲区不再使用,若没有其他使用者使用,则分配器会释放此块内存
 void (*put)(void *buf_priv);
 struct dma_buf *(*get_dmabuf)(void *buf_priv, unsigned long flags);
 // 获取用户空间指针指向的内存,在V4L2_MEMORY_USERPTR模式中使用
 void *(*get_userptr)(void *alloc_ctx, unsigned long vaddr,
 unsigned long size, enum dma_data_direction dma_dir);
 // 告诉分配器USERPTR缓冲区不再使用
 void (*put_userptr)(void *buf_priv);
 // 缓冲区每次从用户空间添加到队列中就会被调用,对缓存同步很有用
 void (*prepare)(void *buf_priv);
 // 缓冲区每次从内核队列添加到用户空间就会被调用
 void (*finish)(void *buf_priv);
 // 为硬件操作添加共享的struct dma_buf,在V4L2_MEMORY_DMABUF模式中使用
 // alloc_ctx-分配上下文,dbuf-共享的dma_buf
 void *(*attach_dmabuf)(void *alloc_ctx, struct dma_buf *dbuf,
 unsigned long size, enum dma_data_direction dma_dir);
 // 通知缓冲区的exporter目前的DMABUF不再使用
 void (*detach_dmabuf)(void *buf_priv);
 // 从分配器请求访问DMABUF,此DMABUF的分配器将通知驱动该DMABUF将要被使用
 int (*map_dmabuf)(void *buf_priv);
 // 释放访问DMABUF的控制权,此DMABUF的分配器将通知驱动该DMABUF已经使用完毕
 void (*unmap_dmabuf)(void *buf_priv);
 // 返回给定缓冲区的内核虚拟地址,该缓冲区与私有数据结构向关联
 void *(*vaddr)(void *buf_priv);
 // 返回给定缓冲区的分配器定义的cookie
 void *(*cookie)(void *buf_priv);
 // 返回此缓冲区的当前使用者,若只有videobuf2层使用,则返回1
 unsigned int (*num_users)(void *buf_priv);
 // 建立用户空间到给定缓冲区虚拟地址区域的映射
 int (*mmap)(void *buf_priv, struct vm_area_struct *vma);
 };
 
 四、使用方法分析:
 videobuf2的使用方法复杂,需要结合具体的驱动实例进行说明,这样比较好理解。下图是imx6ull平台上,CSI控制器的videobuf2使用方法总结。应用可以通过调用open、close、ioctl、mmap、read系统调用访问Video设备,内核根据不同的系统调用采用相对应的方法访问videobuf2。下面从这些系统调用入手,分析内核中videobuf2的使用方法。  1、open 应用调用open打开Video设备,获取设备的描述符。内核中首先调用v4l2_open,然后调用驱动提供的mx6s_csi_open函数。缓冲区队列vb2_queue就是在mx6s_csi_open函数中完成初始化。缓冲区队列数据结构vb2_queue一般嵌入到其他结构体中,由驱动进行动态分配。首先必须设置缓冲区类型type、I/O模型io_modes、缓冲区操作函数集合ops、缓冲区内存管理函数集合mem_ops、时间戳类型timestamp_flags(通常设置为V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC),其他成员可根据实际情况设置,最后调用vb2_queue_init完成缓冲区队列vb2_queue的初始化。[include/media/videobuf2-core.h] q-缓冲区队列数据结构struct vb2_queue指针
 返回值-0成功,小于0失败
 int vb2_queue_init(struct vb2_queue *q)
 {
 // 必须设置下面的成员,否则返回-EINVAL的错误
 if (WARN_ON(!q) || WARN_ON(!q->ops) || WARN_ON(!q->mem_ops) ||
 WARN_ON(!q->type) || WARN_ON(!q->io_modes) ||
 WARN_ON(!q->ops->queue_setup) || WARN_ON(!q->ops->buf_queue) ||
 WARN_ON(q->timestamp_flags & ~(V4L2_BUF_FLAG_TIMESTAMP_MASK |
 V4L2_BUF_FLAG_TSTAMP_SRC_MASK)))
 return -EINVAL;
 
 // 驱动必须选择合适的时间戳,否则内核会发出警告
 WARN_ON((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) ==
 V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN);
 
 INIT_LIST_HEAD(&q->queued_list);  // 初始化queued_list链表节点
 INIT_LIST_HEAD(&q->done_list);    // 初始化done_list链表节点
 spin_lock_init(&q->done_lock);    // 初始化自旋锁
 mutex_init(&q->mmap_lock);        // 初始化互斥锁
 init_waitqueue_head(&q->done_wq); // 初始化等待队列头
 // 若缓冲区大小驱动没有设置,则内核使用默认值初始化
 if (q->buf_struct_size == 0)
 q->buf_struct_size = sizeof(struct vb2_buffer);
 return 0;
 }
 
 2、ioctl:
 V4L2子系统定义了很多ioctl命令供应用程序使用。VIDIOC_REQBUFS命令用于向内核申请缓冲区,VIDIOC_QUERYBUF命令用于获取缓冲区信息,VIDIOC_QBUF命令将读取完数据的空缓存返还给驱动的缓存队列,VIDIOC_DQBUF命令将填充满数据的缓存从驱动中返回给应用,VIDIOC_STREAMOFF命令用于关闭流,即停止图像采集,VIDIOC_STREAMON命令用于开启流,即开启图像采集。内核中的调用流程为v4l2_ioctl->video_ioctl2->__video_do_ioctl->根据不同的命令调用不同的驱动函数->调用对应的videobuf2处理函数,具体调用流程参考上图。下面具体分析一下ioctl调用的videobuf2处理函数。 [include/media/videobuf2-core.h]VIDIOC_REQBUFS:使用VIDIOC_REQBUFS命令调用ioctl,最终会调用到vb2_reqbufs函数,内核使用vb2_reqbufs函数创建缓冲区。
 // q-缓冲区队列数据结构struct vb2_queue指针
 // req-申请缓冲区所需信息存放的结构体,应用需要设置里面的成员
 // 返回值-0成功,小于0失败
 int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req);
 [include/uapi/linux/videodev2.h]
 struct v4l2_requestbuffers {
 __u32  count;       // 申请缓冲区的数量,一帧图像对应一个缓冲区
 __u32  type;  // 缓冲区类型,摄像头为V4L2_BUF_TYPE_VIDEO_CAPTURE
 __u32  memory;  // 通常为V4L2_MEMORY_MMAP或V4L2_MEMORY_USERPTR
 __u32  reserved[2]; // 保留
 };
 
 
 vb2_reqbufs调用流程可总结如下: (1)验证缓冲区的memory type和buffer type是否正确。 __verify_memory_type函数用于校验缓冲区类型和缓冲区内存类型。vb2_queue中的缓冲区类型type和v4l2_requestbuffers中的缓冲区类型type需一致。缓冲区内存类型必须是V4L2_MEMORY_MMAP、V4L2_MEMORY_USERPTR、V4L2_MEMORY_DMABUF其中之一。 若是V4L2_MEMORY_MMAP类型,则q->io_modes必须设置为VB2_MMAP,mem_ops->alloc、q->mem_ops->put和q->mem_ops->mmap的函数指针也必须设置。 若是V4L2_MEMORY_USERPTR类型,则q->io_modes必须设置为VB2_USERPTR,mem_ops->get_userptr和q->mem_ops->put_userptr的函数指针也必须设置。 若是V4L2_MEMORY_DMABUF类型,则q->io_modes必须设置为VB2_DMABUF,mem_ops->attach_dmabuf、q->mem_ops->detach_dmabuf、q->mem_ops->map_dmabuf和q->mem_ops->unmap_dmabuf的函数指针也必须设置。 (2)判断缓冲区参数是否正确,若不正确,则需要做一些处理。 申请的缓冲区数量为0或缓冲区队列中缓冲区数量不为0或申请的缓冲区内存类型和缓冲区队列中缓冲区内存类型不一致,则进入额外的处理逻辑。若内存类型为V4L2_MEMORY_MMAP且缓冲区正在使用,则直接返回错误。清理处于PREPARED或QUEUED状态的缓冲区并释放缓冲区内存。 (3)计算需要分配的缓冲区数量。 (4)调用驱动实现的函数queue_setup,驱动函数需要设置num_buffers、num_buffers、q->plane_sizes和q->alloc_ctx。 (5)调用__vb2_queue_alloc分配缓冲区内存。此时缓冲区的状态为VB2_BUF_STATE_DEQUEUED。后面详细说明该函数。 (6)若分配的缓冲区数量小于需要分配的数量,需要再次调用驱动提供的queue_setup函数。 (7)设置分配的缓冲区数量并向应用返回分配的缓冲区数量。vb2_reqbufs // 验证缓冲区的memory type和buffer type是否正确
 ->__verify_memory_type
 ->__reqbufs
 // 申请的缓冲区数量为0或缓冲区队列中缓冲区数量不为0或
 // 申请的缓冲区内存类型和缓冲区队列中缓冲区内存类型不一致,则需要则额外的处理
 if (req->count == 0 || q->num_buffers != 0 || q->memory != req->memory) {
 mutex_lock(&q->mmap_lock);
 // 内存类型为V4L2_MEMORY_MMAP且缓冲区正在使用,则直接返回错误
 if (q->memory == V4L2_MEMORY_MMAP && __buffers_in_use(q)) {
 mutex_unlock(&q->mmap_lock);
 return -EBUSY;
 }
 // 若缓冲区处于PREPARED或QUEUED状态,则需要清理,
 // 一般在申请了缓冲区,但没调用STREAMON,会出现这种情况
 __vb2_queue_cancel(q);
 // 释放已经分配内存的缓冲区
 ret = __vb2_queue_free(q, q->num_buffers);
 mutex_unlock(&q->mmap_lock);
 }
 // 缓冲区数量取应用申请的数量和最大数量中的较小值,VIDEO_MAX_FRAME定义为32
 num_buffers = min_t(unsigned int, req->count, VIDEO_MAX_FRAME);
 // 缓冲区数量取num_buffers和需要最少的缓冲区数量的较大值
 num_buffers = max_t(unsigned int, num_buffers, q->min_buffers_needed);
 // 设置缓冲区队列中缓冲区的内存模型,内存模型应该和应用申请的模型保持一致
 q->memory = req->memory;
 // 回调用驱动提供的queue_setup函数,imx6ull提供的函数为mx6s_videobuf_setup
 // 驱动函数需要设置num_buffers、num_buffers、q->plane_sizes和q->alloc_ctx
 call_qop(...queue_setup...)
 // 分配缓冲区内存
 ->__vb2_queue_alloc
 // 若分配的缓冲区数量小于需要分配的数量,需要再次调用驱动提供的queue_setup函数
 if (!ret && allocated_buffers < num_buffers) {
 num_buffers = allocated_buffers;
 // 若驱动能处理这种情况,则不会返回错误,若无法处理,则会返回错误
 // mx6s_videobuf_setup不具备这种功能
 ret = call_qop(q, queue_setup, q, NULL, &num_buffers,
 &num_planes, q->plane_sizes, q->alloc_ctx);
 if (!ret && allocated_buffers < num_buffers)
 ret = -ENOMEM;
 }
 ->mutex_lock(&q->mmap_lock);  // 缓冲区共享成员访问需要同步
 // 设置分配的缓冲区数量
 q->num_buffers = allocated_buffers;
 ->mutex_unlock(&q->mmap_lock);
 req->count = allocated_buffers  // 向应用返回分配的缓冲区数量
 
 
 queue_setup函数需要驱动提供。imx6ull提供的函数为mx6s_videobuf_setup,主要的作用是设置__reqbufs函数中的缓冲区数量num_buffers、plane的数量num_planes、plane的大小q->plane_sizes及q->alloc_ctx。上述设置的变量都是调用函数以指针的形式传入。static int mx6s_videobuf_setup(struct vb2_queue *vq, const struct v4l2_format *fmt,
 unsigned int *count, unsigned int *num_planes,
 unsigned int sizes[], void *alloc_ctxs[])
 {
 struct mx6s_csi_dev *csi_dev = vb2_get_drv_priv(vq);
 alloc_ctxs[0] = csi_dev->alloc_ctx;  // 设置alloc_ctxs
 sizes[0] = csi_dev->pix.sizeimage;   // 设置plane_sizes为图像的大小
 if (0 == *count)  // 如果传入的缓冲区数量为0,则设置为32
 *count = 32;
 // 如果num_planes为0且缓冲区占用的总内存超过了规定的最大值,则要重新计算缓冲区数量
 // 最大值为64MB,MAX_VIDEO_MEM定义为64
 if (!*num_planes && sizes[0] * *count > MAX_VIDEO_MEM * 1024 * 1024)
 // 则缓冲区数量按最大内存计算
 *count = (MAX_VIDEO_MEM * 1024 * 1024) / sizes[0];
 *num_planes = 1;  // 设置plane的数量为1
 return 0;
 }
 
 
 缓冲区分配主要由__vb2_queue_alloc函数实现。分配num_buffers个缓冲区,即分配num_buffers个struct vb2_buffer结构退。所有的缓冲区内存地址都保存到vb2_queue结构体中的bufs数组中。若是缓冲区内存是V4L2_MEMORY_MMAP类型,则还需要额外分配保存图像的缓冲区,一个缓冲区分配num_planes个保存图像的缓冲区。此缓冲区由vb2_dc_alloc分配。分配完后缓冲器的结构示意如下图所示。V4L2_MEMORY_MMAP类型的缓冲区需要分配额外的内存空间用于存储图像数据,如图中绿框所属,首选分配一个管理的结构体struct vb2_dc_buf,再分配真正存储图像数据的缓冲区,存储图像的缓冲区物理地址和虚拟地址一致,其虚拟地址保存到管理结构体的vaddr成员中,虚拟地址保存到管理结构体的dma_addr成员中,缓冲区大小保存到管理结构体的size成员中。  static int __vb2_queue_alloc(struct vb2_queue *q, enum v4l2_memory memory, unsigned int num_buffers, unsigned int num_planes)
 {
 unsigned int buffer;
 struct vb2_buffer *vb;
 int ret;
 // 总共分配num_buffers个缓冲区
 for (buffer = 0; buffer < num_buffers; ++buffer) {
 // 分配缓冲区内存,缓冲区大小为buf_struct_size,一般由驱动设置,
 // imx6ull平台设置为sizeof(struct mx6s_buffer)
 vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
 
 // 如果是multiplanar buffers,则缓冲区长度length保存的是plane的数量
 if (V4L2_TYPE_IS_MULTIPLANAR(q->type))
 vb->v4l2_buf.length = num_planes;
 
 vb->state = VB2_BUF_STATE_DEQUEUED;  // 设置缓冲区状态
 vb->vb2_queue = q;  // 设置管理缓冲区的缓冲区队列
 vb->num_planes = num_planes;  // 设置plane数量
 vb->v4l2_buf.index = q->num_buffers + buffer;  // 设置缓冲区编号
 vb->v4l2_buf.type = q->type;   // 设置缓冲区类型
 vb->v4l2_buf.memory = memory;  // 设置缓冲区内存类型
 
 // 对于V4L2_MEMORY_MMAP类型,则还需要分配额外的内存用于保存图像数据
 // 然后映射到用户空间,用户可以直接读取额外内存中的数据
 if (memory == V4L2_MEMORY_MMAP) {
 
 // ******分配存储图像数据内存的函数*******
 ->__vb2_buf_mem_alloc(vb);
 // 每一个缓冲区,分配num_planes块额外的内存
 for (plane = 0; plane < vb->num_planes; ++plane) {
 // 分配的内存大小按页对齐
 unsigned long size = PAGE_ALIGN(q->plane_sizes[plane]);
 // 调用驱动提供的alloc函数进行内存分配,
 // imx6ull平台调用vb2_dc_alloc函数
 mem_priv = call_ptr_memop(vb, alloc, q->alloc_ctx[plane],
 size, dma_dir, q->gfp_flags);
 // 将额外分配的内存保存到mem_priv成员中
 vb->planes[plane].mem_priv = mem_priv;
 // 保存长度
 vb->v4l2_planes[plane].length = q->plane_sizes[plane];
 }
 
 // 调用驱动提供的buf_init函数进行初始化,imx6ull没有提供
 ->call_vb_qop(vb, buf_init, vb);
 }
 // 保存缓冲区地址
 q->bufs[q->num_buffers + buffer] = vb;
 }
 // 设置所有缓冲区的每个plane的长度
 ->__setup_lengths(q, buffer);
 vb->v4l2_planes[plane].length = q->plane_sizes[plane]
 // MMAP类型还要设置偏移,每个buffer的每个plane偏移都不一样
 if (memory == V4L2_MEMORY_MMAP)
 ->__setup_offsets(q, buffer);
 return buffer;
 }
 
 
 vb2_dc_alloc由驱动提供,其分配的缓冲区虚拟地址和物理地址都连续,属于第三种类型的videobuf2。[drivers\media\v4l2-core\videobuf2-dma-contig.c] struct vb2_dc_buf {
 struct device  *dev;
 void  *vaddr;          // 内存虚拟地址
 unsigned long  size;   // 内存大小
 dma_addr_t  dma_addr;  // 内存物理地址
 enum dma_data_direction  dma_dir;  // DMA传输方向
 struct sg_table  *dma_sgt;  // SG DMA相关
 /* MMAP相关变量 */
 struct vb2_vmarea_handler  handler;
 atomic_t  refcount;
 struct sg_table  *sgt_base;
 struct vm_area_struct  *vma; // USERPTR相关变量
 struct dma_buf_attachment  *db_attach;  // DMABUF相关变量
 };
 static void *vb2_dc_alloc(void *alloc_ctx, unsigned long size,
 enum dma_data_direction dma_dir, gfp_t gfp_flags)
 {
 struct vb2_dc_conf *conf = alloc_ctx;
 struct device *dev = conf->dev;
 struct vb2_dc_buf *buf;
 // 首先分配一个struct vb2_dc_buf结构体
 buf = kzalloc(sizeof *buf, GFP_KERNEL);
 // 然后分配存储图像数据的缓冲区,此缓冲区的物理地址和虚拟地址都连续
 buf->vaddr = dma_alloc_coherent(dev, size, &buf->dma_addr, GFP_KERNEL | gfp_flags);
 buf->dev = get_device(dev);  // 设置父设备指针
 buf->size = size;  // 保存图像数据缓冲区的大小
 buf->dma_dir = dma_dir;  // 记录DMA传输方向
 // 设置struct vb2_vmarea_handler结构体
 buf->handler.refcount = &buf->refcount;
 buf->handler.put = vb2_dc_put;  // 回调函数
 buf->handler.arg = buf;  // 回调函数参数
 atomic_inc(&buf->refcount);  // 增加引用计数,引用计数为0时释放缓冲区
 return buf;  // 返回分配的vb2_dc_buf地址
 }
 
 
 
 [include/media/videobuf2-core.h]VIDIOC_QUERYBUF:使用VIDIOC_QUERYBUF命令调用ioctl,最终会调用到vb2_querybuf函数,内核使用vb2_querybuf函数将缓冲区信息拷贝到用户空间,主要有时间戳timestamp、标志flags、缓冲区长度length、缓冲区偏移offset等信息。
 // q-缓冲区队列数据结构struct vb2_queue指针
 // b-v4l2_buffer结构体指针,内核将缓冲区信息存放到里面
 // 返回值-0成功,小于0失败
 int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b);
 
 
 [include/media/videobuf2-core.h]VIDIOC_QBUF:使用VIDIOC_QBUF命令调用ioctl,最终会调用到vb2_qbuf函数,内核使用vb2_qbuf函数将读取完数据的空缓存返还给驱动的缓存队列。
 // q-缓冲区队列数据结构struct vb2_queue指针
 // b-v4l2_buffer结构体指针
 // 返回值-0成功,小于0失败
 int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b);
 
 
 vb2_qbuf的主要工作如下: (1)通过bufs数组获取对应编号的缓冲区地址。 (2)根据缓冲区的不同状态做不同的处理。 VB2_BUF_STATE_DEQUEUED状态的缓冲区需要调用__buf_prepare函数执行一些准备工作。首先将缓冲区状态设置为VB2_BUF_STATE_PREPARING,V4L2_MEMORY_MMAP类型调用buf_prepare,imx6ull平台调用mx6s_videobuf_prepare,V4L2_MEMORY_USERPTR调用get_userptr,imx6ull平台调用vb2_dc_get_userptr。VB2_BUF_STATE_PREPARED状态的缓冲区无需准备工作。其他状态的缓冲区直接返回错误,不能进行VIDIOC_QBUF操作。 (3)将缓冲区挂到queued_list链表中。 (4)设置缓冲区状态为VB2_BUF_STATE_QUEUED。 (5)若VIDIOC_STREAMON已经被调用,说明流已经打开,则需要调用__enqueue_in_driver函数将缓冲区添加到驱动的队列中。vb2_qbuf ->vb2_internal_qbuf
 // 获取对应编号的缓冲区地址
 vb = q->bufs[b->index];
 // 检查缓冲区状态,只有VB2_BUF_STATE_DEQUEUED和
 // VB2_BUF_STATE_PREPARED状态才能被加入到驱动的缓存队列
 switch (vb->state) {
 case VB2_BUF_STATE_DEQUEUED:
 
 -> __buf_prepare(vb, b);
 ->__verify_length;  // 验证数据长度
 vb->state = VB2_BUF_STATE_PREPARING;  // 设置缓冲区状态为PREPARING
 vb->v4l2_buf.timestamp.tv_sec = 0;  // 清空时间戳
 vb->v4l2_buf.timestamp.tv_usec = 0;
 vb->v4l2_buf.sequence = 0;  // 设置序列号为0
 
 ->__qbuf_mmap(vb, b);    // V4L2_MEMORY_MMAP类型调用
 ->__fill_vb2_buffer  // 填充信息并校验
 // 调用buf_prepare函数,imx6ull平台调用mx6s_videobuf_prepare函数
 // 主要的作用设置payload和校验缓冲区虚拟地址是否正确
 call_vb_qop(vb, buf_prepare, vb)
 ->__qbuf_userptr(vb, b);  // V4L2_MEMORY_USERPTR类型调用
 ->__fill_vb2_buffer   // 填充信息并校验
 // 调用驱动提供的get_userptr回调函数,
 // imx6ull平台调用vb2_dc_get_userptr回调函数
 call_ptr_memop(...get_userptr...);
 // 调用buf_prepare函数,imx6ull平台调用mx6s_videobuf_prepare函数
 call_vb_qop(vb, buf_prepare, vb);
 // dmabuf目前没有接触到,不讨论
 ->__qbuf_dmabuf(vb, b);  // V4L2_MEMORY_DMABUF类型调用
 break;
 
 case VB2_BUF_STATE_PREPARED:
 break;
 // videobuf2正在准备缓冲区,不能被加入到驱动的缓存队列
 // __buf_prepare函数内部会设置VB2_BUF_STATE_PREPARING状态
 case VB2_BUF_STATE_PREPARING:
 return -EINVAL;
 default:
 return -EINVAL;
 }
 // 将应用传入的缓冲区挂到queued_list链表中
 list_add_tail(&vb->queued_entry, &q->queued_list)
 q->queued_count++;  // queued_list链表中的缓冲区数量加1
 vb->state = VB2_BUF_STATE_QUEUED;
 
 // 如果VIDIOC_STREAMON已经被调用,则需要将缓冲区添加到驱动的缓冲区队列中
 if (q->start_streaming_called)
 // 将缓冲区挂到imx6ull CSI设备驱动结构体的capture链表上
 ->__enqueue_in_driver(vb);
 
 __fill_v4l2_buffer(vb, b);  // 向用户空间填充信息
 
 
 imx6ull平台buf_prepare的回调函数为mx6s_videobuf_prepare,主要的作用是设置payload,检查缓冲区虚拟地址是否存在和payload是否正确设置。static int mx6s_videobuf_prepare(struct vb2_buffer *vb) {
 struct mx6s_csi_dev *csi_dev = vb2_get_drv_priv(vb->vb2_queue);
 int ret = 0;
 // 设置payload,payload为图像大小
 vb2_set_plane_payload(vb, 0, csi_dev->pix.sizeimage);
 // 缓冲区的有效字节数为图像大小
 vb->v4l2_planes[plane_no].bytesused = size
 // 检查缓冲区虚拟地址是否存在和payload是否正确设置
 if (vb2_plane_vaddr(vb, 0) &&
 vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) {
 ret = -EINVAL;
 goto out;
 }
 return 0;
 out:
 return ret;
 }
 
 // 获取plane的虚拟地址
 vb2_plane_vaddr(vb, 0);
 // 调用驱动提供的vaddr函数,imx6ull平台调用vb2_dc_vaddr
 call_ptr_memop(vb, vaddr, vb->planes[plane_no].mem_priv)
 ->vb2_dc_vaddr
 return buf->vaddr  // 返回存储图像内存的虚拟地址
 
 
 [include/media/videobuf2-core.h]VIDIOC_STREAMON:使用VIDIOC_STREAMON命令调用ioctl,最终会调用到vb2_streamon函数,内核使用vb2_streamon函数将开启视频流,对于图像采集设备而言,则设备开始采集图像,并将图像数据保存到缓冲区中。
 // q-缓冲区队列数据结构struct vb2_queue指针
 // type-缓冲区类型
 // 返回值-0成功,小于0失败
 int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type);
 
 
 vb2_streamon主要的工作如下: (1)遍历queued_list链表,首先将缓冲区状态设置为VB2_BUF_STATE_ACTIVE,然后将入队的缓冲区都添加到驱动的队列中。imx6ull平台调用mx6s_videobuf_queue将缓冲区添加到capture链表中。 (2)调用start_streaming开始视频流,imx6ull平台调用mx6s_start_streaming函数使能设备,开始图像采集。 (3)流开启标志设置streaming设置为1。vb2_streamon ->vb2_internal_streamon
 ->vb2_start_streaming
 // 将queued_list上的所有缓冲区添加到驱动中
 list_for_each_entry(vb, &q->queued_list, queued_entry)
 __enqueue_in_driver
 vb->state = VB2_BUF_STATE_ACTIVE  // 设置缓冲区状态
 atomic_inc(&q->owned_by_drv_count)  // 增加驱动使用缓冲区的计数
 // 对每一缓冲区的plane调用prepare函数
 all_void_memop(vb, prepare, vb->planes[plane].mem_priv)
 ->vb2_dc_prepare  // imx6ull平台调用vb2_dc_prepare函数
 // 同步缓冲区
 ->dma_sync_sg_for_device
 // 调用buf_queue函数
 call_void_vb_qop(vb, buf_queue, vb)
 ->mx6s_videobuf_queue
 ->spin_lock_irqsave  // 加锁
 // 将缓冲区挂到imx6ull CSI设备驱动结构体的capture链表上
 list_add_tail(&buf->internal.queue, &csi_dev->capture)
 ->spin_unlock_irqrestore  // 解锁
 q->start_streaming_called = 1  // 设置VIDIOC_STREAMON已被调用标记
 // 回调驱动中的start_streaming函数,视频设备开始工作
 call_qop(q, start_streaming, q, atomic_read(&q->owned_by_drv_count))
 ->mx6s_start_streaming  // imx6ull平台调用mx6s_start_streaming函数
 ->mx6s_csi_enable   // 使能设备,开始图像采集
 q->streaming = 1;  // 设置流开启的标志
 
 
 [include/media/videobuf2-core.h]VIDIOC_DQBUF:使用VIDIOC_DQBUF命令调用ioctl,最终会调用到vb2_dqbuf函数,内核使用vb2_dqbuf函数将填充满数据的缓存从驱动中返回给应用。
 // q-缓冲区队列数据结构struct vb2_queue指针
 // b-v4l2_buffer结构体指针
 // nonblocking-阻塞标志,根据file->f_flags & O_NONBLOCK决定
 // 返回值-0成功,小于0失败
 int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking);
 
 
 vb2_dqbuf主要的工作如下: (1)检查缓冲区是否可用。图像采集模式下,需要等待图像数据填充到缓冲区中才能被使用。非阻塞且无缓冲区可用,则直接返回-EAGAIN错误。阻塞且无缓冲区可用,睡眠等待。 (2)缓冲区可用,则获取一个可用的缓冲区并将其从done_list链表中删除。 (3)将可用的缓冲区信息拷贝到用户空间。 (4)将可用的缓冲区从queued_list链表中删除。 (5)设置缓冲区状态为VB2_BUF_STATE_DEQUEUED。vb2_dqbuf ->vb2_internal_dqbuf
 // 等待缓冲器是否可用,缓冲区可用,返回0
 // 非阻塞且无缓冲区可用,则直接返回-EAGAIN错误
 // 阻塞且无缓冲区可用,睡眠等待
 ->__vb2_wait_for_done_vb
 // 睡眠等待缓冲区
 call_void_qop(q, wait_prepare, q)
 ->vb2_ops_wait_prepare  // wait_prepare最终调用vb2_ops_wait_prepare函数
 ->vb2_ops_wait_prepare
 ->mutex_unlock(vq->lock);  // 释放锁
 ->wait_event_interruptible     // 睡眠等待缓冲区可用
 // 缓冲区可用被唤醒,执行wait_finish函数,最终调用vb2_ops_wait_finish函数
 call_void_qop(q, wait_finish, q)
 ->vb2_ops_wait_finish
 ->mutex_lock(vq->lock);            // 获取锁
 ->spin_lock_irqsave(&q->done_lock, flags)  // 获取自旋锁
 // 获取done_list链表中一个可用的缓冲区
 list_first_entry(&q->done_list, struct vb2_buffer, done_entry)
 ->__verify_planes_array  // 确保取出的缓冲区所有planes可用
 list_del(&(*vb)->done_entry)  // 缓冲区所有planes可用,则将缓冲区从done_list链表移除
 spin_unlock_irqrestore(&q->done_lock, flags)  // 释放自旋锁
 // 调用buf_finish函数,imx6ull平台没有实现
 call_void_vb_qop(vb, buf_finish, vb)
 ->__fill_v4l2_buffer(vb, b)  // 将出队缓冲区信息填充到用户空间
 list_del(&vb->queued_entry); // 将缓冲区从queued_entry链表中删除
 q->queued_count--;  // 入队缓冲区减1
 ->__vb2_dqbuf
 vb->state = VB2_BUF_STATE_DEQUEUED;  // 设置缓冲器为VB2_BUF_STATE_DEQUEUED状态
 
 
 [include/media/videobuf2-core.h]VIDIOC_STREAMOFF:使用VIDIOC_STREAMOFF命令调用ioctl,最终会调用到vb2_streamoff函数,内核使用vb2_streamoff函数关闭流。
 // q-缓冲区队列数据结构struct vb2_queue指针
 // type-缓冲区类型
 // 返回值-0成功,小于0失败
 int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type);
 
 
 vb2_streamoff的主要工作如下: (1)调用驱动提供的函数stop_streaming停止流,imx6ull平台调用mx6s_stop_streaming函数关闭视频采集设备。 (2)清空入队链表和清空完成链表。 (3)将所有缓冲区出队到用户空间并设置VB2_BUF_STATE_DEQUEUED状态。vb2_streamoff ->vb2_internal_streamoff
 // 取消缓冲区
 ->__vb2_queue_cancel
 // 调用驱动提供的停止流的函数
 call_void_qop(q, stop_streaming, q)
 ->mx6s_stop_streaming
 ->mx6s_csi_disable  // 关闭视频采集设备
 q->streaming = 0;  // 清除流开启标志
 q->start_streaming_called = 0;  // 清除VIDIOC_STREAMON被调用标志
 q->queued_count = 0;  // 清除入队缓冲区计数
 q->error = 0;
 INIT_LIST_HEAD(&q->queued_list)  // 清空入队链表
 INIT_LIST_HEAD(&q->done_list)    // 清空完成链表
 ->atomic_set(&q->owned_by_drv_count, 0)  // 设置驱动引用计数为0
 ->wake_up_all(&q->done_wq)  // 唤醒等待在缓冲区队列的线程
 ->__vb2_dqbuf(vb)  // 将所有缓冲区出队到用户空间
 vb->state = VB2_BUF_STATE_DEQUEUED
 
 
 通过分析关于缓冲区的ioctl命令执行流程,可以总结出缓冲区的状态变化过程,如下图所示。黄色表示缓冲区属于用户空间,绿色表示缓冲区属于videobuf2,青色表示缓冲区属于驱动。 (1)通过VIDIOC_REQBUFS命令申请缓冲区后,缓冲区的状态为VB2_BUF_STATE_DEQUEUED,缓冲区属于用户空间。 (2)通过VIDIOC_QBUF命令将缓冲区添加到内核空间,缓冲区的状态先变为VB2_BUF_STATE_PREPARING,然后再变为VB2_BUF_STATE_QUEUED,缓冲区属于videobuf2。若VIDIOC_STREAMON被调用,则start_streaming_called=1,则VIDIOC_QBUF还会把缓冲区添加到驱动缓冲区队列中,此时缓冲区属于驱动。 (3)通过VIDIOC_STREAMON命令开启流后,缓冲区被添加到驱动缓冲区队列中,缓冲区的状态为VB2_BUF_STATE_ACTIVE,此时缓冲区属于驱动。 (4)若DMA数据传输完成,则缓冲区中已被填充数据,则驱动调用vb2_buffer_done将缓冲区状态更改为VB2_BUF_STATE_DONE,此时缓冲区属于videobuf2。  5、mmap: 使用mmap系统调用映射Video设备,最终会调用到vb2_mmap,内核使用vb2_mmap函数将Video设备的缓冲映射到用户空间。要使用mmap,则缓冲区内存类型必须为V4L2_MEMORY_MMAP,虚拟内存区域必须是共享的VM_SHARED,图像输出模式时虚拟内存区域必须是可写的VM_WRITE,图像采集模式时虚拟内存区域必须是可读的VM_READ。[include/media/videobuf2-core.h] // q-缓冲区队列数据结构struct vb2_queue指针
 // vma-虚拟内存区域管理结构体指针
 // 返回值-0成功,小于0失败
 int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma);
 
 
 vb2_mmap的主要工作流程如下: (1)获取缓冲区plane的偏移。 (2)检查映射的内存是否按页对齐,mmap要求内存按页对齐。 (3)调用驱动提供的内存映射函数mmap进行映射。imx6ull平台调用vb2_dc_mmap进行内存映射。vb2_mmap // 获取缓冲区plane的偏移
 ->__find_plane_by_offset
 PAGE_ALIGN  // mmap要求内存按页对齐
 ->mutex_lock(&q->mmap_lock)  // 获取mmap_lock锁
 // 调用驱动提供的mmap函数
 call_memop(vb, mmap, vb->planes[plane].mem_priv, vma)
 ->vb2_dc_mmap  // imx6ull平台调用vb2_dc_mmap进行内存映射
 ->dma_mmap_coherent  // DMA内存一致性映射
 // 设置虚拟内存不能扩大和core dump标志
 vma->vm_flags  |= VM_DONTEXPAND | VM_DONTDUMP;
 vma->vm_private_data = &buf->handler;  // 设置私有数据
 vma->vm_ops  = &vb2_common_vm_ops;  // 设置虚拟内存的操作函数
 vma->vm_ops->open(vma);  // 打开虚拟内存
 ->mutex_unlock(&q->mmap_lock);  // 释放mmap_lock锁
 
 
 使用read系统调用读取Video设备的数据,最终会调用到vb2_read,内核使用vb2_read将Video设备缓冲区中的数据拷贝到用户空间。使用read系统调用会将数据从内核空间拷贝用户空间,而Video设备产生的数据量很大,拷贝会严重影响性能,因此通常采用的是mmap或userptr的方式。[include/media/videobuf2-core.h] // q-缓冲区队列数据结构struct vb2_queue指针
 // data-用户空间缓冲区指针
 // count-读取数据的字节数
 // ppos-文件偏移指针
 // nonblocking-非阻塞标志
 // 返回值-大于等于0成功读取的字节数,小于0失败
 size_t vb2_read(struct vb2_queue *q, char __user *data, size_t count,
 loff_t *ppos, int nonblocking);
 
 
 使用close系统关闭Video设备,最终会调用到vb2_queue_release,内核使用vb2_queue_release关闭Video设备、清理缓冲区队列和释放缓冲区占用的内存。[include/media/videobuf2-core.h] // q-缓冲区队列数据结构struct vb2_queue指针
 // 返回值-无
 void vb2_queue_release(struct vb2_queue *q);
 
 
 vb2_queue_release的主要工作如下: (1)释放文件I/O模拟器占用的资源 (2)停止流,关闭Video设备,清空入队链表和清空完成链表,将所有缓冲区出队到用户空间并设置VB2_BUF_STATE_DEQUEUED状态。 (3)释放缓冲区内存。void vb2_queue_release(struct vb2_queue *q) {
 __vb2_cleanup_fileio(q);  // 释放文件I/O模拟器占用的资源
 __vb2_queue_cancel(q);    // 退出和停止流
 mutex_lock(&q->mmap_lock);
 // 释放缓冲区内存
 __vb2_queue_free(q, q->num_buffers);
 mutex_unlock(&q->mmap_lock);
 }
 
 
 参考资料: Linux内核4.1版本源码内核文档-videobufhttps://blog.csdn.net/tommy_wxie/article/details/11486907https://blog.csdn.net/u013904227/article/details/81054611Android驱动开发权威指南Android驱动开发与移植实战详解https://www.kernel.org/doc/html/v4.11/media/uapi/v4l/v4l2.html 
 文章参考:http://t.csdn.cn/O6QBV 
 
 |