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地址
}
835

被折叠的 条评论
为什么被折叠?



