从应用调用vivi驱动分析v4l2 -- 申请缓存(VIDIOC_REQBUFS)

Linux v4l2架构学习总链接

vivi代码

v4l2测试代码

step 5 : 设置缓存

1,申请缓存

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)

 这里会调用到v4l_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对应vivi驱动的 vidioc_requbufs

static int vidioc_reqbufs(struct file *file, void *priv,
			  struct v4l2_requestbuffers *p)
{
	struct vivi_dev *dev = video_drvdata(file);
	return vb2_reqbufs(&dev->vb_vidq, p);
}

这里就开始分析如何申请缓存了,下面的可能大部分都是贴代码,比较枯燥。但是希望可以从枯燥中学到东西,这样就值得了。

int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
    
        /*
         * vb2_verify_memory_type
         * 1. 检测memory的值,需要是下面3种类型才可以
         *    VB2_MEMORY_MMAP  
         *    VB2_MEMORY_USERPTR
         *    VB2_MEMORY_DMABUF
         *    应用代码中传入的是MMAP,没有问题
         *
         * 2. type值要等于q->type
         *    这里需要明白一个问题参数 struct vb2_queue *q是哪里来的?
         *    根据函数调用知道 q = &dev->vb_vidq
         *    vivi驱动vivi_create_instance函数中,有这样的代码
         *            q = &dev->vb_vidq;
	 *            memset(q, 0, sizeof(dev->vb_vidq));
	 *            q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
         *            q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
	 *            q->drv_priv = dev;
	 *            q->buf_struct_size = sizeof(struct vivi_buffer);
	 *            q->ops = &vivi_video_qops;
	 *            q->mem_ops = &vb2_vmalloc_memops;
         *    可以看到 type等于q->type
         *
         * 3. 对于当前使用MMAP方式
         *    q->mem_ops成员 alloc put mmap不能为空
         *    mem_ops 对应 vb2_vmalloc_memops 不为空,满足条件
         * 
         * 4. q->fileio值要为空
         *    这里暂时还没有看到赋值,分析的时候默认为空
         */        


	int ret = vb2_verify_memory_type(q, req->memory, req->type);


        /*
         * 接着分析vb2_core_reqbufs
         */


	return ret ? ret : vb2_core_reqbufs(q, req->memory, &req->count);
}

接着分析vb2_core_reqbufs

int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
		unsigned int *count)
{
	unsigned int num_buffers, allocated_buffers, num_planes = 0;
	unsigned plane_sizes[VB2_MAX_PLANES] = { };
	int ret;


        /*
         * streaming表示数据流的状态,分析的时候默认为flase
         * 对于这个值什么时候true,后面遇到再分析
         * 对于waiting_in_dqbuf也同样,遇到再分析
         */


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

	if (q->waiting_in_dqbuf && *count) {
		dprintk(1, "another dup()ped fd is waiting for a buffer\n");
		return -EBUSY;
	}


        /*
         * if条件满足,只要符合以下3种就可以
         * 1. 申请的缓冲区个数为0,有人会这么写吗?
              写0的意义是什么?申请之前申请的buffer???

         * 2. 有之前申请且现在没有释放的缓冲区

         * 3. memory类型发生了变化,比如mmap变成了dmabuf

         */


        if (*count == 0 || q->num_buffers != 0 ||
	    (q->memory != VB2_MEMORY_UNKNOWN && q->memory != 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是不是在使用中
                 * 对于判断方式后面分析
                 */                


		if (q->memory == VB2_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
                 * 释放方法同样后面分析,这里主要分析申请
                 * 主意这里会free所有的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.
		 */

            
                /*
                 * 申请0个buffer,走到这里好像释放了之前的buffer....
                 */


		if (*count == 0)
			return 0;
	}



        /*
         * 这里我们可以骗自己,认为是正常的申请
         * 比如首次申请,申请buffer也不为0
         * 给自己减少一些额外的因素
         */


        /*
	 * Make sure the requested values and current defaults are sane.
	 */


        /*
         * VB2_MAX_FRAME 值 32
         * min_buffers_needed vivi驱动中并没有设置,所以为0
         */


	WARN_ON(q->min_buffers_needed > VB2_MAX_FRAME);
    	num_buffers = max_t(unsigned int, *count, q->min_buffers_needed);
	num_buffers = min_t(unsigned int, num_buffers, VB2_MAX_FRAME);
	memset(q->alloc_devs, 0, sizeof(q->alloc_devs));


        /*
         * 记录本次申请的memory类型
         */


	q->memory = 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
         * 对应vivi_video_qops.queue_setup 也就是queue_setup
         */


	ret = call_qop(q, queue_setup, q, &num_buffers, &num_planes,
		       plane_sizes, q->alloc_devs);
	if (ret)
		return ret;
        
}

新的一天,继续写,,,

分析queue_setup

static int queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
				unsigned int *nplanes, unsigned int sizes[], 
				struct device* alloc_devs[])
{
	struct vivi_dev *dev = vb2_get_drv_priv(vq);
	unsigned long size;

	size = dev->width * dev->height * 2;


        /* 
         * *nbuffers 有机会等于0吗?
         * 应该在调用之前就返回了吧
         */


	if (0 == *nbuffers)
		*nbuffers = 32;

        
        /*
         * vid_limit值为16
         * vid_limit * 1024 * 1024 = 16Mbytes
         * 这里加了一个条件,会影响最终可以申请到底buffers的个数
         * 所以说并不是申请多少就一定返回多少的
         */


	while (size * *nbuffers > vid_limit * 1024 * 1024)
		(*nbuffers)--;

        
        /*
         * 对于这个nplanes 字面意思是平面的个数
         * 个人的理解
         * 比如rgb,rgb是连续排放,所以是不是就是1个平面?
         * 对于YUV422P,Y分量放完了,接着U分量,然后接着V分量,这样的话是3个平面?
         * 对于YUV422SP,Y分量放完了,UV分量交错放,这样的话是2个平面?
         * YUV相关的可以看这篇文章 https://www.jianshu.com/p/3e44c2262775
         * 以上只是猜测,待验证
         */



	*nplanes = 1;

	sizes[0] = size;

	/*
	 * videobuf2-vmalloc allocator is context-less so no need to set
	 * alloc_ctxs array.
	 */

	dprintk(dev, 1, "%s, count=%d, size=%ld\n", __func__,
		*nbuffers, size);

	return 0;
}

回到vb2_core_reqbufs接着分析

/*
 * buffer个数,平面个数,平面大小都已经知道了
 */


/* Finally, allocate buffers and video memory */
	allocated_buffers =
		__vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes);
	if (allocated_buffers == 0) {
		dprintk(1, "memory allocation failed\n");
		return -ENOMEM;
	}

跟进 分析__vb2_queue_alloc

static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
			     unsigned int num_buffers, unsigned int num_planes,
			     const unsigned plane_sizes[VB2_MAX_PLANES])
{
	unsigned int buffer, plane;
	struct vb2_buffer *vb;
	int ret;



        /*
         * 之前申请的buffer个数加上现在要申请的,总数不能超过VB2_MAX_FRAME
         * 对于VIDIOC_REQBUFS ioctl 走到这里q->num_buffers值是0
         * 这里为什么还要有个减q->num_buffers的操作呢?
         * 其实这是为VIDIOC_CREATE_BUFS ioctl准备的,扩充buffer
         */



	/* Ensure that q->num_buffers+num_buffers is below VB2_MAX_FRAME */
	num_buffers = min_t(unsigned int, num_buffers,
			    VB2_MAX_FRAME - q->num_buffers);

	for (buffer = 0; buffer < num_buffers; ++buffer) {

                /*
                 * 为每个buffer申请一个struct vb2_buffer结构体,用于管理buffer
                 */

		/* Allocate videobuf buffer structures */
		vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
		if (!vb) {
			dprintk(1, "memory alloc for buffer struct failed\n");
			break;
		}
                
                /*
                 * VB2_BUF_STATE_DEQUEUED,表示buffer在用户空间控制
                 * 猜测是用户空间发起申请的意思
                 * 填充vb的其他成员信息
                 * index: 每个buffer都有自己的编号
                 
                 */


		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;


                /*
                 * 这里明显的可以看到,每个buffer还有平面的结构
                 * 平面个数越多,后面申请的内存空间越大
                 */


		for (plane = 0; plane < num_planes; ++plane) {
			vb->planes[plane].length = plane_sizes[plane];
			vb->planes[plane].min_length = plane_sizes[plane];
		}


                /*
                 * bufs记录每个vb
                 */


		q->bufs[vb->index] = vb;


                /*
                 * MMAP是我们这次分析的重点
                 */


		/* Allocate video buffer memory for the MMAP type */
		if (memory == VB2_MEMORY_MMAP) {
			ret = __vb2_buf_mem_alloc(vb);
			if (ret) {
				dprintk(1, "failed allocating memory for buffer %d\n",
					buffer);
				q->bufs[vb->index] = NULL;
				kfree(vb);
				break;
			}
			....

这里要跟进分析 __vb2_buf_mem_alloc
 

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


        /*
         * 针对每一个平面去操作
         */

	/*
	 * Allocate memory for all planes in this buffer
	 * NOTE: mmapped areas should be page aligned
	 */
	for (plane = 0; plane < vb->num_planes; ++plane) {


                /*
                 * 主意这里是PAGE_SIZE对齐的
                 * 比如申请5000byte,最后对齐后大小则是8192byte
                 * 所以说最终申请到的内存大于等于想要申请的内存
                 */


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

		/* Did it wrap around? */
		if (size < vb->planes[plane].length)
			goto free;

		mem_priv = call_ptr_memop(vb, alloc,
				q->alloc_devs[plane] ? : q->dev,
				q->dma_attrs, size, q->dma_dir, q->gfp_flags);
		if (IS_ERR_OR_NULL(mem_priv)) {
			if (mem_priv)
				ret = PTR_ERR(mem_priv);
			goto free;
		}


                /*
                 * 这里mem_priv的类型是struct vb2_vmalloc_buf
                 * 记录每个mem_priv
                 */


		/* Associate allocator private data with this plane */
		vb->planes[plane].mem_priv = mem_priv;
	}

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

call_ptr_memop最后解析出来是 vb->vb2_queue->mem_ops->alloc

对应代码 vb2_vmalloc_alloc

static void *vb2_vmalloc_alloc(struct device *dev, unsigned long attrs,
			       unsigned long size, enum dma_data_direction dma_dir,
			       gfp_t gfp_flags)
{
	struct vb2_vmalloc_buf *buf;

	buf = kzalloc(sizeof(*buf), GFP_KERNEL | gfp_flags);
	if (!buf)
		return ERR_PTR(-ENOMEM);

	buf->size = size;

        
        /*
         * 对于vmalloc_user具体的实现我也不明白
         * 这里贴一下网上的信息,关键信息如下
         * 1. 申请一段虚拟地址连续的内存给user space使用
         * 2. 添加VM_USERMAP的flag,防止将kernel space的数据泄露到user space
         * 摘自博客:https://blog.csdn.net/tiantao2012/article/details/79285721
         * 总的来说,这里申请了一个平面大小需要的空间(虚拟地址连续)
         */


	buf->vaddr = vmalloc_user(buf->size);
	buf->dma_dir = dma_dir;
	buf->handler.refcount = &buf->refcount;
	buf->handler.put = vb2_vmalloc_put;
	buf->handler.arg = buf;

	if (!buf->vaddr) {
		pr_debug("vmalloc of size %ld failed\n", buf->size);
		kfree(buf);
		return ERR_PTR(-ENOMEM);
	}

        
        /*
         * 设置buffer的引用计数
         */


	refcount_set(&buf->refcount, 1);
	return buf;
}

这里画个图,记录一下buffer的层次结构

 

回到__vb2_queue_alloc继续分析

            if (memory == VB2_MEMORY_MMAP) {


                        /*
                         * __vb2_buf_mem_alloc上面已经分析过
                         */


			ret = __vb2_buf_mem_alloc(vb);
			if (ret) {
				dprintk(1, "failed allocating memory for buffer %d\n",
					buffer);
				q->bufs[vb->index] = NULL;
				kfree(vb);
				break;
			}



                        /* 
                         * 这里主要分析__setup_offset
                         */


			__setup_offsets(vb);
			/*
			 * Call the driver-provided buffer initialization
			 * callback, if given. An error in initialization
			 * results in queue setup failure.
			 */
			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);
				q->bufs[vb->index] = NULL;
				kfree(vb);
				break;
			}
		}

跟进分析 __setup_offset

static void __setup_offsets(struct vb2_buffer *vb)
{
	struct vb2_queue *q = vb->vb2_queue;
	unsigned int plane;
	unsigned long off = 0;

        
        /*
         * 对于第一个vb2_buffer,这里不需要处理
         * 其off值就是为0
         * 对于index非0的情况,下看完下面的for循环再接着看这个
         */


	if (vb->index) {

            
                /*
                 * 之前分析过 vb2_buffer是用来管理buffer的,保存在q->bufs
                 * 获取上一个编号的vb2_buffer
                 * prev->num_planes - 1 这里是为了获取上一个vb2_buffer的最后
                 * 一个plane的信息
                 * 再次更新off的值,这个off点值就是当前的vb2_buffer的
                 * planes[0].m.offset的值
                 * 下面的for循环中就是这么直接赋值的
                 */


		struct vb2_buffer *prev = q->bufs[vb->index - 1];
		struct vb2_plane *p = &prev->planes[prev->num_planes - 1];

		off = PAGE_ALIGN(p->m.offset + p->length);
	}



        /*
         * 对于index为0的情况,假设num_planes值为2,也就是2个平面
         * plane[0].m.offset值为0
         * 那么plane[1].m.offset应该就是
         * plane[0].m.offset + PAGE_ALIGN(plane[0].length)
         * 其实也就是下面的操作,只是先加了,后PAGE_ALIGN
         *
         * 对于index非0的情况
         * off的值就是当前buffer的planes[plane].m.offset的值
         */


	for (plane = 0; plane < vb->num_planes; ++plane) {
		vb->planes[plane].m.offset = off;

		dprintk(3, "buffer %d, plane %d offset 0x%08lx\n",
				vb->index, plane, off);

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

那么这个offset的作用是什么???暂时没有看出来具体的作用。

回到__vb2_queue_alloc继续分析

            if (memory == VB2_MEMORY_MMAP) {


                        /*
                         * __vb2_buf_mem_alloc上面已经分析过
                         */


			ret = __vb2_buf_mem_alloc(vb);
			if (ret) {
				dprintk(1, "failed allocating memory for buffer %d\n",
					buffer);
				q->bufs[vb->index] = NULL;
				kfree(vb);
				break;
			}



                        /* 
                         * 上面已经分析
                         */


			__setup_offsets(vb);
			/*
			 * Call the driver-provided buffer initialization
			 * callback, if given. An error in initialization
			 * results in queue setup failure.
			 */



                        /*
                         * 对应vb->vb2_queue->ops->buf_init
                         * 就是vivi_video_qops.buf_init
                         */


			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);
				q->bufs[vb->index] = NULL;
				kfree(vb);
				break;
			}
		}

vivi_video_qops.buf_init

static int buffer_init(struct vb2_buffer *vb)
{
	struct vivi_dev *dev = vb2_get_drv_priv(vb->vb2_queue);

	BUG_ON(NULL == dev->fmt);

	return 0;
}

并没有做什么实质性的动作。

到这里分析完了 __vb2_queue_alloc

回到 vb2_core_reqbufs中

int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
		unsigned int *count)
{
    
        ...

        allocated_buffers =
		__vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes);
	if (allocated_buffers == 0) {
		dprintk(1, "memory allocation failed\n");
		return -ENOMEM;
	}


        /*
         * 既然能走到这里说明allocated_buffers这个值不为0
         * 这里会判断allocated_buffers是不是小于驱动中要求的最小min_buffers_needed
         * 如果小于的话 将ret给赋值,在这之前ret的值为0
         */


        if (allocated_buffers < q->min_buffers_needed)
    		ret = -ENOMEM;

        /*
         * 这里的if需要2个条件
         * 申请到的buffer个数大于驱动中要求的最小min_buffers_needed
         * 申请到的buffer个数小于用户空间期望的buffer
         * 这2个要同时满足才会进去
         * 这里我们可以这样思考,应用期望申请8个buffer,但是由于某些原因只申请到4个
         * 用户空间对于这4个buffer是否满足不需要驱动关心,应用不满意,本次ioctl后
         * 直接close就行了
         * 这就是驱动的事情了,驱动对未申请到足够的buffer个数是否满意
         */


	/*
	 * Check if driver can handle the allocated number of buffers.
	 */
	if (!ret && allocated_buffers < num_buffers) {
		num_buffers = allocated_buffers;
		/*
		 * num_planes is set by the previous queue_setup(), but since it
		 * signals to queue_setup() whether it is called from create_bufs()
		 * vs reqbufs() we zero it here to signal that queue_setup() is
		 * called for the reqbufs() case.
		 */
		num_planes = 0;


                /*
                 * 对实际申请的buffer个数是否可以接受
                 * 接受就返回0,不接受返回非0
                 */


		ret = call_qop(q, queue_setup, q, &num_buffers,
			       &num_planes, plane_sizes, q->alloc_devs);

		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);
        
        //总感觉这里不对,待验证后确认
        /*
         * 对于这里为什么不是 q->num_buffers += allocated_buffers;
         * 我专门提了个patch
         * 作者回复我VIDIC_REQBUFS ioct会清空所有的buffer
         * 在这里q->num_buffers原来的值就是0
         */
    	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.
	 */


        /*
         * 将实际申请到的buffer的个数赋值,用于返回到用户空间
         */


	*count = allocated_buffers;

        
        /*
         * is_output的值为0,所以这里变量waiting_for_buffers为为true
         */


	q->waiting_for_buffers = !q->is_output;

	return 0;

}

到此这个ioctl过程分析完了,这个过程中,主要明白了buffer中plane的内存的申请到过程。

遗留的一个问题就是offset的作用是什么???

这部分的应用测试代码如下:

        struct v4l2_requestbuffers req;
        req.count = 5;
        req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        req.memory = V4L2_MEMORY_MMAP;
        if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
                printf("Reqbufs fail\n");
                goto err;
        }

        printf("buffer number: %d\n", req.count);

打印信息:

buffer number: 4

为什么是4呢?

vivi驱动代码queue_setup中

	size = dev->width * dev->height * 2;

	if (0 == *nbuffers)
		*nbuffers = 32;

	while (size * *nbuffers > vid_limit * 1024 * 1024)
		(*nbuffers)--;

之前设置的分辨率是 1920*1080,vid_limit = 16

所以值为 16*1024*1024 / (1920*1080*2) = 4.xxx ,所以最后得到的buffer个数是4

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dianlong_lee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值