Transition d'ION vers les tas DMA-BUF (noyau 5.4 uniquement)

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 d'un tas ne nécessitait qu'un accès à l'appareil /dev/ion.
  • Stabilité de l'ABI : contrairement à ION, l'interface IOCTL du framework de tas DMA-BUF est stable au niveau de l'ABI, car elle est gérée dans le noyau Linux en amont.
  • Normalisation : le framework de tas DMA-BUF offre une UAPI bien définie. ION autorisait les indicateurs personnalisés et les ID de tas qui empêchaient le développement d'un framework 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 été désactivée le 1er mars 2021.CONFIG_ION

Arrière-plan

Vous trouverez ci-dessous 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 permettent tous deux à chaque tas de définir ses propres opérations d'allocateur et de DMA-BUF.
  • Les performances d'allocation sont similaires, car les deux schémas nécessitent un seul IOCTL pour l'allocation.

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

Tas ION Tas DMA-BUF
Toutes les allocations ION sont effectuées avec /dev/ion. Chaque tas DMA-BUF est un périphérique à caractères présent à /dev/dma_heap/<heap_name>.
ION est compatible avec les indicateurs privés de tas. Les tas DMA-BUF ne sont pas compatibles avec 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 à /dev/dma_heap/system et /dev/dma_heap/system_uncached.
L'ID/le masque et les indicateurs du tas doivent être spécifiés pour l'allocation. Le nom du tas est utilisé pour l'allocation.

Les sections suivantes listent les composants qui gèrent ION et décrivent comment les transférer vers le framework de tas DMA-BUF.

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

Pilotes de noyau implémentant des tas ION

Les tas ION et DMA-BUF permettent à chaque tas d'implémenter ses propres allocateurs et opérations DMA-BUF. Vous pouvez ainsi 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 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 sont pas compatibles avec les indicateurs privés de tas. Chaque variante du tas doit donc ê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 dans le même pilote. L'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 d'exemple de tas dma-buf 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 propose également une interface d'allocation pour les clients dans le noyau. Au lieu de spécifier le masque et les indicateurs de tas pour sélectionner le type d'allocation, l'interface proposée par les tas DMA-BUF prend un nom de tas en entrée.

L'exemple suivant 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 transmis en tant qu'argument à l'API dma_heap_buffer_alloc().

Tas 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 des DMA-BUFs

Aucune modification n'est requise pour les pilotes qui importent uniquement des DMA-BUFs, 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 d'ION vers les tas DMA-BUF

Pour faciliter la transition des clients de l'espace utilisateur d'ION, une bibliothèque d'abstraction appelée libdmabufheap est disponible. libdmabufheap est compatible avec l'allocation dans les tas DMA-BUF et ION. Il vérifie d'abord si un tas DMA-BUF du nom spécifié existe et, dans le cas contraire, 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 :

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

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

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

La variante de 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 permettre la mise à niveau des appareils, l'API MapNameToIonHeap() permet de mapper un nom de tas sur des paramètres de tas ION (nom ou masque de tas et indicateurs) afin de permettre à ces interfaces d'utiliser des allocations basées sur des noms. 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 pour une utilisation par les clients C.

Implémentation de référence de 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 à un appareil créé, ajoutez une entrée au fichier ueventd.rc de l'appareil. L'exemple de configuration de ueventd pour prendre en charge les tas DMA-BUF montre comment procéder pour le tas système DMA-BUF.

Ajouts sepolicy requis

Ajoutez des autorisations sepolicy pour permettre à un client userspace d'accéder à un nouveau tas DMA-BUF. L'exemple add required permissions montre les autorisations sepolicy créées pour différents clients afin d'accéder au tas système DMA-BUF.

Accéder aux tas de fournisseurs à partir du code du framework

Pour garantir la conformité à Treble, le code du framework ne peut allouer que des catégories de tas de fournisseurs préapprouvées.

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 du framework :

  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 des 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 qu'il puisse être un module fournisseur.
  • Les tests de conformité VTS garantissent que le tas existe à l'adresse /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 (mmapped) à partir de l'espace utilisateur.

Les points précédents sont également valables pour la variante non mise en cache du tas système, bien que son existence ne soit pas obligatoire pour les appareils 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 le noyau commun Android ne prend pas en charge une implémentation de tas sécurisé générique.

  • Enregistrez vos implémentations spécifiques au fournisseur en tant que /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 activer leur utilisation via les HAL Codec2/HAL non binderisées et HAL de même processus. Toutefois, les fonctionnalités génériques du framework Android ne peuvent pas en dépendre en raison de la variabilité de leurs détails d'implémentation. Si une implémentation de tas sécurisé générique est ajoutée au noyau commun Android à l'avenir, elle devra utiliser une ABI différente pour éviter les conflits avec les appareils mis à niveau.

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 HAL C2 est disponible avec l'allocateur de tas DMA-BUF C2.

Exemple de flux de transition pour un tas ION

Pour faciliter la transition des tas ION vers les tas DMA-BUF, libdmabufheap permet de changer un tas à la fois. Les étapes suivantes illustrent un workflow 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, étant donné que le tas ION my_heap accepte un indicateur ION_FLAG_MY_FLAG, nous enregistrons deux tas DMA-BUF :

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

Étape 2 : Créez les modifications ueventd pour les nouveaux tas DMA-BUF my_heap et my_heap_special. À ce stade, les tas sont visibles sous la forme /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 fichiers makefile pour les associer à 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.

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 de nom et d'indicateur, vous pouvez créer le mappage de <ION heap mask, flag> aux noms de tas DMA-BUF équivalents en définissant le paramètre de nom de tas ION sur une valeur vide.

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

Type d'allocation libion libdmabufheap
Attribution de my_heap avec l'option ION_FLAG_MY_FLAG non définie ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) allocator->Alloc("my_heap", size)
Attribution à partir de my_heap avec l'option ION_FLAG_MY_FLAG définie 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 il alloue toujours à partir du tas ION, car il ne dispose pas des autorisations sepolicy requises pour ouvrir le tas DMA-BUF.

Étape 5 : Créez les autorisations sepolicy 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 d'être compatible avec la mise à niveau des appareils (dont les noyaux ne sont compatibles qu'avec les tas ION), vous pouvez également supprimer les appels MapNameToIonHeap().