從 ION 過渡到 DMA-BUF 堆

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

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

android12-5.10 Android的通用內核禁用的分支CONFIG_ION2021年3月1。

背景

下面是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:堆:例如模板從頭創建一個DMA-BUF堆。

內核驅動程序直接從 ION 堆分配

該DMA-BUF堆框架還提供了一個分配接口為內核的客戶。 DMA-BUF heaps 提供的接口不是指定堆掩碼和標誌來選擇分配類型,而是將堆名稱作為輸入。

下面顯示了內核中的 ION 分配 API 及其等效的 DMA-BUF 堆分配 API。內核驅動程序可以使用dma_heap_find() API查詢堆的存在。該API返回一個指向結構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對象。

要切換libionlibdmabufheap ,修改客戶端的行為如下:

  • 跟踪用於分配的堆名稱,而不是頭 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堆參數(堆名稱/掩模和標誌),以允許這些接口也使用基於名稱的分配。這是一個基於域名的分配例

對於文檔通過暴露每個API libdmabufheap可用。該庫也暴露了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_SYSTEM在關閉gki_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 分配器

對DMA-BUF堆接口CODEC2分配器在AOSP是可用的。

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

ION 堆的示例轉換流

以平滑從ION到DMA-BUF堆的過渡, libdmabufheap允許在時間切換一個堆。下面的步驟演示建議的工作流程轉變一個nonlegacy ION堆命名my_heap支持一個標誌, ION_FLAG_MY_FLAG

步驟1:創建在DMA-BUF框架ION堆的等效物。在這個例子中,因為離子堆my_heap支持一個標誌, ION_FLAG_MY_FLAG ,我們註冊了兩個DMA-BUF堆:

  • my_heap行為恰與標誌ION堆的行為相匹配ION_FLAG_MY_FLAG禁用。
  • my_heap_special行為恰與標誌ION堆的行為相匹配ION_FLAG_MY_FLAG啟用。

第2步:創建新的ueventd變化my_heapmy_heap_special DMA-BUF堆。在這一點上,堆是為可見/dev/dma_heap/my_heap/dev/dma_heap/my_heap_special ,與預期的許可。

第三步:從分配客戶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 */ )

代替使用的MapNameToIonHeap()的名稱和標誌參數的API,可以創建從映射<ION heap mask, flag>到等效DMA-BUF堆名通過設置ION堆名稱參數為空。

步驟4:替換ion_alloc_fd()與調用BufferAllocator::Alloc()使用適當的堆名稱。

分配類型利比昂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_FLAGion_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步:驗證分配從新DMA-BUF堆通過檢查發生logcat的

第7步:禁用ION堆my_heap內核。如果客戶端代碼並不需要支持升級後的設備(其內核可能只支持ION堆),你也可以刪除MapNameToIonHeap()調用。