linux之DMA-BUF API使用指南(轉)


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共享緩沖區接口的使用具體包括以下步驟:

  1. 生產者發出通知,其可以共享一塊緩沖區;

  2. 用戶空間獲取與該共享緩沖區關聯的文件描述符,將其傳遞給潛在的消費者;

  3. 每個消費者將其綁定在這個緩沖區上;

  4. 如果需要,緩沖區使用者向消費者發出訪問請求;

  5. 當使用完緩沖區,消費者通知生產者已經完成DMA傳輸;

  6. 當消費者不再使用該共享內存,可以脫離該緩沖區;

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM