In Android 12, GKI 2.0 replaces the ION allocator with DMA-BUF heaps for the following reasons:
- Security: Because each DMA-BUF heap is a separate character device, access
to each heap can be controlled separately with sepolicy. This wasn't
possible with ION because allocation from any heap only required access to
the
/dev/ion
device. - ABI stability: Unlike ION, the DMA-BUF heaps framework's IOCTL interface is ABI stable because it's maintained in the upstream Linux kernel.
- Standardization: The DMA-BUF heaps framework offers a well-defined UAPI. ION allowed custom flags and heap IDs that prevented developing a common testing framework because each device's ION implementation could behave differently.
The android12-5.10
branch of the Android Common Kernel disabled
CONFIG_ION
on March 1, 2021.
Background
The following is a brief comparison between ION and DMA-BUF heaps.
Similarities between the ION and DMA-BUF heaps framework
- The ION and DMA-BUF heaps frameworks are both heap-based DMA-BUF exporters.
- They both let each heap define its own allocator and DMA-BUF ops.
- Allocation performance is similar because both schemes need a single IOCTL for allocation.
Differences between the ION and DMA-BUF heaps framework
ION heaps | DMA-BUF heaps |
---|---|
All ION allocations are done with /dev/ion .
|
Each DMA-BUF heap is a character device that's present at /dev/dma_heap/<heap_name> .
|
ION supports heap private flags. | DMA-BUF heaps don't support heap private flags. Each different kind of
allocation is instead done from a different heap. For example, the cached and
uncached system heap variants are separate heaps located at
/dev/dma_heap/system and
/dev/dma_heap/system_uncached .
|
Heap ID/mask and flags need to be specified for allocation. | The heap name is used for allocation. |
The following sections list the components that deal with ION and describe how to switch them over to the DMA-BUF heaps framework.
Transition kernel drivers from ION to DMA-BUF heaps
Kernel drivers implementing ION heaps
Both ION and DMA-BUF heaps allow each heap to implement its own allocators and DMA-BUF ops. So you can switch from an ION heap implementation to a DMA-BUF heap implementation by using a different set of APIs to register the heap. This table shows the ION heap registration APIs and their equivalent DMA-BUF heap APIs.
ION heaps | DMA-BUF heaps |
---|---|
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 heaps don't support heap private flags. So each variant of the heap
must be registered individually using the dma_heap_add()
API. To facilitate code sharing, it's recommended to register all variants of
the same heap within the same driver.
This dma-buf: system_heap example
shows the implementation of the cached and uncached variants of the system
heap.
Use this dma-buf: heaps: example template to create a DMA-BUF heap from scratch.
Kernel drivers directly allocating from ION heaps
The DMA-BUF heaps framework also offers an allocation interface for in-kernel clients. Instead of specifying the heap mask and flags to select the type of allocation, the interface offered by DMA-BUF heaps takes a heap name as input.
The following shows the in-kernel ION allocation API and its equivalent DMA-BUF
heap allocation APIs. Kernel drivers can use the dma_heap_find()
API to query
the existence of a heap. The API returns a pointer to an instance of
struct dma_heap, which can then be passed as an argument to the
dma_heap_buffer_alloc()
API.
ION heaps | DMA-BUF heaps |
---|---|
struct dma_buf *ion_alloc(size_t len, unsigned int heap_id_mask, unsigned int flags)
|
|
Kernel drivers that use DMA-BUFs
No changes are required for drivers that import only DMA-BUFs, because a buffer allocated from an ION heap behaves exactly the same as a buffer allocated from an equivalent DMA-BUF heap.
Transition the user-space clients of ION to DMA-BUF heaps
To make the transition easy for user-space clients of ION, an abstraction
library called
libdmabufheap
is available. libdmabufheap
supports allocation in DMA-BUF heaps
and ION heaps. It first checks if a DMA-BUF heap of the specified name exists
and if not, falls back to an equivalent ION heap, if one exists.
Clients should initialize a
BufferAllocator
object during their initialization instead of opening /dev/ion using
ion_open()
. This is because file descriptors created by opening
/dev/ion
and /dev/dma_heap/<heap_name>
are managed
internally by the BufferAllocator
object.
To switch from libion
to libdmabufheap
, modify the behavior of clients as
follows:
- Keep track of the heap name to use for allocation, instead of the head ID/mask and heap flag.
- Replace the
ion_alloc_fd()
API, which takes a heap mask and flag argument, with theBufferAllocator::Alloc()
API, which takes a heap name instead.
This table illustrates these changes by showing how libion
and libdmabufheap
do an uncached system heap allocation.
Type of allocation | libion | libdmabufheap |
---|---|---|
Cached allocation from system heap | ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, ION_FLAG_CACHED, &fd)
|
allocator->Alloc("system", size)
|
Uncached allocation from system heap | ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, 0, &fd)
|
allocator->Alloc("system-uncached", size)
|
The
uncached system heap variant
is awaiting approval upstream but is already part of the android12-5.10
branch.
To support upgrading devices, the MapNameToIonHeap()
API allows mapping a heap
name to ION heap parameters (heap name or mask and flags) to let those
interfaces use name-based allocations. Here is a name-based allocation
example.
The documentation for every API exposed by
libdmabufheap
is available. The
library
also exposes a header file for use by C clients.
Reference Gralloc implementation
The Hikey960 gralloc implementation uses libdmabufheap
, so you can use it as a
reference
implementation.
Required ueventd additions
For any new device-specific DMA-BUF heaps created, add a new entry to the
device's ueventd.rc
file.
This Setup ueventd to support DMA-BUF heaps
example
demonstrates how this done for the DMA-BUF system heap.
Required sepolicy additions
Add sepolicy permissions to enable a userspace client to access a new DMA-BUF heap. This add required permissions example shows the sepolicy permissions created for various clients to access the DMA-BUF system heap.
Access vendor heaps from framework code
To ensure Treble compliance, framework code can only allocate from preapproved categories of vendor heaps.
Based on feedback received from partners, Google identified two categories of vendor heaps that must be accessed from framework code:
- Heaps that are based on system heap with device or SoC-specific performance optimizations.
- Heaps to allocate from protected memory.
Heaps based on system heap with device or SoC-specific performance optimizations
To support this use case, the heap implementation of the default DMA-BUF heap system can be overridden.
CONFIG_DMABUF_HEAPS_SYSTEM
is turned off ingki_defconfig
to let it be a vendor module.- VTS compliance tests ensure that the heap exists at
/dev/dma_heap/system
. The tests also verify that the heap can be allocated from, and that the returned file descriptor (fd
) can be memory-mapped (mmapped) from user space.
The preceding points are also true for the uncached variant of the system heap, although its existence isn't mandatory for fully IO-coherent devices.
Heaps to allocate from protected memory
Secure heap implementations must be vendor-specific since the Android Common Kernel doesn't support a generic secure heap implementation.
- Register your vendor-specific implementations as
/dev/dma_heap/system-secure<vendor-suffix>
. - These heap implementations are optional.
- If the heaps exist, VTS tests ensure that allocations can be made from them.
- Framework components are provided with access to these heaps so that they can enable heaps usage through the Codec2 HAL/non-binderized, same-process HALs. However, generic Android framework features can't be dependent on them due to the variability in their implementation details. If a generic secure heap implementation gets added to the Android Common Kernel in the future, it must use a different ABI to avoid conflicts with upgrading devices.
Codec 2 allocator for DMA-BUF heaps
A codec2 allocator for the DMA-BUF heaps interface is available in AOSP.
The component store interface that allows heap parameters to be specified from the C2 HAL is available with the C2 DMA-BUF heap allocator.
Sample transition flow for an ION heap
To smooth the transition from ION to DMA-BUF heaps, libdmabufheap
allows
switching one heap at time. The following steps demonstrate a suggested workflow
for transitioning a nonlegacy ION heap named my_heap
that supports one
flag, ION_FLAG_MY_FLAG
.
Step1: Create equivalents of the ION heap in the DMA-BUF framework. In this
example, because the ION heap my_heap
supports a flag ION_FLAG_MY_FLAG
, we
register two DMA-BUF heaps:
my_heap
behavior exactly matches the behavior of the ION heap with the flagION_FLAG_MY_FLAG
disabled.my_heap_special
behavior exactly matches the behavior of the ION heap with the flagION_FLAG_MY_FLAG
enabled.
Step 2: Create the ueventd changes for the new my_heap
and
my_heap_special
DMA-BUF heaps. At this point, the heaps are visible as
/dev/dma_heap/my_heap
and /dev/dma_heap/my_heap_special
, with
the intended permissions.
Step 3: For clients that allocate from my_heap
, modify their makefiles
to link to libdmabufheap
. During client initialization, instantiate a
BufferAllocator
object and use the MapNameToIonHeap()
API to map the
<ION heap name/mask, flag>
combination to equivalent DMA-BUF heap names.
For example:
allocator->MapNameToIonHeap("my_heap_special" /* name of DMA-BUF heap */, "my_heap" /* name of the ION heap */, ION_FLAG_MY_FLAG /* ion flags */ )
Instead of using the MapNameToIonHeap()
API with the name and flag parameters,
you can create the mapping from
<ION heap mask, flag>
to equivalent DMA-BUF heap
names
by setting the ION heap name parameter to empty.
Step 4: Replace ion_alloc_fd()
invocations with
BufferAllocator::Alloc()
using the appropriate heap name.
Allocation type | libion | libdmabufheap |
---|---|---|
Allocation from my_heap with flag
ION_FLAG_MY_FLAG unset
|
ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd)
|
allocator->Alloc("my_heap", size)
|
Allocation from my_heap with flag
ION_FLAG_MY_FLAG set
|
ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP,
ION_FLAG_MY_FLAG, &fd)
|
allocator->Alloc("my_heap_special", size)
|
At this point, the client is functional but still allocating from the ION heap because it doesn't have the required sepolicy permissions to open the DMA-BUF heap.
Step 5: Create the sepolicy permissions required for the client to access the new DMA-BUF heaps. The client is now fully equipped to allocate from the new DMA-BUF heap.
Step 6: Verify that the allocations are happening from the new DMA-BUF heap by examining logcat.
Step 7: Disable the ION heap my_heap
in the kernel. If the client code
doesn't need to support upgrading devices (whose kernels might only support ION
heaps), you can also remove the MapNameToIonHeap()
invocations.