dma-buf 由浅入深(四) —— mmap

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 由浅入深(二) —— kmap/vmap》和《dma-buf 由浅入深(三) —— map attachment》都是在 kernel space 对 dma-buf 进行访问的,本篇我们将一起来学习,如何在 user space 访问 dma-buf。当然,user space 访问 dma-buf 也属于 CPU Access 的一种。

mmap

为了方便应用程序能直接在用户空间读写 dma-buf 的内存,dma_buf_ops 为我们提供了一个 mmap 回调接口,可以把 dma-buf 的物理内存直接映射到用户空间,这样应用程序就可以像访问普通文件那样访问 dma-buf 的物理内存了。

dma-buf: mmap support

在 Linux 设备驱动中,大多数驱动的 mmap 操作接口都是通过调用 remap_pfn_range() 函数来实现的,dma-buf 也不例外。对于此函数不了解的同学,推荐阅读 参考资料 中彭东林的博客,写的非常好!

除了 dma_buf_ops 提供的 mmap 回调接口外,dma-buf 还为我们提供了 dma_buf_mmap() 内核 API,使得我们可以在其他设备驱动中就地取材,直接引用 dma-buf 的 mmap 实现,以此来间接的实现设备驱动的 mmap 文件操作接口。

示例

dma-buf 由浅入深(四) —— mmap_第1张图片

接下来,我们将通过两个示例来演示如何在 Userspace 访问 dma-buf 的物理内存。

  • 示例一:直接使用 dma-buf 的 fd 做 mmap() 操作
  • 示例二:使用 exporter 的 fd 做 mmap() 操作

示例一

本示例主要演示如何在驱动层实现 dma-buf 的 mmap 回调接口,以及如何在用户空间直接使用 dma-buf 的 fd 进行 mmap() 操作。

exporter 驱动

首先,我们仍然基于第一篇的 exporter-dummy.c 驱动来实现 mmap 回调接口:

exporter-fd.c

#include 
#include 
#include 
#include 
#include 

struct dma_buf *dmabuf_exported;
EXPORT_SYMBOL(dmabuf_exported);

static int exporter_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
{
	void *vaddr = dmabuf->priv;

	return remap_pfn_range(vma, vma->vm_start, virt_to_pfn(vaddr),
				PAGE_SIZE, vma->vm_page_prot);
}

...

static const struct dma_buf_ops exp_dmabuf_ops = {
	...
	.mmap = exporter_mmap,
};

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 long exporter_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int fd = dma_buf_fd(dmabuf_exported, O_CLOEXEC);
	copy_to_user((int __user *)arg, &fd, sizeof(fd));

	return 0;
}
 
static struct file_operations exporter_fops = {
	.owner		= THIS_MODULE,
	.unlocked_ioctl	= exporter_ioctl,
};
 
static struct miscdevice mdev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "exporter",
	.fops = &exporter_fops,
};
 
static int __init exporter_init(void)
{
	dmabuf_exported = exporter_alloc_page();
	return misc_register(&mdev);
}

static void __exit exporter_exit(void)
{
	misc_deregister(&mdev);
}

module_init(exporter_init);
module_exit(exporter_exit);

从上面的示例可以看到,除了要实现 dma-buf 的 mmap 回调接口外,我们还引入了 misc driver,目的是想通过 misc driver 的 ioctl 接口将 dma-buf 的 fd 传递给上层应用程序,这样才能实现应用程序直接使用这个 dma-buf fd 做 mmap() 操作。

为什么非要通过 ioctl 的方式来传递 fd ?这个问题我会在下一篇《dma-buf 由浅入深(五)—— File》中详细讨论。

ioctl 接口中,我们使用到了 dma_buf_fd() 函数,该函数用于创建一个新的 fd,并与该 dma-buf 的文件相绑定。关于该函数,我也会在下一篇中做详细介绍。

userspace 程序

mmap_dmabuf.c

int main(int argc, char *argv[])
{
	int fd;
	int dmabuf_fd = 0;

	fd = open("/dev/exporter", O_RDONLY);
	ioctl(fd, 0, &dmabuf_fd);
	close(fd);

	char *str = mmap(NULL, 4096, PROT_READ, MAP_SHARED, dmabuf_fd, 0);
	printf("read from dmabuf mmap: %s\n", str);

	return 0;
}

可以看到 userspace 的代码非常简单,首先通过 exporter 驱动的 ioctl() 获取到 dma-buf 的 fd,然后直接使用该 fd 做 mmap() 映射,最后使用 printf() 来输出映射后的 buffer 内容。

运行结果

在 my-qemu 仿真环境中执行如下命令:

# insmod /lib/modules/4.14.143/kernel/drivers/dma-buf/exporter-fd.ko
# ./mmap_dmabuf

将看到如下打印结果:

read from dmabuf mmap: hello world!

可以看到,userspace 程序通过 mmap() 接口成功的访问到 dma-buf 的物理内存。

关于应用程序直接使用 dma-buf fd 做 mmap() 操作的案例,Google ADF 的 simple_buffer_alloc 可谓在这一点上发挥的淋漓尽致!详细参考代码如下:

  • kernel-3.18/drivers/video/adf/adf_fops.c
  • android9_r3/bootable/recovery/minui/graphics_adf.cpp


备注:上层 minui 获取到的 surf->fd 其实就是 dma-buf 的 fd。Recovery 模式下应用程序绘图本质上就是 CPU 通过 mmap() 来操作 dma-buf 的物理内存。


示例二

本示例主要演示如何使用 dma_buf_mmap() 内核 API,以此来简化设备驱动的 mmap 文件操作接口的实现。

exporter 驱动

我们基于示例一中的 exporter-fd.c 文件,删除 exporter_ioctl() 函数,新增 exporter_misc_mmap() 函数, 具体修改如下:

exporter-mmap.c

#include 
#include 
#include 
#include 
#include 

struct dma_buf *dmabuf_exported;
EXPORT_SYMBOL(dmabuf_exported);

static int exporter_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
{
	void *vaddr = dmabuf->priv;

	return remap_pfn_range(vma, vma->vm_start, virt_to_pfn(vaddr),
				PAGE_SIZE, vma->vm_page_prot);
}

...
static const struct dma_buf_ops exp_dmabuf_ops = {
	...
	.mmap = exporter_mmap,
};

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 exporter_misc_mmap(struct file *file, struct vm_area_struct *vma)
{
	return dma_buf_mmap(dmabuf_exported, vma, 0);
}

static struct file_operations exporter_fops = {
	.owner	= THIS_MODULE,
	.mmap	= exporter_misc_mmap,
};
 
static struct miscdevice mdev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "exporter",
	.fops = &exporter_fops,
};
 
static int __init exporter_init(void)
{
	dmabuf_exported = exporter_alloc_page();
	return misc_register(&mdev);
}

static void __exit exporter_exit(void)
{
	misc_deregister(&mdev);
}

module_init(exporter_init);
module_exit(exporter_exit);

与示例一的驱动相比,示例二的驱动不再需要把 dma-buf 的 fd 通过 ioctl 传给上层,而是直接将 dma-buf 的 mmap 回调接口嫁接到 misc driver 的 mmap 文件操作接口上。这样上层在对 misc device 进行 mmap() 操作时,实际映射的是 dma-buf 的物理内存。

userspace 程序

mmap_exporter.c

int main(int argc, char *argv[])
{
	int fd;

	fd = open("/dev/exporter", O_RDONLY);

	char *str = mmap(NULL, 4096, PROT_READ, MAP_SHARED, fd, 0);
	printf("read from /dev/exporter mmap: %s\n", str);

	close(fd);

	return 0;
}

与示例一的 userspace 程序相比,示例二不再通过 ioctl() 方式获取 dma-buf 的 fd,而是直接使用 exporter misc device 的 fd 进行 mmap() 操作,此时执行的则是 misc driver 的 mmap 文件操作接口。当然最终输出的结果都是一样的。

运行结果

在 my-qemu 仿真环境中执行如下命令:

# insmod /lib/modules/4.14.143/kernel/drivers/dma-buf/exporter-mmap.ko
# ./mmap_exporter

将看到如下打印结果:

read from /dev/exporter mmap: hello world!

开发环境

内核源码 4.14.143
示例源码 hexiaolong2008-GitHub/sample-code/dma-buf/04
hexiaolong2008-GitHub/sample-code/dma-buf/05
开发平台 Ubuntu14.04/16.04
运行平台 my-qemu 仿真环境

参考资料

  1. 认真分析mmap:是什么 为什么 怎么用
  2. 内存映射函数remap_pfn_range学习——示例分析(1)



上一篇:《dma-buf 由浅入深(三)—— map attachment》
下一篇:《dma-buf 由浅入深(五)—— File》
文章汇总:《DRM(Direct Rendering Manager)学习简介》

你可能感兴趣的:(DMA-BUF)