dma-buf 由浅入深(一) —— 最简单的 dma-buf 驱动程序
dma-buf 由浅入深(二) —— kmap / vmap
dma-buf 由浅入深(三) —— map attachment
dma-buf 由浅入深(四) —— mmap
dma-buf 由浅入深(五) —— File
dma-buf 由浅入深(六) —— begin / end cpu_access
dma-buf 由浅入深(七) —— alloc page 版本
dma-buf 由浅入深(八) —— ION 简化版
在上一篇《最简单的 dma-buf 驱动程序》中,我们学习了编写 dma-buf 驱动程序的三个基本步骤,即 dma_buf_ops 、 dma_buf_export_info、 dma_buf_export()。在本篇中,我们将在 exporter-dummy 驱动的基础上,对其 dma_buf_ops 的 kmap / vmap 接口进行扩展,以此来演示这两个接口的使用方法。
在内核代码中,我们见得最多的 dma-buf API 莫过于 dma_buf_attach()
、dma_buf_map_attachment()
,以至于有些小伙伴会问:dma-buf 难道只能给 DMA 硬件来访问吗?当然不是!在上一篇的 概念 小节中就曾讲过,dma-buf 本质上是 buffer 与 file 的结合,因此它仍然是一块 buffer。不要看它带了 dma 字样就被迷惑了,dma-buf 不仅能用于 DMA 硬件访问,也同样适用于 CPU 软件访问,这也是 dma-buf 在内核中大受欢迎的一个重要原因。
正因如此,我才决定将 dma_buf_kmap()
/ dma_buf_vmap()
作为 dma-buf 系列教程的第二篇文章来讲解,因为这两个接口使用起来实在是比 DMA 操作接口简单太多了!
当然不是!就和内核中 dma-mapping 接口一样,dma-buf 既可以是物理连续的 buffer,也可以是离散的 buffer,这最终取决于 exporter 驱动采用何种方式来分配 buffer。
因此为了尽量让读者易于理解,本篇特意使用了内核中最简单、最常见的 kzalloc()
函数来分配 dma-buf,自然,这块 buffer 就是物理连续的了。
从 linux-3.4 开始,dma-buf 引入了 CPU 操作接口,使得开发人员可以在内核空间里直接使用 CPU 来访问 dma-buf 的物理内存。
- dma-buf: add support for kernel cpu access
如下 dma-buf API 实现了 CPU 在内核空间对 dma-buf 内存的访问:
dma_buf_kmap()
dma_buf_kmap_atomic()
dma_buf_vmap()
(它们的反向操作分别对应各自的 unmap 接口)
通过 dma_buf_kmap() / dma_buf_vmap() 操作,就可以把实际的物理内存,映射到 kernel 空间,并转化成 CPU 可以连续访问的虚拟地址,方便后续软件直接读写这块物理内存。因此,无论这块 buffer 在物理上是否连续,在经过 kmap / vmap 映射后的虚拟地址一定是连续的。
上述的3个接口分别和 linux 内存管理子系统(MM)中的 kmap()、 kmap_atomic() 和 vmap() 函数一一对应,三者的区别如下:
函数 | 说明 |
---|---|
kmap() | 一次只能映射1个page,可能会睡眠,只能在进程上下文中调用 |
kmap_atomic() | 一次只能映射1个page,不会睡眠,可在中断上下文中调用 |
vmap() | 一次可以映射多个pages,且这些pages物理上可以不连续,只能在进程上下文中调用 |
- 从 linux-4.19 开始,dma_buf_kmap_atomic() 不再被支持。
- dma_buf_ops 中的 map / map_atomic 接口名,其实原本就叫 kmap / kmap_atomic,只是后来发现与 highmem.h 中的宏定义重名了,为了避免开发人员在自己的驱动中引用 highmem.h 而带来的命名冲突问题,于是去掉了前面的“k”字。
想了解更多关于 kmap() 、vmap() 的信息,推荐阅读参考资料中的《Linux内核内存管理架构》一文。
本示例分为 exporter 和 importer 两个驱动。
首先是 exporter 驱动,我们基于上一篇的 exporter-dummy.c,对其 exporter_kmap() 和 exporter_vmap() 进行扩展,具体如下:
exporter-kmap.c
#include
#include
#include
struct dma_buf *dmabuf_exported;
EXPORT_SYMBOL(dmabuf_exported);
static void *exporter_kmap(struct dma_buf *dmabuf, unsigned long page_num)
{
return dmabuf->priv;
}
static void *exporter_kmap_atomic(struct dma_buf *dmabuf, unsigned long page_num)
{
return dmabuf->priv;
}
static void *exporter_vmap(struct dma_buf *dmabuf)
{
return dmabuf->priv;
}
static void exporter_release(struct dma_buf *dmabuf)
{
kfree(dmabuf->priv);
}
...
static const struct dma_buf_ops exp_dmabuf_ops = {
.map = exporter_kmap,
.map_atomic = exporter_kmap_atomic,
.vmap = exporter_vmap,
.release = exporter_release,
...
};
static struct dma_buf *exporter_alloc_page(void)
{
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
struct dma_buf *dmabuf;
void *vaddr;
vaddr = kzalloc(PAGE_SIZE, GFP_KERNEL);
exp_info.ops = &exp_dmabuf_ops;
exp_info.size = PAGE_SIZE;
exp_info.flags = O_CLOEXEC;
exp_info.priv = vaddr;
dmabuf = dma_buf_export(&exp_info);
sprintf(vaddr, "hello world!");
return dmabuf;
}
static int __init exporter_init(void)
{
dmabuf_exported = exporter_alloc_page();
return 0;
}
module_init(exporter_init);
然后我们再编写一个 importer 驱动,用于演示如何在 kernel 空间,通过 dma_buf_kmap()
/ dma_buf_vmap()
接口操作 exporter 驱动导出的 dma-buf。
importer-kmap.c
#include
#include
#include
extern struct dma_buf *dmabuf_exported;
static int importer_test(struct dma_buf *dmabuf)
{
void *vaddr;
vaddr = dma_buf_kmap(dmabuf, 0);
pr_info("read from dmabuf kmap: %s\n", (char *)vaddr);
dma_buf_kunmap(dmabuf, 0, vaddr);
vaddr = dma_buf_vmap(dmabuf);
pr_info("read from dmabuf vmap: %s\n", (char *)vaddr);
dma_buf_vunmap(dmabuf, vaddr);
return 0;
}
static int __init importer_init(void)
{
return importer_test(dmabuf_exported);
}
module_init(importer_init);
示例描述:
内核源码 | 4.14.143 |
示例源码 | hexiaolong2008-GitHub/sample-code/dma-buf/02 |
开发平台 | Ubuntu14.04/16.04 |
运行平台 | my-qemu 仿真环境 |
在 my-qemu 仿真环境中执行如下命令:
# insmod /lib/modules/4.14.143/kernel/drivers/dma-buf/exporter-kmap.ko
# insmod /lib/modules/4.14.143/kernel/drivers/dma-buf/importer-kmap.ko
将看到如下打印结果:
read from dmabuf kmap: hello world!
read from dmabuf vmap: hello world!
注意:执行 insmod 命令时,必须先加载 exporter-kmap.ko,后加载 importer-kmap.ko,否则将出现符号依赖错误。
或者直接使用“modprobe importer_kmap”命令来自动解决模块依赖问题。
通过本篇,我们学习了 dma_buf_kmap()
、dma_buf_vmap()
函数的底层实现,以及如何使用这两个 API,它们是 CPU 在 kernel 空间访问 dma-buf 的典型代表。在下一篇,我们将一起来学习如何通过 DMA 硬件来访问 dma-buf 的物理内存。
wahaha02博客:Linux内核内存管理架构
i915 drm selftests: mock_dmabuf.c
上一篇:《dma-buf 由浅入深(一)—— 对简单的 dma-buf 驱动程序》
下一篇:《dma-buf 由浅入深(三)—— map attachment》
文章汇总:《DRM(Direct Rendering Manager)学习简介》