1.Videobuf2简介
videobuf2作为v4l2驱动程序和用户之间的数据传输的桥梁,用来分配和处理视频缓冲区,实现IO系统调用,包括read()、poll()、以及mmap()。
实现流式IO相关v4l2 ioctl()调用,包括缓冲区分配、缓冲区入队出队以及数据流的打开、关闭等控制。
2.video buffer缓冲区的类型
- vb2_dma_sg_memops(DMA scatter/gather memory):物理地址和虚拟地址都是离散的缓冲区,几乎所有的用户空间缓冲区都是这样的,使用这种缓冲区时,需要硬件支持scatter(分散)/gather(收集) dma操作;
- vb2_vmalloc_memops(vmallo memory):物理地址分散虚拟地址连续的缓冲区:即vmalloc分配的缓冲区,这种情况不能使用dma操作,在video buffer中这种缓冲区类型为USERPTR;
- vb2_dma_contig_memops(DMA contig memory):物理地址连续的缓冲区,在分段式系统上这种类型的缓冲区可能不可用,长时间运行可能导致大量的内存碎片,从而难以获取到连续的内存空间,但是简单的DMA控制器只能使用这种类型的缓冲区;
3.Videobuf2结构体关键属性
v4l2_buffer用于用户空间和内核驱动交换数据,vb2_buffer是缓存队列的基本单位。
当IO流开始时,帧以v4l2_buffer格式在应用层和驱动层之间进行传输,一帧数据的三个状态是:
- 在用户空间:通过VIDIOC_DQBUF将缓冲区数据取出;
- 在驱动输出队列中:当驱动将缓冲区填充后,转移到输出队列;
- 在驱动输入队列中:用户通过VIDIOC_QBUF 把缓冲区交给驱动;
3.1vb2_buffer 结构体
struct vb2_buffer {
struct vb2_queue *vb2_queue; //buffer所在的队列
unsigned int index; //buffer序列号 vb2_queue->bufs[index]
unsigned int type; //buffer类型,用户设定的
unsigned int memory; //buffer内存 mmap、usrptr、dmabuf三者之一
unsigned int num_planes; //对应的plane数量
u64 timestamp;
struct media_request *request;
struct media_request_object req_obj;
enum vb2_buffer_state state; //该buffer当前的状态
unsigned int synced:1;
unsigned int prepared:1;
unsigned int copied_timestamp:1;
unsigned int need_cache_sync_on_prepare:1;
unsigned int need_cache_sync_on_finish:1;
struct vb2_plane planes[VB2_MAX_PLANES];
struct list_head queued_entry; //用户空间调用VIDIOC_QBUF queue的buffer入口,vb2_core_qbuf()时插入,vb2_core_dqbuf()时移除
struct list_head done_entry;//等待出队到用户空间的buffer入口点,会放入vb2_queue->done_list链表中,vb2_buffer_done()执行插入动作,__vb2_get_done_vb()执行移除动作
...
};
vb2_buffer->queued_entry和vb2_buffer->done_entry分别在vb2_queue->queued_list和vb2_queue->done_list两个链表上插入/移出
3.2vb2_buffer_state 结构体
enum vb2_buffer_state {
VB2_BUF_STATE_DEQUEUED, vb2_buffer->queued_entry从 vb2_queue->queued_list上移出,缓存交给用户空间处理
VB2_BUF_STATE_IN_REQUEST,
VB2_BUF_STATE_PREPARING, videobuf2准备缓冲区
VB2_BUF_STATE_QUEUED, vb2-buffer加入到 vb2_queue->queued_list中 vb2_core_qbuf()函数设置,不在驱动中
VB2_BUF_STATE_ACTIVE, vb2-buffer正在被驱动填充处理
VB2_BUF_STATE_DONE, 驱动填充vb2-buffer后,放入 vb2_queue->done_list中,等待用户取走,还没到用户空间
VB2_BUF_STATE_ERROR, 出错,通知用户空间进行dequeue
};
3.3vb2_queue 结构体
struct vb2_queue {
unsigned int type; //videobuf2类型,见枚举enum v4l2_buf_type
unsigned int io_modes; //IO模式,见枚举enum vb2_io_modes
...
unsigned int fileio_read_once:1; //读取第一个缓冲区后报告EOF
unsigned int fileio_write_immediately:1; //write写入的数据都添加到缓冲队列中
unsigned int allow_zero_bytesused:1; //允许将byteused = 0 传递给驱动程序
...
struct mutex *lock; //保护vb2_queue的互斥锁,如果设置为NULL,表示Videobuf2不使用这个锁
void *owner; //文件句柄属于的模块,即调用reqbuf的文件句柄
const struct vb2_ops *ops; //实现开关视频流等回调函数,管理队列中vb2 buffer
const struct vb2_mem_ops *mem_ops; //实现mmap等内存管理回调函数,操作vb2 buffer内存,具体方式有vb2_vmalloc_memops、vb2_dma_sg_memops、vb2_dma_contig_memops
const struct vb2_buf_ops *buf_ops; //主要用来操作vb2 buffer或者v4l2 buffer结构体
void *drv_priv; //驱动的私有数据
u32 subsystem_flags;
unsigned int buf_struct_size; //驱动的缓冲结构体大小,若为0表示驱动不想定义自己缓冲结构,使用sizeof(struct vb2_buffer)
u32 timestamp_flags;
gfp_t gfp_flags; //分配缓冲区时的内存标志,通常为0
u32 min_buffers_needed; //需要的最小缓冲区数量
struct device *alloc_devs[VB2_MAX_PLANES];
/* private: internal use only */
struct mutex mmap_lock; //保护缓冲区的分配,释放和映射
unsigned int memory; //memroy的类型,从userspace传下来,见枚举enum v4l2_memory
enum dma_data_direction
struct vb2_buffer *bufs[VB2_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; //等待缓冲区出队的等待队列
unsigned int streaming:1; //视频流的状态
unsigned int start_streaming_called:1; //stream on成功调用的标志
unsigned int error:1;
unsigned int waiting_for_buffers:1; //在poll函数中使用,以检查是否还在等待数据
...
}
3.4vb2_ops函数集的实现
struct vb2_ops {
int (*queue_setup)(struct vb2_queue *q, unsigned int *num_buffers, unsigned int *num_planes,unsigned int sizes[], struct device *alloc_devs[]);
void (*wait_prepare)(struct vb2_queue *q);
void (*wait_finish)(struct vb2_queue *q);
int (*buf_out_validate)(struct vb2_buffer *vb);
int (*buf_init)(struct vb2_buffer *vb);
int (*buf_prepare)(struct vb2_buffer *vb);
void (*buf_finish)(struct vb2_buffer *vb);
void (*buf_cleanup)(struct vb2_buffer *vb);
int (*start_streaming)(struct vb2_queue *q, unsigned int count);
void (*stop_streaming)(struct vb2_queue *q);
void (*buf_queue)(struct vb2_buffer *vb);
void (*buf_request_complete)(struct vb2_buffer *vb);
};
驱动中注册vb2 ops
static struct vb2_ops dmarx_vb2_ops = {
.queue_setup = rkisp_queue_setup,
.buf_queue = rkisp_buf_queue,
.wait_prepare = vb2_ops_wait_prepare,
.wait_finish = vb2_ops_wait_finish,
.stop_streaming = dmarx_stop_streaming,
.start_streaming = dmarx_start_streaming,
};
- queue_setup:在分配缓冲区前queue_setup会被VIDIOC_REQBUFS和VIDIOC_CREATE_BUFS调用,此函数可以调用两次,如果分配的缓冲区无法分配用户空间指定的的缓冲区个数,则使用实际的缓冲区数并且再次调用,并将分配的buffer数量保存到变量num_buffers中,将buffer的plane数量保存到num_plane中,每一个plane的大小在其size数组中设置,alloc_ctxs数组保存每一个plane的特定数据,最后将num_buffer的值付给vb2_queue->num_buffer变量。
- buf_queue:在应用层调用VIDIOC_STREAMON后,该函数启动dma传输。
- wait_prepare和 wait_finish:wait_prepare
释放互斥锁,调用qbuf时内核判断是否阻塞调用,若阻塞且数据没准备好内核调用wait_prepare释放锁并且进行休眠等待,直到数据准备好被唤醒,然后再调用wait_finish重新持有锁; - stop_streaming:调用之后视频流处于关闭状态,驱动需要关掉DMA或者等待DMA结束,调用b2_buffer_done来归还所有驱动持有的buffers(参数使用VB2_BUF_STATE_DONE或者VB2_BUF_STATE_ERR),可能需要用到vb2_wait_for_all_buffers来等待所有的buffer,该函数是用来等待所有的 buffer被归还给videobuf2。
- start_streaming:调用后视频流进入开启状态,在调用之前驱动必须先调用buf_queue接收buffer,必须将buffer放到驱动自己维护的一个buffer队列。
- buf_init:在mmap方式下分配完buffer之后会被调用一次或者在userptr情况下请求完buffer之后调用一次来对于buffer进行一些初始化操作,如果初始化失败会导致queue_setup失败,比较少用。
3.5vb2_mem_ops结构体
struct vb2_mem_ops是用来操作vb2 buffer内存的函数集合,buffer的类型为enum v4l2_memory, 具体实现方式有vb2_cma_sg_memops/vb2_dma_contig_memops/vb2_vmalloc_memops/vb2_dma_sg_memops
buffer的类型如下
(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 {
void *(*alloc)(struct device *dev, unsigned long attrs,unsigned long size,enum dma_data_direction dma_dir,
gfp_t gfp_flags); //分配保存图像的buffer,设置buffer大小,并返回分配的图像buffer地址
void (*put)(void *buf_priv);
struct dma_buf *(*get_dmabuf)(void *buf_priv, unsigned long flags); //获取DMA缓冲区fd
void *(*get_userptr)(struct device *dev, unsigned long vaddr,unsigned long size,
enum dma_data_direction dma_dir);
void (*put_userptr)(void *buf_priv);
void (*prepare)(void *buf_priv); //内核获取buffer用做缓存同步,用户空间每次将buffer入队列时会调用
void (*finish)(void *buf_priv); //内核将buffer出队
void *(*attach_dmabuf)(struct device *dev, struct dma_buf *dbuf,unsigned long size, enum dma_data_direction dma_dir);
//在V4L2_MEMORY_DMABUF下使用,实现DMA硬件对dma-buf访问
void (*detach_dmabuf)(void *buf_priv); //通知缓冲区的exporter目前的DMABUF不再使用
int (*map_dmabuf)(void *buf_priv); //映射分配的dma地址
void (*unmap_dmabuf)(void *buf_priv);//释放分配的dma地址
void *(*vaddr)(void *buf_priv); //返回缓冲区dma映射的虚拟地址
void *(*cookie)(void *buf_priv); //返回缓冲区的dma_addr
unsigned int (*num_users)(void *buf_priv);//atomic_read(&buf->refcount),在reqbuf调用,返回1为video2buf调用
int (*mmap)(void *buf_priv, struct vm_area_struct *vma); //建立用户空间与内核空间的映射关系
};
例如vb2_cma_sg_memops的初始化
const struct vb2_mem_ops vb2_cma_sg_memops = {
.alloc = vb2_cma_sg_alloc,
.put = vb2_cma_sg_put,
.get_userptr = vb2_cma_sg_get_userptr,
.put_userptr = vb2_cma_sg_put_userptr,
.prepare = vb2_cma_sg_prepare,
.finish = vb2_cma_sg_finish,
.vaddr = vb2_cma_sg_vaddr,
.mmap = vb2_cma_ssg_mmap,
.num_users = vb2_cma_sg_num_users,
.get_dmabuf = vb2_cma_sg_get_dmabuf,
.map_dmabuf = vb2_cma_sg_map_dmabuf,
.unmap_dmabuf = vb2_cma_sg_unmap_dmabuf,
.attach_dmabuf = vb2_cma_sg_attach_dmabuf,
.detach_dmabuf = vb2_cma_sg_detach_dmabuf,
.cookie = vb2_cma_sg_cookie,
};
EXPORT_SYMBOL_GPL(vb2_cma_sg_memops);
4.vb2 buffer驱动开发者注意事项
(1)必须正确实现 vb2_ops 中的回调函数
(2)填充完缓冲区后需要调用 vb2_buffer_done()
(3)需要处理内存映射和用户指针两种模式
(4)需要考虑多平面视频格式的支持