videobuf2是嵌入到v4l2子系统,以供驱动与用户空间提供数据申请与交互的接口集合,它实现了包括buffer分配,根据状态可以入队出队的控制流。
文件目录
kernel/drivers/media/v4l2-core 相关videobuf2相关文件如下,videobuf相关的文件可以不关心,只讨论videobuf2
├── videobuf2-core.c
├── videobuf2-dma-contig.c
├── videobuf2-dma-sg.c
├── videobuf2-dvb.c
├── videobuf2-internal.h
├── videobuf2-memops.c
├── videobuf2-v4l2.c
├── videobuf2-vmalloc.c
├── videobuf-core.c
├── videobuf-dma-contig.c
├── videobuf-dma-sg.c
├── videobuf-dvb.c
└── videobuf-vmalloc.
既然是buffer相关。一般分配有几种可能。
- buffer物理地址不连续,硬件上支持scatter/gather DMA,rkisp内部有iommu,可以实现分段dma。
- buffer 虚拟地址连续,物理不连续,也就是vmalloc操作,也很难使用dma
- 物理地址连续,一般可以从cma内存申请(预留)。
分别对应文件videobuf2-dma-sg.c,videobuf2-vmalloc.c,videobuf2-dma-contig.c
核心文件是videobuf2-core.c,videobuf2-v4l2.c
videobuf2-core.h里面vb2_queue是总的videobuf2结构体,每个video节点需要实现。
Rk定义结构体
struct rkisp1_vdev_node {
struct vb2_queue buf_queue;//这个就是要实现的总结构体
struct video_device vdev;//包含video文件操作函数,vb2重要结构
struct media_pad pad;
};
Video节点操作
static int rkisp1_register_stream_vdev(struct rkisp1_stream *stream)
{
struct rkisp1_device *dev = stream->ispdev;
struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
struct video_device *vdev = &stream->vnode.vdev;
struct rkisp1_vdev_node *node;
……
vdev->ioctl_ops = &rkisp1_v4l2_ioctl_ops;
vdev->fops = &rkisp1_fops;
……
rkisp_init_vb2_queue(&node->buf_queue, stream,
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);//mark,初始化vb2_queue
vdev->queue = &node->buf_queue;//这里就是video节点能访问queue
ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
……
ret = media_entity_init(&vdev->entity, 1, &node->pad, 0);
}
static int rkisp_init_vb2_queue(struct vb2_queue *q,
struct rkisp1_stream *stream,
enum v4l2_buf_type buf_type)
{
q->type = buf_type;
q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
q->drv_priv = stream;
q->ops = &rkisp1_vb2_ops;//各家厂商实现
q->mem_ops = &vb2_dma_contig_memops;
q->buf_struct_size = sizeof(struct rkisp1_buffer);
q->min_buffers_needed = CIF_ISP_REQ_BUFS_MIN;
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->lock = &stream->ispdev->apilock;
后面初始化还会调用q->buf_ops = &v4l2_buf_ops;
以上是初始化过程,下面看下结构体一些变量
struct vb2_queue {
unsigned int type;//buf类型,V4L2_BUF_TYPE_VIDEO_CAPTURE
//rk用的V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,支持多plane
unsigned int io_modes;//看上面定义支持3种stream类型
unsigned fileio_read_once:1;
unsigned fileio_write_immediately:1;
unsigned allow_zero_bytesused:1;
unsigned use_dma_bidirectional:1;
struct mutex *lock;//锁
void *owner;
const struct vb2_ops *ops;//比较重要,rk实现,buf管理函数
const struct vb2_mem_ops *mem_ops;//内存相关操作
const struct vb2_buf_ops *buf_ops;//相关buf的填充以及赋值用户空间地址
void *drv_priv;//厂商私有数据区
unsigned int buf_struct_size;//自定义buffer结构大小,//vb2_v4l2_buffer必须放在开头,可以用container_of访问总结构
u32 timestamp_flags;//时间戳
gfp_t gfp_flags;
u32 min_buffers_needed;
/* private: internal use only */
struct mutex mmap_lock;
unsigned int memory;
struct vb2_buffer *bufs[VB2_MAX_FRAME];// 一个视频缓冲区
unsigned int num_buffers;//buffer个数
struct list_head queued_list;//存放用户enqueue的buffer
unsigned int queued_count;
atomic_t owned_by_drv_count;
struct list_head done_list;// 存放处理好等待dequeue的buffer
spinlock_t done_lock;
wait_queue_head_t done_wq;
void *alloc_ctx[VB2_MAX_PLANES];
unsigned int plane_sizes[VB2_MAX_PLANES];
unsigned int streaming:1;
unsigned int start_streaming_called:1;
unsigned int error:1;
unsigned int waiting_for_buffers:1;
unsigned int is_multiplanar:1;
unsigned int is_output:1;
unsigned int last_buffer_dequeued:1;
struct vb2_fileio_data *fileio;
struct vb2_threadio_data *threadio;
enum dma_data_direction dma_dir;
#ifdef CONFIG_VIDEO_ADV_DEBUG
/*
* Counters for how often these queue-related ops are
* called. Used to check for unbalanced ops.
*/
u32 cnt_queue_setup;
u32 cnt_wait_prepare;
u32 cnt_wait_finish;
u32 cnt_start_streaming;
u32 cnt_stop_streaming;
#endif
};
接下来看vb2_buffer的定义
struct vb2_buffer {//这个是设备内部帧缓存描述,v4l2_buffer是用户态
struct vb2_queue *vb2_queue; //包含外部指针
unsigned int index; //buf的编号
unsigned int type;
unsigned int memory;
unsigned int num_planes; //plane的个数
struct vb2_plane planes[VB2_MAX_PLANES]; //看下面
/* 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;//buffer的状态,buffer轮转的时候要设置
struct list_head queued_entry;
struct list_head done_entry;
…..
再来看,以前不明白plane的意思,后来发现yuv数据可以不连续,比如Y数据完成后,后面不是紧接着uv,可以是其他地址,这个数据就是多plane。
最后带M的格式是多plane,比如V4L2_PIX_FMT_NV21M
struct vb2_plane {
void *mem_priv; //当前plane的私有数据
struct dma_buf *dbuf;//共享buf目标
unsigned int dbuf_mapped;//标记是否映射
unsigned int bytesused;//占用大小
unsigned int length;//plane大小
union {
unsigned int offset; //mmap记录偏移
unsigned long userptr; //针对userptr模式
int fd; //dma 记录fd
} m;
unsigned int data_offset;// 这个plane中数据开始的偏移值
};
这里再贴下vb2_ops定义,在videobuf2-core.h文件定义前面有各个函数的注释以及说明时候会被调用。
struct vb2_ops {
int (*queue_setup)(struct vb2_queue *q, const void *parg,
unsigned int *num_buffers, unsigned int *num_planes,
unsigned int sizes[], void *alloc_ctxs[]);
// alloc_ctxs指针数组包含了每个视频层的“分配上下文”,可以指定申请buffer的属性,比如writecombine模式
void *vb2_dma_contig_init_ctx(struct device *dev)
voidvb2_dma_contig_cleanup_ctx(void *alloc_ctx)
void (*wait_prepare)(struct vb2_queue *q);
void (*wait_finish)(struct vb2_queue *q);
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);
};
其实我真正关心的是buffer的申请与返回。数据的流转,dma怎么设置。下一节先看request_buffer。
摘抄网上的一段,理解下流程,后面会详细介绍这些过程
1,调用vb2_queue_init 初始化队列 q 。
2,调用reqbuf 时候会根据请求(v4l2_requestbuffers)分配vb2结构,并且加入到q->buf中
3,调用querybuf时候,根据信息(v4l2_buffer)返回q->buf中对应的vb2_buffer的信息(v4l2_buffer)
4,mmap上面信息对应的 vb空间到用户空间
5,调用qbuf 时,将对应的vb2_buffer ( vivi_bufer->list )添加到 q->queued_list 队列中
6,使用select 调用poll 休眠等待 q->done_list 有数据
7, 调用qbuf 和 vidioc_streamon 时候都会查询,如果这两个条件都成立,则调用q->ops->buf_queue 将 核心中的vb2_buffer调入我们写的驱动中,放入一个列表,然后等待(上面的poll过程休眠)我们的驱动程序将数据放入该vb2_buffer
8, 数据存放完成后 调用vb2_buffer_done函数,即将上面有数据的vb2_buffer放入q->done_list中,然后唤醒上面poll休眠的进程
9, poll唤醒后会调用dqbuf将q->done_list 中的vb2_buffer提出来后,将此vb2的信息(v4l2_buffer)返回
10, 应用程序得到buffer信息后,就去对应的mmap后的用户空间中读数据。