Transizione dagli heap ION agli DMA-BUF

In Android 12, GKI 2.0 sostituisce l'allocatore ION con gli heap DMA-BUF per i seguenti motivi:

  • Sicurezza: poiché ogni heap DMA-BUF è un dispositivo carattere separato, l'accesso a ogni heap può essere controllato separatamente con sepolicy. Con ION non era possibile perché l'allocazione da qualsiasi heap richiedeva solo l'accesso al dispositivo /dev/ion.
  • Stabilità ABI: a differenza di ION, l'interfaccia IOCTL del framework di heap DMA-BUF è stabile in ABI perché viene mantenuta nel kernel Linux upstream.
  • Standardizzazione: il framework degli heap DMA-BUF offre un'UAPI ben definita. ION consentiva flag e ID heap personalizzati che impedivano lo sviluppo di un framework di test comune perché l'implementazione di ION di ciascun dispositivo poteva comportarsi in modo diverso.

Il ramo android12-5.10 dell'Android Common Kernel è stato disattivato CONFIG_ION il 1° marzo 2021.

Background

Di seguito è riportato un breve confronto tra gli heap ION e DMA-BUF.

Somiglianze tra il framework degli heap ION e DMA-BUF

  • I framework degli heap ION e DMA-BUF sono entrambi esportatori DMA-BUF basati su heap.
  • Entrambi consentono a ogni heap di definire il proprio allocatore e le proprie operazioni DMA-BUF.
  • Le prestazioni di allocazione sono simili perché entrambi gli schemi richiedono un singolo IOCTL per l'allocazione.

Differenze tra il framework heap ION e DMA-BUF

Heap ION Heap DMA-BUF
Tutte le allocazioni ION vengono eseguite con /dev/ion. Ogni heap DMA-BUF è un dispositivo a caratteri presente in /dev/dma_heap/<heap_name>.
ION supporta i flag privati dell'heap. Gli heap DMA-BUF non supportano i flag privati heap. Ogni tipo diverso di allocazione viene invece eseguito da un heap diverso. Ad esempio, le varianti dell'heap di sistema memorizzate nella cache e non memorizzate nella cache sono heap distinti situati in /dev/dma_heap/system e /dev/dma_heap/system_uncached.
Per l'allocazione è necessario specificare l'ID/la maschera dell'heap e i flag. Il nome dell'heap viene utilizzato per l'allocazione.

Le sezioni seguenti elencano i componenti che gestiscono ION e descrivono come passare al framework degli heap DMA-BUF.

Eseguire la transizione dei driver del kernel dagli heap ION a DMA-BUF

Driver del kernel che implementano gli heap ION

Entrambi gli heap ION e DMA-BUF consentono a ogni heap di implementare i propri allocatori e operazioni DMA-BUF. Pertanto, puoi passare da un'implementazione dell'heap ION a un'implementazione dell'heap DMA-BUF utilizzando un insieme diverso di API per registrare l'heap. Questa tabella mostra le API di registrazione dell'heap ION e le API di heap DMA-BUF equivalenti.

heap ION Heap 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);

Gli heap DMA-BUF non supportano i flag privati dell'heap. Pertanto, ogni variante dell'heap deve essere registrata singolarmente utilizzando l'API dma_heap_add(). Per facilitare la condivisione del codice, è consigliabile registrare tutte le varianti dello stesso heap nello stesso driver. Questo esempio dma-buf: system_heap mostra l'implementazione delle varianti dell'heap di sistema memorizzate e non memorizzate nella cache.

Utilizza questo modello dma-buf: heaps: example per creare un heap DMA-BUF da zero.

Driver del kernel che allocano direttamente dagli heap ION

Il framework degli heap DMA-BUF offre anche un'interfaccia di allocazione per i client in-kernel. Anziché specificare la maschera e i flag dell'heap per selezionare il tipo di allocazione, l'interfaccia offerta dagli heap DMA-BUF accetta come input un nome dell'heap.

Di seguito è riportata l'API di allocazione ION in-kernel e le API di allocazione heap DMA-BUF equivalenti. I driver del kernel possono utilizzare l'API dma_heap_find() per eseguire query sull'esistenza di un heap. L'API restituisce un puntatore a un'istanza di struct dma_heap, che può essere passato come argomento all'API dma_heap_buffer_alloc().

Heap ION heap 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)

Driver del kernel che utilizzano DMA-BUF

Non sono necessarie modifiche per i driver che importano solo DMA-BUF, perché un buffer allocato da un heap ION si comporta esattamente come un buffer allocato da un heap DMA-BUF equivalente.

Eseguire la transizione dei client nello spazio utente di ION agli heap DMA-BUF

Per semplificare la transizione per i client nello spazio utente di ION, è disponibile una libreria di astrazione chiamata libdmabufheap. libdmabufheap supporta l'allocazione negli heap DMA-BUF e negli heap ION. Innanzitutto, controlla se esiste un heap DMA-BUF del nome specificato e, in caso contrario, passa a un heap ION equivalente, se esistente.

I client devono inizializzare un oggetto BufferAllocator durante l'inizializzazione anziché aprire /dev/ion using ion_open(). Questo accade perché i descrittori file creati aprendo /dev/ion e /dev/dma_heap/<heap_name> sono gestiti internamente dall'oggetto BufferAllocator.

Per passare da libion a libdmabufheap, modifica il comportamento dei client come segue:

  • Tieni traccia del nome dell'heap da utilizzare per l'allocazione, al posto dell'ID/mask head e del flag dell'heap.
  • Sostituisci l'API ion_alloc_fd(), che richiede una maschera heap e un argomento flag, con l'API BufferAllocator::Alloc(), che utilizza invece un nome heap.

Questa tabella illustra queste modifiche mostrando come libion e libdmabufheap effettuano un'allocazione dell'heap di sistema non memorizzata nella cache.

Tipo di allocazione libion libdmabufheap
Allocazione memorizzata nella cache dall'heap di sistema ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, ION_FLAG_CACHED, &fd) allocator->Alloc("system", size)
Allocazione non memorizzata nella cache dall'heap di sistema ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, 0, &fd) allocator->Alloc("system-uncached", size)

La variante dell'heap di sistema non memorizzata nella cache è in attesa di approvazione a monte, ma fa già parte del ramo android12-5.10.

Per supportare l'upgrade dei dispositivi, l'API MapNameToIonHeap() consente di mappare un nome heap ai parametri dell'heap ION (nome dell'heap o maschera e flag) per consentire a queste interfacce di utilizzare allocazioni basate sul nome. Ecco un esempio di allocazione basata sul nome.

È disponibile la documentazione per ogni API esposta da libdmabufheap. La libreria espone anche un file di intestazione per l'utilizzo da parte dei client C.

Implementazione di riferimento di Gralloc

L'implementazione di gralloc di Hikey960 utilizza libdmabufheap, quindi puoi utilizzarla come implementazione di riferimento.

Aggiunta di ueventd obbligatoria

Per ogni nuovo heap DMA-BUF specifico del dispositivo creato, aggiungi una nuova voce al file ueventd.rc del dispositivo. L'esempio di configurazione per supportare gli heap DMA-BUF mostra come questa operazione viene eseguita per l'heap del sistema DMA-BUF.

Aggiunta di sepolicy obbligatoria

Aggiungi autorizzazioni sepolicy per consentire a un client dello spazio utente di accedere a un nuovo heap DMA-BUF. Questo esempio di aggiunta delle autorizzazioni obbligatorie mostra le autorizzazioni sepolicy create per vari client per accedere all'heap di sistema DMA-BUF.

Accedere agli heap del fornitore dal codice del framework

Per garantire la conformità a Treble, il codice del framework può eseguire l'allocazione solo da categorie di heap del fornitore preapprovate.

In base al feedback ricevuto dai partner, Google ha identificato due categorie di heap di fornitori a cui è necessario accedere dal codice del framework:

  1. Heap basate sull'heap di sistema con ottimizzazioni del rendimento specifiche per il dispositivo o il SoC.
  2. Heap da allocare dalla memoria protetta.

Heap basati sull'heap di sistema con ottimizzazioni delle prestazioni specifiche per dispositivo o SoC

Per supportare questo caso d'uso, l'implementazione dell'heap del sistema di heap DMA-BUF predefinito può essere sostituita.

  • CONFIG_DMABUF_HEAPS_SYSTEM è disattivato in gki_defconfig per consentirgli di essere un modulo del fornitore.
  • I test di conformità VTS assicurano che l'heap esista in /dev/dma_heap/system. I test verificano inoltre che l'heap possa essere allocato e che il descrittore file restituito (fd) possa essere mappato in memoria (mmapped) dallo spazio utente.

I punti precedenti valgono anche per la variante non memorizzata nella cache dell'heap di sistema, anche se la sua esistenza non è obbligatoria per i dispositivi completamente coerenti con l'I/O.

Heap da allocare dalla memoria protetta

Le implementazioni dell'heap sicuro devono essere specifiche del fornitore, poiché il kernel Android Common non supporta un'implementazione generica dell'heap sicuro.

  • Registra le implementazioni specifiche del fornitore come /dev/dma_heap/system-secure<vendor-suffix>.
  • Queste implementazioni dell'heap sono facoltative.
  • Se gli heap esistono, i test VTS assicurano che sia possibile effettuare allocazioni da essi.
  • Ai componenti del framework viene fornito l'accesso a questi heap in modo che possano consentire l'utilizzo degli heap tramite gli HAL Codec2 HAL/lo stesso processo non binderizzati. Tuttavia, le funzionalità generiche del framework Android non possono dipendere da queste librerie a causa della variabilità dei dettagli di implementazione. Se in futuro verrà aggiunta al kernel comune di Android un'implementazione generica dell'heap sicuro, dovrà utilizzare un ABI diverso per evitare conflitti con i dispositivi in fase di upgrade.

Allocator codec 2 per gli heap DMA-BUF

In AOSP è disponibile un allocatore codec2 per l'interfaccia degli heap DMA-BUF.

L'interfaccia del repository dei componenti che consente di specificare i parametri dell'heap dall'HAL C2 è disponibile con l'allocatore dell'heap DMA-BUF C2.

Esempio di flusso di transizione per un heap ION

Per rendere più agevole la transizione dagli heap ION a quelli DMA-BUF, libdmabufheap consente di cambiare un heap alla volta. I passaggi che seguono mostrano un flusso di lavoro suggerito per la transizione di un heap ION non legacy denominato my_heap che supporta un flag, ION_FLAG_MY_FLAG.

Passaggio 1: crea gli equivalenti dell'heap ION nel framework DMA-BUF. In questo esempio, poiché l'heap ION my_heap supporta un flag ION_FLAG_MY_FLAG, registriamo due heap DMA-BUF:

  • Il comportamento di my_heap corrisponde esattamente a quello dell'heap ION con il flag ION_FLAG_MY_FLAG disattivato.
  • Il comportamento di my_heap_special corrisponde esattamente a quello dell'heap ION con il flag ION_FLAG_MY_FLAG abilitato.

Passaggio 2: crea le modifiche a ueventd per i nuovi heap DMA-BUF my_heap e my_heap_special. A questo punto, gli heap sono visibili come /dev/dma_heap/my_heap e /dev/dma_heap/my_heap_special, con le autorizzazioni previste.

Passaggio 3: per i client che eseguono l'allocazione da my_heap, modifica i file make per collegarli a libdmabufheap. Durante l'inizializzazione del client, crea un oggetto BufferAllocator e utilizza l'API MapNameToIonHeap() per mappare la combinazione <ION heap name/mask, flag> ai nomi dell'heap DMA-BUF equivalenti.

Ad esempio:

allocator->MapNameToIonHeap("my_heap_special" /* name of DMA-BUF heap */, "my_heap" /* name of the ION heap */, ION_FLAG_MY_FLAG /* ion flags */ )

Anziché utilizzare l'API MapNameToIonHeap() con i parametri name e flag, puoi creare il mapping da <ION heap mask, flag> ai nomi heap DMA-BUF equivalenti impostando il parametro del nome heap ION su vuoto.

Passaggio 4: sostituisci le chiamate ion_alloc_fd() con BufferAllocator::Alloc() utilizzando il nome dell'heap appropriato.

Tipo di allocazione leone libdmabufheap
Allokazione da my_heap con flag ION_FLAG_MY_FLAG non impostato ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) allocator->Alloc("my_heap", size)
Allokazione da my_heap con flag impostato su 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)

A questo punto, il client è funzionale, ma esegue ancora l'allocazione dall'heap ION perché non dispone delle autorizzazioni sepolicy necessarie per aprire l'heap DMA-BUF.

Passaggio 5: crea le autorizzazioni sepolicy necessarie per consentire al client di accedere ai nuovi heap DMA-BUF. Il client è ora completamente equipaggiato per l'allocazione dal nuovo heap DMA-BUF.

Passaggio 6: verifica che le allocazioni vengano eseguite dal nuovo heap DMA-BUF esaminando logcat.

Passaggio 7: disattiva l'heap ION my_heap nel kernel. Se il codice client non deve supportare l'upgrade dei dispositivi (i cui kernel potrebbero supportare solo gli heap ION), puoi anche rimuovere le chiamate MapNameToIonHeap().