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 子系統中得到了充分應用。
- LWN: DMA buffer sharing in 3.3
- Patch: dma-buf: Documentation for buffer sharing framework
- Patch: dma-buf: Introduce dma buffer sharing mechanism
- 第一個使用 dma-buf 的 DRM 分支: drm-prime-dmabuf
概念
dma-buf 的出現就是爲了解決各個驅動之間 buffer 共享的問題,因此它本質上是 buffer 與 file 的結合,即 dma-buf 既是塊物理 buffer,又是個 linux file。buffer 是內容,file 是媒介,只有通過 file 這個媒介才能實現同一 buffer 在不同驅動之間的流轉。
一個典型的 dma-buf 應用框圖如下:
通常,我們將分配 buffer 的模塊稱爲 exporter
,將使用該 buffer 的模塊稱爲 importer
或 user
。但在本系列文章中,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 驅動,因爲它什麼事情也不做。注意:該代碼已經是精簡的不能再精簡了,少一行代碼都不行!
#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個步驟:
dma_buf_ops
DEFINE_DMA_BUF_EXPORT_INFO
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
運行截圖:
在實際運行的過程中,細心的小夥伴可能會發現,該 exporter_dummy.ko 只能被 insmod,無法被 rmmod。關於該問題的原因,我將在《dma-buf 由淺入深(五)—— File》中爲大家解答。
總結
- dma-buf 本質上是 buffer + file 的結合。
- 編寫 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 的使用技巧。
參考資料
下一篇:《dma-buf 由淺入深(二)—— kmap / vmap》
文章彙總:《DRM(Direct Rendering Manager)學習簡介》