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

系列文章
Linux V4L2驱动框架分析之(一):架构介绍
Linux V4L2驱动框架分析之(二):平台v4l2设备驱动
Linux V4L2驱动框架分析之(三):v4l2设备的缓存管理
Linux V4L2驱动框架分析之(四):sensor驱动

v4l2设备读取数据的方式有两种,一种是read方式,一种是streaming方式。read方式很容易理解,就是通过read函数读取,而streaming方式是在内核空间中维护一个缓存队列,然后将内存映射到用户空间,应用读取图像数据就是一个不断地出队列和入队列的过程,如下图所示:
Linux V4L2驱动框架分析之(三):v4l2设备的缓存管理_第1张图片
使用streaming方式,需要管理多块缓冲,内核通过vb2_queue来管理,vb2_queue即缓冲队列。

应用程序查询设备功能,判断设备是否支持streaming方式:

if (ioctl(fd, VIDIOC_QUERYCAP, cap) < 0)
{
    printf("ERR(%s):VIDIOC_QUERYCAP failed\n", __func__);
    return -1;
}

传入的cap参数是一个truct v4l2_capability结构体的指针,该结构体的定义如下:

/* include/uapi/linux/videodev2.h */
struct v4l2_capability {
	__u8	driver[16];
	__u8	card[32];
	__u8	bus_info[32];
	__u32   version;
	__u32	capabilities;
	__u32	device_caps;
	__u32	reserved[3];
};

其中最重要的是capabilities字段,这个字段标记着v4l2设备的功能,capabilities有以下部分标记位:
Linux V4L2驱动框架分析之(三):v4l2设备的缓存管理_第2张图片
struct video_device结构体有一成员queue:

struct video_device
{
	......
	struct vb2_queue *queue;
	......
};

采用streaming方式时,平台v4l2设备驱动需要设置与初始化该成员。

struct vb2_queue结构体定义如下:

struct vb2_queue {
	
	......

	const struct vb2_ops		*ops;
	const struct vb2_mem_ops	*mem_ops; 
	const struct vb2_buf_ops	*buf_ops;

	......

	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;
	wait_queue_head_t		done_wq;
	
	......
};

一般在v4l2_file_operations的open回调里设置并调用vb2_queue_init函数初始化vb2_queue,最关健的要设置vb2_queue的ops、mem_ops成员。

vb2_queue的mem_ops成员里有申请、释放和映射缓存的等回调函数,数据类型为struct 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);
};

vb2_queue支持这三种形式缓存,内核提供了三种vb2_mem_ops操作函数集:

/* 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,
};

根据具体设备使用缓存区的方式,选择对应的vb2_mem_ops。

应用程序申请缓存:

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

到内核态的执行流程:
Linux V4L2驱动框架分析之(三):v4l2设备的缓存管理_第3张图片
申请缓冲之后,把缓冲放入队列:

struct v4l2_buffer v4l2_buffer;

for(i = 0; i < nr_bufs; i++)
{
	memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
	v4l2_buffer.index = i; //想要放入队列的缓存
	v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	v4l2_buffer.memory = V4L2_MEMORY_MMAP;	

    ret = ioctl(fd, VIDIOC_QBUF, &v4l2_buffer);
    if(ret < 0)
    {
        printf("Unable to queue buffer.\n");
        return -1;
    }
}

到内核态的执行流程:
Linux V4L2驱动框架分析之(三):v4l2设备的缓存管理_第4张图片
把缓冲放入队列之后就是映射缓存了。因为如果使用read方式读取的话,图像数据是从内核空间拷贝会应用空间,而一副图像的数据一般来讲是比较大的,所以效率会比较低。而如果使用映射的方式,讲内核空间的内存应用到用户空间,那么用户空间读取数据就想在操作内存一样,不需要经过内核空间到用户空间的拷贝,大大提高效率。

struct v4l2_buffer v4l2_buffer;
void* addr;

memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
v4l2_buffer.index = i; //想要查询的缓存
v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer.memory = V4L2_MEMORY_MMAP;

/* 查询缓存信息 */
ret = ioctl(fd, VIDIOC_QUERYBUF, &v4l2_buffer);
if(ret < 0)
{
    printf("Unable to query buffer.\n");
    return -1;
}

/* 映射 */
addr = mmap(NULL /* start anywhere */ ,
            v4l2_buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
            fd, v4l2_buffer.m.offset);

获取图像数据其实就是一个不断地入队列和出队列地过程,在出队列前要调用poll等待数据准备完成,应用程序poll操作到内核态:
Linux V4L2驱动框架分析之(三):v4l2设备的缓存管理_第5张图片
具体的驱动程序在获得到图像数据后,需要从struct vb2_buffer的queued_list链表得到一个struct vb2_buffer,把数据填入vb2_buffer,之后将该vb2_buffer挂入到struct vb2_buffer的done_list链表,最终唤醒进程。

数据准备完成,进缓存出队:
Linux V4L2驱动框架分析之(三):v4l2设备的缓存管理_第6张图片
如果是映射缓存,出队后,应用程序可直接操作缓存里的图像数据,处理完数据之后再次把缓存放入队列。

你可能感兴趣的:(Linux驱动程序,linux,驱动程序)