从vivi学习V4L2架构(七):申请的缓冲帧从内核空间映射到用户空间

一、应用层代码实现

    for(i = 0; i < req.count; i++) {
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buf)) {
            printf("Querybuf fail\n");
            goto err;
        }

        printf("buffer[%d]: length = %d, offset = %d\n", i, buf.length, buf.m.offset);
        buffers[i].length = buf.length;
        buffers[i].start =
            mmap (NULL /* start anywhere */,
                    buf.length,
                    PROT_READ | PROT_WRITE /* required */,
                    MAP_SHARED /* recommended */,
                    fd, buf.m.offset);

        if (MAP_FAILED == buffers[i].start) {
            printf ("mmap failed\n");
            req.count = i;
            goto unmmap;
        }
    }

二、获取缓冲区的offset和length

static long __video_do_ioctl(struct file *file,
		unsigned int cmd, void *arg)
{
... ...
const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
... ...

	case VIDIOC_QUERYBUF:
	{
		struct v4l2_buffer *p = arg;

		if (!ops->vidioc_querybuf)
			break;
		ret = check_fmt(ops, p->type);//先判断是否支持是支持的type
		if (ret)
			break;
        //下面调用video_device.ioctl_ops的vidioc_querybuf函数
        //主要是根据上层传下来的buf.index,找到之前分配好的空间,并把
        //空间地址通过offset传给上层
		ret = ops->vidioc_querybuf(file, fh, p);
		if (!ret)
			dbgbuf(cmd, vfd, p);
		break;
	}

... ...
}

下面具体看一下vidioc_querybuf函数

static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
	struct vivi_dev *dev = video_drvdata(file);
	return vb2_querybuf(&dev->vb_vidq, p);
}

int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b)
{
	struct vb2_buffer *vb;
    
    //类型判断,不满足返回
	if (b->type != q->type) {
		dprintk(1, "querybuf: wrong buffer type\n");
		return -EINVAL;
	}

    //index是否超过了之前申请的buffer最大数量,如超过返回
	if (b->index >= q->num_buffers) {
		dprintk(1, "querybuf: buffer index out of range\n");
		return -EINVAL;
	}
    //通过buf.index获取到对应vb2_buffer
	vb = q->bufs[b->index];

	return __fill_v4l2_buffer(vb, b);
}

__fill_v4l2_buffer又做了些什么

static int __fill_v4l2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *b)
{
	struct vb2_queue *q = vb->vb2_queue;
	int ret = 0;

	/* Copy back data such as timestamp, flags, input, etc. */
    //如上英文注释拷贝时间戳,标志等等
	memcpy(b, &vb->v4l2_buf, offsetof(struct v4l2_buffer, m));
	b->input = vb->v4l2_buf.input;
	b->reserved = vb->v4l2_buf.reserved;

    //判断是否是多个planar,vivi这里不是所以跳到else
	if (V4L2_TYPE_IS_MULTIPLANAR(q->type)) {
		ret = __verify_planes_array(vb, b);
		if (ret)
			return ret;

		/*
		 * Fill in plane-related data if userspace provided an array
		 * for it. The memory and size is verified above.
		 */
		memcpy(b->m.planes, vb->v4l2_planes,
			b->length * sizeof(struct v4l2_plane));
	} else {
		/*
		 * We use length and offset in v4l2_planes array even for
		 * single-planar buffers, but userspace does not.
		 */
        //由于是单plane,所以这里直接使用v4l2_planes[0]
        //这里获取到了length
		b->length = vb->v4l2_planes[0].length;
		b->bytesused = vb->v4l2_planes[0].bytesused;
        //下面这里就获取到了m.offset
		if (q->memory == V4L2_MEMORY_MMAP)
			b->m.offset = vb->v4l2_planes[0].m.mem_offset;
		else if (q->memory == V4L2_MEMORY_USERPTR)
			b->m.userptr = vb->v4l2_planes[0].m.userptr;
	}

	/*
	 * Clear any buffer state related flags.
	 */
	b->flags &= ~V4L2_BUFFER_STATE_FLAGS;

    //参考上一篇,申请完内存空间vb->state=VB2_BUF_STATE_DEQUEUED
	switch (vb->state) {
	case VB2_BUF_STATE_QUEUED:
	case VB2_BUF_STATE_ACTIVE:
		b->flags |= V4L2_BUF_FLAG_QUEUED;
		break;
	case VB2_BUF_STATE_ERROR:
		b->flags |= V4L2_BUF_FLAG_ERROR;
		/* fall through */
	case VB2_BUF_STATE_DONE:
		b->flags |= V4L2_BUF_FLAG_DONE;
		break;
	case VB2_BUF_STATE_DEQUEUED:
		/* nothing */
		break;
	}

    //这里暂时不知道要干啥,先放着
	if (vb->num_planes_mapped == vb->num_planes)
		b->flags |= V4L2_BUF_FLAG_MAPPED;

	return ret;
}

三、映射到用户空间

mmap系统调用最后会进入kernel,反正有一番处理最后应该会到/dev/videoX对应file_operationsd的mmap函数

static const struct file_operations v4l2_fops = {
	.owner = THIS_MODULE,
	.read = v4l2_read,
	.write = v4l2_write,
	.open = v4l2_open,
	.get_unmapped_area = v4l2_get_unmapped_area,
	.mmap = v4l2_mmap,
	.unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = v4l2_compat_ioctl32,
#endif
	.release = v4l2_release,
	.poll = v4l2_poll,
	.llseek = no_llseek,
};

v4l2_mmap主要判断video_device的fops->mmap是否定义,如定义则继续调用

static int v4l2_mmap(struct file *filp, struct vm_area_struct *vm)
{
	struct video_device *vdev = video_devdata(filp);
	int ret = -ENODEV;
    
    //对应vivi驱动是定义了。
	if (!vdev->fops->mmap)
		return ret;
	if (vdev->lock && mutex_lock_interruptible(vdev->lock))
		return -ERESTARTSYS;
    //直接调用vivi.fops的mmap
	if (video_is_registered(vdev))
		ret = vdev->fops->mmap(filp, vm);
	if (vdev->lock)
		mutex_unlock(vdev->lock);
	return ret;
}

最后调用到vivi_mmap

static const struct v4l2_file_operations vivi_fops = {
	.owner		= THIS_MODULE,
	.open		= v4l2_fh_open,
	.release        = vivi_close,
	.read           = vivi_read,
	.poll		= vivi_poll,
	.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
	.mmap           = vivi_mmap,
};


static int vivi_mmap(struct file *file, struct vm_area_struct *vma)
{
	struct vivi_dev *dev = video_drvdata(file);
	int ret;

	dprintk(dev, 1, "mmap called, vma=0x%08lx\n", (unsigned long)vma);

    //vb2_mmap应该就是根据传进去的offset,最后获取到虚拟映射地址和大小
	ret = vb2_mmap(&dev->vb_vidq, vma);
	dprintk(dev, 1, "vma start=0x%08lx, size=%ld, ret=%d\n",
		(unsigned long)vma->vm_start,
		(unsigned long)vma->vm_end - (unsigned long)vma->vm_start,
		ret);
	return ret;
}

再看一下vb2_mmap

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

	if (q->memory != V4L2_MEMORY_MMAP) {
		dprintk(1, "Queue is not currently set up for mmap\n");
		return -EINVAL;
	}

	/*
	 * Check memory area access mode.
	 */
	if (!(vma->vm_flags & VM_SHARED)) {
		dprintk(1, "Invalid vma flags, VM_SHARED needed\n");
		return -EINVAL;
	}
	if (V4L2_TYPE_IS_OUTPUT(q->type)) {
		if (!(vma->vm_flags & VM_WRITE)) {
			dprintk(1, "Invalid vma flags, VM_WRITE needed\n");
			return -EINVAL;
		}
	} else {
		if (!(vma->vm_flags & VM_READ)) {
			dprintk(1, "Invalid vma flags, VM_READ needed\n");
			return -EINVAL;
		}
	}

	/*
	 * Find the plane corresponding to the offset passed by userspace.
	 */
    //通过offset找到所对应的vb2_buffer对应的index和vb2_plane index
	ret = __find_plane_by_offset(q, off, &buffer, &plane);
	if (ret)
		return ret;

	vb = q->bufs[buffer];
	vb_plane = &vb->planes[plane];

    //第一个参数就是之前申请的内存地址
	ret = q->mem_ops->mmap(vb_plane->mem_priv, vma);
	if (ret)
		return ret;

	vb_plane->mapped = 1;
	vb->num_planes_mapped++;

	dprintk(3, "Buffer %d, plane %d successfully mapped\n", buffer, plane);
	return 0;
}
EXPORT_SYMBOL_GPL(vb2_mmap);

接着看一下vb2_mem_ops.mmap干了什么

const struct vb2_mem_ops vb2_vmalloc_memops = {
	.alloc		= vb2_vmalloc_alloc,
	.put		= vb2_vmalloc_put,
	.vaddr		= vb2_vmalloc_vaddr,
	.mmap		= vb2_vmalloc_mmap,
	.num_users	= vb2_vmalloc_num_users,
};
EXPORT_SYMBOL_GPL(vb2_vmalloc_memops);

static int vb2_vmalloc_mmap(void *buf_priv, struct vm_area_struct *vma)
{
	struct vb2_vmalloc_buf *buf = buf_priv;
	int ret;

	if (!buf) {
		printk(KERN_ERR "No memory to map\n");
		return -EINVAL;
	}

    //根据buf->vaddr转化成对应的vma->vma_start和vma->vma_end
	ret = remap_vmalloc_range(vma, buf->vaddr, 0);
	if (ret) {
		printk(KERN_ERR "Remapping vmalloc memory, error: %d\n", ret);
		return ret;
	}

	/*
	 * Make sure that vm_areas for 2 buffers won't be merged together
	 */
	vma->vm_flags		|= VM_DONTEXPAND;

	/*
	 * Use common vm_area operations to track buffer refcount.
	 */
	vma->vm_private_data	= &buf->handler;
	vma->vm_ops		= &vb2_common_vm_ops;

    //这个open主要做就是增加引用计数
	vma->vm_ops->open(vma);

	return 0;
}

你可能感兴趣的:(v4l2,学习)