Linux v4l2子系统(3):video buffer
1. videobuffer介绍
videobuf2用于链接v4l2驱动层与用户层,提供数据传输通道,它可以分配并管理视频帧数据。
videobuf层实现了很多ioctl函数,包括buffer分配、入队、出队和数据流控制。
video buffer支持三种类型的struct vb2_mem_ops:
- vb2_dma_sg_memops(DMA scatter/gather memory):在虚拟地址和物理地址上都是分散。几乎所有用户空间buffer都是这种类型,在内核空间中,这种类型的buffer并不总是能够满足需要,因为这要求硬件可以进行分散的DMA操作。
- vb2_vmalloc_memops(vmalloc memory):虚拟地址连续,物理分散。也就是通过vmalloc分配的buffer,换句话说很难使用DMA来操作这些buffer。
- vb2_dma_contig_memops(DMA contig memory):在分段式系统上面分配这种类型的buffer是不可靠的,但是简单DMA控制器只能够适用于这种类型的buffer。
trace events调试接口:
/sys/kernel/debug/tracing/events/vb2
/sys/kernel/debug/tracing/events/v4l2
2. videobufer数据结构
struct v4l2_buffer用于用户空间和内核驱动交换数据,struct vb_buffer是缓存队列的基本单位。
当开始IO流是,帧以v4l2_buffer格式在应用和驱动之间传输,一个缓冲区可以有三种状态:
在驱动输入队列中:用户空间通过VIDIOC_QBUF把缓冲区,驱动程序将对此队列中的缓冲区进行处理。对于一个视频捕捉设备,输入队列中的缓冲区是空的,驱动将会往里面填充数据。
在驱动输出队列中:当驱动将一个输入队列中的缓冲区填满后,就转移到输出队列中,等待用户空间处理。
用户空间处理:用户空间通过VIDIOC_DQBUF将缓冲区数据取出,通过mmap等方式进行处理。
当用户空间拿到v4l2_buffer,可以获取到缓冲区相关信息。
bytesused使图像所占字节数,如果是mmap方式,m.offset是内核空间图像数据存放的起始地址,传递给mmap函数作为一个偏移,通过mmap映射返回一个缓冲区指针p;p+bytesused就是图像数据在进程虚拟地址空间所占区域。
struct v4l2_buffer { __u32 index; __u32 type; __u32 bytesused; __u32 flags; __u32 field; struct timeval timestamp; struct v4l2_timecode timecode; __u32 sequence; /* memory location */ __u32 memory; union { __u32 offset; unsigned long userptr; struct v4l2_plane *planes; __s32 fd; } m; __u32 length; __u32 reserved2; __u32 reserved; }; struct vb2_buffer { struct vb2_queue *vb2_queue;-------------------当前缓存所在的缓存队列。 unsigned int index;------------------------当前buffer在vb2_queue上的序号,vb2_queue->bufs[index]可以找到此缓存。 unsigned int type;-------------------------v4l2_buf_type类型,用户空间设定内存的类型。 unsigned int memory;-----------------------vb2_memory类型,mmap/userptr/dmabuf三者之一,表示缓存处理方式。 unsigned int num_planes; struct vb2_plane planes[VB2_MAX_PLANES]; /* private: internal use only * * state: current buffer state; do not change * queued_entry: entry on the queued buffers list, which holds * all buffers queued from userspace * done_entry: entry on the list that stores all buffers ready * to be dequeued to userspace */ enum vb2_buffer_state state;--------------------当前buffer所处状态 struct list_head queued_entry;------------------存放用户空间VIDIOC_QBUF后的缓存链表入口点,vb2_core_qbuf()时插入,vb2_core_dqbuf()时移除。 struct list_head done_entry;--------------------待dequeue到用户空间的buffer入口,放入vb2_queue->done_list链表中。vb2_buffer_done()执行插入动作,__vb2_get_done_vb()执行移除动作。 #ifdef CONFIG_VIDEO_ADV_DEBUG ... #endif };
vb2_buffer在vb2_queue的不同链表上移动,也表现于不同的状态。
vb2_buffer->queued_entry和vb2_buffer->done_entry分别在vb2_queue->queued_list和vb2_queue->done_list两个链表上插入/移出。
vb2_core_qbuf()将vb2_buffer->queued_entry加入到vb2_queue->queued_list中,vb2_buffer->state变为VB2_BUF_STATE_QUEUED。
vb2_core_dqbuf()将vb2_buffer_queued_entry移除,vb2_buffer->state变成VB2_BUF_STATE_DEQUEUED。
在vb2_core_dqbuf()->__vb2_get_done_vb()将vb2_buffer->done_entry移除,vb2_buffer_done()中将vb2_buffer->done_entry加入到vb2_queue->done_list中。
enum vb2_buffer_state { VB2_BUF_STATE_DEQUEUED,------------------------------将vb2_buffer->queued_entry从vb2_queue->queued_list上移出,缓存交给用户空间处理。 VB2_BUF_STATE_PREPARING,----------------------------- VB2_BUF_STATE_PREPARED, VB2_BUF_STATE_QUEUED,--------------------------------表示缓存被加入到vb2_queue->queued_list中。vb2_core_qbuf()设置。 VB2_BUF_STATE_REQUEUEING, VB2_BUF_STATE_ACTIVE,--------------------------------当前的缓存正在被驱动处理。 VB2_BUF_STATE_DONE,----------------------------------驱动处理完缓存放入vb2_queue->done_list链表中,但是还没有交给用户空间进行处理。 VB2_BUF_STATE_ERROR, };
3. vb2_queue
struct vb2_queue代表一个video buffer队列,struct vb2_buffer是队列中的成员。
3.1 vb2队列重要操作函数集
struct vb2_ops是管理队列中vb2_buffer的函数集,具体实例是由具体的驱动提供的。比如UVC由uvc_queue_init()初始化,实例为uvc_queue_qops;DW MIPI设备对应vb2_video_qops。
struct vb2_mem_ops是操作vb2_buffer内存的函数集,有几种不同方式vb2_vmalloc_memops、vb2_dma_sg_memops、vb2_dma_contig_memops。
struct vb2_buf_ops主要操作vb2_buffer或者v4l2_buffer结构体,在vb2_queue_init()对vb2_queue进行初始化的时候指定为v4l2_buf_ops。
vb2_ops和vb2_mem_ops有具体的驱动选择合适的方式,vb2_buf_ops和具体的驱动无关。
struct vb2_queue {
unsigned int type;
unsigned int io_modes;---------------------访问IO的方式:mmap、userptr等
unsigned fileio_read_once:1;
unsigned fileio_write_immediately:1;
unsigned allow_zero_bytesused:1;
struct mutex *lock;
void *owner;
const struct vb2_ops *ops;---------------------buffer队列操作函数集,操作的对象时vb2_buffer。
const struct vb2_mem_ops *mem_ops;-----------------buffer memory操作函数集
const struct vb2_buf_ops *buf_ops;
void *drv_priv;
unsigned int buf_struct_size;
u32 timestamp_flags;
gfp_t gfp_flags;
u32 min_buffers_needed;
/* private: internal use only */
struct mutex mmap_lock;
unsigned int memory;
struct vb2_buffer *bufs[VB2_MAX_FRAME];---------代表每个buffer
unsigned int num_buffers;-------------------分配的buffer个数
struct list_head queued_list;
unsigned int queued_count;
atomic_t owned_by_drv_count;
struct list_head done_list;
spinlock_t done_lock;
wait_queue_head_t done_wq;
void *alloc_ctx[VB2_MAX_PLANES];
unsigned int plane_sizes[VB2_MAX_PLANES];
unsigned int streaming:1;
unsigned int start_streaming_called:1;
unsigned int error:1;
unsigned int waiting_for_buffers:1;
unsigned int is_multiplanar:1;
unsigned int is_output:1;
unsigned int last_buffer_dequeued:1;
struct vb2_fileio_data *fileio;
struct vb2_threadio_data *threadio;
#ifdef CONFIG_VIDEO_ADV_DEBUG
...
#endif
};
struct vb2_ops {
int (*queue_setup)(struct vb2_queue *q, const void *parg,
unsigned int *num_buffers, unsigned int *num_planes,
unsigned int sizes[], void *alloc_ctxs[]);---------------队列初始化,在真正的memory分配之前,由VIDIOC_REQBUFS和VIDIOC_CREATE_BUFS两个ioctl调用。
void (*wait_prepare)(struct vb2_queue *q);--------------------------释放所有在ioctl操作函数执行时被持有的锁;该回调函数在ioctl需要等待一个新的buffer到达的时候被调用;需要在阻塞访问的时候避免死锁。
void (*wait_finish)(struct vb2_queue *q);---------------------------请求所有的在上面wait_prepare()回调所释放的锁,需要在睡眠等待新的buffer到来之后继续运行。
int (*buf_init)(struct vb2_buffer *vb);-----------------------------在mmap方式下,分配完buffer或者USRPTR情况下请求完buffer之后被调用一次。
int (*buf_prepare)(struct vb2_buffer *vb);--------------------------每次重新入队VIDIOC_QBUF时候以及VIDIOC_PREPARE_BUF操作时被调用,驱动可以做一些硬件操作之前的初始化工作。
void (*buf_finish)(struct vb2_buffer *vb);--------------------------每次buffer被取出时调用,并且在buffer到达用户空间之前,所以驱动可以访问/修改buffer内容,buffer状态可以是VB2_BUF_STATE_DONE/VB2_BUF_STATE_ERROR/VB2_BUF_STATE_DEQUEUED/VB2_BUF_STATE_PREPARED。
void (*buf_cleanup)(struct vb2_buffer *vb);-------------------------buffer被释放之前调用一次,每个buffer仅有一次,驱动可以在这里面做一些额外操作。
int (*start_streaming)(struct vb2_queue *q, unsigned int count);----进入streaming状态时被调用一次,一般情况下,驱动在该回调函数执行之前通过buf_queue()回调来接收buffer,这里驱动需要把buffer放到驱动自己维护的队列里面。count是已经被queue的buffer数量,驱动可以获取他。
void (*stop_streaming)(struct vb2_queue *q);------------------------在streaming被禁止的时候调用,驱动需要关闭DMA或者等待DMA结束,调用vb2_buffer_done()来归还驱动持有的buffers(),可能需要用到vb2_wait_for_all_buffers()来等待所有的buffer,该函数是用来等待所有的buffer被归还给videobuf2.
void (*buf_queue)(struct vb2_buffer *vb);---------------------------传递vb给驱动,驱动可以在这里开启硬件操作。驱动填充buffer之后需要调用vb2_buffer_done()归还buffer,该函数总是在VIDIOC_STREAMON
操作之后调用。
};
struct vb2_buf_ops {
int (*verify_planes_array)(struct vb2_buffer *vb, const void *pb);
int (*fill_user_buffer)(struct vb2_buffer *vb, void *pb);
int (*fill_vb2_buffer)(struct vb2_buffer *vb, const void *pb,
struct vb2_plane *planes);
int (*set_timestamp)(struct vb2_buffer *vb, const void *pb);
};
struct vb2_mem_ops {
void *(*alloc)(void *alloc_ctx, 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);----获取struct dma_buf数据结构
void *(*get_userptr)(void *alloc_ctx, 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)(void *alloc_ctx, 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);-------------------------返回用户空间使用buffer数目
int (*mmap)(void *buf_priv, struct vm_area_struct *vma);-------把缓冲区映射到用户空间。
};
3.2 v4l2_buf_ops
static const struct vb2_buf_ops v4l2_buf_ops = { .verify_planes_array = __verify_planes_array_core,----------验证v4l2_buffer的planes。 .fill_user_buffer = __fill_v4l2_buffer,---------------------更新v4l2_buffer数据结构,返回给用户空间。 .fill_vb2_buffer = __fill_vb2_buffer,-----------------------根据用户空间提供的v4l2_buffer数据结构田中内核使用的vb2_buffer,并且验证vb2_buffer->planes。 .set_timestamp = __set_timestamp,----------------------- };
下面是v4l2_buf_ops主要成员解释。
static int __fill_vb2_buffer(struct vb2_buffer *vb, const void *pb, struct vb2_plane *planes)--------------参数pb是用户空间传入的v4l2_buffer类型数据结构。 { struct vb2_queue *q = vb->vb2_queue; const struct v4l2_buffer *b = pb; struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); unsigned int plane; int ret; ret = __verify_length(vb, b); if (ret < 0) { dprintk(1, "plane parameters verification failed: %d\n", ret); return ret; } if (b->field == V4L2_FIELD_ALTERNATE && q->is_output) { dprintk(1, "the field is incorrectly set to ALTERNATE " "for an output buffer\n"); return -EINVAL; } vbuf->timestamp.tv_sec = 0; vbuf->timestamp.tv_usec = 0; vbuf->sequence = 0; if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) { ... if (V4L2_TYPE_IS_OUTPUT(b->type)) { for (plane = 0; plane < vb->num_planes; ++plane) { struct vb2_plane *pdst = &planes[plane]; struct v4l2_plane *psrc = &b->m.planes[plane]; if (psrc->bytesused == 0) vb2_warn_zero_bytesused(vb); if (vb->vb2_queue->allow_zero_bytesused) pdst->bytesused = psrc->bytesused; else pdst->bytesused = psrc->bytesused ? psrc->bytesused : pdst->length; pdst->data_offset = psrc->data_offset; } } } else { ... if (V4L2_TYPE_IS_OUTPUT(b->type)) { if (b->bytesused == 0) vb2_warn_zero_bytesused(vb); if (vb->vb2_queue->allow_zero_bytesused) planes[0].bytesused = b->bytesused; else planes[0].bytesused = b->bytesused ? b->bytesused : planes[0].length; } else planes[0].bytesused = 0; } vbuf->flags = b->flags & ~V4L2_BUFFER_MASK_FLAGS; if ((vb->vb2_queue->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) != V4L2_BUF_FLAG_TIMESTAMP_COPY || !V4L2_TYPE_IS_OUTPUT(b->type)) { vbuf->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; } if (V4L2_TYPE_IS_OUTPUT(b->type)) { vbuf->flags &= ~V4L2_BUF_FLAG_TIMECODE; vbuf->field = b->field; } else { vbuf->flags &= ~V4L2_BUFFER_OUT_FLAGS; } return 0; } static int __fill_v4l2_buffer(struct vb2_buffer *vb, void *pb) { struct v4l2_buffer *b = pb; struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct vb2_queue *q = vb->vb2_queue; unsigned int plane; /* Copy back data such as timestamp, flags, etc. */ b->index = vb->index; b->type = vb->type; b->memory = vb->memory; b->bytesused = 0; b->flags = vbuf->flags; b->field = vbuf->field; b->timestamp = vbuf->timestamp; b->timecode = vbuf->timecode; b->sequence = vbuf->sequence; b->reserved2 = 0; b->reserved = 0; if (q->is_multiplanar) { b->length = vb->num_planes; for (plane = 0; plane < vb->num_planes; ++plane) { struct v4l2_plane *pdst = &b->m.planes[plane]; struct vb2_plane *psrc = &vb->planes[plane]; pdst->bytesused = psrc->bytesused; pdst->length = psrc->length; if (q->memory == VB2_MEMORY_MMAP) pdst->m.mem_offset = psrc->m.offset; else if (q->memory == VB2_MEMORY_USERPTR) pdst->m.userptr = psrc->m.userptr; else if (q->memory == VB2_MEMORY_DMABUF) pdst->m.fd = psrc->m.fd; pdst->data_offset = psrc->data_offset; memset(pdst->reserved, 0, sizeof(pdst->reserved)); } } else { b->length = vb->planes[0].length; b->bytesused = vb->planes[0].bytesused; if (q->memory == VB2_MEMORY_MMAP)----------------------mmap类型缓存更新offset。 b->m.offset = vb->planes[0].m.offset; else if (q->memory == VB2_MEMORY_USERPTR) b->m.userptr = vb->planes[0].m.userptr; else if (q->memory == VB2_MEMORY_DMABUF) b->m.fd = vb->planes[0].m.fd; } b->flags &= ~V4L2_BUFFER_MASK_FLAGS; b->flags |= q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK; if ((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) != V4L2_BUF_FLAG_TIMESTAMP_COPY) { b->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; b->flags |= q->timestamp_flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK; } 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; case VB2_BUF_STATE_DONE: b->flags |= V4L2_BUF_FLAG_DONE; break; case VB2_BUF_STATE_PREPARED: b->flags |= V4L2_BUF_FLAG_PREPARED; break; case VB2_BUF_STATE_PREPARING: case VB2_BUF_STATE_DEQUEUED: case VB2_BUF_STATE_REQUEUEING: break; } if (vb2_buffer_in_use(q, vb)) b->flags |= V4L2_BUF_FLAG_MAPPED; return 0; } static int __set_timestamp(struct vb2_buffer *vb, const void *pb) { const struct v4l2_buffer *b = pb; struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct vb2_queue *q = vb->vb2_queue; if (q->is_output) { if ((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) == V4L2_BUF_FLAG_TIMESTAMP_COPY) vbuf->timestamp = b->timestamp; vbuf->flags |= b->flags & V4L2_BUF_FLAG_TIMECODE; if (b->flags & V4L2_BUF_FLAG_TIMECODE) vbuf->timecode = b->timecode; } return 0; }; static int __verify_planes_array_core(struct vb2_buffer *vb, const void *pb) { return __verify_planes_array(vb, pb); } static int __verify_planes_array(struct vb2_buffer *vb, const struct v4l2_buffer *b) { if (!V4L2_TYPE_IS_MULTIPLANAR(b->type)) return 0; /* Is memory for copying plane information present? */ if (NULL == b->m.planes) { dprintk(1, "multi-planar buffer passed but " "planes array not provided\n"); return -EINVAL; } if (b->length < vb->num_planes || b->length > VB2_MAX_PLANES) { dprintk(1, "incorrect planes array length, " "expected %d, got %d\n", vb->num_planes, b->length); return -EINVAL; } return 0; }
3.3 vb2_vmalloc_memops
下面就来看看vb2_vmalloc_memops函数集。
const struct vb2_mem_ops vb2_vmalloc_memops = { .alloc = vb2_vmalloc_alloc,----------------------------分配size大小的虚拟地址连续的内存,被VIDIOC_REQBUFS调用。 .put = vb2_vmalloc_put,--------------------------------释放缓存资源 .get_userptr = vb2_vmalloc_get_userptr, .put_userptr = vb2_vmalloc_put_userptr, #ifdef CONFIG_HAS_DMA .get_dmabuf = vb2_vmalloc_get_dmabuf, #endif .map_dmabuf = vb2_vmalloc_map_dmabuf, .unmap_dmabuf = vb2_vmalloc_unmap_dmabuf, .attach_dmabuf = vb2_vmalloc_attach_dmabuf, .detach_dmabuf = vb2_vmalloc_detach_dmabuf, .vaddr = vb2_vmalloc_vaddr,-----------------------------获取当前vb2_vmalloc_buf缓存的vaddr .mmap = vb2_vmalloc_mmap, .num_users = vb2_vmalloc_num_users,-------------------------当前vb2_vmalloc_buf有几个使用者,即其refcount。 };
下面是vb2_vmalloc_memops主要成员解释。
static void *vb2_vmalloc_alloc(void *alloc_ctx, 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 NULL; buf->size = size;-------------------------------------------传入的size表示缓存大小 buf->vaddr = vmalloc_user(buf->size);-----------------------vmalloc_user()分配大小为size的虚拟连续空间,并且初始化为0。同时将对应vma区域flags置为VM_USERMAP。在VMALLOC_START-VMALLOC_END之间。 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 NULL; } atomic_inc(&buf->refcount); return buf; } static void vb2_vmalloc_put(void *buf_priv) { struct vb2_vmalloc_buf *buf = buf_priv; if (atomic_dec_and_test(&buf->refcount)) { vfree(buf->vaddr);-------------------------------------如果没有人使用,释放内存。 kfree(buf);--------------------------------------------释放vb2_vmalloc_buf本身。 } } static void *vb2_vmalloc_vaddr(void *buf_priv) { struct vb2_vmalloc_buf *buf = buf_priv; if (!buf->vaddr) { pr_err("Address of an unallocated plane requested " "or cannot map user pointer\n"); return NULL; } return buf->vaddr; } static unsigned int vb2_vmalloc_num_users(void *buf_priv) { struct vb2_vmalloc_buf *buf = buf_priv; return atomic_read(&buf->refcount); } static int vb2_vmalloc_mmap(void *buf_priv, struct vm_area_struct *vma) { struct vb2_vmalloc_buf *buf = buf_priv; int ret; if (!buf) { pr_err("No memory to map\n"); return -EINVAL; } ret = remap_vmalloc_range(vma, buf->vaddr, 0); if (ret) { pr_err("Remapping vmalloc memory, error: %d\n", ret); return ret; } vma->vm_flags |= VM_DONTEXPAND; vma->vm_private_data = &buf->handler; vma->vm_ops = &vb2_common_vm_ops; vma->vm_ops->open(vma); return 0; }
3.4 vb2_dma_contig_memops
对于使用DMA Contig进行内存分配释放,vb2_mem_ops指向的是vb2_dma_contig_memops。
const struct vb2_mem_ops vb2_dma_contig_memops = { .alloc = vb2_dc_alloc, .put = vb2_dc_put, .get_dmabuf = vb2_dc_get_dmabuf, .cookie = vb2_dc_cookie, .vaddr = vb2_dc_vaddr, .mmap = vb2_dc_mmap, .get_userptr = vb2_dc_get_userptr, .put_userptr = vb2_dc_put_userptr, .prepare = vb2_dc_prepare, .finish = vb2_dc_finish, .map_dmabuf = vb2_dc_map_dmabuf, .unmap_dmabuf = vb2_dc_unmap_dmabuf, .attach_dmabuf = vb2_dc_attach_dmabuf, .detach_dmabuf = vb2_dc_detach_dmabuf, .num_users = vb2_dc_num_users, };
vb2_dc_mmap会被应用层mmap调用,进行内核内存到用户空间的映射。
static int vb2_dc_mmap(void *buf_priv, struct vm_area_struct *vma) { struct vb2_dc_buf *buf = buf_priv; int ret; if (!buf) { printk(KERN_ERR "No buffer to map\n"); return -EINVAL; } vma->vm_pgoff = 0; 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);-------------------------------------------------------调用vb2_common_vm_open(),将vb2_vmarea_handler->refcount加1。 return 0; } static inline int 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) { struct dma_map_ops *ops = get_dma_ops(dev); BUG_ON(!ops); if (ops->mmap)---------------------------------------------------------------如果平台定义了dma_map_ops->mmap()则优先使用。 return ops->mmap(dev, vma, cpu_addr, dma_addr, size, attrs); return dma_common_mmap(dev, vma, cpu_addr, dma_addr, size);------------------否则使用平台提供的dma_common_mmap()。 } static int csky_dma_mmap(struct device *dev, struct vm_area_struct *vma, void *cpu_addr, dma_addr_t dma_addr, size_t size, unsigned long attrs) { int ret = -ENXIO; unsigned long user_count = vma_pages(vma); unsigned long count = PAGE_ALIGN(size) >> PAGE_SHIFT; unsigned long pfn = dma_to_phys(dev, dma_addr) >> PAGE_SHIFT; unsigned long off = vma->vm_pgoff; vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); #if (LINUX_VERSION_CODE >> 8) == (KERNEL_VERSION(4,9,0) >> 8) if (dma_mmap_from_coherent(dev, vma, cpu_addr, size, &ret))------------------如果不是从per-device中申请的连续内存,则处理失败,进入下面映射。 #else if (dma_mmap_from_dev_coherent(dev, vma, cpu_addr, size, &ret)) #endif return ret; if (off < count && user_count <= (count - off)) { ret = remap_pfn_range(vma, vma->vm_start, pfn + off, user_count << PAGE_SHIFT, vma->vm_page_prot); } return ret; } //===========================================================================================
以用户空间mmap申请大小640*480*12/8=0x70800字节,对齐到PAGE,大小为0x71000。
vma的映射空间由mmap分配。
所以csky_dma_mmap()中的size为0x71000,count为113个。
同时vma空间大小也对应为0x7100,所以user_count也为113个。
对应分配的dma_addr地址为0x40080000,所以pfn为262272,且off为0。
这些参数组合输入到remap_pfn_range()表示,将页面号262272开始的共113个页面的物理地址映射到,虚拟地址空间0x2ac17000~0x2ac88000。在remap_pfn_range()中进行MMU设置等操作。
log如下:
mmap buffer_0 v_add=0x2ac17000 size=0x70800
mmap buffer_1 v_add=0x2ac88000 size=0x70800
mmap buffer_2 v_add=0x2acf9000 size=0x70800
mmap buffer_0 v_add=0x2ac17000 size=0x2f7600
mmap buffer_1 v_add=0x2af0f000 size=0x2f7600
mmap buffer_2 v_add=0x2b207000 size=0x2f7600
[ 29.889093] csky_dma_mmap dma_addr=0x40080000 size=0x00071000 off=0 pfn=262272 count=113 user_count=113, vm_start=0x2ac17000, vm_end=0x2ac88000
[ 29.889352] csky_dma_mmap dma_addr=0x40100000 size=0x00071000 off=0 pfn=262400 count=113 user_count=113, vm_start=0x2ac88000, vm_end=0x2acf9000
[ 29.889539] csky_dma_mmap dma_addr=0x40180000 size=0x00071000 off=0 pfn=262528 count=113 user_count=113, vm_start=0x2acf9000, vm_end=0x2ad6a000
[ 31.584531] csky_dma_mmap dma_addr=0x40100000 size=0x002f8000 off=0 pfn=262400 count=760 user_count=760, vm_start=0x2ac17000, vm_end=0x2af0f000
[ 31.585779] csky_dma_mmap dma_addr=0x40400000 size=0x002f8000 off=0 pfn=263168 count=760 user_count=760, vm_start=0x2af0f000, vm_end=0x2b207000
[ 31.586896] csky_dma_mmap dma_addr=0x40700000 size=0x002f8000 off=0 pfn=263936 count=760 user_count=760, vm_start=0x2b207000, vm_end=0x2b4ff000
//===========================================================================================
int dma_common_mmap(struct device *dev, struct vm_area_struct *vma,
void *cpu_addr, dma_addr_t dma_addr, size_t size)
{
int ret = -ENXIO;
#if defined(CONFIG_MMU) && !defined(CONFIG_ARCH_NO_COHERENT_DMA_MMAP)
unsigned long user_count = vma_pages(vma);
unsigned long count = PAGE_ALIGN(size) >> PAGE_SHIFT;
unsigned long pfn = page_to_pfn(virt_to_page(cpu_addr));------------------------------cpu_addr是虚拟地址,如果虚拟地址在HIGHMEM,那么则不能通过virt_to_page()进行转换。
unsigned long off = vma->vm_pgoff;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
if (dma_mmap_from_coherent(dev, vma, cpu_addr, size, &ret))
return ret;
if (off < count && user_count <= (count - off)) {
ret = remap_pfn_range(vma, vma->vm_start,
pfn + off,
user_count << PAGE_SHIFT,
vma->vm_page_prot);
}
#endif /* CONFIG_MMU && !CONFIG_ARCH_NO_COHERENT_DMA_MMAP */
return ret;
}