AIDL per HAL

Android 11 introduce la possibilità di utilizzare AIDL per HAL in Android. Ciò rende possibile implementare parti di Android senza HIDL. Transizione degli HAL in modo che utilizzino esclusivamente AIDL ove possibile (quando gli HAL upstream utilizzano HIDL, è necessario utilizzare HIDL).

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

Motivazione

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

  • Utilizzare un unico linguaggio IPC significa avere solo una cosa da apprendere, eseguire il debug, ottimizzare e proteggere.
  • AIDL supporta il controllo delle versioni sul posto per i proprietari di un'interfaccia:
    • I proprietari possono aggiungere metodi alla fine delle interfacce o campi ai parcelable. Ciò significa che è più semplice modificare le versioni del codice nel corso degli anni e anche 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 tipo, quindi non è necessario ribasare le estensioni downstream su versioni più recenti delle interfacce.
  • Un'interfaccia AIDL esistente può essere utilizzata direttamente quando il suo proprietario sceglie di stabilizzarla. Prima era necessario creare una copia intera dell'interfaccia in HIDL.

Scrittura di un'interfaccia HAL AIDL

Affinché un'interfaccia AIDL possa essere utilizzata tra il sistema e il fornitore, l'interfaccia necessita di 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 essere nel manifest VINTF per funzionare. Testare questo (e i requisiti correlati, come verificare che le interfacce rilasciate siano congelate) utilizzando il test 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 raccoglitore prima che venga inviato ad un altro processo. Il downgrade di un servizio alla stabilità del fornitore non è supportato in Java perché tutte le app vengono eseguite in un contesto di sistema.

Inoltre, per la massima portabilità del codice e per evitare potenziali problemi come librerie aggiuntive non necessarie, disabilitare il backend CPP.

Tieni presente che l'uso dei backends nell'esempio di codice riportato di seguito è corretto, poiché sono presenti tre backend (Java, NDK e CPP). Il codice seguente spiega come selezionare specificamente il backend CPP per disabilitarlo.

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

Trovare le interfacce HAL AIDL

Le interfacce AIDL stabili AOSP per gli HAL si trovano nelle stesse directory di base delle interfacce HIDL, nelle cartelle aidl .

  • hardware/interfacce
  • framework/hardware/interfacce
  • sistema/hardware/interfacce

Dovresti inserire le interfacce di estensione in altre sottodirectory hardware/interfaces in vendor o hardware .

Interfacce di estensione

Android ha una serie di interfacce AOSP ufficiali con ogni versione. Quando i partner Android desiderano aggiungere funzionalità a queste interfacce, non devono modificarle direttamente perché ciò significherebbe che il loro runtime Android è incompatibile con il runtime Android AOSP. Per i dispositivi GMS, evitare di modificare queste interfacce è anche ciò che garantisce che l'immagine GSI possa continuare a funzionare.

Gli interni possono registrarsi in due modi diversi:

Tuttavia, quando viene registrata un'estensione, quando i componenti specifici del fornitore (ovvero non parte dell'AOSP upstream) utilizzano l'interfaccia, non vi è alcuna possibilità di conflitto di unione. Tuttavia, quando vengono apportate modifiche downstream ai componenti AOSP upstream, possono verificarsi conflitti di unione e si consigliano le seguenti strategie:

  • le aggiunte all'interfaccia potranno essere upstreamate su AOSP nella prossima versione
  • le aggiunte all'interfaccia che consentono ulteriore flessibilità, senza conflitti di fusione, potranno essere aggiornate nella prossima versione

Parcelable di estensione: ParcelableHolder

ParcelableHolder è un Parcelable che può contenere un altro Parcelable . Il caso d'uso principale di ParcelableHolder è rendere estensibile un Parcelable . Ad esempio, immagina che gli implementatori del dispositivo si aspettino di essere in grado di estendere un Parcelable definito da AOSP, AospDefinedParcelable , per includere le loro funzionalità a valore aggiunto.

In precedenza senza ParcelableHolder , gli implementatori del dispositivo non potevano modificare un'interfaccia AIDL stabile definita da AOSP perché sarebbe un errore aggiungere più campi:

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

Come visto nel codice precedente, questa pratica viene interrotta perché i campi aggiunti dall'implementatore del dispositivo potrebbero avere un conflitto quando il Parcelable verrà revisionato nelle prossime versioni di Android.

Utilizzando ParcelableHolder , il proprietario di un parcelable può definire un punto di estensione in un Parcelable .

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

Quindi gli implementatori del dispositivo possono definire il proprio Parcelable per la propria estensione.

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

Infine, il nuovo Parcelable può essere allegato al Parcelable originale tramite 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>();

Costruire contro il runtime AIDL

AIDL ha tre diversi backend: Java, NDK, CPP. Per utilizzare Stable AIDL, è necessario utilizzare sempre la copia di sistema di libbinder su system/lib*/libbinder.so e parlare su /dev/binder . Per il codice sull'immagine del fornitore, ciò significa che non è possibile utilizzare libbinder (da VNDK): questa libreria ha un'API C++ instabile e componenti interni instabili. Invece, il codice del fornitore nativo deve utilizzare il backend NDK di AIDL, collegarsi a libbinder_ndk (che è supportato dal sistema libbinder.so ) e collegarsi alle librerie -ndk_platform create dalle voci aidl_interface .

Nomi delle istanze del server HAL AIDL

Per convenzione, i servizi HAL AIDL 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 .

Scrittura di un server HAL AIDL

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

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

Altrimenti, dovrebbero registrare normalmente un servizio AIDL. Quando si eseguono test VTS, si prevede che tutti gli HAL AIDL dichiarati siano disponibili.

Scrivere un client AIDL

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

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

Conversione di un HAL esistente da HIDL ad AIDL

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

Caratteristiche hidl2aidl :

  • Crea file .aidl basati sui file .hal per il pacchetto specificato
  • Crea regole di compilazione per il pacchetto AIDL appena creato con tutti i backend abilitati
  • Creare metodi di traduzione nei backend Java, CPP e NDK per la traduzione dai tipi HIDL ai tipi AIDL
  • Crea regole di compilazione per le librerie di traduzione con le dipendenze richieste
  • Crea asserzioni statiche per garantire che gli enumeratori HIDL e AIDL abbiano gli stessi valori nei backend CPP e NDK

Seguire questi passaggi per convertire un pacchetto di file .hal in file .aidl:

  1. Costruisci lo strumento situato in system/tools/hidl/hidl2aidl .

    La creazione di questo strumento dalla fonte più recente offre l'esperienza più completa. Puoi utilizzare la versione più recente per convertire le interfacce sui rami più vecchi delle versioni precedenti.

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

    Facoltativamente, utilizzare l'argomento -l per aggiungere il contenuto 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>
    

    Per esempio:

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

    • conversion.log contiene tutti i problemi non gestiti da risolvere prima.
    • I file .aidl generati potrebbero contenere avvisi e suggerimenti che potrebbero richiedere un'azione. Questi commenti iniziano con // .
    • Cogli l'occasione per ripulire e apportare miglioramenti al pacchetto.
    • Controlla l'annotazione @JavaDerive per le funzionalità che potrebbero essere necessarie, come toString o equals .
  4. Costruisci solo gli obiettivi di cui hai bisogno.

    • Disabilita i backend che non verranno utilizzati. Preferire il backend NDK rispetto al backend CPP, vedere scelta del runtime .
    • Rimuovi le librerie di traduzione o qualsiasi codice generato che non verrà utilizzato.
  5. Vedere Principali differenze AIDL/HIDL .

    • L'utilizzo Status integrato e delle eccezioni 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 lo erano in HIDL.

Sepolicy per gli HAL AIDL

Un tipo di servizio AIDL visibile al codice del fornitore deve avere l'attributo hal_service_type . Altrimenti, la configurazione della sepolicy è la stessa di qualsiasi altro servizio AIDL (sebbene siano presenti attributi speciali per gli HAL). Ecco una definizione di esempio 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 sarebbe già contrassegnato come hal_foo_service ). Tuttavia, se un client framework supporta più nomi di istanze, è necessario aggiungere ulteriori nomi di istanze nei file service_contexts specifici del dispositivo.

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

Gli attributi HAL devono essere aggiunti quando creiamo un nuovo tipo di HAL. Un attributo HAL specifico potrebbe essere associato a più tipi di servizio (ognuno dei quali può avere più istanze, come abbiamo appena discusso). Per un HAL, foo , abbiamo hal_attribute(foo) . Questa macro definisce gli attributi hal_foo_client e hal_foo_server . Per un dato dominio, le macro hal_client_domain e hal_server_domain associano un dominio a un dato attributo HAL. Ad esempio, il server di sistema che è un client di questo HAL corrisponde alla policy 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, creiamo anche un dominio come hal_foo_default per HAL di riferimento o di esempio. Tuttavia, alcuni dispositivi utilizzano questi domini per i propri server. Distinguere tra domini per più server è importante solo se disponiamo di più server che servono la stessa interfaccia e necessitano di un set di autorizzazioni diverso nelle loro implementazioni. In tutte queste macro, hal_foo non è effettivamente un oggetto sepolicy. Questo token viene invece utilizzato da queste macro per fare riferimento al gruppo di attributi associati a una coppia client-server.

Tuttavia, finora, non abbiamo associato hal_foo_service e hal_foo (la coppia di attributi di hal_attribute(foo) ). 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 ottenere l'HAL e i processi hal_foo_server possono registrare l'HAL. L'applicazione di queste regole di registrazione viene eseguita dal gestore del contesto ( servicemanager ). Tieni presente che i nomi dei servizi potrebbero non corrispondere sempre agli attributi HAL. Ad esempio, potremmo vedere hal_attribute_service(hal_foo, hal_foo2_service) . In generale, tuttavia, poiché ciò implica che i servizi vengono sempre utilizzati insieme, potremmo prendere in considerazione la rimozione di hal_foo2_service e l'utilizzo hal_foo_service per tutti i nostri contesti di servizio. La maggior parte degli HAL che impostano più hal_attribute_service sono dovuti al fatto che il nome dell'attributo HAL originale non è sufficientemente generale e non può essere modificato.

Mettendo tutto insieme, un HAL di esempio assomiglia a questo:

    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 allegate

Un'estensione può essere collegata a qualsiasi interfaccia del raccoglitore, sia che si tratti di un'interfaccia di livello superiore registrata direttamente con il gestore del servizio o di un'interfaccia secondaria. Quando ottieni un'estensione, devi confermare che il tipo di estensione è quello previsto. Le estensioni possono essere impostate solo dal processo che serve un raccoglitore.

Le estensioni allegate devono essere utilizzate ogni volta che un'estensione modifica la funzionalità di un HAL esistente. Quando è necessaria una funzionalità completamente nuova, non è necessario utilizzare questo meccanismo ed è possibile registrare un'interfaccia di estensione direttamente con il gestore del servizio. Le interfacce di estensione collegate hanno più senso quando sono collegate a interfacce secondarie, perché queste gerarchie possono essere profonde o con più istanze. L'utilizzo di un'estensione globale per rispecchiare la gerarchia dell'interfaccia del raccoglitore di un altro servizio richiederebbe un'accurata contabilità per fornire funzionalità equivalenti alle estensioni direttamente collegate.

Per impostare un'estensione sul raccoglitore, utilizzare le seguenti API:

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

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

  • Nel backend NDK: AIBinder_getExtension
  • Nel backend Java: android.os.IBinder.getExtension
  • Nel backend CPP: android::IBinder::getExtension
  • Nel 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 può essere trovato in hardware/interfaces/tests/extension/vibrator .

Principali differenze AIDL/HIDL

Quando si utilizzano HAL AIDL o interfacce HAL AIDL, tenere presente le differenze rispetto alla scrittura di HAL HIDL.

  • La sintassi del linguaggio AIDL è più vicina 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 int di stato costanti nei file di interfaccia e utilizza EX_SERVICE_SPECIFIC nei backend CPP/NDK e ServiceSpecificException nel backend Java. Vedere Gestione degli errori .
  • AIDL non avvia automaticamente i pool di thread quando vengono inviati gli oggetti del raccoglitore. Devono essere avviati manualmente (vedi gestione dei thread ).
  • AIDL non si interrompe in caso di errori di trasporto non controllati (HIDL Return si interrompe in caso di errori non controllati).
  • AIDL può dichiarare solo un tipo per file.
  • Gli argomenti AIDL possono essere specificati come in/out/inout oltre al parametro di output (non ci sono "callback sincroni").
  • AIDL utilizza un fd come tipo primitivo anziché handle.
  • HIDL utilizza versioni principali per modifiche incompatibili e versioni secondarie per modifiche compatibili. In AIDL vengono apportate modifiche compatibili con le versioni precedenti. AIDL non ha un concetto esplicito di versioni principali; invece, 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 raccoglitore per abilitare l'ereditarietà della priorità in tempo reale.