Transition de ION vers les tas DMA-BUF

Dans Android 12, GKI 2.0 remplace l'allocateur ION par des tas DMA-BUF pour les raisons suivantes :

  • Sécurité : étant donné que chaque tas DMA-BUF est un périphérique de caractères distinct, l'accès à chaque tas peut être contrôlé séparément avec sepolicy. Cela n'était pas possible avec ION car l'allocation à partir de n'importe quel tas nécessitait uniquement l'accès au périphérique /dev/ion .
  • Stabilité ABI : contrairement à ION, l'interface IOCTL du framework de tas DMA-BUF est garantie d'être stable ABI car elle est maintenue dans le noyau Linux en amont.
  • Standardisation : le framework de tas DMA-BUF offre un UAPI bien défini. ION autorisait des indicateurs personnalisés et des identifiants de tas qui empêchaient le développement d'un cadre de test commun, car l'implémentation ION de chaque appareil pouvait se comporter différemment.

La branche android12-5.10 du noyau commun Android a désactivé CONFIG_ION le 1er mars 2021 .

Arrière-plan

Ce qui suit est une brève comparaison entre les tas ION et DMA-BUF.

Similitudes entre le framework de tas ION et DMA-BUF

  • Les frameworks de tas ION et DMA-BUF sont tous deux des exportateurs DMA-BUF basés sur des tas.
  • Ils laissent tous deux chaque tas définir son propre allocateur et ses propres opérations DMA-BUF.
  • Les performances d'allocation sont similaires car les deux systèmes nécessitent un seul IOCTL pour l'allocation.

Différences entre le framework de tas ION et DMA-BUF

Tas d'ION Tas DMA-BUF
Toutes les allocations ION sont effectuées avec /dev/ion . Chaque tas DMA-BUF est un périphérique de caractères présent dans /dev/dma_heap/<heap_name> .
ION prend en charge les indicateurs privés de tas. Les tas DMA-BUF ne prennent pas en charge les indicateurs privés de tas. Chaque type d'allocation différent est effectué à partir d'un tas différent. Par exemple, les variantes de tas système mises en cache et non mises en cache sont des tas distincts situés dans /dev/dma_heap/system et /dev/dma_heap/system_uncached .
L'ID/masque de tas et les indicateurs doivent être spécifiés pour l'allocation. Le nom du tas est utilisé pour l'allocation.

Les sections suivantes répertorient les composants qui traitent avec ION et décrivent comment les basculer vers le framework de tas DMA-BUF.

Transition des pilotes de noyau de ION vers les tas DMA-BUF

Pilotes de noyau implémentant les tas ION

Les tas ION et DMA-BUF permettent à chaque tas d'implémenter ses propres allocateurs et opérations DMA-BUF. Vous pouvez donc passer d'une implémentation de tas ION à une implémentation de tas DMA-BUF en utilisant un ensemble différent d'API pour enregistrer le tas. Ce tableau présente les API d'enregistrement du tas ION et leurs API de tas DMA-BUF équivalentes.

Tas d'ION Tas 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);

Les tas DMA-BUF ne prennent pas en charge les indicateurs privés de tas. Ainsi, chaque variante du tas doit être enregistrée individuellement à l'aide de l'API dma_heap_add() . Pour faciliter le partage de code, il est recommandé d'enregistrer toutes les variantes du même tas au sein du même pilote. Cet exemple dma-buf: system_heap montre l'implémentation des variantes mises en cache et non mises en cache du tas système.

Utilisez ce modèle dma-buf:heaps: example pour créer un tas DMA-BUF à partir de zéro.

Pilotes de noyau allouant directement à partir des tas ION

Le framework de tas DMA-BUF offre également une interface d'allocation pour les clients du noyau. Au lieu de spécifier le masque de tas et les indicateurs pour sélectionner le type d'allocation, l'interface offerte par les tas DMA-BUF prend un nom de tas en entrée.

Ce qui suit montre l'API d'allocation ION dans le noyau et ses API d'allocation de tas DMA-BUF équivalentes. Les pilotes du noyau peuvent utiliser l'API dma_heap_find() pour interroger l'existence d'un tas. L'API renvoie un pointeur vers une instance de struct dma_heap , qui peut ensuite être transmise comme argument à l'API dma_heap_buffer_alloc() .

Tas d'ION Tas 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)

Pilotes de noyau qui utilisent les DMA-BUF

Aucune modification n'est requise pour les pilotes qui importent uniquement des DMA-BUF, car un tampon alloué à partir d'un tas ION se comporte exactement de la même manière qu'un tampon alloué à partir d'un tas DMA-BUF équivalent.

Transition des clients de l'espace utilisateur de ION vers les tas DMA-BUF

Pour faciliter la transition pour les clients de l'espace utilisateur de ION, une bibliothèque d'abstraction appelée libdmabufheap est disponible. libdmabufheap prend en charge l'allocation dans les tas DMA-BUF et les tas ION. Il vérifie d'abord si un tas DMA-BUF du nom spécifié existe et sinon, revient à un tas ION équivalent, s'il en existe un.

Les clients doivent initialiser un objet BufferAllocator lors de leur initialisation au lieu d'ouvrir /dev/ion using ion_open() . En effet, les descripteurs de fichiers créés en ouvrant /dev/ion et /dev/dma_heap/<heap_name> sont gérés en interne par l'objet BufferAllocator .

Pour passer de libion ​​à libdmabufheap , modifiez le comportement des clients comme suit :

  • Gardez une trace du nom du tas à utiliser pour l'allocation, au lieu de l'ID/masque de tête et de l'indicateur du tas.
  • Remplacez l'API ion_alloc_fd() , qui prend un masque de tas et un argument d'indicateur, par l'API BufferAllocator::Alloc() , qui prend un nom de tas à la place.

Ce tableau illustre ces changements en montrant comment libion ​​et libdmabufheap effectuent une allocation de tas système non mis en cache.

Type d'attribution Libion libdmabufheap
Allocation mise en cache à partir du tas système ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, ION_FLAG_CACHED, &fd) allocator->Alloc("system", size)
Allocation non mise en cache du tas système ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, 0, &fd) allocator->Alloc("system-uncached", size)

La variante du tas système non mis en cache est en attente d'approbation en amont mais fait déjà partie de la branche android12-5.10 .

Pour prendre en charge la mise à niveau des appareils, l'API MapNameToIonHeap() permet de mapper un nom de tas aux paramètres de tas ION (nom/masque de tas et indicateurs) pour permettre à ces interfaces d'utiliser également des allocations basées sur le nom. Voici un exemple d'allocation basée sur le nom .

La documentation de chaque API exposée par libdmabufheap est disponible. La bibliothèque expose également un fichier d'en-tête à utiliser par les clients C.

Implémentation de référence Gralloc

L'implémentation gralloc Hikey960 utilise libdmabufheap , vous pouvez donc l'utiliser comme implémentation de référence .

Ajouts ueventd requis

Pour tout nouveau tas DMA-BUF spécifique au périphérique créé, ajoutez une nouvelle entrée au fichier ueventd.rc du périphérique. Cet exemple d'événement d'installation pour prendre en charge les tas DMA-BUF montre comment cela est effectué pour le tas système DMA-BUF.

Ajouts de politique de sécurité requis

Ajoutez des autorisations de stratégie de sécurité pour permettre à un client d'espace utilisateur d'accéder à un nouveau tas DMA-BUF. Cet exemple d'ajout d'autorisations requises montre les autorisations de stratégie créées pour divers clients afin d'accéder au tas système DMA-BUF.

Accéder aux tas du fournisseur à partir du code-cadre

Pour garantir la conformité Treble, le code-cadre ne peut allouer qu'à partir de catégories pré-approuvées de tas de fournisseurs.

Sur la base des commentaires reçus des partenaires, Google a identifié deux catégories de tas de fournisseurs auxquels il faut accéder à partir du code-cadre :

  1. Tas basés sur le tas système avec des optimisations de performances spécifiques à l'appareil ou au SoC.
  2. Tas à allouer à partir de la mémoire protégée.

Tas basés sur le tas système avec optimisations de performances spécifiques à l'appareil ou au SoC

Pour prendre en charge ce cas d'utilisation, l'implémentation du tas du système de tas DMA-BUF par défaut peut être remplacée.

  • CONFIG_DMABUF_HEAPS_SYSTEM est désactivé dans gki_defconfig pour lui permettre d'être un module fournisseur.
  • Les tests de conformité VTS garantissent que le tas existe sur /dev/dma_heap/system . Les tests vérifient également que le tas peut être alloué et que le descripteur de fichier renvoyé ( fd ) peut être mappé en mémoire (mmappé) à partir de l'espace utilisateur.

Les points précédents sont également vrais pour la variante non mise en cache du tas système, bien que son existence ne soit pas obligatoire pour les périphériques entièrement cohérents avec les E/S.

Tas à allouer à partir de la mémoire protégée

Les implémentations de tas sécurisé doivent être spécifiques au fournisseur, car Android Common Kernel ne prend pas en charge une implémentation générique de tas sécurisé.

  • Enregistrez vos implémentations spécifiques au fournisseur sous /dev/dma_heap/system-secure<vendor-suffix> .
  • Ces implémentations de tas sont facultatives.
  • Si les tas existent, les tests VTS garantissent que des allocations peuvent être effectuées à partir d'eux.
  • Les composants du framework ont ​​accès à ces tas afin de pouvoir permettre leur utilisation via les HAL Codec2/HAL non liés, de même processus. Cependant, les fonctionnalités génériques du framework Android ne peuvent pas en dépendre en raison de la variabilité des détails de leur implémentation. Si une implémentation générique de tas sécurisé est ajoutée au noyau commun Android à l'avenir, elle doit utiliser un ABI différent pour éviter les conflits avec la mise à niveau des appareils.

Allocateur de codec 2 pour les tas DMA-BUF

Un allocateur codec2 pour l'interface des tas DMA-BUF est disponible dans AOSP.

L'interface du magasin de composants qui permet de spécifier les paramètres de tas à partir du C2 HAL est disponible avec l'allocateur de tas C2 DMA-BUF.

Exemple de flux de transition pour un tas ION

Pour faciliter la transition des tas ION vers les tas DMA-BUF, libdmabufheap permet de basculer un tas à la fois. Les étapes suivantes illustrent un flux de travail suggéré pour la transition d'un tas ION non hérité nommé my_heap qui prend en charge un indicateur, ION_FLAG_MY_FLAG .

Étape 1 : Créez des équivalents du tas ION dans le framework DMA-BUF. Dans cet exemple, comme le tas ION my_heap prend en charge un indicateur ION_FLAG_MY_FLAG , nous enregistrons deux tas DMA-BUF :

  • Le comportement my_heap correspond exactement au comportement du tas ION avec l'indicateur ION_FLAG_MY_FLAG désactivé.
  • Le comportement de my_heap_special correspond exactement au comportement du tas ION avec l'indicateur ION_FLAG_MY_FLAG activé.

Étape 2 : créez les modifications ueventd pour les nouveaux tas my_heap et my_heap_special DMA-BUF. À ce stade, les tas sont visibles sous les noms /dev/dma_heap/my_heap et /dev/dma_heap/my_heap_special , avec les autorisations prévues.

Étape 3 : Pour les clients qui allouent à partir de my_heap , modifiez leurs makefiles pour qu'ils soient liés à libdmabufheap . Lors de l'initialisation du client, instanciez un objet BufferAllocator et utilisez l'API MapNameToIonHeap() pour mapper la combinaison <ION heap name/mask, flag> aux noms de tas DMA-BUF équivalents.

Par exemple:

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

Au lieu d'utiliser l'API MapNameToIonHeap() avec les paramètres name et flag, vous pouvez créer le mappage de <ION heap mask, flag> vers des noms de tas DMA-BUF équivalents en définissant le paramètre de nom de tas ION sur vide.

Étape 4 : remplacez les invocations ion_alloc_fd() par BufferAllocator::Alloc() en utilisant le nom de tas approprié.

Type d'attribution Libion libdmabufheap
Allocation de my_heap avec l'indicateur ION_FLAG_MY_FLAG non défini ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) allocator->Alloc("my_heap", size
Allocation de my_heap avec l'indicateur ION_FLAG_MY_FLAG défini ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, ION_FLAG_MY_FLAG, &fd) allocator->Alloc("my_heap_special", size)

À ce stade, le client est fonctionnel mais continue d'allouer à partir du tas ION car il ne dispose pas des autorisations de stratégie requises pour ouvrir le tas DMA-BUF.

Étape 5 : créez les autorisations de stratégie de sécurité requises pour que le client puisse accéder aux nouveaux tas DMA-BUF. Le client est désormais entièrement équipé pour allouer à partir du nouveau tas DMA-BUF.

Étape 6 : Vérifiez que les allocations sont effectuées à partir du nouveau tas DMA-BUF en examinant logcat .

Étape 7 : Désactivez le tas ION my_heap dans le noyau. Si le code client n'a pas besoin de prendre en charge la mise à niveau des périphériques (dont les noyaux peuvent uniquement prendre en charge les tas ION), vous pouvez également supprimer les invocations MapNameToIonHeap() .