dma-buf 由浅入深(二) —— kmap / vmap

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_opsdma_buf_export_infodma_buf_export()。在本篇中,我们将在 exporter-dummy 驱动的基础上,对其 dma_buf_opskmap / vmap 接口进行扩展,以此来演示这两个接口的使用方法。

dma-buf 只能用于 DMA 硬件访问吗?

在内核代码中,我们见得最多的 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-buf 只能分配离散 buffer 吗?

当然不是!就和内核中 dma-mapping 接口一样,dma-buf 既可以是物理连续的 buffer,也可以是离散的 buffer,这最终取决于 exporter 驱动采用何种方式来分配 buffer。
因此为了尽量让读者易于理解,本篇特意使用了内核中最简单、最常见的 kzalloc() 函数来分配 dma-buf,自然,这块 buffer 就是物理连续的了。

CPU Access

从 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物理上可以不连续,只能在进程上下文中调用
  1. 从 linux-4.19 开始,dma_buf_kmap_atomic() 不再被支持。
  2. 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);

示例描述:

  1. exporter 通过 kzalloc 分配了一个 PAGE 大小的物理连续 buffer,并向该 buffer 写入了“hello world!” 字符串;
  2. importer 驱动通过 extern 关键字导入了 exporter 的 dma-buf,并通过 dma_buf_kmap()dma_buf_vmap() 函数读取该 buffer 的内容并输出到终端显示。

开发环境

内核源码 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)学习简介》

你可能感兴趣的:(DRM,(Direct,Rendering,Manager),dma-buf,DRM,内存管理)