V4L2之buffer分配和映射

说明

本文主要讲的是NXP的imx8mm,源码是由NXP提供的,不同的下游厂家的开发板也应该是一样的。

数据结构

这个是buffer操作相关的核心结构体:

struct vb2_queue {
	...
	struct mutex			*lock;
	void				*owner;
	/* 下面的3个结构体非常重要,是操作的核心,通过它们可以很好的了解代码层次 */
	/* 主要作用是将导出驱动的buffer queue管理,比如来自用户空间的buffer的初始化、
	 * 队列管理、流等相关操作
	 */
	const struct vb2_ops		*ops;
	/* 可能会调用3中不同的buf操作函数,主要区别是使用不同的内存分配方式
	 * vb2_dma_contig_memops - videobuf2-dma-contig.c
	 * vb2_dma_sg_memops 	 - videobuf2-dma-sg.c
	 * vb2_vmalloc_memops	 - videobuf2-vmalloc.c
	 */
	const struct vb2_mem_ops	*mem_ops;
	/* 可以不用关心这个结构体,主要的作用是在用户空间和内核空间传递buffer的信息
	 * 默认情况下由vb2的core提供
	 */
	const struct vb2_buf_ops	*buf_ops;
	...
	/* private: internal use only */
	struct mutex			mmap_lock;
	unsigned int			memory;
	enum dma_data_direction		dma_dir;
	struct vb2_buffer		*bufs[VB2_MAX_FRAME];
	unsigned int			num_buffers;

	struct list_head		queued_list;
	unsigned int			queued_count;
	struct list_head		done_list;
	spinlock_t			done_lock;
	wait_queue_head_t		done_wq;

	struct device			*alloc_devs[VB2_MAX_PLANES];
	...
};

操作的回调函数

static struct vb2_ops mx6s_videobuf_ops = {
	.queue_setup     = mx6s_videobuf_setup,
	.buf_prepare     = mx6s_videobuf_prepare,
	.buf_queue       = mx6s_videobuf_queue,
	.wait_prepare    = vb2_ops_wait_prepare,
	.wait_finish     = vb2_ops_wait_finish,
	.start_streaming = mx6s_start_streaming,
	.stop_streaming	 = mx6s_stop_streaming,
};
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);
};
struct vb2_buf_ops {
	int (*verify_planes_array)(struct vb2_buffer *vb, const void *pb);
	void (*fill_user_buffer)(struct vb2_buffer *vb, void *pb);
	int (*fill_vb2_buffer)(struct vb2_buffer *vb, const void *pb,
				struct vb2_plane *planes);
	void (*copy_timestamp)(struct vb2_buffer *vb, const void *pb);
};

实际的操作函数流程举例:

所有的操作中,弄清楚内存分配和映射是重要的,只要弄清楚这两个操作,其它的操作都非常好理解。比如:在应用程序中的VIDIOC_REQBUFS的操作,会依次调用到

static int __vb2_queue_alloc(...)
{
	unsigned int buffer, plane;
	struct vb2_buffer *vb;

	for (buffer = 0; buffer < num_buffers; ++buffer) {
		vb = kzalloc(q->buf_struct_size, GFP_KERNEL);

		vb->state = VB2_BUF_STATE_DEQUEUED;
		vb->vb2_queue = q;
		vb->num_planes = num_planes;
		vb->index = q->num_buffers + buffer;
		vb->type = q->type;
		vb->memory = memory;
		for (plane = 0; plane < num_planes; ++plane) {
			vb->planes[plane].length = plane_sizes[plane];
			vb->planes[plane].min_length = plane_sizes[plane];
		}
		/* 这里将q和vb联系起来了
		 * 实际就是struct vb2_queue和struct vb2_buffer 
		 */
		q->bufs[vb->index] = vb;

		/* Allocate video buffer memory for the MMAP type */
		if (memory == VB2_MEMORY_MMAP) {
		    /* 实际的内存分配函数 */
			ret = __vb2_buf_mem_alloc(vb);

			/* 没有实现这个函数,不会执行实际的函数体 */
			ret = call_vb_qop(vb, buf_init, vb);
		}
	}
	return buffer;
}

为指定的buffer分配视频内存:

static int __vb2_buf_mem_alloc(struct vb2_buffer *vb)
{
	struct vb2_queue *q = vb->vb2_queue;
	void *mem_priv;
	int plane;

	for (plane = 0; plane < vb->num_planes; ++plane) {
		unsigned long size = PAGE_ALIGN(vb->planes[plane].length);

		mem_priv = call_ptr_memop(vb, alloc,
				q->alloc_devs[plane] ? : q->dev,
				q->dma_attrs, size, q->dma_dir, q->gfp_flags);

		/* 将分配的内存地址保存到这里 */
		vb->planes[plane].mem_priv = mem_priv;
	}
}

视频buffer映射:

int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma)
{
	unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
	struct vb2_buffer *vb;
	unsigned int buffer = 0, plane = 0;

	vb = q->bufs[buffer];

	length = PAGE_ALIGN(vb->planes[plane].length);

	/* vb->planes[plane].mem_priv保存有request函数申请的mda内存 */
	ret = call_memop(vb, mmap, vb->planes[plane].mem_priv, vma);
}

通过分析上面的两个函数可以知道,request函数会申请装载视频数据的buffer,而mmap函数会将申请到的buffuer映射到应用程序的用户空间中。从而用户空间可以操作对应的数据buffer。

内存管理

以buffer内存分配为例,buffer分配有3种方式:

struct vb2_mem_ops vb2_dma_contig_memops;	//videobuf2-dma-contig.c
struct vb2_mem_ops vb2_dma_sg_memops;		//videobuf2-dma-sg.c
struct vb2_mem_ops vb2_vmalloc_memops;		//videobuf2-vmalloc.c

V4L2之buffer分配和映射_第1张图片

内存映射

内存的映射也对应着3种方式。

驱动中的映射函数:

static int vb2_dc_mmap(void *buf_priv, struct vm_area_struct *vma)
{
	struct vb2_dc_buf *buf = buf_priv;

	ret = dma_mmap_attrs(buf->dev, vma, buf->cookie,
		buf->dma_addr, buf->size, buf->attrs);

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

将dma分配的内存映射到用户空间。

dma_mmap_attrs(struct device *dev, struct vm_area_struct *vma, void *cpu_addr,
	       dma_addr_t dma_addr, size_t size, unsigned long attrs)
{
	const struct dma_map_ops *ops = get_dma_ops(dev);

	if (ops->mmap){
	     /* 这里调用的是哪里的函数?怎么分析? 
		 * 在arch/arm64/mm/dma-mapping.c有具体的实现
		 * 使用快捷键cs查找
		 */
		return ops->mmap(dev, vma, cpu_addr, dma_addr, size, attrs);
	}
	return dma_common_mmap(dev, vma, cpu_addr, dma_addr, size);
}

最终会调用到位于arch/arm64/mm/dma-mapping.c中的3种实现方式之一:

struct dma_map_ops swiotlb_dma_ops ;
struct dma_map_ops dummy_dma_ops ;
struct dma_map_ops iommu_dma_ops ;

相关内容阅读

链接: V4L2框架
链接: V4L2之设备注册
链接: V4L2之mmap()函数
链接: V4L2之events
链接: V4L2之buffer分配和映射

你可能感兴趣的:(V4L2,视频,buffer,vb2,映射,内存分配)