Transitioning from ION to DMA-BUF Heaps

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)

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)

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 the BufferAllocator::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:

  1. Heaps that are based on system heap with device or SoC-specific performance optimizations.
  2. 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 in gki_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 flag ION_FLAG_MY_FLAG disabled.
  • my_heap_special behavior exactly matches the behavior of the ION heap with the flag ION_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.