dma-buf 由淺入深(一) —— 最簡單的 dma-buf 驅動程序

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 簡化版


前言

最近因爲工作內容發生了變化,導致《最簡單的DRM驅動程序》系列文章暫停了更新,但我仍然會堅持把它寫完。由於在後面講解 DRM 驅動程序的過程中,會不可避免的介紹到 dma-buf,因此這裏將該模塊單獨提取出來,形成一個系列文章,爲後續講解 DRM PRIME 打下基礎。

如果你和我一樣,是一位從事Android多媒體底層開發的工程師,那麼你對 dma-buf 這個詞語一定不會陌生,因爲不管是 Video、Camera 還是 Display、GPU,它們的buffer都來自於ION,而 ION 正是基於 dma-buf 實現的。

假如你對 dma-buf 的理解並不深刻,又期望找個時間來徹底公關一下,那麼很高興,這幾篇文章一定能讓你對 dma-buf 有個更深入、更透徹的理解。

歷史

dma-buf 最初的原型爲 shrbuf,由 Marek Szyprowski (Samsung)於2011年8月2日首次提出,他實現了 “Buffer Sharing” 的概念驗證(Proof-of-Concept),並在三星平臺的 V4L2 驅動中實現了 camera 與 display 的 buffer 共享問題。該 patch 發表後,在內核社區引起了巨大反響,因爲當時關於 buffer 共享問題很早就開始討論了,但是由於內核沒有現成的框架支持,導致各個廠商實現的驅動五花八門,此時急需要一個統一的框架來解決 buffer 共享問題。

於是 Sumit Semwal (Linaro) 基於 Marek Szyprowski 的 patch 重構了一套新的框架,也就是我們今天看到的 dma-buf 核心代碼,它經歷了社區開發人員給出的重重考驗,並最終於 2012 年 2 月 merge 到了 Linux-3.3 主線版本中,這也是 dma-buf 的第一個正式版本。此後 dma-buf 被廣泛應用於內核多媒體驅動開發中,尤其在 V4L2、DRM 子系統中得到了充分應用。

概念

dma-buf 的出現就是爲了解決各個驅動之間 buffer 共享的問題,因此它本質上是 buffer 與 file 的結合,即 dma-buf 既是塊物理 buffer,又是個 linux file。buffer 是內容,file 是媒介,只有通過 file 這個媒介才能實現同一 buffer 在不同驅動之間的流轉。

一個典型的 dma-buf 應用框圖如下:
在這裏插入圖片描述

通常,我們將分配 buffer 的模塊稱爲 exporter,將使用該 buffer 的模塊稱爲 importeruser。但在本系列文章中,importer 特指內核空間的使用者,user 特指用戶空間的使用者。

有的人習慣將 exporter 說成是生產者,importer 說成是消費者,我個人認爲這樣的說法並不嚴謹。舉例來說,Android 系統中,graphic buffer 都是由 ION 來分配的,GPU 負責填充該 buffer,DPU 負責顯示該 buffer。那麼在這裏,ION 則是 exporter,GPU 和 DPU 則都是 importer。但是從生產者/消費者模型來講,GPU 則是生產者,DPU 是消費者,因此不能片面的認爲 exporter 就是生產者。

最簡單的 dma-buf 驅動程序

如下代碼演示瞭如何編寫一個最簡單的 dma-buf 驅動程序,我將其稱爲 dummy 驅動,因爲它什麼事情也不做。注意:該代碼已經是精簡的不能再精簡了,少一行代碼都不行!

exporter-dummy.c

#include <linux/dma-buf.h>
#include <linux/module.h>

static struct sg_table *exporter_map_dma_buf(struct dma_buf_attachment *attachment,
					 enum dma_data_direction dir)
{
	return NULL;
}

static void exporter_unmap_dma_buf(struct dma_buf_attachment *attachment,
			       struct sg_table *table,
			       enum dma_data_direction dir)
{
}

static void exporter_release(struct dma_buf *dmabuf)
{
}

static void *exporter_kmap_atomic(struct dma_buf *dmabuf, unsigned long page_num)
{
	return NULL;
}

static void *exporter_kmap(struct dma_buf *dmabuf, unsigned long page_num)
{
	return NULL;
}

static int exporter_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
{
	return -ENODEV;
}

static const struct dma_buf_ops exp_dmabuf_ops = {
	.map_dma_buf = exporter_map_dma_buf,
	.unmap_dma_buf = exporter_unmap_dma_buf,
	.release = exporter_release,
	.map_atomic = exporter_kmap_atomic,
	.map = exporter_kmap,
	.mmap = exporter_mmap,
};

static int __init exporter_init(void)
{
	DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
	struct dma_buf *dmabuf;

	exp_info.ops = &exp_dmabuf_ops;
	exp_info.size = PAGE_SIZE;
	exp_info.flags = O_CLOEXEC;
	exp_info.priv = "null";

	dmabuf = dma_buf_export(&exp_info);

	return 0;
}

module_init(exporter_init);

從上面的代碼來看,要實現一個 dma-buf exporter驅動,需要執行3個步驟:

  1. dma_buf_ops
  2. DEFINE_DMA_BUF_EXPORT_INFO
  3. dma_buf_export()

注意: 其中 dma_buf_ops 的回調接口中,如下接口又是必須要實現的,缺少任何一個都將導致 dma_buf_export() 函數調用失敗!

  • map_dma_buf
  • unmap_dma_buf
  • map
  • map_atomic
  • mmap
  • release

從 linux-4.19 開始,map_atomic 接口被廢棄,map 和 mmap 接口不再被強制要求。

開發環境

內核源碼 4.14.143
示例源碼 hexiaolong2008-GitHub/sample-code/dma-buf/01
開發平臺 Ubuntu14.04/16.04
運行平臺 my-qemu 仿真環境

編譯

dma-buf 的核心代碼由 CONFIG_DMA_SHARED_BUFFER 宏來控制是否參與編譯,而該 config 並不是一個顯式的菜單項,我們無法直接在 menuconfig 菜單中找到它,因此我這裏就直接簡單粗暴的修改 Kconfig 文件,設置 default y 來實現 dma-buf.c 的強制編譯:

linux-4.14.43/drivers/base/Kconfig:

config DMA_SHARED_BUFFER
	bool
	default y

或者你也可以通過 menuconfig 菜單選擇那些依賴 dma-buf 的設備驅動,如 DRM VGEM。

然後編譯 exporter_dummy.ko 文件,並打包到 my-qemu 環境中。

運行

在 my-qemu 仿真環境中執行如下命令:

# insmod /lib/modules/4.14.143/kernel/drivers/dma-buf/exporter-dummy.ko
# lsmod
exporter_dummy 16384 1 - Live 0x7f000000

通過如下命令來查看 dma-buf 的相關信息:

# cat /sys/kernel/debug/dma_buf/bufinfo
Dma-buf Objects:
size		flags		mode		count		exp_name
00004096	00000000	00000005	00000001	exporter_dummy
		Attached Devices:
Total 0 devices attached

Total 1 objects, 4096 bytes

運行截圖:
my-qemu運行截圖
在實際運行的過程中,細心的小夥伴可能會發現,該 exporter_dummy.ko 只能被 insmod,無法被 rmmod。關於該問題的原因,我將在《dma-buf 由淺入深(五)—— File》中爲大家解答。

總結

  1. dma-buf 本質上是 buffer + file 的結合。
  2. 編寫 dma-buf 驅動的三個步驟:
    (1)dma_buf_ops
    (2)DEFINE_DMA_BUF_EXPORT_INFO
    (3)dma_buf_export()

通過本篇我們學習瞭如何編寫一個最簡單的 dma-buf 驅動程序。但是該驅動什麼事情也做不了,因爲它的 dma_buf_ops 回調函數都是空的。從下一篇起,我們將一步步實現 dma_buf_ops 的回調函數,讓大家一步步的掌握 dma-buf 的使用技巧。

參考資料

敬葉:Linux DMA-BUF




下一篇:《dma-buf 由淺入深(二)—— kmap / vmap》
文章彙總:《DRM(Direct Rendering Manager)學習簡介》

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