在 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:示例模板可从头开始创建 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)
|
|
使用 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
如何执行未缓存的系统堆分配来说明这些更改。
分配类型 | 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-BUFF 堆的示例展示了针对 DMA-BUF 系统堆是如何做到这一点的。
必需的 sepolicy 添加项
添加 sepolicy 权限,使用户空间客户端能够访问新的 DMA-BUF 堆。 这个添加必需权限示例介绍了为支持各客户端访问 DMA-BUF 系统堆而创建的 sepolicy 权限。
通过框架代码访问供应商堆
为确保 Treble 合规性,框架代码只能从预先批准的供应商堆类别中进行分配。
根据合作伙伴的反馈,Google 确定了两类必须通过框架代码访问的供应商堆:
- 基于系统堆的堆(具有设备或 SoC 专用的性能优化功能)。
- 从受保护的内存中分配的堆。
基于系统堆的堆(具有设备或 SoC 专用的性能优化功能)
为了支持此用例,可以替换默认 DMA-BUF 堆系统的堆实现。
gki_defconfig
中的CONFIG_DMABUF_HEAPS_SYSTEM
已关闭,这使其可成为供应商模块。- VTS 合规性测试会确保堆位于
/dev/dma_heap/system
中。 此外,这些测试还会验证是否可以通过堆进行分配,以及返回的文件描述符 (fd
) 是否可以从用户空间内存映射 (mmapped)。
上述要点也适用于系统堆的未缓存变体,但对于完全 IO 一致的设备,并不强制要求存在这种堆。
从受保护的内存中分配的堆
安全的堆实现必须特定于供应商,因为 Android 通用内核不支持通用的安全堆实现。
- 将您的供应商专用实现注册为
/dev/dma_heap/system-secure<vendor-suffix>
。 - 这些堆实现是可选的。
- 如果这些堆存在,VTS 测试会确保可以通过这些堆进行分配。
- 框架组件可以获得对这些堆的访问权限,因此,它们可以通过 Codec2 HAL/非捆绑式 Same-Process HAL 支持堆的使用。不过,通用 Android 框架功能不能依赖于它们,因为它们的实现详情具有变化性。如果未来将通用的安全堆实现添加到 Android 通用内核,该实现必须使用其他 ABI,以避免与升级设备发生冲突。
适用于 DMA-BUF 堆的 Codec2 分配器
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_heap
和 my_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()
调用。