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 文件操作接口。

示例

在這裏插入圖片描述

接下來,我們將通過兩個示例來演示如何在 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 <linux/dma-buf.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

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 可謂在這一點上發揮的淋漓盡致!詳細參考代碼如下:


備註:上層 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 <linux/dma-buf.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

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)學習簡介》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章