1. videobuffer2的介绍
一. vb2涉及的数据结构:
struct v4l2_buffer { //供用户态使用
__u32 index;
__u32 type;
__u32 bytesused;
__u32 flags;
__u32 field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
/* memory location */
__u32 memory;
union {
__u32 offset; //MMAP方式
unsigned long userptr; //userptr
struct v4l2_plane *planes;
__s32 fd; //dma
} m;
__u32 length;
__u32 reserved2;
__u32 reserved;
};
struct v4l2_requestbuffers { //由用户指定
__u32 count; //buffer的个数
__u32 type; /* enum v4l2_buf_type */ //vb2的类型 (不是很重要,与申请的内存无关)
__u32 memory; /* enum v4l2_memory */ //3种类型 VB2_MEMORY_MMAP,VB2_MEMORY_MMAP,VB2_MEMORY_DMABUF
__u32 reserved[2];
};
struct vb2_queue { //由内核态维护
unsigned int type;
unsigned int io_modes;
const struct vb2_ops *ops; //vb2_queue的接口操作,比如buf_prepare
const struct vb2_mem_ops *mem_ops; //内存申请方面,针对MMAP类型模式
const struct vb2_buf_ops *buf_ops; //vb2_queue的接口操作
void *drv_priv;
unsigned int buf_struct_size;
u32 timestamp_flags;
gfp_t gfp_flags;
u32 min_buffers_needed; //指定至少需要的buffer数,用户层申请的buffer块数不得小于此值
/* private: internal use only */
struct mutex mmap_lock;
unsigned int memory; //用户指定的v4l2_memory(3种类型)
struct vb2_buffer *bufs[VB2_MAX_FRAME]; //存放vb2_buffer数据,格式是用户申请的count数目
unsigned int num_buffers; //实际申请到的buffer数目,防止用户态恶意申请非常大的buffer块数
struct list_head queued_list; //用户态申请的buffer块放在此列表内
unsigned int queued_count;
//以下dqbuf取数据的标志有关
struct list_head done_list;
spinlock_t done_lock;
wait_queue_head_t done_wq; //dqbuf取数据时会检测这个变量
};
struct vb2_buffer { //由内核态维护
struct vb2_queue *vb2_queue;
unsigned int index;
unsigned int type;
unsigned int memory;
unsigned int num_planes;
struct vb2_plane planes[VB2_MAX_PLANES]; //length:记录一幅图像的大小
u64 timestamp;
/* private: internal use only
*
* state: current buffer state; do not change
* queued_entry: entry on the queued buffers list, which holds
* all buffers queued from userspace
* done_entry: entry on the list that stores all buffers ready
* to be dequeued to userspace
*/
enum vb2_buffer_state state;
struct list_head queued_entry;
struct list_head done_entry;
}
struct vb2_plane {
void *mem_priv; //存放具体的一幅图片的大小(针对MMAP类型模式)
struct dma_buf *dbuf; //(针对DMA类型模式)
unsigned int dbuf_mapped;
unsigned int bytesused;
unsigned int length; //记录一幅图像的大小
unsigned int min_length; //记录一幅图像的最小的大小
union {
unsigned int offset;
unsigned long userptr; //(针对USERPTR类型模式)
int fd;
} m;
unsigned int data_offset;
};
二. 常用的vb2接口介绍
2.1 以下3个回调函数数组在vb2接口使用时被调用。其中vb2_ops是用户根据项目需求自己去实现,其余2个是vb2架构平台自带的实现
static const struct vb2_buf_ops v4l2_buf_ops = {
.verify_planes_array = __verify_planes_array_core,
.fill_user_buffer = __fill_v4l2_buffer, //将内核态的一些信息告知用户态。交流的结构体就是struct v4l2_buffer
.fill_vb2_buffer = __fill_vb2_buffer, //VIDIOC_QBUF时会被调用
.copy_timestamp = __copy_timestamp,
};
static void __fill_v4l2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *pb) //将内核态的一些信息告知用户态。交流的结构体就是struct v4l2_buffer
static int __fill_vb2_buffer(struct vb2_buffer *vb, const void *pb, struct vb2_plane *planes) //通过用户态v4l2_buffer去完成vb2_buffer的填充。针对USERPTR,USERDMA模式
if (b->memory == VB2_MEMORY_USERPTR) {
planes[0].m.userptr = b->m.userptr;
planes[0].length = b->length;
}
if (b->memory == VB2_MEMORY_DMABUF) {
planes[0].m.fd = b->m.fd;
planes[0].length = b->length;
}
//以下是队列的相关操作函数,在使用vb2的标准接口时会调用到以下接口。由于接口的具体实现内容由项目实际情况决定,故作为开放的接口。
const struct vb2_ops v4l2_model_qops = {
.queue_setup = v4l2_model_qops_queue_setup, //VIDIOC_REQBUF时会被调用
/* do nothing */
.wait_prepare = v4l2_model_qops_wait_prepare, //VIDIOC_DQBUF时会被调用
.wait_finish = v4l2_model_qops_wait_finish, //VIDIOC_DQBUF时会被调用
.buf_init = v4l2_model_qops_buf_init, //VIDIOC_REQBUF时会被调用
.buf_prepare = v4l2_model_qops_buf_prepare, //VIDIOC_QBUF时会被调用
.buf_cleanup = v4l2_model_qops_buf_cleanup, //与buf_prepare相对应
.buf_finish = v4l2_model_qops_buf_finish, //VIDIOC_DQBUF时会被调用
.start_streaming = v4l2_model_qops_start_streaming,
.stop_streaming = v4l2_model_qops_stop_streaming,
.buf_queue = v4l2_model_qops_buf_queue,
};
支持3种方式:(用于具体的内存申请)
V4L2_MODEL_BUF_TYPE_VMALLOC (分配的内存在物理地址上是不连续的,虚拟地址连续)
q->mem_ops = &vb2_vmalloc_memops; //申请的物理地址不连续. 内部vmalloc, 返回值 struct vb2_vmalloc_buf *buf;
V4L2_MODEL_BUF_TYPE_DMA_CONT (分配的内存在物理地址上是连续的)
q->mem_ops = &vb2_dma_contig_memops; //申请的物理地址连续. 内部 dma_alloc_from_coherent(), 返回值 struct vb2_dc_buf *buf;
V4L2_MODEL_BUF_TYPE_DMA_SG (需要硬件支持iommu功能,申请的地址物理地址上不连续,虚拟地址也不连续)
q->mem_ops = &vb2_dma_sg_memops; //iommu相关,运行申请的物理地址不连续.
2.2 初始化队列
int vb2_queue_init(struct vb2_queue *q)
q->buf_ops = &v4l2_buf_ops;
vb2_core_queue_init(q); //无实际作用,用于判断
此初始化函数全部由vb2架构平台来实现
2.3 申请buffer块.(涉及的ioctl操作时VIDIOC_REQBUF)
.vidioc_reqbufs = vb2_ioctl_reqbufs,
int vb2_ioctl_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p)
vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory, unsigned int *count) //队列,申请的内存类型,申请的内存个数
*count == 0时,清空目前队列所有的buffer数据
*count != 0时, queue_setup指定,一幅图片的大小以及num_planes,一般num_planes=1
allocated_buffers = __vb2_queue_alloc(q, memory, num_buffers(用户态设置的大小), num_planes(1), plane_sizes(一幅图片的大小)); //具体的申请空间
//内部调用alloc() 和 buf_init()函数,涉及的结构体是struct vb2_buffer以及struct vb2_plane
2.4 QBUF,将用户申请的buffer块压入队列。(涉及的ioctl操作时VIDIOC_QBUF)
.vidioc_qbuf = vb2_ioctl_qbuf,
int vb2_ioctl_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
vb2_core_qbuf(q, b->index(用户态申请的buffer块索引), b(v4l2_buffer));
vb = q->bufs[index]; //取出vb2_buffer
list_add_tail(&vb->queued_entry, &q->queued_list);
q->queued_count++;
__buf_prepare(vb, pb);
switch (q->memory) {
case VB2_MEMORY_MMAP:
ret = __qbuf_mmap(vb, pb);
fill_vb2_buffer()
buf_prepare()
break;
case VB2_MEMORY_USERPTR:
ret = __qbuf_userptr(vb, pb);
fill_vb2_buffer()
get_userptr() //获取用户态申请到的内存空间
buf_init()
buf_prepare()
break;
case VB2_MEMORY_DMABUF:
ret = __qbuf_dmabuf(vb, pb);
break;
2.4 DQBUF,从队列取出申请的buffer。(涉及的ioctl操作时VIDIOC_DQBUF)
.vidioc_dqbuf = vb2_ioctl_dqbuf,
int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking) //从队列取出数据,类似于读数据,涉及是否阻塞的问题
vb2_core_dqbuf(q, NULL, b, nonblocking);
__vb2_get_done_vb(q, &vb, pb, nonblocking); //获取已经完成操作的队列数据
__vb2_wait_for_done_vb(q, nonblocking); //等待done_list上有数据
wait_prepare()
wait_event_interruptible(q->done_wq);
wait_finish()
*vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry); //从done_list上取出数据(何时往done_list上放数据,见以下2.5)
buf_finish()
fill_user_buffer()
list_del(&vb->queued_entry);
q->queued_count--;
__vb2_dqbuf(vb);
unmap_dmabuf
2.5 此函数可以唤醒VIDIOC_DQBUF中等待数据部分(因为若打开方式设置为阻塞,用户态在调用VIDIOC_DQBUF时将一直等待,直到内核态驱动调用vb2_buffer_done函数)
void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state) //往done_list上放数据
finish()
list_add_tail(&vb->done_entry, &q->done_list);
wake_up(&q->done_wq); //唤醒done_wq队列。dqbuf里wait_event_interruptible(q->done_wq);所以dqbuf阻塞状态时会一直阻塞,直到调用vb2_buffer_done()
2.6 查询用户申请的buffer块(struct v4l2_buffer是用户态与内核态下进行数据交流的数据结构。如果使用USERPTR方式,则v4l2_buffer里会保存用户态下申请的数据首地址以及大小)
.vidioc_querybuf = vb2_ioctl_querybuf,
int vb2_ioctl_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b)
vb = q->bufs[b->index]; //根据index取出vb2_buffer
vb2_core_querybuf(q, b->index, b);
fill_user_buffer();
3. 用户态与内核态的数据交流
struct vb2_plane结构体存储的是真正申请的内存的地址以及长度.有3种方式:
a. USERPTR:此种方式由用户态去申请空间(比如用vmalloc,申请的大小可能有限).在VIDIOC_QBUF时会将用户态信息v4l2_buffer转换为内核态下vb2_buffer信息
b. DMA的方式没有仔细研究,确定的是也是由用户态去申请空间
c. MMAP方式: 此方式由内核态去申请,申请方式在内核态下又分为3种,上面有介绍过
问:那么这部分数据在内核态下怎么走?
答:举例以MMAP方式申请,V4L2_MODEL_BUF_TYPE_DMA_CONT类型申请到的一块连续的物理地址值和对应的一个虚拟地址值。
buf_descs[0].addr = vb2_dma_contig_plane_dma_addr(vb, i); //得到物理地址.这个值很有可能写到具体的寄存器中
buf_descs[0].size = vb2_plane_size(vb, i); //得到大小(通常一幅图片大小)
以下2行是针对V4L2_MODEL_BUF_TYPE_SG_CONT类型
buf_descs[j].addr = sg_dma_address(sg);
buf_descs[j].size = sg_dma_len(sg);
所以一旦dma采集到数据,则对应的MMAP方式申请的空间也就有了数据。然后调用video_buf_done()函数将数据放到done_list链表,并执行wakeup()函数来唤醒dqbuf()函数取出数据。此时用户态调用VIDIOC_DQBUF时就可以采集到一帧的视频数据了。
总结:
使用MMAP的方式在内核态下申请数据(又分3种申请方式),申请的数据可以是在物理地址上连续,从而使用DMA。但是USERPTR方式,由于用户态下只能malloc,所以申请的数据基本上不能用于dma传输。
用户指定buffer的个数,具体由内核去分配,分配方式也有用户态指定。内核态的多个buffer以链表的形式进行组织。