v4l2应用框架-摄像头v4l2编程(08)_申请缓存VIDIOC_REQBUFS

v4l2应用框架-摄像头v4l2编程(9)_申请缓存VIDIOC_REQBUFS

使用VIDIOC_REQBUFS命令调用ioctl,最终会调用到vb2_reqbufs函数,内核使用vb2_reqbufs函数创建缓冲区。
用户空间:

ret = ioctl(dev->fd, VIDIOC_REQBUFS, &rb);

内核空间:
头文件

[include/media/videobuf2-core.h]
// q-缓冲区队列数据结构struct vb2_queue指针
// req-申请缓冲区所需信息存放的结构体,应用需要设置里面的成员
// 返回值-0成功,小于0失败
int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req);
[include/uapi/linux/videodev2.h]
struct v4l2_requestbuffers {
     __u32  count;       // 申请缓冲区的数量,一帧图像对应一个缓冲区
     __u32  type;		// 缓冲区类型,摄像头为V4L2_BUF_TYPE_VIDEO_CAPTURE
     __u32  memory;		// 通常为V4L2_MEMORY_MMAP或V4L2_MEMORY_USERPTR
     __u32  reserved[2]; // 保留
};

实现

[drivers/media/v4l2-core/videobuf2-v4l2.c]
int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
    int ret = vb2_verify_memory_type(q, req->memory, req->type);

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

vb2_reqbufs调用流程可总结如下:
(1)验证缓冲区的memory type和buffer type是否正确。
__verify_memory_type函数用于校验缓冲区类型和缓冲区内存类型。
vb2_queue中的缓冲区类型type和v4l2_requestbuffers中的缓冲区类型type需一致。
缓冲区内存类型必须是V4L2_MEMORY_MMAP、V4L2_MEMORY_USERPTR、V4L2_MEMORY_DMABUF其中之一。
若是V4L2_MEMORY_MMAP类型,则q->io_modes必须设置为VB2_MMAP,mem_ops->alloc、q->mem_ops->put和q->mem_ops->mmap的函数指针也必须设置。
若是V4L2_MEMORY_USERPTR类型,则q->io_modes必须设置为VB2_USERPTR,mem_ops->get_userptr和q->mem_ops->put_userptr的函数指针也必须设置。
若是V4L2_MEMORY_DMABUF类型,则q->io_modes必须设置为VB2_DMABUF,mem_ops->attach_dmabuf、q->mem_ops->detach_dmabuf、q->mem_ops->map_dmabuf和q->mem_ops->unmap_dmabuf的函数指针也必须设置。
(2)判断缓冲区参数是否正确,若不正确,则需要做一些处理。
申请的缓冲区数量为0或缓冲区队列中缓冲区数量不为0或申请的缓冲区内存类型和缓冲区队列中缓冲区内存类型不一致,则进入额外的处理逻辑。
若内存类型为V4L2_MEMORY_MMAP且缓冲区正在使用,则直接返回错误。清理处于PREPARED或QUEUED状态的缓冲区并释放缓冲区内存。
(3)计算需要分配的缓冲区数量。
(4)调用驱动实现的函数queue_setup,驱动函数需要设置num_buffers、num_buffers、q->plane_sizes和q->alloc_ctx。
(5)调用__vb2_queue_alloc分配缓冲区内存。此时缓冲区的状态为VB2_BUF_STATE_DEQUEUED。后面详细说明该函数。
(6)若分配的缓冲区数量小于需要分配的数量,需要再次调用驱动提供的queue_setup函数。
(7)设置分配的缓冲区数量并向应用返回分配的缓冲区数量。

    vb2_reqbufs
      // 验证缓冲区的memory type和buffer type是否正确
        ->__verify_memory_type
        ->__reqbufs
            // 申请的缓冲区数量为0或缓冲区队列中缓冲区数量不为0或
            // 申请的缓冲区内存类型和缓冲区队列中缓冲区内存类型不一致,则需要则额外的处理
            if (req->count == 0 || q->num_buffers != 0 || q->memory != req->memory) {
                mutex_lock(&q->mmap_lock);
                // 内存类型为V4L2_MEMORY_MMAP且缓冲区正在使用,则直接返回错误
                if (q->memory == V4L2_MEMORY_MMAP && __buffers_in_use(q)) {
                    mutex_unlock(&q->mmap_lock);
                    return -EBUSY;
                }
                // 若缓冲区处于PREPARED或QUEUED状态,则需要清理,
                // 一般在申请了缓冲区,但没调用STREAMON,会出现这种情况
                __vb2_queue_cancel(q);
                // 释放已经分配内存的缓冲区
                ret = __vb2_queue_free(q, q->num_buffers);
                mutex_unlock(&q->mmap_lock);
            }
            // 缓冲区数量取应用申请的数量和最大数量中的较小值,VIDEO_MAX_FRAME定义为32
            num_buffers = min_t(unsigned int, req->count, VIDEO_MAX_FRAME);
            // 缓冲区数量取num_buffers和需要最少的缓冲区数量的较大值
            num_buffers = max_t(unsigned int, num_buffers, q->min_buffers_needed);
            // 设置缓冲区队列中缓冲区的内存模型,内存模型应该和应用申请的模型保持一致
            q->memory = req->memory;
            // 回调用驱动提供的queue_setup函数,imx6ull提供的函数为mx6s_videobuf_setup
            // 驱动函数需要设置num_buffers、num_buffers、q->plane_sizes和q->alloc_ctx
            call_qop(...queue_setup...)
            // 分配缓冲区内存
            ->__vb2_queue_alloc
            // 若分配的缓冲区数量小于需要分配的数量,需要再次调用驱动提供的queue_setup函数
            if (!ret && allocated_buffers < num_buffers) {
                num_buffers = allocated_buffers;
                // 若驱动能处理这种情况,则不会返回错误,若无法处理,则会返回错误
                // mx6s_videobuf_setup不具备这种功能
                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;
            }
            ->mutex_lock(&q->mmap_lock);  // 缓冲区共享成员访问需要同步
            // 设置分配的缓冲区数量
            q->num_buffers = allocated_buffers;
            ->mutex_unlock(&q->mmap_lock);
            req->count = allocated_buffers  // 向应用返回分配的缓冲区数量

queue_setup函数需要驱动提供。imx6ull提供的函数为mx6s_videobuf_setup,主要的作用是设置__reqbufs函数中的缓冲区数量num_buffers、plane的数量num_planes、plane的大小q->plane_sizes及q->alloc_ctx。上述设置的变量都是调用函数以指针的形式传入。

    static int mx6s_videobuf_setup(struct vb2_queue *vq,
                const struct v4l2_format *fmt,
                unsigned int *count, unsigned int *num_planes,
                unsigned int sizes[], void *alloc_ctxs[])
    {
        struct mx6s_csi_dev *csi_dev = vb2_get_drv_priv(vq);
        alloc_ctxs[0] = csi_dev->alloc_ctx;  // 设置alloc_ctxs
        sizes[0] = csi_dev->pix.sizeimage;   // 设置plane_sizes为图像的大小
        if (0 == *count)  // 如果传入的缓冲区数量为0,则设置为32
            *count = 32;
        // 如果num_planes为0且缓冲区占用的总内存超过了规定的最大值,则要重新计算缓冲区数量
        // 最大值为64MB,MAX_VIDEO_MEM定义为64
        if (!*num_planes && sizes[0] * *count > MAX_VIDEO_MEM * 1024 * 1024)
            // 则缓冲区数量按最大内存计算
            *count = (MAX_VIDEO_MEM * 1024 * 1024) / sizes[0];
        *num_planes = 1;  // 设置plane的数量为1
        return 0;
    }

缓冲区分配主要由__vb2_queue_alloc函数实现。分配num_buffers个缓冲区,即分配num_buffers个struct vb2_buffer结构退。所有的缓冲区内存地址都保存到vb2_queue结构体中的bufs数组中。若是缓冲区内存是V4L2_MEMORY_MMAP类型,则还需要额外分配保存图像的缓冲区,一个缓冲区分配num_planes个保存图像的缓冲区。此缓冲区由vb2_dc_alloc分配。分配完后缓冲器的结构示意如下图所示。V4L2_MEMORY_MMAP类型的缓冲区需要分配额外的内存空间用于存储图像数据,如图中绿框所属,首选分配一个管理的结构体struct vb2_dc_buf,再分配真正存储图像数据的缓冲区,存储图像的缓冲区物理地址和虚拟地址一致,其虚拟地址保存到管理结构体的vaddr成员中,虚拟地址保存到管理结构体的dma_addr成员中,缓冲区大小保存到管理结构体的size成员中。
在这里插入图片描述

    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个缓冲区
        for (buffer = 0; buffer < num_buffers; ++buffer) {
            // 分配缓冲区内存,缓冲区大小为buf_struct_size,一般由驱动设置,
            // imx6ull平台设置为sizeof(struct mx6s_buffer)
            vb = kzalloc(q->buf_struct_size, GFP_KERNEL);

            // 如果是multiplanar buffers,则缓冲区长度length保存的是plane的数量
            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;  // 设置plane数量
            vb->v4l2_buf.index = q->num_buffers + buffer;  // 设置缓冲区编号
            vb->v4l2_buf.type = q->type;   // 设置缓冲区类型
            vb->v4l2_buf.memory = memory;  // 设置缓冲区内存类型

            // 对于V4L2_MEMORY_MMAP类型,则还需要分配额外的内存用于保存图像数据
            // 然后映射到用户空间,用户可以直接读取额外内存中的数据
            if (memory == V4L2_MEMORY_MMAP) {
                
                // ******分配存储图像数据内存的函数*******
                ->__vb2_buf_mem_alloc(vb);
                    // 每一个缓冲区,分配num_planes块额外的内存
                    for (plane = 0; plane < vb->num_planes; ++plane) {
                        // 分配的内存大小按页对齐
                        unsigned long size = PAGE_ALIGN(q->plane_sizes[plane]);
                        // 调用驱动提供的alloc函数进行内存分配,
                        // imx6ull平台调用vb2_dc_alloc函数
                        mem_priv = call_ptr_memop(vb, alloc, q->alloc_ctx[plane],
                                    size, dma_dir, q->gfp_flags);
                        // 将额外分配的内存保存到mem_priv成员中
                        vb->planes[plane].mem_priv = mem_priv;
                        // 保存长度
                        vb->v4l2_planes[plane].length = q->plane_sizes[plane];
                    }

                // 调用驱动提供的buf_init函数进行初始化,imx6ull没有提供
                ->call_vb_qop(vb, buf_init, vb);
            }
            // 保存缓冲区地址
            q->bufs[q->num_buffers + buffer] = vb;
        }
        // 设置所有缓冲区的每个plane的长度
        ->__setup_lengths(q, buffer);
            vb->v4l2_planes[plane].length = q->plane_sizes[plane]
        // MMAP类型还要设置偏移,每个buffer的每个plane偏移都不一样
        if (memory == V4L2_MEMORY_MMAP)
            ->__setup_offsets(q, buffer);
        return buffer;
    }

vb2_dc_alloc由驱动提供,其分配的缓冲区虚拟地址和物理地址都连续,属于第三种类型的videobuf2。

    [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);
        // 然后分配存储图像数据的缓冲区,此缓冲区的物理地址和虚拟地址都连续
        buf->vaddr = dma_alloc_coherent(dev, size, &buf->dma_addr, GFP_KERNEL | gfp_flags);
        buf->dev = get_device(dev);  // 设置父设备指针
        buf->size = size;  // 保存图像数据缓冲区的大小
        buf->dma_dir = dma_dir;  // 记录DMA传输方向
        // 设置struct vb2_vmarea_handler结构体
        buf->handler.refcount = &buf->refcount;
        buf->handler.put = vb2_dc_put;  // 回调函数
        buf->handler.arg = buf;  // 回调函数参数
        atomic_inc(&buf->refcount);  // 增加引用计数,引用计数为0时释放缓冲区
        return buf;  // 返回分配的vb2_dc_buf地址
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值