从 ION 过渡到 DMA-BUF 堆(仅限 5.4 内核)

在 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 堆框架之间的差异

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 堆系统的堆实现。

  • CONFIG_DMABUF_HEAPS_SYSTEMgki_defconfig 中已关闭,使其成为供应商模块。
  • VTS 合规性测试确保堆存在于 /dev/dma_heap/system。测试还验证可以从堆中分配,并且可以从用户空间内存映射 (mmapped) 返回的文件描述符 (fd)。

前面的几点也适用于系统堆的非缓存变体,尽管对于完全 IO 一致的设备,其存在不是强制性的。

从受保护内存分配的堆

安全堆实现必须是特定于供应商的,因为 Android 通用内核不支持通用的安全堆实现。

  • 将您的特定于供应商的实现注册为 /dev/dma_heap/system-secure<vendor-suffix>
  • 这些堆实现是可选的。
  • 如果堆存在,VTS 测试将确保可以从中进行分配。
  • 框架组件被赋予对这些堆的访问权限,以便它们可以通过 Codec2 HAL/非绑定、同进程 HAL 启用堆使用。但是,通用的 Android 框架功能不能依赖于它们,因为它们的实现细节存在差异。如果将来在 Android 通用内核中添加了通用的安全堆实现,则它必须使用不同的 ABI,以避免与升级设备发生冲突。

DMA-BUF 堆的 Codec 2 分配器

AOSP 中提供了一个 用于 DMA-BUF 堆接口的 codec2 分配器

允许从 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 堆名称/掩码,标志> 组合映射到等效的 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 堆掩码,标志> 到等效 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() 调用。