從 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 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 堆框架的區別

離子堆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。

此表通過顯示libionlibdmabufheap如何進行未緩存的系統堆分配來說明這些更改。

分配類型利比昂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 ,因此您可以將其用作參考實現

所需的未事件添加

對於創建的任何新的特定於設備的 DMA-BUF 堆,將新條目添加到設備的ueventd.rc文件中。此Setup ueventd to support DMA-BUF heaps 示例演示瞭如何為 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 Common Kernel,它必須使用不同的 ABI 以避免與升級設備發生衝突。

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

用於 DMA-BUF 堆接口的編解碼器 2 分配器在 AOSP 中可用。

允許從 C2 HAL 指定堆參數的組件存儲接口可用於 C2 DMA-BUF 堆分配器。

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 heap name 參數設置為空來創建從<ION heap mask, flag>到等效 DMA-BUF 堆名稱的映射,而不是使用帶有 name 和 flag 參數的MapNameToIonHeap() API。

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

分配類型利比昂libdmabufheap
未設置標誌ION_FLAG_MY_FLAGmy_heap分配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()調用。