從 ION 轉換到 DMA-BUF 堆

在 Android 12 中,GKI 2.0 將 ION 分配器替換為 DMA-BUF 堆,原因如下:

  • 安全性:由於每個 DMA-BUF 堆都是單獨的字元設備,因此可以使用 sepolicy 單獨控制對每個堆的存取。這對 ION 來說是不可能的,因為從任何堆疊進行分配只需要存取/dev/ion裝置。
  • ABI 穩定性:與 ION 不同,DMA-BUF 堆框架的 IOCTL 介面保證 ABI 穩定,因為它是在上游 Linux 核心中維護的。
  • 標準化:DMA-BUF 堆疊框架提供了定義良好的 UAPI。 ION 允許自訂標誌和堆疊 ID,這阻止了開發通用測試框架,因為每個裝置的 ION 實作可能表現不同。

Android 通用核心的android12-5.10分支於2021 年 3 月 1 日禁用了CONFIG_ION

背景

以下是 ION 和 DMA-BUF 堆的簡要比較。

ION 和 DMA-BUF 堆框架之間的相似之處

  • ION 和 DMA-BUF 堆疊框架都是基於堆疊的 DMA-BUF 導出器。
  • 它們都讓每個堆定義自己的分配器和 DMA-BUF 操作。
  • 分配性能相似,因為兩種方案都需要單一 IOCTL 進行分配。

ION 和 DMA-BUF 堆疊框架之間的差異

離子堆DMA-BUF 堆
所有 ION 分配均透過/dev/ion完成。每個 DMA-BUF 堆都是一個字元設備,位於/dev/dma_heap/<heap_name>
ION 支援堆私有標誌。 DMA-BUF 堆不支援堆私有標誌。每種不同類型的分配都是從不同的堆中完成的。例如,快取和未快取的系統堆變體是位於/dev/dma_heap/system/dev/dma_heap/system_uncached單獨堆。
需要指定堆 ID/遮罩和標誌進行分配。堆名稱用於分配。

以下部分列出了處理 ION 的元件,並描述如何將它們切換到 DMA-BUF 堆疊框架。

將核心驅動程式從 ION 轉換為 DMA-BUF 堆

實作 ION 堆的核心驅動程式

ION 和 DMA-BUF 堆都允許每個堆實現自己的分配器和 DMA-BUF 操作。因此,您可以透過使用一組不同的 API 來註冊堆,從 ION 堆實作切換到 DMA-BUF 堆實作。此表顯示 ION 堆註冊 API 及其等價的 DMA-BUF 堆 API。

離子堆DMA-BUF 堆
void ion_device_add_heap(struct ion_heap *heap) struct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info);
void ion_device_remove_heap(struct ion_heap *heap) void dma_heap_put(struct dma_heap *heap);

DMA-BUF 堆不支援堆私有標誌。因此,必須使用dma_heap_add() API 單獨註冊堆的每個變體。為了促進代碼共享,建議在同一驅動程式中註冊同一堆的所有變體。此dma-buf: system_heap範例顯示了系統堆的快取和未快取變體的實作。

使用此dma-buf:heaps: 範例範本從頭開始建立 DMA-BUF 堆。

核心驅動程式直接從 ION 堆分配

DMA-BUF 堆框架也為核心用戶端提供分配介面。 DMA-BUF 堆提供的介面採用堆名稱作為輸入,而不是指定堆遮罩和標誌來選擇分配類型。

下面顯示了內核內 ION 分配 API 及其等效的 DMA-BUF 堆分配 API。核心驅動程式可以使用dma_heap_find() API 來查詢堆是否存在。 API 傳回一個指向struct dma_heap實例的指針,然後可以將其作為參數傳遞給dma_heap_buffer_alloc() API。

離子堆DMA-BUF 堆
struct dma_buf *ion_alloc(size_t len, unsigned int heap_id_mask, unsigned int flags)

struct dma_heap *dma_heap_find(const char *name)

struct dma_buf *struct dma_buf *dma_heap_buffer_alloc(struct dma_heap *heap, size_t len, unsigned int fd_flags, unsigned int heap_flags)

使用 DMA-BUF 的核心驅動程式

僅導入 DMA-BUF 的驅動程式不需要進行任何更改,因為從 ION 堆分配的緩衝區的行為與從等效 DMA-BUF 堆分配的緩衝區完全相同。

將 ION 的用戶空間用戶端轉換為 DMA-BUF 堆

為了讓 ION 的使用者空間用戶端能夠輕鬆進行轉換,可以使用名為libdmabufheap抽象程式庫。 libdmabufheap支援 DMA-BUF 堆和 ION 堆中的分配。它首先檢查指定名稱的 DMA-BUF 堆是否存在,如果不存在,則回退到等效的 ION 堆(如果存在)。

客戶端應在初始化期間初始化BufferAllocator對象,而不是/dev/ion using ion_open() 。這是因為透過開啟/dev/ion/dev/dma_heap/<heap_name>建立的檔案描述符由BufferAllocator物件在內部管理。

若要從libion​​ 切換到libdmabufheap ,請修改客戶端的行為,如下所示:

  • 追蹤用於分配的堆名稱,而不是頭 ID/掩碼和堆標誌。
  • 將採用堆疊遮罩和標誌參數的ion_alloc_fd() API 替換為採用堆疊名稱的BufferAllocator::Alloc() API。

此表透過顯示libion​​ 和libdmabufheap如何執行未快取的系統堆分配來說明這些變更。

分配類型利比翁libdmabuf堆
從系統堆緩存分配ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, ION_FLAG_CACHED, &fd) allocator->Alloc("system", size)
來自系統堆的未快取分配ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, 0, &fd) allocator->Alloc("system-uncached", size)

未緩存的系統堆變體正在等待上游批准,但已經是android12-5.10分支的一部分。

為了支援升級設備, MapNameToIonHeap() API 允許將堆名稱對應到 ION 堆參數(堆名稱/遮罩和標誌),以允許這些介面也使用基於名稱的分配。這是一個基於名稱的分配範例

libdmabufheap公開的每個 API的文件均可用。該庫還公開了一個頭檔供 C 用戶端使用。

參考 Gralloc 實現

Hikey960 gralloc 實作使用libdmabufheap ,因此您可以將其用作參考實作

所需的 ueventd 添加

對於建立的任何新的特定於裝置的 DMA-BUF 堆,請將新條目新增至裝置的ueventd.rc檔案中。此設定 ueventd 支援 DMA-BUF 堆的範例示範如何為 DMA-BUF 系統堆完成此操作。

所需的 sepolicy 添加

新增 sepolicy 權限以使用戶空間用戶端能夠存取新的 DMA-BUF 堆。此新增所需權限範例顯示了為各種用戶端存取 DMA-BUF 系統堆疊而建立的 sepolicy 權限。

從框架程式碼存取供應商堆

為了確保 Treble 合規性,框架代碼只能從預先批准的供應商堆類別中進行分配。

根據合作夥伴的回饋,Google 確定了必須從框架程式碼存取的兩類供應商堆:

  1. 基於具有設備或 SoC 特定性能最佳化的系統堆的堆。
  2. 從受保護的記憶體中分配的堆。

基於系統堆的堆,具有設備或 SoC 特定的性能最佳化

為了支援此用例,可以覆蓋預設 DMA-BUF 堆系統的堆疊實作。

  • CONFIG_DMABUF_HEAPS_SYSTEMgki_defconfig中關閉,以允許它成為供應商模組。
  • VTS 合規性測試確保堆存在於/dev/dma_heap/system 。這些測試還驗證是否可以從使用者空間分配堆,以及是否可以從使用者空間對傳回的檔案描述符 ( fd ) 進行記憶體映射 (mmapped)。

上述幾點對於系統堆的未緩存變體也適用,儘管它的存在對於完全 IO 一致的設備來說不是強制性的。

從受保護記憶體分配的堆

安全堆實作必須是特定於供應商的,因為 Android 通用核心不支援通用安全堆實作。

  • 將供應商特定的實作註冊為/dev/dma_heap/system-secure<vendor-suffix>
  • 這些堆實作是可選的。
  • 如果堆存在,VTS 測試確保可以從中進行分配。
  • 框架元件可以存取這些堆,以便它們可以透過 Codec2 HAL/非綁定化、同進程 HAL 啟用堆使用。但是,由於實作細節的可變性,通用 Android 框架功能不能依賴它們。如果將來將通用安全堆實作新增至 Android 通用核心中,則它必須使用不同的 ABI 以避免與升級裝置發生衝突。

DMA-BUF 堆的編解碼器 2 分配器

AOSP 中提供了DMA-BUF 堆介面的編解碼器 2 分配器

C2 DMA-BUF 堆分配器提供了允許從 C2 HAL 指定堆參數的元件儲存介面。

ION 堆的範例轉換流程

為了平滑從 ION 堆到 DMA-BUF 堆的過渡, libdmabufheap允許一次切換一個堆。以下步驟示範了用於轉換名為my_heap的非舊版 ION 堆(支援一個標誌ION_FLAG_MY_FLAG )的建議工作流程。

步驟 1:在 DMA-BUF 架構中建立 ION 堆的等效項。在此範例中,由於 ION 堆my_heap支援標誌ION_FLAG_MY_FLAG ,因此我們註冊兩個 DMA-BUF 堆:

  • my_heap行為與禁用ION_FLAG_MY_FLAG標誌的 ION 堆的行為完全匹配。
  • my_heap_special行為與啟用了ION_FLAG_MY_FLAG標誌的 ION 堆的行為完全匹配。

步驟 2:為新的my_heapmy_heap_special DMA-BUF 堆建立 ueventd 變更。此時,堆可見為/dev/dma_heap/my_heap/dev/dma_heap/my_heap_special ,並具有預期的權限。

步驟 3:對於從my_heap分配的客戶端,修改其 makefile 以連結到libdmabufheap 。在客戶端初始化期間,實例化BufferAllocator物件並使用MapNameToIonHeap() API 將<ION heap name/mask, flag>組合對應到等效的 DMA-BUF 堆名稱。

例如:

allocator->MapNameToIonHeap("my_heap_special" /* name of DMA-BUF heap */, "my_heap" /* name of the ION heap */, ION_FLAG_MY_FLAG /* ion flags */ )

您可以透過將 ION 堆名稱參數設為空來建立從<ION heap mask, flag>到等效 DMA-BUF 堆名稱的映射,而不是使用帶有名稱和標誌參數MapNameToIonHeap() API。

步驟 4:使用適當的堆名稱將ion_alloc_fd()呼叫替換為BufferAllocator::Alloc()

分配類型利比翁libdmabuf堆
my_heap分配,未設定標誌ION_FLAG_MY_FLAG ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) allocator->Alloc("my_heap", size
my_heap進行分配,並設定了標誌ION_FLAG_MY_FLAG ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, ION_FLAG_MY_FLAG, &fd) allocator->Alloc("my_heap_special", size)

此時,客戶端可以正常工作,但仍從 ION 堆進行分配,因為它沒有打開 DMA-BUF 堆所需的 sepolicy 權限。

步驟 5:建立用戶端存取新 DMA-BUF 堆所需的 sepolicy 權限。客戶端現在已完全準備好從新的 DMA-BUF 堆疊進行分配。

步驟 6:透過檢查logcat驗證分配是否發生在新的 DMA-BUF 堆疊中。

步驟7:禁用核心中的ION堆my_heap 。如果用戶端程式碼不需要支援升級裝置(其核心可能僅支援 ION 堆),您也可以刪除MapNameToIonHeap()呼叫。