在 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) | |
使用 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
如何进行未缓存的系统堆分配来说明这些更改。
分配类型 | 利比昂 | 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 确定了必须从框架代码访问的两类供应商堆:
- 基于具有设备或 SoC 特定性能优化的系统堆的堆。
- 从受保护内存中分配的堆。
基于具有设备或 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 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_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 heap name 参数设置为空来创建从<ION heap mask, flag>
到等效 DMA-BUF 堆名称的映射,而不是使用带有 name 和 flag 参数的MapNameToIonHeap()
API。
第 4 步:使用适当的堆名称将ion_alloc_fd()
调用替换为BufferAllocator::Alloc()
。
分配类型 | 利比昂 | libdmabufheap |
---|---|---|
未设置标志ION_FLAG_MY_FLAG 的my_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()
调用。