DMA-BUF API使用指南
by JHJ(jianghuijun211@gmail.com)
轉載出自:http://blog.csdn.net/crazyjiang
本文將會告訴驅動開發者什么是dma-buf共享緩沖區接口,如何作為一個生產者及消費者使用共享緩沖區。
任何一個設備驅動想要使用DMA共享緩沖區,就必須為緩沖區的生產者或者消費者。
如果驅動A想用驅動B創建的緩沖區,那么我們稱B為生成者,A為消費者。
生產者:
-
實現和管理緩沖區的操作函數[1];
-
允許其他消費者通過dma-buf接口函數共享緩沖區;
-
實現創建緩沖區的細節;
-
決定在什么存儲設備上申請內存;
-
管理scatterlist的遷徙;
消費者:
-
作為一個緩沖區的消費者;
-
無需擔心緩沖區是如何/在哪里創建的;
-
需要一個可以訪問緩沖區scatterlist的機制,將其映射到自己的地址空間,這樣可以讓自己可以訪問到內存的同塊區域,實現共享內存。
數據結構
dma_buf是核心數據結構,可以理解為生產者對象。
struct dma_buf {
size_t size;
struct file *file;
struct list_head attachments;
const struct dma_buf_ops *ops;
/* mutex to serialize list manipulation and attach/detach */
struct mutex lock;
void *priv;
};
其中
size為緩沖區大小
file為指向共享緩沖區的文件指針
attachments為附着在緩沖區上的設備(消費者)
ops為綁定在該緩沖區的操作函數
priv為生產者的私有數據
dma_buf_attachment可以理解為是消費者對象。
struct dma_buf_attachment {
struct dma_buf *dmabuf;
struct device *dev;
struct list_head node;
void *priv;
};
其中
dmabuf為該消費者附着的共享緩沖區
dev為設備信息
node為連接其他消費者的節點
priv為消費者私有數據
這兩個數據結構的關系如下所示。
外設的dma-buf操作函數
dma_buf共享緩沖區接口的使用具體包括以下步驟:
-
生產者發出通知,其可以共享一塊緩沖區;
-
用戶空間獲取與該共享緩沖區關聯的文件描述符,將其傳遞給潛在的消費者;
-
每個消費者將其綁定在這個緩沖區上;
-
如果需要,緩沖區使用者向消費者發出訪問請求;
-
當使用完緩沖區,消費者通知生產者已經完成DMA傳輸;
-
當消費者不再使用該共享內存,可以脫離該緩沖區;
1. 生產者共享緩沖區
消費者發出通知,請求共享一塊緩沖區。
struct dma_buf *
dma_buf_export(void *priv, struct dma_buf_ops *ops, size_t size, int flags)
如果函數調用成功,則會創建一個數據結構dma_buf,返回其指針。同時還會創建一個匿名文件綁定在該緩沖區上,因此這個緩沖區可以由其他消費者共享了(實際上此時緩沖區可能並未真正創建,這里只是創建了一個抽象的dma_buf)。
2. 用戶空間獲取文件句柄並傳遞給潛在消費者
用戶程序請求一個文件描述符(fd),該文件描述符指向和緩沖區關聯的匿名文件。用戶程序可以將文件描述符共享給驅動程序或者用戶進程程序。
int
dma_buf_fd(struct dma_buf *dmabuf)
該函數創建為匿名文件創建一個文件描述符,返回"fd"或者錯誤。
3. 消費者將其綁定在緩沖區上
現在每個消費者可以通過文件描述符fd獲取共享緩沖區的引用。
struct dma_buf *
dma_buf_get(int fd)
該函數返回一個dma_buf的引用,同時增加它的refcount(該值記錄着dma_buf被多少消費者引用)。
獲取緩沖區應用后,消費者需要將它的設備附着在該緩沖區上,這樣可以讓生產者知道設備的尋址限制。
struct dma_buf_attachment *
dma_buf_attach(struct dma_buf *dmabuf, struct device *dev)
該函數返回一個attachment的數據結構,該結構會用於scatterlist的操作。
dma-buf共享框架有一個記錄位圖,用於管理附着在該共享緩沖區上的消費者。
到這步為止,生產者可以選擇不在實際的存儲設備上分配該緩沖區,而是等待第一個消費者申請共享內存。
4. 如果需要,消費者發出訪問該緩沖區的請求
當消費者想要使用共享內存進行DMA操作,那么它就會通過接口dma_buf_map_attachment來訪問緩沖區。在調用map_dma_buf前至少有一個消費者與之關聯。
struct sg_table *
dma_buf_map_attachment(struct dma_buf_attachment *, enum dma_data_direction);
該函數是dma_buf->ops->map_dma_buf的一個封裝,它可以對使用該接口的對象隱藏"dma_buf->ops->"
struct sg_table *
(*map_dma_buf)(struct dma_buf_attachment *, enum dma_data_direction);
生產者必須實現該函數。它返回一個映射到調用者地址空間的sg_table,該數據結構包含了緩沖區的scatterlist。
如果第一次調用該函數,生產者現在可以掃描附着在共享緩沖區上的消費者,核實附着設備的請求,為緩沖區選擇一個合適的物理存儲空間。
基於枚舉類型dma_data_direction,多個消費者可能同時訪問共享內存(比如讀操作)。
如果被一個信號中斷,map_dma_buf()可能返回-EINTR。
5. 當使用完成,消費者通知生成者DMA傳輸結束
當消費者完成DMA操作,它可以通過接口函數dma_buf_unmap_attachment發送“end-of-DMA”給生產者。
void
dma_buf_unmap_attachment(struct dma_buf_attachment *, struct sg_table *);
該函數是dma_buf->ops->unmap_dma_buf()的封裝,對使用該接口的對象隱藏"dma_buf->ops->"。
在dma_buf_ops結構中,unmap_dma_buf定義成
void
(*unmap_dma_buf)(struct dma_buf_attachment *, struct sg_table *);
unmap_dma_buf意味着消費者結束了DMA操作。生產者必須要實現該函數。
6. 當消費者不再使用該共享內存,則脫離該緩沖區;
當消費者對該共享緩沖區沒有任何興趣后,它應該斷開和該緩沖區的連接。
a. 首先將其從緩沖區中分離出來。
void
dma_buf_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *dmabuf_attach);
此函數從dmabuf的attachment鏈表中移除了該對象,如果消費者實現了dma_buf->ops->detach(),那么它會調用該函數。
b. 然后消費者返回緩沖區的引用給生產者。
void
dma_buf_put(struct dma_buf *dmabuf);
該函數減小緩沖區的refcount。
如果調用該函數后refcount變成0,該文件描述符的"release"函數將會被調用。它會調用dmabuf->ops->release(),企圖釋放生產者為dmabuf申請的內存。
注意事項:
a. attach-detach及{map,unmap}_dma_buf成對執行非常重要。
attach-detach函數調用可以讓生產者明確當前消費者對物理內存的限制。如果可能,它會在不同的存儲設備上申請或/和移動物理頁框。
b. 如果有必要,需要將緩沖區移動到另一個物理地址空間。
如果
-
至少有一個map_dma_buf存在,
-
該緩沖區已經分配了物理內存,
此時另一個消費者打算使用該緩沖區,生產者可能允許其請求。
如果生產者允許其請求:
如果新的消費者有嚴格的DMA尋址限制,而且生產者可以處理這些限制,那么生產者會在map_dma_buf里等待剩余消費者完成緩沖區訪問。一旦所有消費者都完成了訪問並且unmap了緩沖區,生產者可以將該緩沖區轉移到嚴格的物理地址空間,然后再次允許{map,unmap}_dma_buf操作移動后的共享緩沖區。
如果生產者不能滿足新消費者的尋址限制,調用dma_buf_attach() 則會返回失敗。
內核處理器訪問dma-buf緩沖區對象
允許處理器在內核空間作為一個消費者訪問dma-buf對象的原因如下:
-
撤銷/回退操作。比如一個設備連接到USB總線上,在發送數據前內核需要將第一個數據移除。
-
對其他消費者而言這個是全透明的。比如其他用戶空間消費者注意不到一個 dma-buf是否做過一次撤銷/回退操作。
在內核上下文訪問dma_buf需要下面三個步驟:
1. 訪問前的准備工作,包括使相關cache無效,使處理器可以訪問緩沖區對象;
2. 通過dma_buf map接口函數以頁為單位訪問對象;
3. 完成訪問時,需要刷新必要的處理器cache,釋放占用的資源;
1. 訪問前的准備工作
處理器在內核空間打算訪問dma_buf對象前,需要通知生產者。
int
dma_buf_begin_cpu_access(struct dma_buf *dmabuf, size_t start, size_t len,
enum dma_data_direction direction)
生產者可以確保處理器可以訪問這些內存緩沖區,生產者也需要確定處理器在指定區域及指定方向的訪問是一致性的。生產者可以使用訪問區域及訪問方向來優化cache flushing。比如訪問指定范圍外的區域或者不同的方向(用讀操作替換寫操作)會導致陳舊的或者不正確的數據(比如生產者需要將數據拷貝到零時緩沖區)。
該函數調用可能會失敗,比如在OOM(內存緊缺)的情況下。
2. 訪問緩沖區
為了支持處理器可以訪問到駐留在高端內存中的dma_buf對象,需要調用一個和kmap類似的接口函數。訪問dma_buf需要頁對齊。在訪問對象前需要先做映射工作,及需要得到一個內核虛擬地址。操作完后,需要取消該對象的映射。
void *
dma_buf_kmap(struct dma_buf *, unsigned long);
void
dma_buf_kunmap(struct dma_buf *, unsigned long, void *);
該函數有對應的原子操作函數,如下所示。在調用原子操作函數時,生產者和消費者都不能被阻塞。
void *
dma_buf_kmap_atomic(struct dma_buf *, unsigned long);
void
dma_buf_kunmap_atomic(struct dma_buf *, unsigned long, void *);
生產者在同一時間不能同時調用原子操作函數(在任何進程空間)。
如果訪問緩沖區區域不是頁對齊的,雖然kmap對應的區域數據得到了更新,但是在這個區域附近的區域數據也相應得到了更新,這個不是我們所希望的。也就是說kmap更新了自己關心的區域外,還更新了其他區域,對於那些區域的使用者來說,數據就已經失效了。
下圖給出了一個例子,一共有四個連續的頁,其中kmap沒有頁對齊獲取部分緩沖區,即紅色部分,由於會同步cache,其附近的區域數據也會被更新,被更新區域的范圍和cache行的大小有關系。
注意這些調用總是成功的,生產者需要在begin_cpu_access中完成所有的准備,在這其中可能才會有失敗。
3. 完成訪問
當消費者完成對begin_cpu_access指定范圍內的緩沖區訪問,需要通知生產者(刷新cache,同步數據集釋放資源)。
void dma_buf_end_cpu_access(struct dma_buf *dma_buf,
size_t start, size_t len,
enum dma_data_direction dir);
用戶空間通過mmap直接訪問緩沖區
在用戶空間映射一個dma-buf對象,主要有兩個原因:
-
處理器回退/撤銷操作;
-
支持消費者程序中已經存在的mmap接口;
1. 處理器在一個pipeline中回退/撤銷操作
在處理pipeline過程中,有時處理器需要訪問dma-buf中的數據(比如創建thumbnail, snapshots等等)。用戶空間程序通過使用dma-buf的文件描述符fd調用mmap來訪問dma-buf中的數據是一個好辦法,這樣可以避免用戶空間程序對共享內存做一些特殊處理。
進一步說Android的ION框架已經實現了該功能(從用戶空間消費者來說它實現了一個和dma-buf很像的東西,使用fds用作文件句柄)。因此實現該功能對於Android用戶空間來說是有意義的。
沒有特別的接口,用戶程序可以直接基於dma-buf的fd調用mmp。
2. 支持消費者程序中已經存在的mmap接口
與處理器在內核空間訪問dma-buf對象目的一樣,用戶空間消費者可以將生產者的dma-buf緩沖區對象當做本地緩沖區對象一樣使用。這對drm特別重要,其Opengl,X的用戶空間及驅動代碼非常巨大,重寫這部分代碼讓他們用其他方式的mmap,工作量會很大。
int
dma_buf_mmap(struct dma_buf *, struct vm_area_struct *, unsigned long);
參考文獻
[1] struct dma_buf_ops in include/linux/dma-buf.h
[2] All interfaces mentioned above defined in include/linux/dma-buf.h
[3] https://lwn.net/Articles/236486/
[4] Documentation/dma-buf-sharing.txt