LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

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;
}

 

posted on 2024-04-08 23:59  ArnoldLu  阅读(1149)  评论(0编辑  收藏  举报

导航