AIDL pour les HAL

Android 11 introduit la possibilité d'utiliser AIDL pour les HAL dans Android. Cela permet d'implémenter des parties d'Android sans HIDL. Transition HAL pour utiliser AIDL exclusivement lorsque cela est possible (lorsque les HAL en amont utilisent HIDL, HIDL doit être utilisé).

Les HAL utilisant AIDL pour communiquer entre les composants du framework, tels que ceux de system.img , et les composants matériels, tels que ceux de vendor.img , doivent utiliser Stable AIDL. Cependant, pour communiquer au sein d'une partition, par exemple d'une HAL à une autre, il n'y a aucune restriction sur le mécanisme IPC à utiliser.

Motivation

AIDL existe depuis plus longtemps que HIDL et est utilisé dans de nombreux autres endroits, par exemple entre les composants du framework Android ou dans les applications. Maintenant que AIDL prend en charge la stabilité, il est possible d'implémenter une pile entière avec un seul moteur d'exécution IPC. AIDL dispose également d'un meilleur système de gestion des versions que HIDL.

  • Utiliser un seul langage IPC signifie n’avoir qu’une seule chose à apprendre, déboguer, optimiser et sécuriser.
  • AIDL prend en charge le versioning sur place pour les propriétaires d'une interface :
    • Les propriétaires peuvent ajouter des méthodes à la fin des interfaces ou des champs aux parcelles. Cela signifie qu'il est plus facile de versionner le code au fil des ans, et que le coût d'une année sur l'autre est également plus faible (les types peuvent être modifiés sur place et il n'est pas nécessaire de disposer de bibliothèques supplémentaires pour chaque version de l'interface).
    • Les interfaces d'extension peuvent être attachées au moment de l'exécution plutôt que dans le système de types, il n'est donc pas nécessaire de rebaser les extensions en aval sur des versions plus récentes des interfaces.
  • Une interface AIDL existante peut être utilisée directement lorsque son propriétaire choisit de la stabiliser. Avant, il fallait créer une copie entière de l’interface en HIDL.

Écrire une interface AIDL HAL

Pour qu'une interface AIDL soit utilisée entre le système et le fournisseur, l'interface nécessite deux modifications :

  • Chaque définition de type doit être annotée avec @VintfStability .
  • La déclaration aidl_interface doit inclure stability: "vintf", .

Seul le propriétaire d'une interface peut effectuer ces modifications.

Lorsque vous effectuez ces modifications, l'interface doit figurer dans le manifeste VINTF pour fonctionner. Testez cela (et les exigences associées, telles que la vérification que les interfaces publiées sont gelées) à l'aide du test VTS vts_treble_vintf_vendor_test . Vous pouvez utiliser une interface @VintfStability sans ces exigences en appelant soit AIBinder_forceDowngradeToLocalStability dans le backend NDK, android::Stability::forceDowngradeToLocalStability dans le backend C++, ou android.os.Binder#forceDowngradeToSystemStability dans le backend Java sur un objet classeur avant son envoi. à un autre processus. La rétrogradation d'un service vers la stabilité du fournisseur n'est pas prise en charge en Java, car toutes les applications s'exécutent dans un contexte système.

De plus, pour une portabilité maximale du code et pour éviter des problèmes potentiels tels que des bibliothèques supplémentaires inutiles, désactivez le backend CPP.

Notez que l'utilisation des backends dans l'exemple de code ci-dessous est correcte, car il existe trois backends (Java, NDK et CPP). Le code ci-dessous indique comment sélectionner spécifiquement le backend CPP pour le désactiver.

    aidl_interface: {
        ...
        backends: {
            cpp: {
                enabled: false,
            },
        },
    }

Trouver les interfaces AIDL HAL

Les interfaces AOSP Stable AIDL pour les HAL se trouvent dans les mêmes répertoires de base que les interfaces HIDL, dans les dossiers aidl .

  • matériel/interfaces
  • frameworks/matériel/interfaces
  • système/matériel/interfaces

Vous devez placer les interfaces d'extension dans d'autres sous-répertoires hardware/interfaces vendor ou hardware .

Interfaces d'extension

Android dispose d'un ensemble d'interfaces AOSP officielles avec chaque version. Lorsque les partenaires Android souhaitent ajouter des fonctionnalités à ces interfaces, ils ne doivent pas les modifier directement car cela signifierait que leur runtime Android est incompatible avec le runtime Android AOSP. Pour les appareils GMS, éviter de modifier ces interfaces est également ce qui garantit que l'image GSI peut continuer à fonctionner.

Les extensions peuvent s'enregistrer de deux manières différentes :

Cependant, une extension est enregistrée, lorsque des composants spécifiques au fournisseur (c'est-à-dire ne faisant pas partie de l'AOSP en amont) utilisent l'interface, il n'y a aucune possibilité de conflit de fusion. Cependant, lorsque des modifications en aval sont apportées aux composants AOSP en amont, des conflits de fusion peuvent en résulter et les stratégies suivantes sont recommandées :

  • les ajouts d'interface peuvent être intégrés à AOSP dans la prochaine version
  • des ajouts d'interface qui permettent une plus grande flexibilité, sans conflits de fusion, peuvent être intégrés en amont dans la prochaine version

Extension Parcelables : ParcelableHolder

ParcelableHolder est un Parcelable qui peut contenir un autre Parcelable . Le principal cas d'utilisation de ParcelableHolder est de rendre un Parcelable extensible. Par exemple, imaginez que les implémenteurs de périphériques s'attendent à pouvoir étendre un Parcelable défini par l'AOSP, AospDefinedParcelable , pour inclure leurs fonctionnalités à valeur ajoutée.

Auparavant, sans ParcelableHolder , les implémenteurs de périphériques ne pouvaient pas modifier une interface AIDL stable définie par l'AOSP, car ce serait une erreur d'ajouter plus de champs :

parcelable AospDefinedParcelable {
  int a;
  String b;
  String x; // ERROR: added by a device implementer
  int[] y; // added by a device implementer
}

Comme le montre le code précédent, cette pratique est rompue car les champs ajoutés par l'implémenteur du périphérique peuvent avoir un conflit lorsque le Parcelable est révisé dans les prochaines versions d'Android.

En utilisant ParcelableHolder , le propriétaire d'un parcelable peut définir un point d'extension dans un Parcelable .

parcelable AospDefinedParcelable {
  int a;
  String b;
  ParcelableHolder extension;
}

Ensuite, les implémenteurs de périphériques peuvent définir leur propre Parcelable pour leur extension.

parcelable OemDefinedParcelable {
  String x;
  int[] y;
}

Enfin, le nouveau Parcelable peut être attaché au Parcelable d'origine via le champ ParcelableHolder .


// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;

ap.extension.setParcelable(op);

...

OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);

// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();

ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);

...

std::shared_ptr<OemDefinedParcelable> op_ptr;

ap.extension.getParcelable(&op_ptr);

// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);

...

std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);

// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });

ap.extension.set_parcelable(Rc::clone(&op));

...

let op = ap.extension.get_parcelable::<OemDefinedParcelable>();

Construire avec le runtime AIDL

AIDL a trois backends différents : Java, NDK, CPP. Pour utiliser Stable AIDL, vous devez toujours utiliser la copie système de libbinder sur system/lib*/libbinder.so et parler sur /dev/binder . Pour le code sur l'image du fournisseur, cela signifie que libbinder (du VNDK) ne peut pas être utilisé : cette bibliothèque a une API C++ instable et des composants internes instables. Au lieu de cela, le code du fournisseur natif doit utiliser le backend NDK d'AIDL, créer un lien avec libbinder_ndk (qui est soutenu par le système libbinder.so ) et créer un lien avec les bibliothèques -ndk_platform créées par les entrées aidl_interface .

Noms des instances de serveur AIDL HAL

Par convention, les services AIDL HAL ont un nom d'instance au format $package.$type/$instance . Par exemple, une instance du vibrateur HAL est enregistrée sous le nom android.hardware.vibrator.IVibrator/default .

Écrire un serveur AIDL HAL

Les serveurs @VintfStability AIDL doivent être déclarés dans le manifeste VINTF, par exemple comme ceci :

    <hal format="aidl">
        <name>android.hardware.vibrator</name>
        <version>1</version>
        <fqname>IVibrator/default</fqname>
    </hal>

Sinon, ils devraient normalement enregistrer un service AIDL. Lors de l'exécution de tests VTS, on s'attend à ce que tous les HAL AIDL déclarés soient disponibles.

Écrire un client AIDL

Les clients AIDL doivent se déclarer dans la matrice de compatibilité, par exemple comme ceci :

    <hal format="aidl" optional="true">
        <name>android.hardware.vibrator</name>
        <version>1-2</version>
        <interface>
            <name>IVibrator</name>
            <instance>default</instance>
        </interface>
    </hal>

Conversion d'un HAL existant de HIDL en AIDL

Utilisez l'outil hidl2aidl pour convertir une interface HIDL en AIDL.

Caractéristiques hidl2aidl :

  • Créer des fichiers .aidl basés sur les fichiers .hal pour le package donné
  • Créez des règles de construction pour le package AIDL nouvellement créé avec tous les backends activés
  • Créez des méthodes de traduction dans les backends Java, CPP et NDK pour traduire des types HIDL vers les types AIDL.
  • Créer des règles de construction pour traduire les bibliothèques avec les dépendances requises
  • Créez des assertions statiques pour garantir que les énumérateurs HIDL et AIDL ont les mêmes valeurs dans les backends CPP et NDK

Suivez ces étapes pour convertir un package de fichiers .hal en fichiers .aidl :

  1. Créez l'outil situé dans system/tools/hidl/hidl2aidl .

    Construire cet outil à partir de la dernière source offre l’expérience la plus complète. Vous pouvez utiliser la dernière version pour convertir les interfaces des anciennes branches des versions précédentes.

    m hidl2aidl
    
  2. Exécutez l'outil avec un répertoire de sortie suivi du package à convertir.

    Vous pouvez éventuellement utiliser l'argument -l pour ajouter le contenu d'un nouveau fichier de licence en haut de tous les fichiers générés. Assurez-vous d'utiliser la licence et la date correctes.

    hidl2aidl -o <output directory> -l <file with license> <package>
    

    Par exemple:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Lisez les fichiers générés et résolvez tous les problèmes de conversion.

    • conversion.log contient tous les problèmes non gérés à résoudre en premier.
    • Les fichiers .aidl générés peuvent contenir des avertissements et des suggestions pouvant nécessiter une action. Ces commentaires commencent par // .
    • Profitez-en pour faire le ménage et apporter des améliorations au package.
    • Vérifiez l'annotation @JavaDerive pour les fonctionnalités qui pourraient être nécessaires, telles que toString ou equals .
  4. Construisez uniquement les cibles dont vous avez besoin.

    • Désactivez les backends qui ne seront pas utilisés. Préférez le backend NDK au backend CPP, voir choix du runtime .
    • Supprimez les bibliothèques de traduction ou tout code généré qui ne sera pas utilisé.
  5. Voir Différences majeures AIDL/HIDL .

    • L'utilisation du Status et des exceptions intégrés d'AIDL améliore généralement l'interface et supprime le besoin d'un autre type de statut spécifique à l'interface.
    • Les arguments d'interface AIDL dans les méthodes ne sont pas @nullable par défaut comme ils l'étaient dans HIDL.

Politique de confidentialité pour les HAL AIDL

Un type de service AIDL visible par le code fournisseur doit avoir l'attribut hal_service_type . Sinon, la configuration de la politique de sécurité est la même que celle de tout autre service AIDL (bien qu'il existe des attributs spéciaux pour les HAL). Voici un exemple de définition d'un contexte de service HAL :

    type hal_foo_service, service_manager_type, hal_service_type;

Pour la plupart des services définis par la plateforme, un contexte de service avec le type correct est déjà ajouté (par exemple, android.hardware.foo.IFoo/default serait déjà marqué comme hal_foo_service ). Toutefois, si un client Framework prend en charge plusieurs noms d'instance, des noms d'instance supplémentaires doivent être ajoutés dans les fichiers service_contexts spécifiques à l'appareil.

    android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0

Les attributs HAL doivent être ajoutés lorsque nous créons un nouveau type de HAL. Un attribut HAL spécifique peut être associé à plusieurs types de services (chacun d'entre eux pouvant avoir plusieurs instances, comme nous venons de le voir). Pour un HAL, foo , nous avons hal_attribute(foo) . Cette macro définit les attributs hal_foo_client et hal_foo_server . Pour un domaine donné, les macros hal_client_domain et hal_server_domain associent un domaine à un attribut HAL donné. Par exemple, le serveur système étant client de ce HAL correspond à la politique hal_client_domain(system_server, hal_foo) . Un serveur HAL inclut également hal_server_domain(my_hal_domain, hal_foo) . Généralement, pour un attribut HAL donné, nous créons également un domaine comme hal_foo_default pour référence ou exemple de HAL. Cependant, certains appareils utilisent ces domaines pour leurs propres serveurs. La distinction entre les domaines de plusieurs serveurs n'a d'importance que si nous avons plusieurs serveurs qui desservent la même interface et nécessitent un ensemble d'autorisations différent dans leurs implémentations. Dans toutes ces macros, hal_foo n'est pas réellement un objet sepolicy. Au lieu de cela, ce jeton est utilisé par ces macros pour faire référence au groupe d'attributs associé à une paire client-serveur.

Cependant, jusqu'à présent, nous n'avons pas associé hal_foo_service et hal_foo (la paire d'attributs de hal_attribute(foo) ). Un attribut HAL est associé aux services AIDL HAL à l'aide de la macro hal_attribute_service (les HAL HIDL utilisent la macro hal_attribute_hwservice ). Par exemple, hal_attribute_service(hal_foo, hal_foo_service) . Cela signifie que les processus hal_foo_client peuvent obtenir le HAL et que les processus hal_foo_server peuvent enregistrer le HAL. L'application de ces règles d'enregistrement est effectuée par le gestionnaire de contexte ( servicemanager ). Notez que les noms de services peuvent ne pas toujours correspondre aux attributs HAL. Par exemple, nous pourrions voir hal_attribute_service(hal_foo, hal_foo2_service) . Cependant, de manière générale, puisque cela implique que les services sont toujours utilisés ensemble, nous pourrions envisager de supprimer hal_foo2_service et d'utiliser hal_foo_service pour tous nos contextes de service. La plupart des HAL qui définissent plusieurs hal_attribute_service sont dus au fait que le nom de l'attribut HAL d'origine n'est pas assez général et ne peut pas être modifié.

En mettant tout cela ensemble, un exemple HAL ressemble à ceci :

    public/attributes:
    // define hal_foo, hal_foo_client, hal_foo_server
    hal_attribute(foo)

    public/service.te
    // define hal_foo_service
    type hal_foo_service, hal_service_type, protected_service, service_manager_type

    public/hal_foo.te:
    // allow binder connection from client to server
    binder_call(hal_foo_client, hal_foo_server)
    // allow client to find the service, allow server to register the service
    hal_attribute_service(hal_foo, hal_foo_service)
    // allow binder communication from server to service_manager
    binder_use(hal_foo_server)

    private/service_contexts:
    // bind an AIDL service name to the selinux type
    android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0

    private/<some_domain>.te:
    // let this domain use the hal service
    binder_use(some_domain)
    hal_client_domain(some_domain, hal_foo)

    vendor/<some_hal_server_domain>.te
    // let this domain serve the hal service
    hal_server_domain(some_hal_server_domain, hal_foo)

Interfaces d'extension attachées

Une extension peut être attachée à n'importe quelle interface de classeur, qu'il s'agisse d'une interface de niveau supérieur enregistrée directement auprès du gestionnaire de services ou d'une sous-interface. Lorsque vous obtenez une extension, vous devez confirmer que le type de l’extension est celui prévu. Les extensions ne peuvent être définies qu'à partir du processus servant un classeur.

Les extensions attachées doivent être utilisées chaque fois qu'une extension modifie la fonctionnalité d'un HAL existant. Lorsqu'une fonctionnalité entièrement nouvelle est nécessaire, ce mécanisme n'a pas besoin d'être utilisé et une interface d'extension peut être enregistrée directement auprès du gestionnaire de services. Les interfaces d'extension attachées ont plus de sens lorsqu'elles sont attachées à des sous-interfaces, car ces hiérarchies peuvent être profondes ou multi-instances. L’utilisation d’une extension globale pour refléter la hiérarchie de l’interface de classeur d’un autre service nécessiterait une comptabilité approfondie pour fournir des fonctionnalités équivalentes aux extensions directement attachées.

Pour définir une extension sur le classeur, utilisez les API suivantes :

  • Dans le backend NDK : AIBinder_setExtension
  • Dans le backend Java : android.os.Binder.setExtension
  • Dans le backend CPP : android::Binder::setExtension
  • Dans le backend Rust : binder::Binder::set_extension

Pour obtenir une extension sur un classeur, utilisez les API suivantes :

  • Dans le backend NDK : AIBinder_getExtension
  • Dans le backend Java : android.os.IBinder.getExtension
  • Dans le backend CPP : android::IBinder::getExtension
  • Dans le backend Rust : binder::Binder::get_extension

Vous pouvez trouver plus d'informations sur ces API dans la documentation de la fonction getExtension dans le backend correspondant. Un exemple d'utilisation des extensions peut être trouvé dans hardware/interfaces/tests/extension/vibrator .

Principales différences AIDL/HIDL

Lorsque vous utilisez des HAL AIDL ou des interfaces AIDL HAL, soyez conscient des différences par rapport à l'écriture de HAL HIDL.

  • La syntaxe du langage AIDL est plus proche de Java. La syntaxe HIDL est similaire à celle du C++.
  • Toutes les interfaces AIDL ont des statuts d'erreur intégrés. Au lieu de créer des types de statut personnalisés, créez des entiers de statut constants dans les fichiers d'interface et utilisez EX_SERVICE_SPECIFIC dans les backends CPP/NDK et ServiceSpecificException dans le backend Java. Voir Gestion des erreurs .
  • AIDL ne démarre pas automatiquement les pools de threads lorsque des objets classeur sont envoyés. Ils doivent être démarrés manuellement (voir gestion des threads ).
  • AIDL n'abandonne pas en cas d'erreurs de transport non vérifiées (HIDL Return abandonne en cas d'erreurs non vérifiées).
  • AIDL ne peut déclarer qu'un seul type par fichier.
  • Les arguments AIDL peuvent être spécifiés comme in/out/inout en plus du paramètre de sortie (il n'y a pas de « rappels synchrones »).
  • AIDL utilise un fd comme type primitif au lieu de handle.
  • HIDL utilise des versions majeures pour les modifications incompatibles et des versions mineures pour les modifications compatibles. Dans AIDL, des modifications rétrocompatibles sont effectuées en place. L'AIDL n'a pas de concept explicite de versions majeures ; au lieu de cela, cela est incorporé dans les noms des packages. Par exemple, AIDL peut utiliser le nom du package bluetooth2 .
  • AIDL n'hérite pas de la priorité en temps réel par défaut. La fonction setInheritRt doit être utilisée par classeur pour activer l'héritage de priorité en temps réel.