ION ヒープから DMA-BUF ヒープへの移行

Android 12 では、次の理由により、GKI 2.0 で ION アロケータが DMA-BUF ヒープに置き換えられています。

  • セキュリティ: 各 DMA-BUF ヒープは個別のキャラクター デバイスであるため、各ヒープへのアクセスは sepolicy で個別に制御できます。ION の場合、ヒープからの割り当てには /dev/ion デバイスへのアクセスのみが必要だったため、これは不可能でした。
  • ABI の安定性: ION とは異なり、DMA-BUF ヒープ フレームワークの IOCTL インターフェースは、アップストリームの Linux カーネルで維持されているため、ABI の安定性が保証されます。
  • 標準化: 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 オペレーションを定義できます。
  • どちらのスキームでも割り当てに対して 1 つの 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 オペレーションを実装できます。したがって、ION ヒープ実装から DMA-BUF ヒープ実装に切り替えるには、別の API セットを使用してヒープを登録します。次の表に、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)

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)

DMA-BUF を使用するカーネル ドライバ

DMA-BUF のみをインポートするドライバの場合、変更は不要です。ION ヒープから割り当てられたバッファは、同等の DMA-BUF ヒープから割り当てられたバッファとまったく同じように動作します。

ION のユーザー空間クライアントから DMA-BUF ヒープへの移行

ION のユーザー空間クライアントの移行を容易にするために、libdmabufheap という抽象化ライブラリが用意されています。libdmabufheap は、DMA-BUF ヒープと ION ヒープでの割り当てをサポートします。まず、指定された名前の DMA-BUF ヒープが存在するかどうかを確認し、存在しない場合は同等の ION ヒープ(存在する場合)にフォールバックします。

クライアントは初期化時に、/dev/ion using ion_open() を開く代わりに BufferAllocator オブジェクトを初期化する必要があります。これは、/dev/ion/dev/dma_heap/<heap_name> を開くことで作成されるファイル記述子が、BufferAllocator オブジェクトによって内部的に管理されるためです。

libion から libdmabufheap に切り替えるには、クライアントの動作を次のように変更します。

  • ヒープ ID / マスクとヒープフラグの代わりに、割り当てに使用するヒープ名を追跡します。
  • ヒープマスクとフラグ引数を取る ion_alloc_fd() API を、ヒープ名を取る BufferAllocator::Alloc() API に置き換えます。

次の表では、上記の変更を説明するために、libionlibdmabufheap がキャッシュされていないシステムヒープの割り当てをどのように行うかを示しています。

割り当てのタイプ 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 ファイルに新しいエントリを追加します。この DMA-BUF ヒープをサポートするように ueventd をセットアップする場合の例では、DMA-BUF システムヒープに対してこれを行う方法を示しています。

必要な sepolicy の追加

ユーザー空間クライアントが新しい DMA-BUF ヒープにアクセスできるように、sepolicy 権限を追加します。 必要な権限の追加の例は、さまざまなクライアントが DMA-BUF システムヒープにアクセスできるようにするために作成された sepolicy 権限を示しています。

フレームワーク コードからベンダーヒープへのアクセス

Treble に準拠するため、フレームワーク コードはベンダーヒープの事前承認されたカテゴリからのみ割り当てることができます。

パートナーから寄せられたフィードバックに基づいて、フレームワーク コードからアクセスする必要があるベンダーヒープの 2 つのカテゴリが特定されました。

  1. デバイスまたは SoC 固有のパフォーマンス最適化を伴うシステムヒープに基づくヒープ。
  2. 保護されたメモリから割り当てるヒープ。

デバイスまたは SoC 固有のパフォーマンス最適化を伴うシステムヒープに基づくヒープ

このユースケースをサポートするために、デフォルトの DMA-BUF ヒープシステムのヒープ実装をオーバーライドできます。

  • gki_defconfigCONFIG_DMABUF_HEAPS_SYSTEM をオフにして、ベンダー モジュールとして扱われるようにします。
  • VTS 準拠テストでは、ヒープが /dev/dma_heap/system に存在することを確認します。また、このヒープが割り当て可能であることと、返されたファイル記述子(fd)がユーザー空間からメモリマップ(mmap)可能であることも確認します。

上記のポイントは、システムヒープのキャッシュされていないバリアントでも同様ですが、完全に IO に一貫性のあるデバイスでは必須ではありません。

保護されたメモリから割り当てるヒープ

Android 共通カーネルは汎用のセキュアヒープ実装をサポートしていないため、セキュアヒープ実装はベンダー固有のものにする必要があります。

  • ベンダー固有の実装を /dev/dma_heap/system-secure<vendor-suffix> として登録します。
  • これらのヒープ実装は省略可能です。
  • ヒープが存在する場合、VTS テストで、このヒープから割り当てができることを確認します。
  • フレームワーク コンポーネントには、Codec2 HAL またはバインダ化されていない同じプロセス HAL を介してヒープ使用を有効にできるように、これらのヒープへのアクセス権が付与されます。 ただし、実装の詳細のばらつきにより、一般的な Android フレームワーク機能に依存することはできません。将来的に、汎用のセキュアヒープ実装を Android 共通カーネルに追加する場合は、デバイスのアップグレードとの競合を避けるために別の ABI を使用する必要があります。

DMA-BUF ヒープ用の Codec2 アロケータ

AOSP では、DMA-BUF ヒープ インターフェース用の Codec2 アロケータが提供されています。

C2 HAL からヒープ パラメータを指定できるようにするコンポーネント ストア インターフェースは、C2 DMA-BUF ヒープ アロケータで使用できます。

ION ヒープの移行フローの例

ION ヒープから DMA-BUF ヒープへの移行をスムーズにするために、libdmabufheap ではヒープを 1 つずつ切り替えることができます。次の手順では、ION_FLAG_MY_FLAG という 1 つのフラグをサポートする、my_heap という非レガシー ION ヒープを移行するための推奨ワークフローを示しています。

ステップ 1: DMA-BUF フレームワークで、ION ヒープと同等のヒープを作成します。この例では、ION ヒープ my_heap はフラグ ION_FLAG_MY_FLAG をサポートしているため、次の 2 つの 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 ヒープの uevent 変更を作成します。この時点で、ヒープは想定どおりの権限で /dev/dma_heap/my_heap/dev/dma_heap/my_heap_special として表示されます。

ステップ 3: my_heap から割り当てるクライアントの場合、libdmabufheap にリンクするように makefile を変更します。クライアントの初期化中に、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 */ )

名前パラメータとフラグ パラメータを指定して MapNameToIonHeap() API を使用する代わりに、ION ヒープの名前パラメータを空に設定することで、<ION heap mask, flag> から同等の DMA-BUF ヒープ名へのマッピングを作成できます。

ステップ 4: 適切なヒープ名を使用して、ion_alloc_fd() の呼び出しを BufferAllocator::Alloc() に置き換えます。

割り当てタイプ libion libdmabufheap
フラグ ION_FLAG_MY_FLAG が未設定の my_heap からの割り当て ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) allocator->Alloc("my_heap", size
フラグ ION_FLAG_MY_FLAG を設定した my_heap からの割り当て ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, ION_FLAG_MY_FLAG, &fd) allocator->Alloc("my_heap_special", size)

この時点で、クライアントは機能しますが、DMA-BUF ヒープを開くために必要な sepolicy 権限がないため、引き続き ION ヒープから割り当てています。

ステップ 5: クライアントが新しい DMA-BUF ヒープにアクセスするために必要な sepolicy 権限を作成します。クライアントは、新しい DMA-BUF ヒープから割り当てる準備ができています。

ステップ 6: logcat を調べて、割り当てが新しい DMA-BUF ヒープから発生していることを確認します。

ステップ 7: カーネルで ION ヒープ my_heap を無効にします。クライアント コードがデバイスのアップグレードをサポートする必要がない場合(カーネルが ION ヒープのみをサポートしている場合に必要)、MapNameToIonHeap() 呼び出しを削除することもできます。