從 ION 改用 DMA-BUF 堆疊 (僅限 5.4 核心)

在 Android 12 中,GKI 2.0 會以 DMA-BUF 堆積取代 ION 分配器,原因如下:

  • 安全性:由於每個 DMA-BUF 堆積都是獨立的字元裝置,因此可使用 sepolicy 分別控管每個堆積的存取權。使用 ION 時無法這麼做,因為從任何堆積分配記憶體時,只需要存取 /dev/ion 裝置。
  • ABI 穩定性:與 ION 不同,DMA-BUF 堆積架構的 IOCTL 介面是 ABI 穩定版,因為該介面是在上游 Linux 核心中維護。
  • 標準化:DMA-BUF 堆積架構提供明確定義的 UAPI。ION 允許使用自訂標記和堆積 ID,但這會阻礙開發通用測試架構,因為每個裝置的 ION 實作方式可能不同。

Android Common Kernel 的 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 堆積框架之間的差異

ION 堆積 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。

ION 堆積 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: example template 從頭建立 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。

ION 堆積 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。

下表說明這些變更,顯示 libionlibdmabufheap 如何執行未快取的系統堆積分配作業。

分配類型 libion libdmabufheap
系統堆積中快取的分配量 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 堆積。這個新增必要權限範例顯示為各種用戶端建立的 sepolicy 權限,可存取 DMA-BUF 系統堆積。

透過架構程式碼存取供應商堆積

為確保符合 Treble 規範,架構程式碼只能從預先核准的供應商堆積類別中分配。

根據合作夥伴提供的意見回饋,Google 找出兩類必須從架構程式碼存取的供應商堆積:

  1. 以系統堆積為基礎,並針對裝置或 SoC 進行效能最佳化。
  2. 要從受保護的記憶體分配的堆積。

根據系統堆積,並針對裝置或 SoC 進行效能最佳化

如要支援這個使用情境,可以覆寫預設 DMA-BUF 堆積系統的堆積實作。

  • gki_defconfig 中關閉 CONFIG_DMABUF_HEAPS_SYSTEM,讓其成為供應商模組。
  • VTS 遵循性測試可確保堆積存在於 /dev/dma_heap/system。測試也會驗證堆積是否可從中分配,以及傳回的檔案描述元 (fd) 是否可從使用者空間進行記憶體對應 (mmapped)。

系統堆積的未快取變體也適用上述要點,但對於完全 IO 一致的裝置而言,這並非必要。

要從受保護的記憶體配置的堆積

由於 Android Common Kernel 不支援一般安全堆積實作,因此安全堆積實作必須是供應商專屬。

  • 將供應商專屬的實作項目註冊為 /dev/dma_heap/system-secure<vendor-suffix>
  • 這些堆積實作項目為選用項目。
  • 如果堆積存在,VTS 測試會確保可從中進行分配。
  • 架構元件可存取這些堆積,因此能透過 Codec2 HAL/非繫結程序 HAL,啟用堆積使用情形。不過,由於實作細節各不相同,一般 Android 架構功能無法依附於這些功能。如果日後在 Android Common Kernel 中新增一般安全堆積實作項目,則必須使用不同的 ABI,以免與升級裝置發生衝突。

Codec 2 allocator for DMA-BUF heaps

Android 開放原始碼計畫 (AOSP) 提供 DMA-BUF 堆積介面的 codec2 分配器

使用 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()

分配類型 libion libdmabufheap
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() 呼叫。