v4l2框架—申请缓存(VIDIOC_REQBUFS)

1.前言

本文对学习V4L2框架缓存管理做一个笔记。

2.v4l2中关于缓存管理的结构体

在v4l2中,使用vb2_queue结构体作为缓存管理的结构体

struct vb2_queue {
	enum v4l2_buf_type		type;
	unsigned int			io_modes;
	unsigned			fileio_read_once:1;
	unsigned			fileio_write_immediately:1;
	unsigned			allow_zero_bytesused:1;

	struct mutex			*lock;
	struct v4l2_fh			*owner;

	const struct vb2_ops		*ops;
	const struct vb2_mem_ops	*mem_ops;
	void				*drv_priv;
	unsigned int			buf_struct_size;
	u32				timestamp_flags;
	gfp_t				gfp_flags;
	u32				min_buffers_needed;

/* private: internal use only */
	struct mutex			mmap_lock;
	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;
	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;
	unsigned int			error:1;
	unsigned int			waiting_for_buffers:1;

	struct vb2_fileio_data		*fileio;
	struct vb2_threadio_data	*threadio;

...........................
};

这个结构体中重要的成员为vb2_ops,vb2_mem_ops,在xilinx平台中video_device时初始化这两个参数

2.1 vb2_ops

vb2_ops用来对buffer的入队出队进行管理,等后续分析

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[]);

	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);
};

2.2 vb2_mem_ops

vb2_mem_ops用来申请,获取,释放缓存等函数

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);
	void		(*put)(void *buf_priv);
	struct dma_buf *(*get_dmabuf)(void *buf_priv, unsigned long flags);

	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);
	void		(*finish)(void *buf_priv);

	void		*(*attach_dmabuf)(struct device *dev,
					  struct dma_buf *dbuf,
					  unsigned long size,
					  enum dma_data_direction dma_dir);
	void		(*detach_dmabuf)(void *buf_priv);
	int		(*map_dmabuf)(void *buf_priv);
	void		(*unmap_dmabuf)(void *buf_priv);

	void		*(*vaddr)(void *buf_priv);
	void		*(*cookie)(void *buf_priv);

	unsigned int	(*num_users)(void *buf_priv);

	
	int		(*mmap)(void *buf_priv, struct vm_area_struct *vma);
};

在v4l2中实现了三种mem_ops,用来对buffer进行操作,分别为

/* drivers/media/v4l2-core/videobuf2-vmalloc.c 
   分配的缓冲区,虚拟地址连续,物理地址不一定连续
 */
const struct vb2_mem_ops vb2_vmalloc_memops = {
	.alloc		= vb2_vmalloc_alloc,
	.put		= vb2_vmalloc_put,
	.get_userptr	= vb2_vmalloc_get_userptr,
	.put_userptr	= vb2_vmalloc_put_userptr,
#ifdef CONFIG_HAS_DMA
	.get_dmabuf	= vb2_vmalloc_get_dmabuf,
#endif
	.map_dmabuf	= vb2_vmalloc_map_dmabuf,
	.unmap_dmabuf	= vb2_vmalloc_unmap_dmabuf,
	.attach_dmabuf	= vb2_vmalloc_attach_dmabuf,
	.detach_dmabuf	= vb2_vmalloc_detach_dmabuf,
	.vaddr		= vb2_vmalloc_vaddr,
	.mmap		= vb2_vmalloc_mmap,
	.num_users	= vb2_vmalloc_num_users,
};

/* drivers/media/v4l2-core/videobuf2-dma-sg.c 
   对于支持Scatter/Gather的DMA,可使用vb2_dma_sg_memops 
 */
const struct vb2_mem_ops vb2_dma_sg_memops = {
	.alloc		= vb2_dma_sg_alloc,
	.put		= vb2_dma_sg_put,
	.get_userptr	= vb2_dma_sg_get_userptr,
	.put_userptr	= vb2_dma_sg_put_userptr,
	.prepare	= vb2_dma_sg_prepare,
	.finish		= vb2_dma_sg_finish,
	.vaddr		= vb2_dma_sg_vaddr,
	.mmap		= vb2_dma_sg_mmap,
	.num_users	= vb2_dma_sg_num_users,
	.get_dmabuf	= vb2_dma_sg_get_dmabuf,
	.map_dmabuf	= vb2_dma_sg_map_dmabuf,
	.unmap_dmabuf	= vb2_dma_sg_unmap_dmabuf,
	.attach_dmabuf	= vb2_dma_sg_attach_dmabuf,
	.detach_dmabuf	= vb2_dma_sg_detach_dmabuf,
	.cookie		= vb2_dma_sg_cookie,
};

/* drivers/media/v4l2-core/videobuf2-dma-contig.c */
/*分配的缓冲区,物理地址连续*/
const struct vb2_mem_ops vb2_dma_contig_memops = {
	.alloc		= vb2_dc_alloc,
	.put		= vb2_dc_put,
	.get_dmabuf	= vb2_dc_get_dmabuf,
	.cookie		= vb2_dc_cookie,
	.vaddr		= vb2_dc_vaddr,
	.mmap		= vb2_dc_mmap,
	.get_userptr	= vb2_dc_get_userptr,
	.put_userptr	= vb2_dc_put_userptr,
	.prepare	= vb2_dc_prepare,
	.finish		= vb2_dc_finish,
	.map_dmabuf	= vb2_dc_map_dmabuf,
	.unmap_dmabuf	= vb2_dc_unmap_dmabuf,
	.attach_dmabuf	= vb2_dc_attach_dmabuf,
	.detach_dmabuf	= vb2_dc_detach_dmabuf,
	.num_users	= vb2_dc_num_users,
};

3. 申请帧缓冲区

在获取图像数据前需要内核帧缓存区,应用层编程为:

 struct v4l2_requestbuffers
 {
    __u32 count;                    // 缓冲区内缓冲帧的数目
    enum v4l2_buf_type type;        // 缓冲帧数据格式
     enum v4l2_memorymemory;         // 区别是内存映射还是用户指针方式
     __u32 reserved[2];
};

struct v4l2_requestbuffers req;

req.count = nr_bufs; //缓存数量
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;//内存映射方式
 
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0)
{
    printf("ERR(%s):VIDIOC_REQBUFS failed\n", __func__);
    return -1;
}

通过ioctl函数向底层传入VIDIOC_REQBUFS命令来申请帧缓冲区,这里内核中Xilinx平台为例子,执行过程为
在这里插入图片描述

4.详细过程

在创建video_device时初始化了v4l2 ioctl函数集,vb2_ops,vb2_mem_ops,在xilinx-dma.c

//xilinx-dma.c
static const struct v4l2_file_operations xvip_dma_fops = {
	.owner		= THIS_MODULE,
	.unlocked_ioctl	= video_ioctl2,
	.open		= v4l2_fh_open,
	.release	= vb2_fop_release,
	.poll		= vb2_fop_poll,
	.mmap		= vb2_fop_mmap,
};
//xilinx-dma.c
static struct vb2_ops xvip_dma_queue_qops = {
	.queue_setup = xvip_dma_queue_setup,
	.buf_prepare = xvip_dma_buffer_prepare,
	.buf_queue = xvip_dma_buffer_queue,
	.wait_prepare = vb2_ops_wait_prepare,
	.wait_finish = vb2_ops_wait_finish,
	.start_streaming = xvip_dma_start_streaming,
	.stop_streaming = xvip_dma_stop_streaming,
};

//videobuf2-dma-contig.c
const struct vb2_mem_ops vb2_dma_contig_memops = {
	.alloc		= vb2_dc_alloc,
	.put		= vb2_dc_put,
	.get_dmabuf	= vb2_dc_get_dmabuf,
	.cookie		= vb2_dc_cookie,
	.vaddr		= vb2_dc_vaddr,
	.mmap		= vb2_dc_mmap,
	.get_userptr	= vb2_dc_get_userptr,
	.put_userptr	= vb2_dc_put_userptr,
	.prepare	= vb2_dc_prepare,
	.finish		= vb2_dc_finish,
	.map_dmabuf	= vb2_dc_map_dmabuf,
	.unmap_dmabuf	= vb2_dc_unmap_dmabuf,
	.attach_dmabuf	= vb2_dc_attach_dmabuf,
	.detach_dmabuf	= vb2_dc_detach_dmabuf,
	.num_users	= vb2_dc_num_users,
};

dma->video.fops = &xvip_dma_fops;
dma->queue.ops = &xvip_dma_queue_qops;
dma->queue.mem_ops = &vb2_dma_contig_memops;

在应用层掉用ioctl(fd, VIDIOC_REQBUFS, &req)时,会调用xvip_dma_fops->.unlocked_ioctl->video_ioctl2->video_usercopy->__video_do_ioctl->v4l_reqbufs->.vidioc_reqbuf-> vb2_ioctl_reqbufs

static int v4l_reqbufs(const struct v4l2_ioctl_ops *ops,
				struct file *file, void *fh, void *arg)
{
	struct v4l2_requestbuffers *p = arg;
	int ret = check_fmt(file, p->type);

	if (ret)
		return ret;

	CLEAR_AFTER_FIELD(p, memory);

	return ops->vidioc_reqbufs(file, fh, p);
}

这里的 ops->vidioc_reqbufs对应xilinx驱动的 vb2_ioctl_reqbufs

nt vb2_ioctl_reqbufs(struct file *file, void *priv,
			  struct v4l2_requestbuffers *p)
{
	struct video_device *vdev = video_devdata(file);
	/*
	 1.__verify_memory_type判断v4l2_requestbuffers的memory
			是否为VB2_MEMORY_MMAP  ,VB2_MEMORY_USERPTR,VB2_MEMORY_DMABUF
	 		应用层传入的是V4L2_MEMORY_MMAP,可以对应上
	 2.判断v4l2_requestbuffers->type是否与vdev->queue->type是否相等
			type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
			xvip_dma_init(xdev, dma, type, index);
			dma->queue.type = type;
			dma->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
			所以v4l2_requestbuffers->type与vdev->queue->type相等
	  3.应用层使用的是memory类型为V4L2_MEMORY_MMAP,还会判断
	  	if (!(q->io_modes & VB2_MMAP) || !q->mem_ops->alloc ||
	    	!q->mem_ops->put || !q->mem_ops->mmap),即 q->mem_ops的alloc,
	    	put,mmap函数是否存在
	   4.vb2_fileio_is_active(q)
	   		判断q->fileio值是否为空
	*/
	int res = __verify_memory_type(vdev->queue, p->memory, p->type);

	if (res)
		return res;
	//如果判断到vdev->queue->owner以及vdev->queue->owner != file->private_data;
	//成立的话return,在最好一句会赋值
	if (vb2_queue_is_busy(vdev, file))
		return -EBUSY;
	res = __reqbufs(vdev->queue, p);
	/* If count == 0, then the owner has released all buffers and he
	   is no longer owner of the queue. Otherwise we have a new owner. */
	if (res == 0)
                //将 file->private_data 赋值给vdev->queue->owner
		vdev->queue->owner = p->count ? file->private_data : NULL;
	return res;
}

分析__reqbufs

static int __reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
	unsigned int num_buffers, allocated_buffers, num_planes = 0;
	int ret;
/*
	如果为streaming状态,返回
	默认为flase
*/

	if (q->streaming) {
		dprintk(1, "streaming active\n");
		return -EBUSY;
	}

/*
	if 满足条件的条件
		1.申请的buf个数为0
		2.有之前申请没有释放的缓冲区
		2.memory的类型发生变化
*/
	if (req->count == 0 || q->num_buffers != 0 || q->memory != req->memory) {
		/*
		 * We already have buffers allocated, so first check if they
		 * are not in use and can be freed.
		 */
		mutex_lock(&q->mmap_lock);

	/*

		对于之前是mmap的方式,需要判断之前申请的buffer是不是在使用中
		如果buffer在使用中,则return
		*/


		if (q->memory == V4L2_MEMORY_MMAP && __buffers_in_use(q)) {
			mutex_unlock(&q->mmap_lock);
			dprintk(1, "memory in use, cannot free\n");
			return -EBUSY;
		}

		/*
		 * Call queue_cancel to clean up any buffers in the PREPARED or
		 * QUEUED state which is possible if buffers were prepared or
		 * queued without ever calling STREAMON.
		 */
/*
		释放所有的buffer.
		将q->num_buffers赋值为0
*/
		__vb2_queue_cancel(q);
		ret = __vb2_queue_free(q, q->num_buffers);
		mutex_unlock(&q->mmap_lock);
		if (ret)
			return ret;

		/*
		 * In case of REQBUFS(0) return immediately without calling
		 * driver's queue_setup() callback and allocating resources.
		 */
/*
			申请buffer为0,不做任何操作
*/

		if (req->count == 0)
			return 0;
	}

	/*
	 * Make sure the requested values and current defaults are sane.
	 *
	 */
	 /*
		1.取申请的buffer个数,其中VIDEO_MAX_FRAME=32
			 q->min_buffers_needed=0.一般个数为req->count
	*/
	num_buffers = min_t(unsigned int, req->count, VIDEO_MAX_FRAME);
	num_buffers = max_t(unsigned int, num_buffers, q->min_buffers_needed);
	memset(q->plane_sizes, 0, sizeof(q->plane_sizes));
	memset(q->alloc_ctx, 0, sizeof(q->alloc_ctx));

	//memory类型赋值
	q->memory = req->memory;

	/*
	 * Ask the driver how many buffers and planes per buffer it requires.
	 * Driver also sets the size and allocator context for each plane.
	 */

	//调用q->ops->queue_setup即xvip_dma_queue_qops->queue_setup->xvip_dma_queue_setup
	ret = call_qop(q, queue_setup, q, NULL, &num_buffers, &num_planes,
		       q->plane_sizes, q->alloc_ctx);
	if (ret)
		return ret;

	-------

分析xvip_dma_queue_setup

static int
xvip_dma_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt,
		     unsigned int *nbuffers, unsigned int *nplanes,
		     unsigned int sizes[], void *alloc_ctxs[])
{
	struct xvip_dma *dma = vb2_get_drv_priv(vq);

	/* Make sure the image size is large enough. */
	/*确保申请的图像大小小于平台所能支持的大小
		最大为1920*1080*16
	*/
	if (fmt && fmt->fmt.pix.sizeimage < dma->format.sizeimage)
		return -EINVAL;

	/*
		nplanes与颜色空间有关系,暂时分析
		
	*/
	*nplanes = 1;

	/*设置sizes[0],与alloc_ctxs[0]
	* 在dma初始化的时候已经初始化alloc_ctxs
	*/
	sizes[0] = fmt ? fmt->fmt.pix.sizeimage : dma->format.sizeimage;
	alloc_ctxs[0] = dma->alloc_ctx;

	return 0;
}

回到__reqbufs,分析重要函数__vb2_queue_alloc

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为5
	for (buffer = 0; buffer < num_buffers; ++buffer) {
		/* Allocate videobuf buffer structures */
		//每一个buffer申请一个vb2_buffer结构体
		vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
		if (!vb) {
			dprintk(1, "memory alloc for buffer struct failed\n");
			break;
		}

		/* Length stores number of planes for multiplanar buffers */
		//判断是否为多平面
		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;
		//buffer编号
		vb->v4l2_buf.index = q->num_buffers + buffer;
		vb->v4l2_buf.type = q->type;
		vb->v4l2_buf.memory = memory;

		/* Allocate video buffer memory for the MMAP type */
		if (memory == V4L2_MEMORY_MMAP) {
			ret = __vb2_buf_mem_alloc(vb);
			if (ret) {
				dprintk(1, "failed allocating memory for "
						"buffer %d\n", buffer);
				kfree(vb);
				break;
			}
                      ```

跟进重要函数__vb2_buf_mem_alloc,如果memory类型为V4L2_MEMORY_MMAP,则调用__vb2_buf_mem_alloc

static int __vb2_buf_mem_alloc(struct vb2_buffer *vb)
{
	struct vb2_queue *q = vb->vb2_queue;
	enum dma_data_direction dma_dir =
		V4L2_TYPE_IS_OUTPUT(q->type) ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
	void *mem_priv;
	int plane;

	/*
	 * Allocate memory for all planes in this buffer
	 * NOTE: mmapped areas should be page aligned
	 */
	 //针对于多平面进行操作
	for (plane = 0; plane < vb->num_planes; ++plane) {

		//字节对齐,q->plane_sizes[plane]在queue_set中被初始化
		unsigned long size = PAGE_ALIGN(q->plane_sizes[plane]);
		//调用vb->q->mem_ops->alloc.即vb2_dma_contig_memops->alloc->vb2_dc_alloc
		mem_priv = call_ptr_memop(vb, alloc, q->alloc_ctx[plane],
				      size, dma_dir, q->gfp_flags);
		if (IS_ERR_OR_NULL(mem_priv))
			goto free;

		/* Associate allocator private data with this plane */
		//设置私有变量,以及对应的平面大小
		vb->planes[plane].mem_priv = mem_priv;
		vb->v4l2_planes[plane].length = q->plane_sizes[plane];
	}

	return 0;
free:
	/* Free already allocated memory if one of the allocations failed */
	for (; plane > 0; --plane) {
		call_void_memop(vb, put, vb->planes[plane - 1].mem_priv);
		vb->planes[plane - 1].mem_priv = NULL;
	}

	return -ENOMEM;
}

分析vb2_dma_contig_memops->alloc->vb2_dc_alloc,其分配的缓冲区虚拟地址和物理地址都连续

 [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);
	if (!buf)
		return ERR_PTR(-ENOMEM);

	//然后分配存储图像数据的缓冲区,此缓冲区的物理地址和虚拟地址都连续
	buf->vaddr = dma_alloc_coherent(dev, size, &buf->dma_addr,
						GFP_KERNEL | gfp_flags);
	if (!buf->vaddr) {
		dev_err(dev, "dma_alloc_coherent of size %ld failed\n", size);
		kfree(buf);
		return ERR_PTR(-ENOMEM);
	}

	/* Prevent the device from being released while the buffer is used */
	buf->dev = get_device(dev);
	buf->size = size;//保存图像缓冲区大小
	buf->dma_dir = dma_dir;//设置DMA传输方向

	//设置vb2_dc_buf->vb2_vmarea_handler结构体
	buf->handler.refcount = &buf->refcount;
	buf->handler.put = vb2_dc_put;
	buf->handler.arg = buf;

	//增加引用计数
	atomic_inc(&buf->refcount);

	//返回分配的buffer大小
	return buf;
}

回到__vb2_queue_alloc继续分析

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为5
	for (buffer = 0; buffer < num_buffers; ++buffer) {
		/* Allocate videobuf buffer structures */
		//每一个buffer申请一个vb2_buffer结构体
		vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
		if (!vb) {
			dprintk(1, "memory alloc for buffer struct failed\n");
			break;
		}

		/* Length stores number of planes for multiplanar buffers */
		//判断是否为多平面
		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;
		//buffer编号
		vb->v4l2_buf.index = q->num_buffers + buffer;
		vb->v4l2_buf.type = q->type;
		vb->v4l2_buf.memory = memory;

		/* Allocate video buffer memory for the MMAP type */
		if (memory == V4L2_MEMORY_MMAP) {
			ret = __vb2_buf_mem_alloc(vb);
			if (ret) {
				dprintk(1, "failed allocating memory for "
						"buffer %d\n", buffer);
				kfree(vb);
				break;
			}
			/*
			 * Call the driver-provided buffer initialization
			 * callback, if given. An error in initialization
			 * results in queue setup failure.
			 */
                         //用驱动提供的buf_init函数进行初始化,xilinx没有提供
			ret = call_vb_qop(vb, buf_init, vb);
			if (ret) {
				dprintk(1, "buffer %d %p initialization"
					" failed\n", buffer, vb);
				__vb2_buf_mem_free(vb);
				kfree(vb);
				break;
			}
		}
                
                //保存缓冲区的地址
		q->bufs[q->num_buffers + buffer] = vb;
	}

        //设置所有缓冲区的每个plane的长度
	__setup_lengths(q, buffer);
------------------------------------------------------------------
static void __setup_lengths(struct vb2_queue *q, unsigned int n)
{
	unsigned int buffer, plane;
	struct vb2_buffer *vb;

	for (buffer = q->num_buffers; buffer < q->num_buffers + n; ++buffer) {
		vb = q->bufs[buffer];
		if (!vb)
			continue;

		for (plane = 0; plane < vb->num_planes; ++plane)
			vb->v4l2_planes[plane].length = q->plane_sizes[plane];
	}
}
---------------------------------------------------------------------
        //MMAP方式需要设置每一个plane的偏移值
	if (memory == V4L2_MEMORY_MMAP)
		__setup_offsets(q, buffer);

	dprintk(1, "allocated %d buffers, %d plane(s) each\n",
			buffer, num_planes);

	return buffer;
}

分析__setup_offset

static void __setup_offsets(struct vb2_queue *q, unsigned int n)
{
	unsigned int buffer, plane;
	struct vb2_buffer *vb;
	unsigned long off;

	//如果q->num_buffers存在
	if (q->num_buffers) {
		struct v4l2_plane *p;
		//获取上一个编号的vb2_buffer
		vb = q->bufs[q->num_buffers - 1];
		/*获取上一个编号vb2_buffer保存的v4l2_plane,
		*从前面的赋值来看vb->num_planes=0
		*/
		p = &vb->v4l2_planes[vb->num_planes - 1];
		//更新off,off的值为p->m.mem_offset + p->length
		off = PAGE_ALIGN(p->m.mem_offset + p->length);
	} else {
		//如果为第一个vb2_buffer,不需要处理,offset为0
		off = 0;
	}

	for (buffer = q->num_buffers; buffer < q->num_buffers + n; ++buffer) {
		vb = q->bufs[buffer];
		if (!vb)
			continue;

		//对多平面来说,假设平面有4个,那么
		//vb->v4l2_planes[1].m.mem_offset  = 
		//PAGE_ALIGN(vb->v4l2_planes[plane].length) + vb->v4l2_planes[plane].m.mem_offset
		for (plane = 0; plane < vb->num_planes; ++plane) {
			//将前面得到的off值赋值给vb->v4l2_planes[plane].m.mem_offset
			vb->v4l2_planes[plane].m.mem_offset = off;

			dprintk(3, "buffer %d, plane %d offset 0x%08lx\n",
					buffer, plane, off);
			//则off = PAGE_ALIGN(vb->v4l2_planes[plane].length) + vb->v4l2_planes[plane].m.mem_offset
			off += vb->v4l2_planes[plane].length;
			off = PAGE_ALIGN(off);
		}
	}
}

再次回到__reqbufs

static int __reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
	unsigned int num_buffers, allocated_buffers, num_planes = 0;
	int ret;
/*
	如果为streaming状态,返回
	默认为flase
*/

	if (q->streaming) {
		dprintk(1, "streaming active\n");
		return -EBUSY;
	}

/*
	if 满足条件的条件
		1.申请的buf个数为0
		2.有之前申请没有释放的缓冲区
		2.memory的类型发生变化
*/
	if (req->count == 0 || q->num_buffers != 0 || q->memory != req->memory) {
		/*
		 * We already have buffers allocated, so first check if they
		 * are not in use and can be freed.
		 */
		mutex_lock(&q->mmap_lock);

	/*

		对于之前是mmap的方式,需要判断之前申请的buffer是不是在使用中
		如果buffer在使用中,则return
		*/


		if (q->memory == V4L2_MEMORY_MMAP && __buffers_in_use(q)) {
			mutex_unlock(&q->mmap_lock);
			dprintk(1, "memory in use, cannot free\n");
			return -EBUSY;
		}

		/*
		 * Call queue_cancel to clean up any buffers in the PREPARED or
		 * QUEUED state which is possible if buffers were prepared or
		 * queued without ever calling STREAMON.
		 */
/*
		释放所有的buffer.
		将q->num_buffers赋值为0
*/
		__vb2_queue_cancel(q);
		ret = __vb2_queue_free(q, q->num_buffers);
		mutex_unlock(&q->mmap_lock);
		if (ret)
			return ret;

		/*
		 * In case of REQBUFS(0) return immediately without calling
		 * driver's queue_setup() callback and allocating resources.
		 */
/*
			申请buffer为0,不做任何操作
*/

		if (req->count == 0)
			return 0;
	}

	/*
	 * Make sure the requested values and current defaults are sane.
	 *
	 */
	 /*
		1.取申请的buffer个数,其中VIDEO_MAX_FRAME=32
			 q->min_buffers_needed=0.一般个数为req->count
	*/
	num_buffers = min_t(unsigned int, req->count, VIDEO_MAX_FRAME);
	num_buffers = max_t(unsigned int, num_buffers, q->min_buffers_needed);
	memset(q->plane_sizes, 0, sizeof(q->plane_sizes));
	memset(q->alloc_ctx, 0, sizeof(q->alloc_ctx));

	//memory类型赋值
	q->memory = req->memory;

	/*
	 * Ask the driver how many buffers and planes per buffer it requires.
	 * Driver also sets the size and allocator context for each plane.
	 */

	//调用q->ops->queue_setup即xvip_dma_queue_qops->queue_setup->xvip_dma_queue_setup
	ret = call_qop(q, queue_setup, q, NULL, &num_buffers, &num_planes,
		       q->plane_sizes, q->alloc_ctx);
	if (ret)
		return ret;

	/* Finally, allocate buffers and video memory */
	//获取申请到的buffer个数
	allocated_buffers = __vb2_queue_alloc(q, req->memory, num_buffers, num_planes);
	if (allocated_buffers == 0) {
		dprintk(1, "memory allocation failed\n");
		return -ENOMEM;
	}

	/*
	 * There is no point in continuing if we can't allocate the minimum
	 * number of buffers needed by this vb2_queue.
	 */
	if (allocated_buffers < q->min_buffers_needed)
		ret = -ENOMEM;

	/*
	 * Check if driver can handle the allocated number of buffers.
	 */
	//如果分配的缓冲区小于需要分配的数量,需要再次调用驱动提供的queue_setup函数
	//从上面的分析,暂时不知道这种情况怎么样会出现
	if (!ret && allocated_buffers < num_buffers) {
		num_buffers = allocated_buffers;

		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;

		/*
		 * Either the driver has accepted a smaller number of buffers,
		 * or .queue_setup() returned an error
		 */
	}

	mutex_lock(&q->mmap_lock);
	//将申请到的buffer个数allocated_buffers赋值给q->num_buffers
	q->num_buffers = allocated_buffers;

	if (ret < 0) {
		/*
		 * Note: __vb2_queue_free() will subtract 'allocated_buffers'
		 * from q->num_buffers.
		 */
		__vb2_queue_free(q, allocated_buffers);
		mutex_unlock(&q->mmap_lock);
		return ret;
	}
	mutex_unlock(&q->mmap_lock);

	/*
	 * Return the number of successfully allocated buffers
	 * to the userspace.
	 */
	//将分配的缓冲区个数返回给应用层
	req->count = allocated_buffers;
	q->waiting_for_buffers = !V4L2_TYPE_IS_OUTPUT(q->type);

	return 0;
}

以上
参考资料:

Linux V4L2驱动框架分析之(三):v4l2设备的缓存管理_m0_46525308的博客-CSDN博客

Linux V4L2子系统-videobuf2框架分析(三)_业余程序员plus的博客-CSDN博客

从应用调用vivi驱动分析v4l2 – 申请缓存(VIDIOC_REQBUFS)_dianlong_lee的博客-CSDN博客

Linux内核源代码在线查看

Linux虚拟地址按字节对齐:PAGE_ALIGN(addr)_tukery的博客-CSDN博客_linux地址对齐

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
针对V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE类型,VIDIOC_REQBUFSVIDIOC_QUERYBUF需要按照以下步骤进行: 1. VIDIOC_REQBUFS:首先,使用VIDIOC_REQBUFS命令来请求缓冲区,需要设置struct v4l2_requestbuffers结构体中的成员,如下所示: ``` struct v4l2_requestbuffers reqbuf; reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; reqbuf.memory = V4L2_MEMORY_MMAP; reqbuf.count = 4; ``` 其中,type成员表明请求的是视频捕获缓冲区,memory成员表明使用内存映射方式,count成员表明请求4个缓冲区。 2. VIDIOC_QUERYBUF:请求缓冲区之后,需要使用VIDIOC_QUERYBUF命令来查询每个缓冲区的信息,需要设置struct v4l2_buffer和struct v4l2_plane结构体中的成员,如下所示: ``` struct v4l2_buffer buf; struct v4l2_plane planes[VIDEO_MAX_PLANES]; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; buf.memory = V4L2_MEMORY_MMAP; buf.index = 0; buf.length = VIDEO_MAX_PLANES; buf.m.planes = planes; for (int i = 0; i < buf.length; i++) { buf.m.planes[i].length = buffer_size; buf.m.planes[i].m.mem_offset = i * buffer_size; buf.m.planes[i].bytesused = 0; } ``` 其中,type成员和memory成员同样表明请求的是视频捕获缓冲区,index成员表明查询第一个缓冲区的信息,length成员表明缓冲区中的平面数,m.planes成员表示缓冲区中的每个平面的信息,包括长度、内存偏移和已使用的字节数。需要注意的是,buffer_size是每个平面的大小,需要根据实际情况设置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值