Linux DMA-BUF
DMA-BUF基本概念
简介
如果设备驱动想要共享DMA缓冲区,可以让一个驱动来导出,一个驱动来使用。类似于消费者生产者模型,Linux DMA-BUF就是基于这种方式来实现的。
Linux内核中的DMA BUF这部分代码主要由Sumit Semwal贡献,可以在Linux内核源码树目录下执行如下命令查看相关提交。
git log --author="Sumit Semwal"
生产者要完成的工作:
- 实现对缓冲区的管理操作
- 允许使用者通过
dma_buf
共享接口来共享内存 - 管理内存分配的细节
- 决定实际的存储位置
- 管理散列表迁移
使用者的好处:
- 可以有很多使用者共用这部分内存
- 不需要关心怎样分配,以及分配在何处
- 需要一个入口去访问散列表,以映射到自己的地址空间
基本用法
DMA BUF只能操作设备DMA缓冲区,使用上包括如下步骤。
- 生产者声明要导出缓冲
- 用户空间获取关联缓冲区的文件描述符,将描述符传递给潜在的消费者
- 在使用前,每个用户将自己连接到缓冲区
- 需要使用时,用户向生产者申请访问缓冲
- 使用结束时,用户向生产者通知EOD(end of DMA)
- 当完全不需要使用缓冲区的时候,用户断开和缓冲区的连接
使用步骤
1. 导出缓冲
struct dma_buf *dma_buf_export_named(void *priv, struct dma_buf_ops *ops, size_t size, int flags, const char *exp_name); #define dma_buf_export(priv, ops, size, flags) \ dma_buf_export_named(priv, ops, size, flags, __FILE__)
- return
- 失败返回NULL
- exp_name
- 生产者的名字
- dma_buf_export()
- 最后一个参数传递
KBUILD_MODNAME
分配一个dma_buf
将其加入到全局链表db_list
中。
2. 获取文件描述符
int dma_buf_fd(struct dma_buf *dmabuf, int flags);
- return
- 失败返回ERROR
获取一个空闲的文件描述符来管理dmabuf->file
。
3. 连接缓冲
连接的作用是让生产者知道设备缓冲的约束。在这一步完成之后,生产者仍可以不实际分配存储,但是当使用者请求要访问的时候就必须要准备好存储。
struct dma_buf *dma_buf_get(int fd); struct dma_buf_attachment *dma_buf_attach(struct dma_buf *dmabuf, struct device *dev)
4. 申请访问
struct sg_table * dma_buf_map_attachment(struct dma_buf_attachment *, enum dma_data_direction); void dma_buf_unmap_attachment(struct dma_buf_attachment *, struct sg_table *);
这两个函数必须有生产者来实现,注意的是这两个函数将加锁的任务丢给生产者了。
6. 断开连接
void dma_buf_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *dmabuf_attach); void dma_buf_put(struct dma_buf *dmabuf);
注意事项
连接和断开的目的是让生产者了解约束。如果在申请访问并已经分配好存储之后,需要迁移存储,而此时又有人试图连接,应当在此时允许连接。如果新的用户有更多的约束,那么应该在申请的时候阻塞,直到其它使用者通知结束,再将缓冲区移动到新的区域。如果生产者不能满足约束,那么在连接的时候就应该返回错误。
工作原理
概念图
生产者和消费者模型如下所示,首先声明要导出缓冲区,并提供一个接口来导出文件描述符。因为是匿名文件,所以不论是用户空间还是内核空间,都没有办法直接看到文件描述符,要共享缓冲区又必须借助文件描述符。内核空间相对好办一点,可以直接调用生产者提供的接口来获取描述符,而用户空间要获取描述就没有标准接口可以使用,所以只好通过特定的ioctl来获取文件描述符。
Figure 1: DMA BUF机理
更形象的例子如下所示。
Figure 2: DMA BUF示例
应用示例
以V4L2为例,核心数据结构为vb2_mem_ops
,该结构的定义在include/media/videobuf2-core.h
中,在drivers/media/v4l2-core/videobuf2-dma-sg.c
中实现,在drivers/media/v4l2-core/videobuf2-dma-contig.c
中也有实现。这里我们只讨论和DMA BUF相关的部分,并且以videobuf2-dma-sg.c
为对象进行研究,由于videobuf2-dma-contig.c
内存连续,相对更好处理,详细可以自行对比。
const struct vb2_mem_ops vb2_dma_sg_memops = { .alloc = vb2_dma_sg_alloc, .put = vb2_dma_sg_put, .get_userptr = vb2_dma_sg_get_userptr, .put_userptr = vb2_dma_sg_put_userptr, .prepare = vb2_dma_sg_prepare, .finish = vb2_dma_sg_finish, .vaddr = vb2_dma_sg_vaddr, .mmap = vb2_dma_sg_mmap, .num_users = vb2_dma_sg_num_users, .get_dmabuf = vb2_dma_sg_get_dmabuf, .map_dmabuf = vb2_dma_sg_map_dmabuf, .unmap_dmabuf = vb2_dma_sg_unmap_dmabuf, .attach_dmabuf = vb2_dma_sg_attach_dmabuf, .detach_dmabuf = vb2_dma_sg_detach_dmabuf, .cookie = vb2_dma_sg_cookie, };
函数vb2_expbuf()
用于以文件描述符导出缓冲区,这个函数就是调用vb2_mem_ops->get_dmabuf
来进行导出的。
int vb2_expbuf(struct vb2_queue *q, struct v4l2_exportbuffer *eb) { // no ERROR check struct vb2_buffer *vb = NULL; struct vb2_plane *vb_plane; int ret; struct dma_buf *dbuf; vb = q->bufs[eb->index]; vb_plane = &vb->planes[eb->plane]; dbuf = call_memop(q, get_dmabuf, vb_plane->mem_priv); ret = dma_buf_fd(dbuf, eb->flags); dprintk(3, "buffer %d, plane %d exported as %d descriptor\n", eb->index, eb->plane, ret); eb->fd = ret; return 0; } static struct dma_buf *vb2_dma_sg_get_dmabuf(void *buf_priv, unsigned long flags) { struct vb2_dma_sg_buf *buf = buf_priv; struct dma_buf *dbuf; if (WARN_ON(!buf->dma_sgt)) return NULL; dbuf = dma_buf_export(buf, &vb2_dma_sg_dmabuf_ops, buf->size, flags, NULL); if (IS_ERR(dbuf)) return NULL; atomic_inc(&buf->refcount); // dmabuf keeps reference to vb2 buffer return dbuf; }
这里vb2_dma_sg_buf
就是exporter的核心数据结构,这个数据结构中包括了用于管理缓冲区的sg_table
,有必要可以了解一下这个结构体是如何初始化的,可以参考vb2_mem_ops->alloc()
,也就是vb2_dma_sg_alloc()
。比较上层的函数是vb2_reqbufs()
,用于申请内存。
要导出缓冲区,最关键是要定义一组缓冲区的操作方法供用户使用,这组操作方法便由vb2_dma_sg_dmabuf_ops
提供。
static struct dma_buf_ops vb2_dma_sg_dmabuf_ops = { .attach = vb2_dma_sg_dmabuf_ops_attach, .detach = vb2_dma_sg_dmabuf_ops_detach, .map_dma_buf = vb2_dma_sg_dmabuf_ops_map, .unmap_dma_buf = vb2_dma_sg_dmabuf_ops_unmap, .kmap = vb2_dma_sg_dmabuf_ops_kmap, .kmap_atomic = vb2_dma_sg_dmabuf_ops_kmap, .vmap = vb2_dma_sg_dmabuf_ops_vmap, .mmap = vb2_dma_sg_dmabuf_ops_mmap, .release = vb2_dma_sg_dmabuf_ops_release, };
- map_dma_buf/unmap_dma_buf
前面的attach和detach并没有获取锁,这是因为在函数
dma_buf_attach()
已经获取了锁了。而在dma_buf_map_attachment()
中却没有获取锁,所以这里面就需要去获取锁。至于获取锁的原因,因为在这里面会检查缓存DMA方向,如果不是NONE就会去unmap,不加锁就可能unmap一个正在使用的散列表,所以加锁是必要的。
至于为什么不在
dma_buf_map_attachment()
中加锁,可以参考 dma-buf: don't hold the mutex around map/unmap calls,因为在外部没有什么需要保护的,所以直接转移到dmabuf->ops->map_dma_buf()
中。由于代码中采用了取巧的做法,即在map的时候采取检查并做unmap的工作,所以unmap实际是什么工作也没有做。这样做存在一个问题,就是最后一次传递之后就无法unmap了。
可以通过如下方式获取文件描述符。
int buffer_export(int v4lfd, enum v4l2_buf_type bt, int index, int *dmafd) { struct v4l2_exportbuffer expbuf; memset(&expbuf, 0, sizeof(expbuf)); expbuf.type = bt; expbuf.index = index; // int ioctl(int fd, int request, struct v4l2_exportbuffer *argp); if (ioctl(v4lfd, VIDIOC_EXPBUF, &expbuf) == -1) { perror("VIDIOC_EXPBUF"); return -1; } *dmafd = expbuf.fd; return 0; }
参考资料
- 2012 Sumit Semwal. DMA Buffer Sharing API Guide
- 2012 Rob Clark, Sumit Semwal. DMA Buffer Sharing Framework: An Introduction
- 2013 Lucas Stach, Philipp Zabel. Next-Generation DMABUF
- 2012 Rob Clark. GStreamer and dmabuf
- 2014 LinuxTV Developers. Linux Media Infrastructure API
- 2012 git log qmx6_kernel/drivers/base/dma-buf.c
- 2012 dma-buf: Introduce dma buffer sharing mechanism
- 2012 dma-buf: mmap support
- 2012 Jonathan Corbet. DMA buffer sharing in 3.3
- 2011 Jonathan Corbet. Sharing buffers between devices
- 2012 Thomas M. Zeng. The Android ION memory allocator
- 2014 Bottomley. Dynamic DMA mapping using the generic device