AIDL per HAL

Android 11 introduce la possibilità di utilizzare AIDL per gli HAL in Android, rendendo possibile implementare parti di Android senza HIDL. Esegui la transizione delle HAL in modo che utilizzino AIDL esclusivamente, ove possibile (quando le HAL upstream utilizzano HIDL, è necessario utilizzare HIDL).

Le HAL che utilizzano AIDL per comunicare tra i componenti del framework, come quelli in system.img, e i componenti hardware, come quelli in vendor.img, devono utilizzare AIDL stabile. Tuttavia, per comunicare all'interno di una partizione, ad esempio da un HAL all'altro, non esistono restrizioni sul meccanismo IPC da utilizzare.

Motivazione

AIDL esiste da più tempo di HIDL e viene utilizzato in molti altri contesti, ad esempio tra i componenti del framework Android o nelle app. Ora che AIDL supporta la stabilità, è possibile implementare un intero stack con un singolo runtime IPC. AIDL ha anche un sistema di controllo delle versioni migliore rispetto a HIDL. Ecco alcuni vantaggi di AIDL:

  • L'utilizzo di un unico linguaggio IPC significa avere una sola cosa da imparare, eseguire il debug, ottimizzare e proteggere.
  • AIDL supporta il controllo delle versioni in loco per i proprietari di un'interfaccia:
    • I proprietari possono aggiungere metodi alla fine delle interfacce o campi ai parcelable. Ciò significa che è più facile gestire le versioni del codice nel corso degli anni e che il costo anno su anno è inferiore (i tipi possono essere modificati sul posto e non sono necessarie librerie aggiuntive per ogni versione dell'interfaccia).
    • Le interfacce di estensione possono essere collegate in fase di esecuzione anziché nel sistema di tipi, quindi non è necessario eseguire il rebase delle estensioni downstream sulle versioni più recenti delle interfacce.
  • Un'interfaccia AIDL esistente può essere utilizzata direttamente quando il proprietario sceglie di stabilizzarla. In precedenza, era necessario creare un'intera copia dell'interfaccia in HIDL.

Compilare in base al runtime AIDL

AIDL ha tre diversi backend: Java, NDK e CPP. Per utilizzare AIDL stabile, utilizza sempre la copia di sistema di libbinder in system/lib*/libbinder.so e parla di /dev/binder. Per il codice nell'immagine vendor, ciò significa che libbinder (da VNDK) non può essere utilizzato: questa libreria ha un'API C++ instabile e interni instabili. Il codice fornitore nativo deve invece utilizzare il backend NDK di AIDL, collegarsi a libbinder_ndk (supportato da libbinder.so di sistema) e alle librerie NDK create dalle voci aidl_interface. Per i nomi esatti dei moduli, consulta le regole di denominazione dei moduli.

Scrivi un'interfaccia HAL AIDL

Affinché un'interfaccia AIDL possa essere utilizzata tra il sistema e il fornitore, l'interfaccia deve subire due modifiche:

  • Ogni definizione di tipo deve essere annotata con @VintfStability.
  • La dichiarazione aidl_interface deve includere stability: "vintf",.

Solo il proprietario di un'interfaccia può apportare queste modifiche.

Quando apporti queste modifiche, l'interfaccia deve trovarsi nel manifest VINTF per funzionare. Testa questo requisito (e quelli correlati, come la verifica che le interfacce rilasciate siano bloccate) utilizzando il test Vendor Test Suite (VTS) vts_treble_vintf_vendor_test. Puoi utilizzare un'interfaccia @VintfStability senza questi requisiti chiamando AIBinder_forceDowngradeToLocalStability nel backend NDK, android::Stability::forceDowngradeToLocalStability nel backend C++ o android.os.Binder#forceDowngradeToSystemStability nel backend Java su un oggetto binder prima che venga inviato a un altro processo.

Inoltre, per la massima portabilità del codice ed evitare potenziali problemi come librerie aggiuntive non necessarie, disattiva il backend C++.

Il codice mostra come disattivare il backend CPP:

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

Trovare interfacce HAL AIDL

Le interfacce AIDL stabili di AOSP per gli HAL si trovano all'interno delle cartelle aidl nelle stesse directory di base delle interfacce HIDL:

  • hardware/interfaces è per le interfacce in genere fornite dall'hardware.
  • frameworks/hardware/interfaces è per le interfacce di alto livello fornite all'hardware.
  • system/hardware/interfaces è per le interfacce di basso livello fornite all'hardware.

Inserisci le interfacce delle estensioni in altre sottodirectory hardware/interfaces in vendor o hardware.

Interfacce delle estensioni

Android ha un insieme di interfacce AOSP ufficiali a ogni release. Quando i partner Android vogliono aggiungere funzionalità a queste interfacce, non devono modificarle direttamente perché ciò rende il runtime Android incompatibile con il runtime Android AOSP. Evita di modificare queste interfacce in modo che l'immagine GSI possa continuare a funzionare.

Le estensioni possono registrarsi in due modi diversi:

Indipendentemente dalla modalità di registrazione di un'estensione, quando i componenti specifici del fornitore (ovvero non parte di AOSP upstream) utilizzano l'interfaccia, i conflitti di unione non sono possibili. Tuttavia, quando vengono apportate modifiche downstream ai componenti AOSP upstream, possono verificarsi conflitti di unione e sono consigliate le seguenti strategie:

  • Esegui l'upstream delle aggiunte all'interfaccia in AOSP nella prossima release.
  • Aggiunte all'interfaccia upstream che consentono una maggiore flessibilità (senza conflitti di unione) nella prossima release.

Oggetti Parcelable di estensione: ParcelableHolder

ParcelableHolder è un'istanza dell'interfaccia Parcelable che può contenere un'altra istanza di Parcelable.

Il caso d'uso principale di ParcelableHolder è rendere Parcelable estensibile. Ad esempio, immagina che gli implementatori di dispositivi si aspettino di poter estendere un Parcelable e un AospDefinedParcelable definiti da AOSP per includere le loro funzionalità a valore aggiunto.

Utilizza l'interfaccia ParcelableHolder per estendere Parcelable con le tue funzionalità a valore aggiunto. L'interfaccia ParcelableHolder contiene un'istanza di Parcelable. Se provi ad aggiungere campi direttamente a Parcelable, si verifica un errore:

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

Come si vede nel codice precedente, questa pratica è interrotta perché i campi aggiunti dall'implementatore del dispositivo potrebbero avere un conflitto quando Parcelable viene rivisto nelle prossime versioni di Android.

Utilizzando ParcelableHolder, il proprietario di un oggetto Parcelable può definire un punto di estensione in un'istanza di Parcelable:

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

Quindi, gli implementatori del dispositivo possono definire la propria istanza Parcelable per la loro estensione:

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

La nuova istanza Parcelable può essere collegata all'istanza Parcelable originale con il campo 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>();

Nomi delle istanze del server HAL AIDL

Per convenzione, i servizi AIDL HAL hanno un nome di istanza nel formato $package.$type/$instance. Ad esempio, un'istanza dell'HAL del vibratore è registrata come android.hardware.vibrator.IVibrator/default.

Scrivere un server HAL AIDL

@VintfStability I server AIDL devono essere dichiarati nel manifest VINTF, ad esempio:

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

In caso contrario, devono registrare normalmente un servizio AIDL. Quando esegui i test VTS, è previsto che tutte le HAL AIDL dichiarate siano disponibili.

Scrivere un client AIDL

I client AIDL devono dichiararsi nella matrice di compatibilità, ad esempio:

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

Convertire un HAL esistente da HIDL ad AIDL

Utilizza lo strumento hidl2aidl per convertire un'interfaccia HIDL in AIDL.

Funzionalità di hidl2aidl:

  • Crea file AIDL (.aidl) in base ai file HAL (.hal) per il pacchetto specificato.
  • Crea regole di build per il pacchetto AIDL appena creato con tutti i backend attivati.
  • Crea metodi di traduzione nei backend Java, CPP e NDK per la traduzione dai tipi HIDL ai tipi AIDL.
  • Crea regole di build per le librerie di traduzione con le dipendenze richieste.
  • Crea asserzioni statiche per assicurarti che gli enumeratori HIDL e AIDL abbiano gli stessi valori nei backend CPP e NDK.

Per convertire un pacchetto di file HAL in file AIDL:

  1. Crea lo strumento che si trova in system/tools/hidl/hidl2aidl.

    La creazione di questo strumento dall'ultima fonte offre l'esperienza più completa. Puoi utilizzare l'ultima versione per convertire le interfacce dei rami precedenti delle versioni precedenti:

    m hidl2aidl
  2. Esegui lo strumento con una directory di output seguita dal pacchetto da convertire.

    (Facoltativo) Utilizza l'argomento -l per aggiungere i contenuti di un nuovo file di licenza all'inizio di tutti i file generati. Assicurati di utilizzare la licenza e la data corrette:

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

    Ad esempio:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
  3. Leggi i file generati e risolvi eventuali problemi relativi alla conversione:

    • conversion.log contiene problemi non gestiti da risolvere per primi.
    • I file AIDL generati potrebbero contenere avvisi e suggerimenti che richiedono un intervento. Questi commenti iniziano con //.
    • Pulisci e apporta miglioramenti al pacchetto.
    • Controlla l'annotazione @JavaDerive per le funzionalità che potrebbero essere necessarie, ad esempio toString o equals.
  4. Crea solo i target di cui hai bisogno:

    • Disattiva i backend che non verranno utilizzati. Preferisci il backend NDK al backend CPP; vedi Compilare in base al runtime AIDL.
    • Rimuovi le librerie di traduzione o qualsiasi codice generato che non verrà utilizzato.
  5. Consulta Differenze principali tra AIDL e HIDL:

    • L'utilizzo di Status ed eccezioni integrate di AIDL in genere migliora l'interfaccia ed elimina la necessità di un altro tipo di stato specifico dell'interfaccia.
    • Gli argomenti dell'interfaccia AIDL nei metodi non sono @nullable per impostazione predefinita come in HIDL.

SEPolicy per HAL AIDL

Un tipo di servizio AIDL visibile al codice fornitore deve avere l'attributo hal_service_type. In caso contrario, la configurazione sepolicy è la stessa di qualsiasi altro servizio AIDL (anche se esistono attributi speciali per gli HAL). Ecco un esempio di definizione di un contesto di servizio HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

Per la maggior parte dei servizi definiti dalla piattaforma, viene già aggiunto un contesto di servizio con il tipo corretto (ad esempio, android.hardware.foo.IFoo/default è già contrassegnato come hal_foo_service). Tuttavia, se un client framework supporta più nomi di istanza, è necessario aggiungere nomi di istanza aggiuntivi nei file service_contexts specifici del dispositivo:

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

Quando crei un nuovo tipo di HAL, devi aggiungere gli attributi HAL. Un attributo HAL specifico potrebbe essere associato a più tipi di servizio (ognuno dei quali può avere più istanze, come appena descritto). Per un HAL, foo, c'è hal_attribute(foo). Questa macro definisce gli attributi hal_foo_client e hal_foo_server. Per un determinato dominio, le macro hal_client_domain e hal_server_domain associano un dominio a un determinato attributo HAL. Ad esempio, il server di sistema che è un client di questo HAL corrisponde al criterio hal_client_domain(system_server, hal_foo). Un server HAL include in modo simile hal_server_domain(my_hal_domain, hal_foo).

In genere, per un determinato attributo HAL, crea anche un dominio come hal_foo_default per HAL di riferimento o di esempio. Tuttavia, alcuni dispositivi utilizzano questi domini per i propri server. La distinzione tra domini per più server è importante solo se esistono più server che gestiscono la stessa interfaccia e richiedono un insieme di autorizzazioni diverso nelle loro implementazioni. In tutte queste macro, hal_foo non è un oggetto sepolicy. Questo token viene invece utilizzato da queste macro per fare riferimento al gruppo di attributi associati a una coppia server client.

Tuttavia, finora hal_foo_service e hal_foo (la coppia di attributi di hal_attribute(foo)) non sono associati. Un attributo HAL è associato ai servizi HAL AIDL utilizzando la macro hal_attribute_service (gli HAL HIDL utilizzano la macro hal_attribute_hwservice), ad esempio, hal_attribute_service(hal_foo, hal_foo_service). Ciò significa che i processi hal_foo_client possono accedere all'HAL e i processi hal_foo_server possono registrare l'HAL. L'applicazione di queste regole di registrazione viene eseguita dal gestore contesto (servicemanager).

I nomi dei servizi potrebbero non corrispondere sempre agli attributi HAL, ad esempio, hal_attribute_service(hal_foo, hal_foo2_service). In generale, poiché ciò implica che i servizi vengono sempre utilizzati insieme, puoi rimuovere hal_foo2_service e utilizzare hal_foo_service per tutti i contesti di servizio. Quando gli HAL impostano più istanze di hal_attribute_service, è perché il nome dell'attributo HAL originale non è abbastanza generico e non può essere modificato.

Mettendo insieme tutti questi elementi, un esempio di HAL ha il seguente aspetto:

    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)

Interfacce di estensione collegate

Un'estensione può essere collegata a qualsiasi interfaccia binder, che sia un'interfaccia di primo livello registrata direttamente con il service manager o una sottointerfaccia. Quando ottieni un'estensione, devi confermare che il tipo di estensione sia quello previsto. Puoi impostare le estensioni solo dal processo che gestisce un raccoglitore.

Utilizza le estensioni allegate ogni volta che un'estensione modifica la funzionalità di un HAL esistente. Quando è necessaria una funzionalità completamente nuova, questo meccanismo non è necessario e puoi registrare un'interfaccia di estensione direttamente con il service manager. Le interfacce di estensione collegate sono più utili se collegate a interfacce secondarie, perché queste gerarchie possono essere profonde o con più istanze. L'utilizzo di un'estensione globale per eseguire il mirroring della gerarchia dell'interfaccia del binder di un altro servizio richiede una contabilità approfondita per fornire funzionalità equivalenti alle estensioni collegate direttamente.

Per impostare un'estensione su un raccoglitore, utilizza le seguenti API:

  • Backend NDK: AIBinder_setExtension
  • Backend Java: android.os.Binder.setExtension
  • Backend CPP: android::Binder::setExtension
  • Backend Rust: binder::Binder::set_extension

Per ottenere un'estensione su un raccoglitore, utilizza le seguenti API:

  • Backend NDK: AIBinder_getExtension
  • Backend Java: android.os.IBinder.getExtension
  • Backend CPP: android::IBinder::getExtension
  • Backend Rust: binder::Binder::get_extension

Puoi trovare ulteriori informazioni su queste API nella documentazione della funzione getExtension nel backend corrispondente. Un esempio di come utilizzare le estensioni è in hardware/interfaces/tests/extension/vibrator.

Differenze principali tra AIDL e HIDL

Quando utilizzi HAL AIDL o interfacce HAL AIDL, tieni presente le differenze rispetto alla scrittura di HAL HIDL.

  • La sintassi del linguaggio AIDL è più simile a Java. La sintassi HIDL è simile a C++.
  • Tutte le interfacce AIDL hanno stati di errore integrati. Invece di creare tipi di stato personalizzati, crea costanti di stato intere nei file di interfaccia e utilizza EX_SERVICE_SPECIFIC nei backend CPP e NDK e ServiceSpecificException nel backend Java. Consulta la sezione Gestione degli errori.
  • AIDL non avvia automaticamente i pool di thread quando vengono inviati gli oggetti binder. Devi avviarle manualmente (vedi Gestione dei thread).
  • AIDL non interrompe l'esecuzione in caso di errori di trasporto non controllati (HIDL Return interrompe l'esecuzione in caso di errori non controllati).
  • AIDL può dichiarare un solo tipo per file.
  • Gli argomenti AIDL possono essere specificati come in, out o inout oltre al parametro di output (non sono presenti callback sincroni).
  • AIDL utilizza fd come tipo primitivo anziché handle.
  • HIDL utilizza le versioni principali per le modifiche incompatibili e le versioni secondarie per le modifiche compatibili. In AIDL, le modifiche compatibili con le versioni precedenti vengono eseguite sul posto. AIDL non ha un concetto esplicito di versioni principali; al contrario, questo è incorporato nei nomi dei pacchetti. Ad esempio, AIDL potrebbe utilizzare il nome del pacchetto bluetooth2.
  • AIDL non eredita la priorità in tempo reale per impostazione predefinita. La funzione setInheritRt deve essere utilizzata per ogni binder per abilitare l'ereditarietà della priorità in tempo reale.

Test per HAL

Questa sezione descrive le best practice per testare gli HAL. Queste pratiche sono valide anche se il test di integrazione per la tua HAL non è in VTS.

Android si basa su VTS per verificare le implementazioni HAL previste. VTS contribuisce a garantire che Android possa essere compatibile con le implementazioni dei fornitori precedenti. Le implementazioni che non superano VTS presentano problemi di compatibilità noti che potrebbero impedirne il funzionamento con le versioni future del sistema operativo.

Esistono due parti principali di VTS per gli HAL.

1. Verifica che gli HAL sul dispositivo siano noti e previsti da Android

Android si basa su un elenco statico e preciso di tutti gli HAL installati. Questo elenco è espresso nel manifest VINTF. Test speciali a livello di piattaforma verificano l'integrità dei livelli HAL nell'intero sistema. Prima di scrivere test specifici per l'HAL, devi eseguire anche questi test, in quanto possono indicare se un HAL ha configurazioni VINTF incoerenti.

Questo insieme di test è disponibile in test/vts-testcase/hal/treble/vintf. Se stai lavorando all'implementazione di un HAL fornitore, utilizza vts_treble_vintf_vendor_test per verificarla. Puoi eseguire questo test con il comando atest vts_treble_vintf_vendor_test.

Questi test sono responsabili della verifica di:

  • Ogni interfaccia @VintfStability dichiarata in un file manifest VINTF è bloccata a una versione rilasciata nota. In questo modo viene verificato che entrambe le parti dell'interfaccia concordino sulla definizione esatta di quella versione dell'interfaccia. Questo è necessario per il funzionamento di base.
  • Tutte le HAL dichiarate in un manifest VINTF sono disponibili sul dispositivo. Qualsiasi client con autorizzazioni sufficienti per utilizzare un servizio HAL dichiarato deve essere in grado di ottenere e utilizzare questi servizi in qualsiasi momento.
  • Tutte le HAL dichiarate in un manifest VINTF forniscono la versione dell'interfaccia che dichiarano nel manifest.
  • Su un dispositivo non vengono forniti HAL ritirati. Android non supporta più le versioni precedenti delle interfacce HAL, come descritto nel ciclo di vita di FCM.
  • Le HAL richieste sono presenti sul dispositivo. Alcuni HAL sono necessari per il corretto funzionamento di Android.

2. Verifica il comportamento previsto di ogni HAL

Ogni interfaccia HAL ha i propri test VTS per verificare il comportamento previsto dai client. I casi di test vengono eseguiti su ogni istanza di un'interfaccia HAL dichiarata e impongono un comportamento specifico in base alla versione dell'interfaccia implementata.

In C++, puoi ottenere un elenco di tutti gli HAL installati sul sistema con la funzione android::getAidlHalInstanceNames in libaidlvintf_gtest_helper. In Rust, usa binder::get_declared_instances.

Questi test tentano di coprire ogni aspetto dell'implementazione HAL su cui si basa o potrebbe basarsi in futuro il framework Android.

Questi test includono la verifica del supporto delle funzionalità, la gestione degli errori e qualsiasi altro comportamento che un client potrebbe aspettarsi dal servizio.

Tappe fondamentali di VTS per lo sviluppo di HAL

I test VTS (o qualsiasi altro test) devono essere aggiornati durante la creazione o la modifica delle interfacce HAL di Android.

I test VTS devono essere completati e pronti per verificare le implementazioni dei fornitori prima che vengano bloccati per le release dell'API Android Vendor. Devono essere pronte prima che le interfacce vengano bloccate, in modo che gli sviluppatori possano creare le proprie implementazioni, verificarle e fornire feedback agli sviluppatori dell'interfaccia HAL.

Test su Cuttlefish

Quando l'hardware non è disponibile, Android utilizza Cuttlefish come veicolo di sviluppo per le interfacce HAL. Ciò consente di eseguire test di integrazione scalabili di Android.

hal_implementation_test verifica che Cuttlefish abbia implementazioni delle versioni più recenti dell'interfaccia HAL per assicurarsi che Android sia pronto a gestire le nuove interfacce e che i test VTS siano pronti a testare le nuove implementazioni del fornitore non appena saranno disponibili nuovi hardware e dispositivi.