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 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 <linux/dma-buf.h>
#include <linux/module.h>
#include <linux/slab.h>

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 <linux/dma-buf.h>
#include <linux/module.h>
#include <linux/slab.h>

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!

注意:執行 lsmod 命令時,必須先加載 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)學習簡介》

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