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 guaranteed to be 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.
Transitioning 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.
Transitioning 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/mask and flags) to allow those interfaces
to also 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.
Accessing vendor heaps from framework code
To ensure Treble compliance, framework code can only allocate from pre-approved 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 allow it to 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.