Transizione dagli heap ION a DMA-BUF (solo kernel 5.4)

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 degli heap DMA-BUF è stabile ABI perché viene mantenuta nel kernel Linux upstream.
  • Standardizzazione: il framework degli heap DMA-BUF offre un'UAPI ben definita. ION consentiva di utilizzare flag personalizzati e ID heap che impedivano lo sviluppo di un framework di test comune perché l'implementazione di ION di ogni 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 operazioni DMA-BUF.
  • Il rendimento dell'allocazione è simile perché entrambi gli schemi richiedono un singolo IOCTL per l'allocazione.

Differenze tra il framework degli heap ION e DMA-BUF

Heap ION Heap DMA-BUF
Tutte le allocazioni di 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 dell'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

Sia gli heap ION sia quelli DMA-BUF consentono a ogni heap di implementare i propri allocatori e le proprie 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. Di conseguenza, ogni variante dell'heap deve essere registrata singolarmente utilizzando l'API dma_heap_add(). Per facilitare la condivisione del codice, consigliamo di registrare tutte le varianti dello stesso heap all'interno dello stesso driver. Questo esempio dma-buf: system_heap mostra l'implementazione delle varianti memorizzate nella cache e non memorizzate nella cache dell'heap di sistema.

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

Allocazione diretta dei driver del kernel 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, anziché dell'ID/della maschera della testa e del flag dell'heap.
  • Sostituisci l'API ion_alloc_fd(), che accetta un'espressione di maschera e un argomento di flag, con l'API BufferAllocator::Alloc(), che accetta invece un nome dell'heap.

Questa tabella illustra queste modifiche mostrando in che modo libion e libdmabufheap eseguono 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 da utilizzare dai client C.

Implementazione di Gralloc di riferimento

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

Aggiunta di ueventd obbligatoria

Per ogni nuovo heap DMA-BUF creato per il dispositivo, aggiungi una nuova voce al file ueventd.rc del dispositivo. Questo esempio di configurazione di ueventd per supportare gli heap DMA-BUF illustra come eseguire questa operazione per l'heap di sistema DMA-BUF.

Aggiunta di sepolicy obbligatoria

Aggiungi le autorizzazioni sepolicy per consentire a un client nello 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ò essere allocato solo da categorie preapprovate di heap dei fornitori.

In base al feedback ricevuto dai partner, Google ha identificato due categorie di heap del fornitore a cui deve essere eseguito l'accesso 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, è possibile eseguire l'override dell'implementazione dell'heap del sistema heap DMA-BUF predefinito.

  • 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 per il 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.
  • I componenti del framework dispongono dell'accesso a questi heap in modo da poter attivare l'utilizzo degli heap tramite HAL Codec2/HAL dello stesso processo non incapsulati. 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 my_heap corrisponde esattamente a quello dell'heap ION con il flag ION_FLAG_MY_FLAG disabilitato.
  • 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 clienti che effettuano l'allocazione da my_heap, modifica i loro makefile 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 libion libdmabufheap
Allocazione 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 dispone ora di tutti gli strumenti necessari 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: disabilita 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().