本文主要讲的是NXP的imx8mm,源码是由NXP提供的,不同的下游厂家的开发板也应该是一样的。
这个是buffer操作相关的核心结构体:
struct vb2_queue {
...
struct mutex *lock;
void *owner;
/* 下面的3个结构体非常重要,是操作的核心,通过它们可以很好的了解代码层次 */
/* 主要作用是将导出驱动的buffer queue管理,比如来自用户空间的buffer的初始化、
* 队列管理、流等相关操作
*/
const struct vb2_ops *ops;
/* 可能会调用3中不同的buf操作函数,主要区别是使用不同的内存分配方式
* vb2_dma_contig_memops - videobuf2-dma-contig.c
* vb2_dma_sg_memops - videobuf2-dma-sg.c
* vb2_vmalloc_memops - videobuf2-vmalloc.c
*/
const struct vb2_mem_ops *mem_ops;
/* 可以不用关心这个结构体,主要的作用是在用户空间和内核空间传递buffer的信息
* 默认情况下由vb2的core提供
*/
const struct vb2_buf_ops *buf_ops;
...
/* private: internal use only */
struct mutex mmap_lock;
unsigned int memory;
enum dma_data_direction dma_dir;
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;
spinlock_t done_lock;
wait_queue_head_t done_wq;
struct device *alloc_devs[VB2_MAX_PLANES];
...
};
static struct vb2_ops mx6s_videobuf_ops = {
.queue_setup = mx6s_videobuf_setup,
.buf_prepare = mx6s_videobuf_prepare,
.buf_queue = mx6s_videobuf_queue,
.wait_prepare = vb2_ops_wait_prepare,
.wait_finish = vb2_ops_wait_finish,
.start_streaming = mx6s_start_streaming,
.stop_streaming = mx6s_stop_streaming,
};
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);
};
struct vb2_buf_ops {
int (*verify_planes_array)(struct vb2_buffer *vb, const void *pb);
void (*fill_user_buffer)(struct vb2_buffer *vb, void *pb);
int (*fill_vb2_buffer)(struct vb2_buffer *vb, const void *pb,
struct vb2_plane *planes);
void (*copy_timestamp)(struct vb2_buffer *vb, const void *pb);
};
所有的操作中,弄清楚内存分配和映射是重要的,只要弄清楚这两个操作,其它的操作都非常好理解。比如:在应用程序中的VIDIOC_REQBUFS的操作,会依次调用到
static int __vb2_queue_alloc(...)
{
unsigned int buffer, plane;
struct vb2_buffer *vb;
for (buffer = 0; buffer < num_buffers; ++buffer) {
vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
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;
for (plane = 0; plane < num_planes; ++plane) {
vb->planes[plane].length = plane_sizes[plane];
vb->planes[plane].min_length = plane_sizes[plane];
}
/* 这里将q和vb联系起来了
* 实际就是struct vb2_queue和struct vb2_buffer
*/
q->bufs[vb->index] = vb;
/* Allocate video buffer memory for the MMAP type */
if (memory == VB2_MEMORY_MMAP) {
/* 实际的内存分配函数 */
ret = __vb2_buf_mem_alloc(vb);
/* 没有实现这个函数,不会执行实际的函数体 */
ret = call_vb_qop(vb, buf_init, vb);
}
}
return buffer;
}
为指定的buffer分配视频内存:
static int __vb2_buf_mem_alloc(struct vb2_buffer *vb)
{
struct vb2_queue *q = vb->vb2_queue;
void *mem_priv;
int plane;
for (plane = 0; plane < vb->num_planes; ++plane) {
unsigned long size = PAGE_ALIGN(vb->planes[plane].length);
mem_priv = call_ptr_memop(vb, alloc,
q->alloc_devs[plane] ? : q->dev,
q->dma_attrs, size, q->dma_dir, q->gfp_flags);
/* 将分配的内存地址保存到这里 */
vb->planes[plane].mem_priv = mem_priv;
}
}
视频buffer映射:
int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma)
{
unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
struct vb2_buffer *vb;
unsigned int buffer = 0, plane = 0;
vb = q->bufs[buffer];
length = PAGE_ALIGN(vb->planes[plane].length);
/* vb->planes[plane].mem_priv保存有request函数申请的mda内存 */
ret = call_memop(vb, mmap, vb->planes[plane].mem_priv, vma);
}
通过分析上面的两个函数可以知道,request函数会申请装载视频数据的buffer,而mmap函数会将申请到的buffuer映射到应用程序的用户空间中。从而用户空间可以操作对应的数据buffer。
以buffer内存分配为例,buffer分配有3种方式:
struct vb2_mem_ops vb2_dma_contig_memops; //videobuf2-dma-contig.c
struct vb2_mem_ops vb2_dma_sg_memops; //videobuf2-dma-sg.c
struct vb2_mem_ops vb2_vmalloc_memops; //videobuf2-vmalloc.c
内存的映射也对应着3种方式。
驱动中的映射函数:
static int vb2_dc_mmap(void *buf_priv, struct vm_area_struct *vma)
{
struct vb2_dc_buf *buf = buf_priv;
ret = dma_mmap_attrs(buf->dev, vma, buf->cookie,
buf->dma_addr, buf->size, buf->attrs);
vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
vma->vm_private_data = &buf->handler;
vma->vm_ops = &vb2_common_vm_ops;
vma->vm_ops->open(vma);
}
将dma分配的内存映射到用户空间。
dma_mmap_attrs(struct device *dev, struct vm_area_struct *vma, void *cpu_addr,
dma_addr_t dma_addr, size_t size, unsigned long attrs)
{
const struct dma_map_ops *ops = get_dma_ops(dev);
if (ops->mmap){
/* 这里调用的是哪里的函数?怎么分析?
* 在arch/arm64/mm/dma-mapping.c有具体的实现
* 使用快捷键cs查找
*/
return ops->mmap(dev, vma, cpu_addr, dma_addr, size, attrs);
}
return dma_common_mmap(dev, vma, cpu_addr, dma_addr, size);
}
最终会调用到位于arch/arm64/mm/dma-mapping.c中的3种实现方式之一:
struct dma_map_ops swiotlb_dma_ops ;
struct dma_map_ops dummy_dma_ops ;
struct dma_map_ops iommu_dma_ops ;
链接: V4L2框架
链接: V4L2之设备注册
链接: V4L2之mmap()函数
链接: V4L2之events
链接: V4L2之buffer分配和映射